blob: 3cb789f0ac482160ab5944a43f0d6c6c9689d299 [file] [log] [blame]
halcanary8b2bc252015-10-06 09:41:47 -07001/*
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
Brian Osmanee6aa802017-07-12 17:26:34 -04008#include "SkAutoPixmapStorage.h"
9#include "SkColorPriv.h"
10#include "SkImage.h"
11#include "SkParsePath.h"
halcanary8b2bc252015-10-06 09:41:47 -070012#include "SkPath.h"
Brian Osmanee6aa802017-07-12 17:26:34 -040013#include "SkSurface.h"
halcanary8b2bc252015-10-06 09:41:47 -070014#include "gm.h"
15
Brian Osmanee6aa802017-07-12 17:26:34 -040016// 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).
caryclark1f17ab52015-12-16 08:53:41 -080027
Brian Osmanee6aa802017-07-12 17:26:34 -040028static bool draw_path_cell(SkCanvas* canvas, SkImage* img, int expectedCaps) {
29 // Draw the image
30 canvas->drawImage(img, 0, 0);
halcanary8b2bc252015-10-06 09:41:47 -070031
Brian Osmanee6aa802017-07-12 17:26:34 -040032 int w = img->width(), h = img->height();
halcanary8b2bc252015-10-06 09:41:47 -070033
Brian Osmanee6aa802017-07-12 17:26:34 -040034 // Read the pixels back
35 SkImageInfo info = SkImageInfo::MakeN32Premul(w, h);
36 SkAutoPixmapStorage pmap;
37 pmap.alloc(info);
Brian Osman5e117772018-06-20 14:53:06 -040038 if (!img->readPixels(pmap, 0, 0)) {
39 return false;
40 }
halcanary8b2bc252015-10-06 09:41:47 -070041
Brian Osmanee6aa802017-07-12 17:26:34 -040042 // To account for rasterization differences, we scan the middle two rows [y, y+1] of the image
43 SkASSERT(h % 2 == 0);
44 int y = (h - 1) / 2;
halcanary8b2bc252015-10-06 09:41:47 -070045
Brian Osmanee6aa802017-07-12 17:26:34 -040046 bool inBlob = false;
47 int numBlobs = 0;
48 for (int x = 0; x < w; ++x) {
49 // We drew white-on-black. We can look for any non-zero value. Just check red.
50 // And we care if either row is non-zero, so just add them to simplify everything.
51 uint32_t v = SkGetPackedR32(*pmap.addr32(x, y)) + SkGetPackedR32(*pmap.addr32(x, y + 1));
halcanary8b2bc252015-10-06 09:41:47 -070052
Brian Osmanee6aa802017-07-12 17:26:34 -040053 if (!inBlob && v) {
54 ++numBlobs;
55 }
56 inBlob = SkToBool(v);
caryclark1f17ab52015-12-16 08:53:41 -080057 }
58
Brian Osmanee6aa802017-07-12 17:26:34 -040059 SkPaint outline;
60 outline.setStyle(SkPaint::kStroke_Style);
61 if (numBlobs == expectedCaps) {
62 outline.setColor(0xFF007F00); // Green
63 } else if (numBlobs > expectedCaps) {
64 outline.setColor(0xFF7F7F00); // Yellow -- more geometry than expected
65 } else {
66 outline.setColor(0xFF7F0000); // Red -- missing some geometry
caryclark1f17ab52015-12-16 08:53:41 -080067 }
68
Brian Osmanee6aa802017-07-12 17:26:34 -040069 canvas->drawRect(SkRect::MakeWH(w, h), outline);
70 return numBlobs == expectedCaps;
71}
caryclark1f17ab52015-12-16 08:53:41 -080072
Brian Osmanee6aa802017-07-12 17:26:34 -040073static const SkPaint::Cap kCaps[] = {
74 SkPaint::kButt_Cap,
75 SkPaint::kRound_Cap,
76 SkPaint::kSquare_Cap
caryclark1f17ab52015-12-16 08:53:41 -080077};
78
Brian Osmanee6aa802017-07-12 17:26:34 -040079static const SkScalar kWidths[] = { 0.0f, 0.9f, 1.0f, 1.1f, 15.0f, 25.0f };
caryclark1f17ab52015-12-16 08:53:41 -080080
Brian Osmanee6aa802017-07-12 17:26:34 -040081// Full set of path structures for single contour case (each primitive with and without a close)
82static const char* kAllVerbs[] = {
83 nullptr,
84 "z ",
85 "l 0 0 ",
86 "l 0 0 z ",
87 "q 0 0 0 0 ",
88 "q 0 0 0 0 z ",
89 "c 0 0 0 0 0 0 ",
90 "c 0 0 0 0 0 0 z ",
91 "a 0 0 0 0 0 0 0 ",
92 "a 0 0 0 0 0 0 0 z "
93};
94
95// Reduced set of path structures for double contour case, to keep total number of cases down
96static const char* kSomeVerbs[] = {
97 nullptr,
98 "z ",
99 "l 0 0 ",
100 "l 0 0 z ",
101 "q 0 0 0 0 ",
102 "q 0 0 0 0 z ",
103};
104
105static const int kCellWidth = 50;
106static const int kCellHeight = 20;
107static const int kCellPad = 2;
108
109static const int kNumRows = SK_ARRAY_COUNT(kCaps) * SK_ARRAY_COUNT(kWidths);
110static const int kNumColumns = SK_ARRAY_COUNT(kAllVerbs);
111static const int kTotalWidth = kNumColumns * (kCellWidth + kCellPad) + kCellPad;
112static const int kTotalHeight = kNumRows * (kCellHeight + kCellPad) + kCellPad;
113
114static const int kDblContourNumColums = SK_ARRAY_COUNT(kSomeVerbs) * SK_ARRAY_COUNT(kSomeVerbs);
115static const int kDblContourTotalWidth = kDblContourNumColums * (kCellWidth + kCellPad) + kCellPad;
116
117// 50% transparent versions of the colors used for positive/negative triage icons on gold.skia.org
118static const SkColor kFailureRed = 0x7FE7298A;
119static const SkColor kSuccessGreen = 0x7F1B9E77;
120
121static void draw_zero_length_capped_paths(SkCanvas* canvas, bool aa) {
122 canvas->translate(kCellPad, kCellPad);
123
124 SkImageInfo info = canvas->imageInfo().makeWH(kCellWidth, kCellHeight);
125 auto surface = canvas->makeSurface(info);
126 if (!surface) {
127 surface = SkSurface::MakeRasterN32Premul(kCellWidth, kCellHeight);
128 }
129
130 SkPaint paint;
131 paint.setColor(SK_ColorWHITE);
132 paint.setAntiAlias(aa);
133 paint.setStyle(SkPaint::kStroke_Style);
134
135 int numFailedTests = 0;
136 for (auto cap : kCaps) {
137 for (auto width : kWidths) {
138 paint.setStrokeCap(cap);
139 paint.setStrokeWidth(width);
140 canvas->save();
141
142 for (auto verb : kAllVerbs) {
143 SkString pathStr;
144 pathStr.appendf("M %f %f ", (kCellWidth - 1) * 0.5f, (kCellHeight - 1) * 0.5f);
145 if (verb) {
146 pathStr.append(verb);
147 }
148
149 SkPath path;
150 SkParsePath::FromSVGString(pathStr.c_str(), &path);
151
152 surface->getCanvas()->clear(SK_ColorTRANSPARENT);
153 surface->getCanvas()->drawPath(path, paint);
154 auto img = surface->makeImageSnapshot();
155
156 // All cases should draw one cap, except for butt capped, and dangling moves
157 // (without a verb or close), which shouldn't draw anything.
158 int expectedCaps = ((SkPaint::kButt_Cap == cap) || !verb) ? 0 : 1;
159
160 if (!draw_path_cell(canvas, img.get(), expectedCaps)) {
161 ++numFailedTests;
162 }
163 canvas->translate(kCellWidth + kCellPad, 0);
164 }
165 canvas->restore();
166 canvas->translate(0, kCellHeight + kCellPad);
167 }
168 }
169
170 canvas->drawColor(numFailedTests > 0 ? kFailureRed : kSuccessGreen);
171}
172
173DEF_SIMPLE_GM_BG(zero_length_paths_aa, canvas, kTotalWidth, kTotalHeight, SK_ColorBLACK) {
174 draw_zero_length_capped_paths(canvas, true);
175}
176
177DEF_SIMPLE_GM_BG(zero_length_paths_bw, canvas, kTotalWidth, kTotalHeight, SK_ColorBLACK) {
178 draw_zero_length_capped_paths(canvas, false);
179}
180
181static void draw_zero_length_capped_paths_dbl_contour(SkCanvas* canvas, bool aa) {
182 canvas->translate(kCellPad, kCellPad);
183
184 SkImageInfo info = canvas->imageInfo().makeWH(kCellWidth, kCellHeight);
185 auto surface = canvas->makeSurface(info);
186 if (!surface) {
187 surface = SkSurface::MakeRasterN32Premul(kCellWidth, kCellHeight);
188 }
189
190 SkPaint paint;
191 paint.setColor(SK_ColorWHITE);
192 paint.setAntiAlias(aa);
193 paint.setStyle(SkPaint::kStroke_Style);
194
195 int numFailedTests = 0;
196 for (auto cap : kCaps) {
197 for (auto width : kWidths) {
198 paint.setStrokeCap(cap);
199 paint.setStrokeWidth(width);
200 canvas->save();
201
202 for (auto firstVerb : kSomeVerbs) {
203 for (auto secondVerb : kSomeVerbs) {
204 int expectedCaps = 0;
205
206 SkString pathStr;
207 pathStr.append("M 9.5 9.5 ");
208 if (firstVerb) {
209 pathStr.append(firstVerb);
210 ++expectedCaps;
211 }
212 pathStr.append("M 40.5 9.5 ");
213 if (secondVerb) {
214 pathStr.append(secondVerb);
215 ++expectedCaps;
216 }
217
218 SkPath path;
219 SkParsePath::FromSVGString(pathStr.c_str(), &path);
220
221 surface->getCanvas()->clear(SK_ColorTRANSPARENT);
222 surface->getCanvas()->drawPath(path, paint);
223 auto img = surface->makeImageSnapshot();
224
225 if (SkPaint::kButt_Cap == cap) {
226 expectedCaps = 0;
227 }
228
229 if (!draw_path_cell(canvas, img.get(), expectedCaps)) {
230 ++numFailedTests;
231 }
232 canvas->translate(kCellWidth + kCellPad, 0);
233 }
234 }
235 canvas->restore();
236 canvas->translate(0, kCellHeight + kCellPad);
237 }
238 }
239
240 canvas->drawColor(numFailedTests > 0 ? kFailureRed : kSuccessGreen);
241}
242
243DEF_SIMPLE_GM_BG(zero_length_paths_dbl_aa, canvas, kDblContourTotalWidth, kTotalHeight,
244 SK_ColorBLACK) {
245 draw_zero_length_capped_paths_dbl_contour(canvas, true);
246}
247
248DEF_SIMPLE_GM_BG(zero_length_paths_dbl_bw, canvas, kDblContourTotalWidth, kTotalHeight,
249 SK_ColorBLACK) {
250 draw_zero_length_capped_paths_dbl_contour(canvas, false);
251}