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