1 /* 2 * Copyright 2012 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 "Test.h" 9 #include "TestClassDef.h" 10 #include "SkBitmap.h" 11 #include "SkCanvas.h" 12 #include "SkDashPathEffect.h" 13 #include "SkSurface.h" 14 15 static SkCanvas* create(SkBitmap::Config config, int w, int h, int rb, 16 void* addr = NULL) { 17 SkBitmap bm; 18 bm.setConfig(config, w, h, rb); 19 if (addr) { 20 bm.setPixels(addr); 21 } else { 22 bm.allocPixels(); 23 } 24 return new SkCanvas(bm); 25 } 26 27 static SkCanvas* new_canvas(int w, int h) { 28 return create(SkBitmap::kARGB_8888_Config, w, h, 0, NULL); 29 } 30 31 /////////////////////////////////////////////////////////////////////////////// 32 33 // test that we can draw an aa-rect at coordinates > 32K (bigger than fixedpoint) 34 static void test_big_aa_rect(skiatest::Reporter* reporter) { 35 SkBitmap output; 36 SkPMColor pixel[1]; 37 output.setConfig(SkBitmap::kARGB_8888_Config, 1, 1, 4); 38 output.setPixels(pixel); 39 40 SkSurface* surf = SkSurface::NewRasterPMColor(300, 33300); 41 SkCanvas* canvas = surf->getCanvas(); 42 43 SkRect r = { 0, 33000, 300, 33300 }; 44 int x = SkScalarRoundToInt(r.left()); 45 int y = SkScalarRoundToInt(r.top()); 46 47 // check that the pixel in question starts as transparent (by the surface) 48 if (canvas->readPixels(&output, x, y)) { 49 REPORTER_ASSERT(reporter, 0 == pixel[0]); 50 } else { 51 REPORTER_ASSERT_MESSAGE(reporter, false, "readPixels failed"); 52 } 53 54 SkPaint paint; 55 paint.setAntiAlias(true); 56 paint.setColor(SK_ColorWHITE); 57 58 canvas->drawRect(r, paint); 59 60 // Now check that it is BLACK 61 if (canvas->readPixels(&output, x, y)) { 62 // don't know what swizzling PMColor did, but white should always 63 // appear the same. 64 REPORTER_ASSERT(reporter, 0xFFFFFFFF == pixel[0]); 65 } else { 66 REPORTER_ASSERT_MESSAGE(reporter, false, "readPixels failed"); 67 } 68 surf->unref(); 69 } 70 71 /////////////////////////////////////////////////////////////////////////////// 72 73 static void moveToH(SkPath* path, const uint32_t raw[]) { 74 const float* fptr = (const float*)raw; 75 path->moveTo(fptr[0], fptr[1]); 76 } 77 78 static void cubicToH(SkPath* path, const uint32_t raw[]) { 79 const float* fptr = (const float*)raw; 80 path->cubicTo(fptr[0], fptr[1], fptr[2], fptr[3], fptr[4], fptr[5]); 81 } 82 83 // This used to assert, because we performed a cast (int)(pt[0].fX * scale) to 84 // arrive at an int (SkFDot6) rather than calling sk_float_round2int. The assert 85 // was that the initial line-segment produced by the cubic was not monotonically 86 // going down (i.e. the initial DY was negative). By rounding the floats, we get 87 // the more proper result. 88 // 89 // http://code.google.com/p/chromium/issues/detail?id=131181 90 // 91 92 // we're not calling this test anymore; is that for a reason? 93 94 static void test_crbug131181() { 95 /* 96 fX = 18.8943768, 97 fY = 129.121277 98 }, { 99 fX = 18.8937435, 100 fY = 129.121689 101 }, { 102 fX = 18.8950119, 103 fY = 129.120422 104 }, { 105 fX = 18.5030727, 106 fY = 129.13121 107 */ 108 uint32_t data[] = { 109 0x419727af, 0x43011f0c, 0x41972663, 0x43011f27, 110 0x419728fc, 0x43011ed4, 0x4194064b, 0x43012197 111 }; 112 113 SkPath path; 114 moveToH(&path, &data[0]); 115 cubicToH(&path, &data[2]); 116 117 SkAutoTUnref<SkCanvas> canvas(new_canvas(640, 480)); 118 119 SkPaint paint; 120 paint.setAntiAlias(true); 121 canvas->drawPath(path, paint); 122 } 123 124 // This used to assert in debug builds (and crash writing bad memory in release) 125 // because we overflowed an intermediate value (B coefficient) setting up our 126 // stepper for the quadratic. Now we bias that value by 1/2 so we don't overflow 127 static void test_crbug_140803() { 128 SkBitmap bm; 129 bm.setConfig(SkBitmap::kARGB_8888_Config, 2700, 30*1024); 130 bm.allocPixels(); 131 SkCanvas canvas(bm); 132 133 SkPath path; 134 path.moveTo(2762, 20); 135 path.quadTo(11, 21702, 10, 21706); 136 SkPaint paint; 137 paint.setAntiAlias(true); 138 canvas.drawPath(path, paint); 139 } 140 141 // Need to exercise drawing an inverse-path whose bounds intersect the clip, 142 // but whose edges do not (since its a quad which draws only in the bottom half 143 // of its bounds). 144 // In the debug build, we used to assert in this case, until it was fixed. 145 // 146 static void test_inversepathwithclip() { 147 SkPath path; 148 149 path.moveTo(0, SkIntToScalar(20)); 150 path.quadTo(SkIntToScalar(10), SkIntToScalar(10), 151 SkIntToScalar(20), SkIntToScalar(20)); 152 path.toggleInverseFillType(); 153 154 SkPaint paint; 155 156 SkAutoTUnref<SkCanvas> canvas(new_canvas(640, 480)); 157 canvas.get()->save(); 158 canvas.get()->clipRect(SkRect::MakeWH(SkIntToScalar(19), SkIntToScalar(11))); 159 160 paint.setAntiAlias(false); 161 canvas.get()->drawPath(path, paint); 162 paint.setAntiAlias(true); 163 canvas.get()->drawPath(path, paint); 164 165 canvas.get()->restore(); 166 167 // Now do the test again, with the path flipped, so we only draw in the 168 // top half of our bounds, and have the clip intersect our bounds at the 169 // bottom. 170 path.reset(); // preserves our filltype 171 path.moveTo(0, SkIntToScalar(10)); 172 path.quadTo(SkIntToScalar(10), SkIntToScalar(20), 173 SkIntToScalar(20), SkIntToScalar(10)); 174 canvas.get()->clipRect(SkRect::MakeXYWH(SkIntToScalar(0), SkIntToScalar(19), 175 SkIntToScalar(19), SkIntToScalar(11))); 176 177 paint.setAntiAlias(false); 178 canvas.get()->drawPath(path, paint); 179 paint.setAntiAlias(true); 180 canvas.get()->drawPath(path, paint); 181 } 182 183 static void test_bug533() { 184 #ifdef SK_SCALAR_IS_FLOAT 185 /* 186 http://code.google.com/p/skia/issues/detail?id=533 187 This particular test/bug only applies to the float case, where the 188 coordinates are very large. 189 */ 190 SkPath path; 191 path.moveTo(64, 3); 192 path.quadTo(-329936, -100000000, 1153, 330003); 193 194 SkPaint paint; 195 paint.setAntiAlias(true); 196 197 SkAutoTUnref<SkCanvas> canvas(new_canvas(640, 480)); 198 canvas.get()->drawPath(path, paint); 199 #endif 200 } 201 202 static void test_crbug_140642() { 203 /* 204 * We used to see this construct, and due to rounding as we accumulated 205 * our length, the loop where we apply the phase would run off the end of 206 * the array, since it relied on just -= each interval value, which did not 207 * behave as "expected". Now the code explicitly checks for walking off the 208 * end of that array. 209 210 * A different (better) fix might be to rewrite dashing to do all of its 211 * length/phase/measure math using double, but this may need to be 212 * coordinated with SkPathMeasure, to be consistent between the two. 213 214 <path stroke="mintcream" stroke-dasharray="27734 35660 2157846850 247" 215 stroke-dashoffset="-248.135982067"> 216 */ 217 218 #ifdef SK_SCALAR_IS_FLOAT 219 const SkScalar vals[] = { 27734, 35660, 2157846850.0f, 247 }; 220 SkDashPathEffect dontAssert(vals, 4, -248.135982067f); 221 #endif 222 } 223 224 static void test_crbug_124652() { 225 #ifdef SK_SCALAR_IS_FLOAT 226 /* 227 http://code.google.com/p/chromium/issues/detail?id=124652 228 This particular test/bug only applies to the float case, where 229 large values can "swamp" small ones. 230 */ 231 SkScalar intervals[2] = {837099584, 33450}; 232 SkAutoTUnref<SkDashPathEffect> dash( 233 new SkDashPathEffect(intervals, 2, -10, false)); 234 #endif 235 } 236 237 static void test_bigcubic() { 238 #ifdef SK_SCALAR_IS_FLOAT 239 SkPath path; 240 path.moveTo(64, 3); 241 path.cubicTo(-329936, -100000000, -329936, 100000000, 1153, 330003); 242 243 SkPaint paint; 244 paint.setAntiAlias(true); 245 246 SkAutoTUnref<SkCanvas> canvas(new_canvas(640, 480)); 247 canvas.get()->drawPath(path, paint); 248 #endif 249 } 250 251 // we used to assert if the bounds of the device (clip) was larger than 32K 252 // even when the path itself was smaller. We just draw and hope in the debug 253 // version to not assert. 254 static void test_giantaa() { 255 const int W = 400; 256 const int H = 400; 257 SkAutoTUnref<SkCanvas> canvas(new_canvas(33000, 10)); 258 canvas.get()->clear(SK_ColorTRANSPARENT); 259 260 SkPaint paint; 261 paint.setAntiAlias(true); 262 SkPath path; 263 path.addOval(SkRect::MakeXYWH(-10, -10, 20 + W, 20 + H)); 264 canvas.get()->drawPath(path, paint); 265 } 266 267 // Extremely large path_length/dash_length ratios may cause infinite looping 268 // in SkDashPathEffect::filterPath() due to single precision rounding. 269 // The test is quite expensive, but it should get much faster after the fix 270 // for http://crbug.com/165432 goes in. 271 static void test_infinite_dash(skiatest::Reporter* reporter) { 272 SkPath path; 273 path.moveTo(0, 0); 274 path.lineTo(5000000, 0); 275 276 SkScalar intervals[] = { 0.2f, 0.2f }; 277 SkDashPathEffect dash(intervals, 2, 0); 278 279 SkPath filteredPath; 280 SkPaint paint; 281 paint.setStyle(SkPaint::kStroke_Style); 282 paint.setPathEffect(&dash); 283 284 paint.getFillPath(path, &filteredPath); 285 // If we reach this, we passed. 286 REPORTER_ASSERT(reporter, true); 287 } 288 289 // http://crbug.com/165432 290 // Limit extreme dash path effects to avoid exhausting the system memory. 291 static void test_crbug_165432(skiatest::Reporter* reporter) { 292 SkPath path; 293 path.moveTo(0, 0); 294 path.lineTo(10000000, 0); 295 296 SkScalar intervals[] = { 0.5f, 0.5f }; 297 SkDashPathEffect dash(intervals, 2, 0); 298 299 SkPaint paint; 300 paint.setStyle(SkPaint::kStroke_Style); 301 paint.setPathEffect(&dash); 302 303 SkPath filteredPath; 304 SkStrokeRec rec(paint); 305 REPORTER_ASSERT(reporter, !dash.filterPath(&filteredPath, path, &rec, NULL)); 306 REPORTER_ASSERT(reporter, filteredPath.isEmpty()); 307 } 308 309 DEF_TEST(DrawPath, reporter) { 310 test_giantaa(); 311 test_bug533(); 312 test_bigcubic(); 313 test_crbug_124652(); 314 test_crbug_140642(); 315 test_crbug_140803(); 316 test_inversepathwithclip(); 317 // why? 318 if (false) test_crbug131181(); 319 test_infinite_dash(reporter); 320 test_crbug_165432(reporter); 321 test_big_aa_rect(reporter); 322 } 323