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 #include "gm/gm.h"
26 #include "include/core/SkCanvas.h"
27 #include "include/core/SkColor.h"
28 #include "include/core/SkPaint.h"
29 #include "include/core/SkPathBuilder.h"
30 #include "include/core/SkPathMeasure.h"
31 #include "include/core/SkPoint.h"
32 #include "include/core/SkRect.h"
33 #include "include/core/SkScalar.h"
34 #include "src/core/SkPointPriv.h"
35 
36 #include <cstddef>
37 
38 const SkScalar OVERSTROKE_WIDTH = 500.0f;
39 const SkScalar NORMALSTROKE_WIDTH = 3.0f;
40 
41 //////// path and paint builders
42 
make_normal_paint()43 SkPaint make_normal_paint() {
44     SkPaint p;
45     p.setAntiAlias(true);
46     p.setStyle(SkPaint::kStroke_Style);
47     p.setStrokeWidth(NORMALSTROKE_WIDTH);
48     p.setColor(SK_ColorBLUE);
49 
50     return p;
51 }
52 
make_overstroke_paint()53 SkPaint make_overstroke_paint() {
54     SkPaint p;
55     p.setAntiAlias(true);
56     p.setStyle(SkPaint::kStroke_Style);
57     p.setStrokeWidth(OVERSTROKE_WIDTH);
58 
59     return p;
60 }
61 
quad_path()62 SkPath quad_path() {
63     return SkPathBuilder().moveTo(0, 0)
64                           .lineTo(100, 0)
65                           .quadTo(50, -40, 0, 0)
66                           .close()
67                           .detach();
68 }
69 
cubic_path()70 SkPath cubic_path() {
71     SkPath path;
72     path.moveTo(0, 0);
73     path.cubicTo(25, 75,
74                  75, -50,
75                  100, 0);
76 
77     return path;
78 }
79 
oval_path()80 SkPath oval_path() {
81     SkRect oval = SkRect::MakeXYWH(0, -25, 100, 50);
82 
83     return SkPathBuilder().arcTo(oval, 0, 359, true).close().detach();
84 }
85 
ribs_path(SkPath path,SkScalar radius)86 SkPath 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             SkPointPriv::RotateCCW(&tan);
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 
draw_ribs(SkCanvas * canvas,SkPath path)110 void 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 
draw_small_quad(SkCanvas * canvas)122 void 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 
draw_large_quad(SkCanvas * canvas)133 void 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 
draw_quad_fillpath(SkCanvas * canvas)141 void 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 
draw_stroked_quad(SkCanvas * canvas)154 void 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 
draw_small_cubic(SkCanvas * canvas)162 void 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 
draw_large_cubic(SkCanvas * canvas)170 void 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 
draw_cubic_fillpath(SkCanvas * canvas)178 void 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 
draw_stroked_cubic(SkCanvas * canvas)191 void 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 
draw_small_oval(SkCanvas * canvas)199 void 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 
draw_large_oval(SkCanvas * canvas)208 void 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 
draw_oval_fillpath(SkCanvas * canvas)216 void 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 
draw_stroked_oval(SkCanvas * canvas)229 void 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 
237 void (*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 
242 DEF_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