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 #include "SkPointPriv.h"
31 
32 const SkScalar OVERSTROKE_WIDTH = 500.0f;
33 const SkScalar NORMALSTROKE_WIDTH = 3.0f;
34 
35 //////// path and paint builders
36 
make_normal_paint()37 SkPaint make_normal_paint() {
38     SkPaint p;
39     p.setAntiAlias(true);
40     p.setStyle(SkPaint::kStroke_Style);
41     p.setStrokeWidth(NORMALSTROKE_WIDTH);
42     p.setColor(SK_ColorBLUE);
43 
44     return p;
45 }
46 
make_overstroke_paint()47 SkPaint make_overstroke_paint() {
48     SkPaint p;
49     p.setAntiAlias(true);
50     p.setStyle(SkPaint::kStroke_Style);
51     p.setStrokeWidth(OVERSTROKE_WIDTH);
52 
53     return p;
54 }
55 
quad_path()56 SkPath quad_path() {
57     SkPath path;
58     path.moveTo(0, 0);
59     path.lineTo(100, 0);
60     path.quadTo(50, -40,
61                 0, 0);
62     path.close();
63 
64     return path;
65 }
66 
cubic_path()67 SkPath cubic_path() {
68     SkPath path;
69     path.moveTo(0, 0);
70     path.cubicTo(25, 75,
71                  75, -50,
72                  100, 0);
73 
74     return path;
75 }
76 
oval_path()77 SkPath oval_path() {
78     SkRect oval = SkRect::MakeXYWH(0, -25, 100, 50);
79 
80     SkPath path;
81     path.arcTo(oval, 0, 359, true);
82     path.close();
83 
84     return path;
85 }
86 
ribs_path(SkPath path,SkScalar radius)87 SkPath ribs_path(SkPath path, SkScalar radius) {
88     SkPath ribs;
89 
90     const SkScalar spacing = 5.0f;
91     float accum = 0.0f;
92 
93     SkPathMeasure meas(path, false);
94     SkScalar length = meas.getLength();
95     SkPoint pos;
96     SkVector tan;
97     while (accum < length) {
98         if (meas.getPosTan(accum, &pos, &tan)) {
99             tan.scale(radius);
100             SkPointPriv::RotateCCW(&tan);
101 
102             ribs.moveTo(pos.x() + tan.x(), pos.y() + tan.y());
103             ribs.lineTo(pos.x() - tan.x(), pos.y() - tan.y());
104         }
105         accum += spacing;
106     }
107 
108     return ribs;
109 }
110 
draw_ribs(SkCanvas * canvas,SkPath path)111 void draw_ribs(SkCanvas *canvas, SkPath path) {
112     SkPath ribs = ribs_path(path, OVERSTROKE_WIDTH/2.0f);
113     SkPaint p = make_normal_paint();
114     p.setStrokeWidth(1);
115     p.setColor(SK_ColorBLUE);
116     p.setColor(SK_ColorGREEN);
117 
118     canvas->drawPath(ribs, p);
119 }
120 
121 ///////// quads
122 
draw_small_quad(SkCanvas * canvas)123 void draw_small_quad(SkCanvas *canvas) {
124     // scaled so it's visible
125     // canvas->scale(8, 8);
126 
127     SkPaint p = make_normal_paint();
128     SkPath path = quad_path();
129 
130     draw_ribs(canvas, path);
131     canvas->drawPath(path, p);
132 }
133 
draw_large_quad(SkCanvas * canvas)134 void draw_large_quad(SkCanvas *canvas) {
135     SkPaint p = make_overstroke_paint();
136     SkPath path = quad_path();
137 
138     canvas->drawPath(path, p);
139     draw_ribs(canvas, path);
140 }
141 
draw_quad_fillpath(SkCanvas * canvas)142 void draw_quad_fillpath(SkCanvas *canvas) {
143     SkPath path = quad_path();
144     SkPaint p = make_overstroke_paint();
145 
146     SkPaint fillp = make_normal_paint();
147     fillp.setColor(SK_ColorMAGENTA);
148 
149     SkPath fillpath;
150     p.getFillPath(path, &fillpath);
151 
152     canvas->drawPath(fillpath, fillp);
153 }
154 
draw_stroked_quad(SkCanvas * canvas)155 void draw_stroked_quad(SkCanvas *canvas) {
156     canvas->translate(400, 0);
157     draw_large_quad(canvas);
158     draw_quad_fillpath(canvas);
159 }
160 
161 ////////// cubics
162 
draw_small_cubic(SkCanvas * canvas)163 void draw_small_cubic(SkCanvas *canvas) {
164     SkPaint p = make_normal_paint();
165     SkPath path = cubic_path();
166 
167     draw_ribs(canvas, path);
168     canvas->drawPath(path, p);
169 }
170 
draw_large_cubic(SkCanvas * canvas)171 void draw_large_cubic(SkCanvas *canvas) {
172     SkPaint p = make_overstroke_paint();
173     SkPath path = cubic_path();
174 
175     canvas->drawPath(path, p);
176     draw_ribs(canvas, path);
177 }
178 
draw_cubic_fillpath(SkCanvas * canvas)179 void draw_cubic_fillpath(SkCanvas *canvas) {
180     SkPath path = cubic_path();
181     SkPaint p = make_overstroke_paint();
182 
183     SkPaint fillp = make_normal_paint();
184     fillp.setColor(SK_ColorMAGENTA);
185 
186     SkPath fillpath;
187     p.getFillPath(path, &fillpath);
188 
189     canvas->drawPath(fillpath, fillp);
190 }
191 
draw_stroked_cubic(SkCanvas * canvas)192 void draw_stroked_cubic(SkCanvas *canvas) {
193     canvas->translate(400, 0);
194     draw_large_cubic(canvas);
195     draw_cubic_fillpath(canvas);
196 }
197 
198 ////////// ovals
199 
draw_small_oval(SkCanvas * canvas)200 void draw_small_oval(SkCanvas *canvas) {
201     SkPaint p = make_normal_paint();
202 
203     SkPath path = oval_path();
204 
205     draw_ribs(canvas, path);
206     canvas->drawPath(path, p);
207 }
208 
draw_large_oval(SkCanvas * canvas)209 void draw_large_oval(SkCanvas *canvas) {
210     SkPaint p = make_overstroke_paint();
211     SkPath path = oval_path();
212 
213     canvas->drawPath(path, p);
214     draw_ribs(canvas, path);
215 }
216 
draw_oval_fillpath(SkCanvas * canvas)217 void draw_oval_fillpath(SkCanvas *canvas) {
218     SkPath path = oval_path();
219     SkPaint p = make_overstroke_paint();
220 
221     SkPaint fillp = make_normal_paint();
222     fillp.setColor(SK_ColorMAGENTA);
223 
224     SkPath fillpath;
225     p.getFillPath(path, &fillpath);
226 
227     canvas->drawPath(fillpath, fillp);
228 }
229 
draw_stroked_oval(SkCanvas * canvas)230 void draw_stroked_oval(SkCanvas *canvas) {
231     canvas->translate(400, 0);
232     draw_large_oval(canvas);
233     draw_oval_fillpath(canvas);
234 }
235 
236 ////////// gm
237 
238 void (*examples[])(SkCanvas *canvas) = {
239     draw_small_quad,    draw_stroked_quad, draw_small_cubic,
240     draw_stroked_cubic, draw_small_oval,   draw_stroked_oval,
241 };
242 
243 DEF_SIMPLE_GM(OverStroke, canvas, 500, 500) {
244     const size_t length = sizeof(examples) / sizeof(examples[0]);
245     const size_t width = 2;
246 
247     for (size_t i = 0; i < length; i++) {
248         int x = (int)(i % width);
249         int y = (int)(i / width);
250 
251         canvas->save();
252         canvas->translate(150.0f * x, 150.0f * y);
253         canvas->scale(0.2f, 0.2f);
254         canvas->translate(300.0f, 400.0f);
255 
256         examples[i](canvas);
257 
258         canvas->restore();
259     }
260 }
261