1 /*
2  * Copyright 2015 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 #include "gm/gm.h"
9 #include "include/core/SkCanvas.h"
10 #include "include/core/SkColor.h"
11 #include "include/core/SkColorPriv.h"
12 #include "include/core/SkImage.h"
13 #include "include/core/SkImageInfo.h"
14 #include "include/core/SkPaint.h"
15 #include "include/core/SkPath.h"
16 #include "include/core/SkRect.h"
17 #include "include/core/SkRefCnt.h"
18 #include "include/core/SkScalar.h"
19 #include "include/core/SkString.h"
20 #include "include/core/SkSurface.h"
21 #include "include/core/SkTypes.h"
22 #include "include/gpu/GrDirectContext.h"
23 #include "include/utils/SkParsePath.h"
24 #include "src/core/SkAutoPixmapStorage.h"
25 
26 // GM to test combinations of stroking zero length paths with different caps and other settings
27 // Variables:
28 // * Antialiasing: On, Off
29 // * Caps: Butt, Round, Square
30 // * Stroke width: 0, 0.9, 1, 1.1, 15, 25
31 // * Path form: M, ML, MLZ, MZ
32 // * Path contours: 1 or 2
33 // * Path verbs: Line, Quad, Cubic, Conic
34 //
35 // Each test is drawn to a 50x20 offscreen surface, and expected to produce some number (0 - 2) of
36 // visible pieces of cap geometry. These are counted by scanning horizontally for peaks (blobs).
37 
draw_path_cell(GrDirectContext * dContext,SkCanvas * canvas,SkImage * img,int expectedCaps)38 static bool draw_path_cell(GrDirectContext* dContext, SkCanvas* canvas, SkImage* img,
39                            int expectedCaps) {
40     // Draw the image
41     canvas->drawImage(img, 0, 0);
42 
43     int w = img->width(), h = img->height();
44 
45     // Read the pixels back
46     SkImageInfo info = SkImageInfo::MakeN32Premul(w, h);
47     SkAutoPixmapStorage pmap;
48     pmap.alloc(info);
49     if (!img->readPixels(dContext, pmap, 0, 0)) {
50         return false;
51     }
52 
53     // To account for rasterization differences, we scan the middle two rows [y, y+1] of the image
54     SkASSERT(h % 2 == 0);
55     int y = (h - 1) / 2;
56 
57     bool inBlob = false;
58     int numBlobs = 0;
59     for (int x = 0; x < w; ++x) {
60         // We drew white-on-black. We can look for any non-zero value. Just check red.
61         // And we care if either row is non-zero, so just add them to simplify everything.
62         uint32_t v = SkGetPackedR32(*pmap.addr32(x, y)) + SkGetPackedR32(*pmap.addr32(x, y + 1));
63 
64         if (!inBlob && v) {
65             ++numBlobs;
66         }
67         inBlob = SkToBool(v);
68     }
69 
70     SkPaint outline;
71     outline.setStyle(SkPaint::kStroke_Style);
72     if (numBlobs == expectedCaps) {
73         outline.setColor(0xFF007F00); // Green
74     } else if (numBlobs > expectedCaps) {
75         outline.setColor(0xFF7F7F00); // Yellow -- more geometry than expected
76     } else {
77         outline.setColor(0xFF7F0000); // Red -- missing some geometry
78     }
79 
80     canvas->drawRect(SkRect::MakeWH(w, h), outline);
81     return numBlobs == expectedCaps;
82 }
83 
84 static const SkPaint::Cap kCaps[] = {
85     SkPaint::kButt_Cap,
86     SkPaint::kRound_Cap,
87     SkPaint::kSquare_Cap
88 };
89 
90 static const SkScalar kWidths[] = { 0.0f, 0.9f, 1.0f, 1.1f, 15.0f, 25.0f };
91 
92 // Full set of path structures for single contour case (each primitive with and without a close)
93 static const char* kAllVerbs[] = {
94     nullptr,
95     "z ",
96     "l 0 0 ",
97     "l 0 0 z ",
98     "q 0 0 0 0 ",
99     "q 0 0 0 0 z ",
100     "c 0 0 0 0 0 0 ",
101     "c 0 0 0 0 0 0 z ",
102     "a 0 0 0 0 0 0 0 ",
103     "a 0 0 0 0 0 0 0 z "
104 };
105 
106 // Reduced set of path structures for double contour case, to keep total number of cases down
107 static const char* kSomeVerbs[] = {
108     nullptr,
109     "z ",
110     "l 0 0 ",
111     "l 0 0 z ",
112     "q 0 0 0 0 ",
113     "q 0 0 0 0 z ",
114 };
115 
116 static const int kCellWidth = 50;
117 static const int kCellHeight = 20;
118 static const int kCellPad = 2;
119 
120 static const int kNumRows = SK_ARRAY_COUNT(kCaps) * SK_ARRAY_COUNT(kWidths);
121 static const int kNumColumns = SK_ARRAY_COUNT(kAllVerbs);
122 static const int kTotalWidth = kNumColumns * (kCellWidth + kCellPad) + kCellPad;
123 static const int kTotalHeight = kNumRows * (kCellHeight + kCellPad) + kCellPad;
124 
125 static const int kDblContourNumColums = SK_ARRAY_COUNT(kSomeVerbs) * SK_ARRAY_COUNT(kSomeVerbs);
126 static const int kDblContourTotalWidth = kDblContourNumColums * (kCellWidth + kCellPad) + kCellPad;
127 
128 // 50% transparent versions of the colors used for positive/negative triage icons on gold.skia.org
129 static const SkColor kFailureRed = 0x7FE7298A;
130 static const SkColor kSuccessGreen = 0x7F1B9E77;
131 
draw_zero_length_capped_paths(SkCanvas * canvas,bool aa,SkString * errorMsg)132 static skiagm::DrawResult draw_zero_length_capped_paths(SkCanvas* canvas, bool aa,
133                                                         SkString* errorMsg) {
134     auto rContext = canvas->recordingContext();
135     auto dContext = GrAsDirectContext(rContext);
136 
137     if (!dContext && rContext) {
138         *errorMsg = "Not supported in DDL mode";
139         return skiagm::DrawResult::kSkip;
140     }
141 
142     canvas->translate(kCellPad, kCellPad);
143 
144     SkImageInfo info = canvas->imageInfo().makeWH(kCellWidth, kCellHeight);
145     auto surface = canvas->makeSurface(info);
146     if (!surface) {
147         surface = SkSurface::MakeRasterN32Premul(kCellWidth, kCellHeight);
148     }
149 
150     SkPaint paint;
151     paint.setColor(SK_ColorWHITE);
152     paint.setAntiAlias(aa);
153     paint.setStyle(SkPaint::kStroke_Style);
154 
155     int numFailedTests = 0;
156     for (auto cap : kCaps) {
157         for (auto width : kWidths) {
158             paint.setStrokeCap(cap);
159             paint.setStrokeWidth(width);
160             canvas->save();
161 
162             for (auto verb : kAllVerbs) {
163                 SkString pathStr;
164                 pathStr.appendf("M %f %f ", (kCellWidth - 1) * 0.5f, (kCellHeight - 1) * 0.5f);
165                 if (verb) {
166                     pathStr.append(verb);
167                 }
168 
169                 SkPath path;
170                 SkParsePath::FromSVGString(pathStr.c_str(), &path);
171 
172                 surface->getCanvas()->clear(SK_ColorTRANSPARENT);
173                 surface->getCanvas()->drawPath(path, paint);
174                 auto img = surface->makeImageSnapshot();
175 
176                 // All cases should draw one cap, except for butt capped, and dangling moves
177                 // (without a verb or close), which shouldn't draw anything.
178                 int expectedCaps = ((SkPaint::kButt_Cap == cap) || !verb) ? 0 : 1;
179 
180                 if (!draw_path_cell(dContext, canvas, img.get(), expectedCaps)) {
181                     ++numFailedTests;
182                 }
183                 canvas->translate(kCellWidth + kCellPad, 0);
184             }
185             canvas->restore();
186             canvas->translate(0, kCellHeight + kCellPad);
187         }
188     }
189 
190     canvas->drawColor(numFailedTests > 0 ? kFailureRed : kSuccessGreen);
191     return skiagm::DrawResult::kOk;
192 }
193 
DEF_SIMPLE_GM_BG_CAN_FAIL(zero_length_paths_aa,canvas,errorMsg,kTotalWidth,kTotalHeight,SK_ColorBLACK)194 DEF_SIMPLE_GM_BG_CAN_FAIL(zero_length_paths_aa, canvas, errorMsg,
195                           kTotalWidth, kTotalHeight, SK_ColorBLACK) {
196     return draw_zero_length_capped_paths(canvas, true, errorMsg);
197 }
198 
DEF_SIMPLE_GM_BG_CAN_FAIL(zero_length_paths_bw,canvas,errorMsg,kTotalWidth,kTotalHeight,SK_ColorBLACK)199 DEF_SIMPLE_GM_BG_CAN_FAIL(zero_length_paths_bw, canvas, errorMsg,
200                           kTotalWidth, kTotalHeight, SK_ColorBLACK) {
201     return draw_zero_length_capped_paths(canvas, false, errorMsg);
202 }
203 
draw_zero_length_capped_paths_dbl_contour(SkCanvas * canvas,bool aa,SkString * errorMsg)204 static skiagm::DrawResult draw_zero_length_capped_paths_dbl_contour(SkCanvas* canvas, bool aa,
205                                                                     SkString* errorMsg) {
206     auto rContext = canvas->recordingContext();
207     auto dContext = GrAsDirectContext(rContext);
208 
209     if (!dContext && rContext) {
210         *errorMsg = "Not supported in DDL mode";
211         return skiagm::DrawResult::kSkip;
212     }
213     canvas->translate(kCellPad, kCellPad);
214 
215     SkImageInfo info = canvas->imageInfo().makeWH(kCellWidth, kCellHeight);
216     auto surface = canvas->makeSurface(info);
217     if (!surface) {
218         surface = SkSurface::MakeRasterN32Premul(kCellWidth, kCellHeight);
219     }
220 
221     SkPaint paint;
222     paint.setColor(SK_ColorWHITE);
223     paint.setAntiAlias(aa);
224     paint.setStyle(SkPaint::kStroke_Style);
225 
226     int numFailedTests = 0;
227     for (auto cap : kCaps) {
228         for (auto width : kWidths) {
229             paint.setStrokeCap(cap);
230             paint.setStrokeWidth(width);
231             canvas->save();
232 
233             for (auto firstVerb : kSomeVerbs) {
234                 for (auto secondVerb : kSomeVerbs) {
235                     int expectedCaps = 0;
236 
237                     SkString pathStr;
238                     pathStr.append("M 9.5 9.5 ");
239                     if (firstVerb) {
240                         pathStr.append(firstVerb);
241                         ++expectedCaps;
242                     }
243                     pathStr.append("M 40.5 9.5 ");
244                     if (secondVerb) {
245                         pathStr.append(secondVerb);
246                         ++expectedCaps;
247                     }
248 
249                     SkPath path;
250                     SkParsePath::FromSVGString(pathStr.c_str(), &path);
251 
252                     surface->getCanvas()->clear(SK_ColorTRANSPARENT);
253                     surface->getCanvas()->drawPath(path, paint);
254                     auto img = surface->makeImageSnapshot();
255 
256                     if (SkPaint::kButt_Cap == cap) {
257                         expectedCaps = 0;
258                     }
259 
260                     if (!draw_path_cell(dContext, canvas, img.get(), expectedCaps)) {
261                         ++numFailedTests;
262                     }
263                     canvas->translate(kCellWidth + kCellPad, 0);
264                 }
265             }
266             canvas->restore();
267             canvas->translate(0, kCellHeight + kCellPad);
268         }
269     }
270 
271     canvas->drawColor(numFailedTests > 0 ? kFailureRed : kSuccessGreen);
272     return skiagm::DrawResult::kOk;
273 }
274 
DEF_SIMPLE_GM_BG_CAN_FAIL(zero_length_paths_dbl_aa,canvas,errorMsg,kDblContourTotalWidth,kTotalHeight,SK_ColorBLACK)275 DEF_SIMPLE_GM_BG_CAN_FAIL(zero_length_paths_dbl_aa, canvas, errorMsg,
276                           kDblContourTotalWidth, kTotalHeight, SK_ColorBLACK) {
277     return draw_zero_length_capped_paths_dbl_contour(canvas, true, errorMsg);
278 }
279 
DEF_SIMPLE_GM_BG_CAN_FAIL(zero_length_paths_dbl_bw,canvas,errorMsg,kDblContourTotalWidth,kTotalHeight,SK_ColorBLACK)280 DEF_SIMPLE_GM_BG_CAN_FAIL(zero_length_paths_dbl_bw, canvas, errorMsg,
281                           kDblContourTotalWidth, kTotalHeight, SK_ColorBLACK) {
282     return draw_zero_length_capped_paths_dbl_contour(canvas, false, errorMsg);
283 }
284