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