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 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 if (!img->readPixels(pmap, 0, 0)) { 39 return false; 40 } 41 42 // 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; 45 46 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)); 52 53 if (!inBlob && v) { 54 ++numBlobs; 55 } 56 inBlob = SkToBool(v); 57 } 58 59 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 67 } 68 69 canvas->drawRect(SkRect::MakeWH(w, h), outline); 70 return numBlobs == expectedCaps; 71 } 72 73 static const SkPaint::Cap kCaps[] = { 74 SkPaint::kButt_Cap, 75 SkPaint::kRound_Cap, 76 SkPaint::kSquare_Cap 77 }; 78 79 static const SkScalar kWidths[] = { 0.0f, 0.9f, 1.0f, 1.1f, 15.0f, 25.0f }; 80 81 // Full set of path structures for single contour case (each primitive with and without a close) 82 static 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 96 static 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 105 static const int kCellWidth = 50; 106 static const int kCellHeight = 20; 107 static const int kCellPad = 2; 108 109 static const int kNumRows = SK_ARRAY_COUNT(kCaps) * SK_ARRAY_COUNT(kWidths); 110 static const int kNumColumns = SK_ARRAY_COUNT(kAllVerbs); 111 static const int kTotalWidth = kNumColumns * (kCellWidth + kCellPad) + kCellPad; 112 static const int kTotalHeight = kNumRows * (kCellHeight + kCellPad) + kCellPad; 113 114 static const int kDblContourNumColums = SK_ARRAY_COUNT(kSomeVerbs) * SK_ARRAY_COUNT(kSomeVerbs); 115 static 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 118 static const SkColor kFailureRed = 0x7FE7298A; 119 static const SkColor kSuccessGreen = 0x7F1B9E77; 120 121 static 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 173 DEF_SIMPLE_GM_BG(zero_length_paths_aa, canvas, kTotalWidth, kTotalHeight, SK_ColorBLACK) { 174 draw_zero_length_capped_paths(canvas, true); 175 } 176 177 DEF_SIMPLE_GM_BG(zero_length_paths_bw, canvas, kTotalWidth, kTotalHeight, SK_ColorBLACK) { 178 draw_zero_length_capped_paths(canvas, false); 179 } 180 181 static 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 243 DEF_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 248 DEF_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 } 252