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 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 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 171 DEF_SIMPLE_GM_BG(zero_length_paths_aa, canvas, kTotalWidth, kTotalHeight, SK_ColorBLACK) { 172 draw_zero_length_capped_paths(canvas, true); 173 } 174 175 DEF_SIMPLE_GM_BG(zero_length_paths_bw, canvas, kTotalWidth, kTotalHeight, SK_ColorBLACK) { 176 draw_zero_length_capped_paths(canvas, false); 177 } 178 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 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 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