1/*
2 * Copyright 2016 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8
9/*
10 * This GM exercises stroking of paths with large stroke lengths, which is
11 * referred to as "overstroke" for brevity. In Skia as of 8/2016 we offset
12 * each part of the curve the request amount even if it makes the offsets
13 * overlap and create holes. There is not a really great algorithm for this
14 * and several other 2D graphics engines have the same bug.
15 *
16 * If we run this using Nvidia Path Renderer with:
17 * `path/to/dm --match OverStroke -w gm_out --gpu --config nvpr16`
18 * then we get correct results, so that is a possible direction of attack -
19 * use the GPU and a completely different algorithm to get correctness in
20 * Skia.
21 *
22 * See crbug.com/589769 skbug.com/5405 skbug.com/5406
23 */
24
25
26#include "gm.h"
27#include "SkPaint.h"
28#include "SkPath.h"
29#include "SkPathMeasure.h"
30
31const SkScalar OVERSTROKE_WIDTH = 500.0f;
32const SkScalar NORMALSTROKE_WIDTH = 3.0f;
33
34//////// path and paint builders
35
36SkPaint make_normal_paint() {
37    SkPaint p;
38    p.setAntiAlias(true);
39    p.setStyle(SkPaint::kStroke_Style);
40    p.setStrokeWidth(NORMALSTROKE_WIDTH);
41    p.setColor(SK_ColorBLUE);
42
43    return p;
44}
45
46SkPaint make_overstroke_paint() {
47    SkPaint p;
48    p.setAntiAlias(true);
49    p.setStyle(SkPaint::kStroke_Style);
50    p.setStrokeWidth(OVERSTROKE_WIDTH);
51
52    return p;
53}
54
55SkPath quad_path() {
56    SkPath path;
57    path.moveTo(0, 0);
58    path.lineTo(100, 0);
59    path.quadTo(50, -40,
60                0, 0);
61    path.close();
62
63    return path;
64}
65
66SkPath cubic_path() {
67    SkPath path;
68    path.moveTo(0, 0);
69    path.cubicTo(25, 75,
70                 75, -50,
71                 100, 0);
72
73    return path;
74}
75
76SkPath oval_path() {
77    SkRect oval = SkRect::MakeXYWH(0, -25, 100, 50);
78
79    SkPath path;
80    path.arcTo(oval, 0, 359, true);
81    path.close();
82
83    return path;
84}
85
86SkPath ribs_path(SkPath path, SkScalar radius) {
87    SkPath ribs;
88
89    const SkScalar spacing = 5.0f;
90    float accum = 0.0f;
91
92    SkPathMeasure meas(path, false);
93    SkScalar length = meas.getLength();
94    SkPoint pos;
95    SkVector tan;
96    while (accum < length) {
97        if (meas.getPosTan(accum, &pos, &tan)) {
98            tan.scale(radius);
99            tan.rotateCCW();
100
101            ribs.moveTo(pos.x() + tan.x(), pos.y() + tan.y());
102            ribs.lineTo(pos.x() - tan.x(), pos.y() - tan.y());
103        }
104        accum += spacing;
105    }
106
107    return ribs;
108}
109
110void draw_ribs(SkCanvas *canvas, SkPath path) {
111    SkPath ribs = ribs_path(path, OVERSTROKE_WIDTH/2.0f);
112    SkPaint p = make_normal_paint();
113    p.setStrokeWidth(1);
114    p.setColor(SK_ColorBLUE);
115    p.setColor(SK_ColorGREEN);
116
117    canvas->drawPath(ribs, p);
118}
119
120///////// quads
121
122void draw_small_quad(SkCanvas *canvas) {
123    // scaled so it's visible
124    // canvas->scale(8, 8);
125
126    SkPaint p = make_normal_paint();
127    SkPath path = quad_path();
128
129    draw_ribs(canvas, path);
130    canvas->drawPath(path, p);
131}
132
133void draw_large_quad(SkCanvas *canvas) {
134    SkPaint p = make_overstroke_paint();
135    SkPath path = quad_path();
136
137    canvas->drawPath(path, p);
138    draw_ribs(canvas, path);
139}
140
141void draw_quad_fillpath(SkCanvas *canvas) {
142    SkPath path = quad_path();
143    SkPaint p = make_overstroke_paint();
144
145    SkPaint fillp = make_normal_paint();
146    fillp.setColor(SK_ColorMAGENTA);
147
148    SkPath fillpath;
149    p.getFillPath(path, &fillpath);
150
151    canvas->drawPath(fillpath, fillp);
152}
153
154void draw_stroked_quad(SkCanvas *canvas) {
155    canvas->translate(400, 0);
156    draw_large_quad(canvas);
157    draw_quad_fillpath(canvas);
158}
159
160////////// cubics
161
162void draw_small_cubic(SkCanvas *canvas) {
163    SkPaint p = make_normal_paint();
164    SkPath path = cubic_path();
165
166    draw_ribs(canvas, path);
167    canvas->drawPath(path, p);
168}
169
170void draw_large_cubic(SkCanvas *canvas) {
171    SkPaint p = make_overstroke_paint();
172    SkPath path = cubic_path();
173
174    canvas->drawPath(path, p);
175    draw_ribs(canvas, path);
176}
177
178void draw_cubic_fillpath(SkCanvas *canvas) {
179    SkPath path = cubic_path();
180    SkPaint p = make_overstroke_paint();
181
182    SkPaint fillp = make_normal_paint();
183    fillp.setColor(SK_ColorMAGENTA);
184
185    SkPath fillpath;
186    p.getFillPath(path, &fillpath);
187
188    canvas->drawPath(fillpath, fillp);
189}
190
191void draw_stroked_cubic(SkCanvas *canvas) {
192    canvas->translate(400, 0);
193    draw_large_cubic(canvas);
194    draw_cubic_fillpath(canvas);
195}
196
197////////// ovals
198
199void draw_small_oval(SkCanvas *canvas) {
200    SkPaint p = make_normal_paint();
201
202    SkPath path = oval_path();
203
204    draw_ribs(canvas, path);
205    canvas->drawPath(path, p);
206}
207
208void draw_large_oval(SkCanvas *canvas) {
209    SkPaint p = make_overstroke_paint();
210    SkPath path = oval_path();
211
212    canvas->drawPath(path, p);
213    draw_ribs(canvas, path);
214}
215
216void draw_oval_fillpath(SkCanvas *canvas) {
217    SkPath path = oval_path();
218    SkPaint p = make_overstroke_paint();
219
220    SkPaint fillp = make_normal_paint();
221    fillp.setColor(SK_ColorMAGENTA);
222
223    SkPath fillpath;
224    p.getFillPath(path, &fillpath);
225
226    canvas->drawPath(fillpath, fillp);
227}
228
229void draw_stroked_oval(SkCanvas *canvas) {
230    canvas->translate(400, 0);
231    draw_large_oval(canvas);
232    draw_oval_fillpath(canvas);
233}
234
235////////// gm
236
237void (*examples[])(SkCanvas *canvas) = {
238    draw_small_quad,    draw_stroked_quad, draw_small_cubic,
239    draw_stroked_cubic, draw_small_oval,   draw_stroked_oval,
240};
241
242DEF_SIMPLE_GM(OverStroke, canvas, 500, 500) {
243    const size_t length = sizeof(examples) / sizeof(examples[0]);
244    const size_t width = 2;
245
246    for (size_t i = 0; i < length; i++) {
247        int x = (int)(i % width);
248        int y = (int)(i / width);
249
250        canvas->save();
251        canvas->translate(150.0f * x, 150.0f * y);
252        canvas->scale(0.2f, 0.2f);
253        canvas->translate(300.0f, 400.0f);
254
255        examples[i](canvas);
256
257        canvas->restore();
258    }
259}
260