GradientTest.cpp revision 3d1a6bc5f3124dd5cd237ccc39ead26fe4b3355f
1/* 2 * Copyright 2011 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 "SkCanvas.h" 9#include "SkColorPriv.h" 10#include "SkColorShader.h" 11#include "SkGradientShader.h" 12#include "SkLinearGradient.h" 13#include "SkShader.h" 14#include "SkSurface.h" 15#include "SkTemplates.h" 16#include "SkTLazy.h" 17#include "Test.h" 18 19// https://code.google.com/p/chromium/issues/detail?id=448299 20// Giant (inverse) matrix causes overflow when converting/computing using 32.32 21// Before the fix, we would assert (and then crash). 22static void test_big_grad(skiatest::Reporter* reporter) { 23 const SkColor colors[] = { SK_ColorRED, SK_ColorBLUE }; 24 const SkPoint pts[] = {{ 15, 14.7112684f }, { 0.709064007f, 12.6108112f }}; 25 SkPaint paint; 26 paint.setShader(SkGradientShader::MakeLinear(pts, colors, nullptr, 2, 27 SkShader::kClamp_TileMode)); 28 29 SkBitmap bm; 30 bm.allocN32Pixels(2000, 1); 31 SkCanvas c(bm); 32 33 const SkScalar affine[] = { 34 1.06608627e-06f, 4.26434525e-07f, 6.2855f, 2.6611f, 273.4393f, 244.0046f 35 }; 36 SkMatrix matrix; 37 matrix.setAffine(affine); 38 c.concat(matrix); 39 40 c.drawPaint(paint); 41} 42 43struct GradRec { 44 int fColorCount; 45 const SkColor* fColors; 46 const SkScalar* fPos; 47 const SkPoint* fPoint; // 2 48 const SkScalar* fRadius; // 2 49 SkShader::TileMode fTileMode; 50 51 void gradCheck(skiatest::Reporter* reporter, const sk_sp<SkShader>& shader, 52 SkShader::GradientInfo* info, 53 SkShader::GradientType gt) const { 54 SkAutoTMalloc<SkColor> colorStorage(fColorCount); 55 SkAutoTMalloc<SkScalar> posStorage(fColorCount); 56 57 info->fColorCount = fColorCount; 58 info->fColors = colorStorage; 59 info->fColorOffsets = posStorage.get(); 60 REPORTER_ASSERT(reporter, shader->asAGradient(info) == gt); 61 62 REPORTER_ASSERT(reporter, info->fColorCount == fColorCount); 63 REPORTER_ASSERT(reporter, 64 !memcmp(info->fColors, fColors, fColorCount * sizeof(SkColor))); 65 REPORTER_ASSERT(reporter, 66 !memcmp(info->fColorOffsets, fPos, fColorCount * sizeof(SkScalar))); 67 REPORTER_ASSERT(reporter, fTileMode == info->fTileMode); 68 } 69}; 70 71 72static void none_gradproc(skiatest::Reporter* reporter, const GradRec&, const GradRec&) { 73 sk_sp<SkShader> s(SkShader::MakeEmptyShader()); 74 REPORTER_ASSERT(reporter, SkShader::kNone_GradientType == s->asAGradient(nullptr)); 75} 76 77static void color_gradproc(skiatest::Reporter* reporter, const GradRec& rec, const GradRec&) { 78 sk_sp<SkShader> s(new SkColorShader(rec.fColors[0])); 79 REPORTER_ASSERT(reporter, SkShader::kColor_GradientType == s->asAGradient(nullptr)); 80 81 SkShader::GradientInfo info; 82 info.fColors = nullptr; 83 info.fColorCount = 0; 84 s->asAGradient(&info); 85 REPORTER_ASSERT(reporter, 1 == info.fColorCount); 86} 87 88static void linear_gradproc(skiatest::Reporter* reporter, const GradRec& buildRec, 89 const GradRec& checkRec) { 90 sk_sp<SkShader> s(SkGradientShader::MakeLinear(buildRec.fPoint, buildRec.fColors, buildRec.fPos, 91 buildRec.fColorCount, buildRec.fTileMode)); 92 93 SkShader::GradientInfo info; 94 checkRec.gradCheck(reporter, s, &info, SkShader::kLinear_GradientType); 95 REPORTER_ASSERT(reporter, !memcmp(info.fPoint, checkRec.fPoint, 2 * sizeof(SkPoint))); 96} 97 98static void radial_gradproc(skiatest::Reporter* reporter, const GradRec& buildRec, 99 const GradRec& checkRec) { 100 sk_sp<SkShader> s(SkGradientShader::MakeRadial(buildRec.fPoint[0], buildRec.fRadius[0], 101 buildRec.fColors, buildRec.fPos, 102 buildRec.fColorCount, buildRec.fTileMode)); 103 104 SkShader::GradientInfo info; 105 checkRec.gradCheck(reporter, s, &info, SkShader::kRadial_GradientType); 106 REPORTER_ASSERT(reporter, info.fPoint[0] == checkRec.fPoint[0]); 107 REPORTER_ASSERT(reporter, info.fRadius[0] == checkRec.fRadius[0]); 108} 109 110static void sweep_gradproc(skiatest::Reporter* reporter, const GradRec& buildRec, 111 const GradRec& checkRec) { 112 sk_sp<SkShader> s(SkGradientShader::MakeSweep(buildRec.fPoint[0].fX, buildRec.fPoint[0].fY, 113 buildRec.fColors, buildRec.fPos, 114 buildRec.fColorCount)); 115 116 SkShader::GradientInfo info; 117 checkRec.gradCheck(reporter, s, &info, SkShader::kSweep_GradientType); 118 REPORTER_ASSERT(reporter, info.fPoint[0] == checkRec.fPoint[0]); 119} 120 121static void conical_gradproc(skiatest::Reporter* reporter, const GradRec& buildRec, 122 const GradRec& checkRec) { 123 sk_sp<SkShader> s(SkGradientShader::MakeTwoPointConical(buildRec.fPoint[0], 124 buildRec.fRadius[0], 125 buildRec.fPoint[1], 126 buildRec.fRadius[1], 127 buildRec.fColors, 128 buildRec.fPos, 129 buildRec.fColorCount, 130 buildRec.fTileMode)); 131 132 SkShader::GradientInfo info; 133 checkRec.gradCheck(reporter, s, &info, SkShader::kConical_GradientType); 134 REPORTER_ASSERT(reporter, !memcmp(info.fPoint, checkRec.fPoint, 2 * sizeof(SkPoint))); 135 REPORTER_ASSERT(reporter, !memcmp(info.fRadius, checkRec.fRadius, 2 * sizeof(SkScalar))); 136} 137 138// Ensure that repeated color gradients behave like drawing a single color 139static void TestConstantGradient(skiatest::Reporter*) { 140 const SkPoint pts[] = { 141 { 0, 0 }, 142 { SkIntToScalar(10), 0 } 143 }; 144 SkColor colors[] = { SK_ColorBLUE, SK_ColorBLUE }; 145 const SkScalar pos[] = { 0, SK_Scalar1 }; 146 SkPaint paint; 147 paint.setShader(SkGradientShader::MakeLinear(pts, colors, pos, 2, SkShader::kClamp_TileMode)); 148 SkBitmap outBitmap; 149 outBitmap.allocN32Pixels(10, 1); 150 SkCanvas canvas(outBitmap); 151 canvas.drawPaint(paint); 152 SkAutoLockPixels alp(outBitmap); 153 for (int i = 0; i < 10; i++) { 154 // The following is commented out because it currently fails 155 // Related bug: https://code.google.com/p/skia/issues/detail?id=1098 156 157 // REPORTER_ASSERT(reporter, SK_ColorBLUE == outBitmap.getColor(i, 0)); 158 } 159} 160 161typedef void (*GradProc)(skiatest::Reporter* reporter, const GradRec&, const GradRec&); 162 163static void TestGradientShaders(skiatest::Reporter* reporter) { 164 static const SkColor gColors[] = { SK_ColorRED, SK_ColorGREEN, SK_ColorBLUE }; 165 static const SkScalar gPos[] = { 0, SK_ScalarHalf, SK_Scalar1 }; 166 static const SkPoint gPts[] = { 167 { 0, 0 }, 168 { SkIntToScalar(10), SkIntToScalar(20) } 169 }; 170 static const SkScalar gRad[] = { SkIntToScalar(1), SkIntToScalar(2) }; 171 172 GradRec rec; 173 rec.fColorCount = SK_ARRAY_COUNT(gColors); 174 rec.fColors = gColors; 175 rec.fPos = gPos; 176 rec.fPoint = gPts; 177 rec.fRadius = gRad; 178 rec.fTileMode = SkShader::kClamp_TileMode; 179 180 static const GradProc gProcs[] = { 181 none_gradproc, 182 color_gradproc, 183 linear_gradproc, 184 radial_gradproc, 185 sweep_gradproc, 186 conical_gradproc, 187 }; 188 189 for (size_t i = 0; i < SK_ARRAY_COUNT(gProcs); ++i) { 190 gProcs[i](reporter, rec, rec); 191 } 192} 193 194static void TestGradientOptimization(skiatest::Reporter* reporter) { 195 static const struct { 196 GradProc fProc; 197 bool fIsClampRestricted; 198 } gProcInfo[] = { 199 { linear_gradproc , false }, 200 { radial_gradproc , false }, 201 { sweep_gradproc , true }, // sweep is funky in that it always pretends to be kClamp. 202 { conical_gradproc, false }, 203 }; 204 205 static const SkColor gC_00[] = { 0xff000000, 0xff000000 }; 206 static const SkColor gC_01[] = { 0xff000000, 0xffffffff }; 207 static const SkColor gC_11[] = { 0xffffffff, 0xffffffff }; 208 static const SkColor gC_001[] = { 0xff000000, 0xff000000, 0xffffffff }; 209 static const SkColor gC_011[] = { 0xff000000, 0xffffffff, 0xffffffff }; 210 static const SkColor gC_0011[] = { 0xff000000, 0xff000000, 0xffffffff, 0xffffffff }; 211 212 static const SkScalar gP_01[] = { 0, 1 }; 213 static const SkScalar gP_001[] = { 0, 0, 1 }; 214 static const SkScalar gP_011[] = { 0, 1, 1 }; 215 static const SkScalar gP_0x1[] = { 0, .5f, 1 }; 216 static const SkScalar gP_0011[] = { 0, 0, 1, 1 }; 217 218 static const SkPoint gPts[] = { {0, 0}, {1, 1} }; 219 static const SkScalar gRadii[] = { 1, 2 }; 220 221 static const struct { 222 const SkColor* fCol; 223 const SkScalar* fPos; 224 int fCount; 225 226 const SkColor* fExpectedCol; 227 const SkScalar* fExpectedPos; 228 int fExpectedCount; 229 bool fRequiresNonClamp; 230 } gTests[] = { 231 { gC_001, gP_001, 3, gC_01, gP_01, 2, false }, 232 { gC_001, gP_011, 3, gC_00, gP_01, 2, true }, 233 { gC_001, gP_0x1, 3, gC_001, gP_0x1, 3, false }, 234 { gC_001, nullptr, 3, gC_001, gP_0x1, 3, false }, 235 236 { gC_011, gP_001, 3, gC_11, gP_01, 2, true }, 237 { gC_011, gP_011, 3, gC_01, gP_01, 2, false }, 238 { gC_011, gP_0x1, 3, gC_011, gP_0x1, 3, false }, 239 { gC_011, nullptr, 3, gC_011, gP_0x1, 3, false }, 240 241 { gC_0011, gP_0011, 4, gC_0011, gP_0011, 4, false }, 242 }; 243 244 for (size_t i = 0; i < SK_ARRAY_COUNT(gProcInfo); ++i) { 245 for (int mode = 0; mode < SkShader::kTileModeCount; ++mode) { 246 if (gProcInfo[i].fIsClampRestricted && mode != SkShader::kClamp_TileMode) { 247 continue; 248 } 249 250 for (size_t t = 0; t < SK_ARRAY_COUNT(gTests); ++t) { 251 GradRec rec; 252 rec.fColorCount = gTests[t].fCount; 253 rec.fColors = gTests[t].fCol; 254 rec.fPos = gTests[t].fPos; 255 rec.fTileMode = static_cast<SkShader::TileMode>(mode); 256 rec.fPoint = gPts; 257 rec.fRadius = gRadii; 258 259 GradRec expected = rec; 260 if (!gTests[t].fRequiresNonClamp || mode != SkShader::kClamp_TileMode) { 261 expected.fColorCount = gTests[t].fExpectedCount; 262 expected.fColors = gTests[t].fExpectedCol; 263 expected.fPos = gTests[t].fExpectedPos; 264 } 265 266 gProcInfo[i].fProc(reporter, rec, expected); 267 } 268 } 269 } 270} 271 272static void test_nearly_vertical(skiatest::Reporter* reporter) { 273 auto surface(SkSurface::MakeRasterN32Premul(200, 200)); 274 275 const SkPoint pts[] = {{ 100, 50 }, { 100.0001f, 50000 }}; 276 const SkColor colors[] = { SK_ColorBLACK, SK_ColorWHITE }; 277 const SkScalar pos[] = { 0, 1 }; 278 SkPaint paint; 279 paint.setShader(SkGradientShader::MakeLinear(pts, colors, pos, 2, SkShader::kClamp_TileMode)); 280 281 surface->getCanvas()->drawPaint(paint); 282} 283 284static void test_vertical(skiatest::Reporter* reporter) { 285 auto surface(SkSurface::MakeRasterN32Premul(200, 200)); 286 287 const SkPoint pts[] = {{ 100, 50 }, { 100, 50 }}; 288 const SkColor colors[] = { SK_ColorBLACK, SK_ColorWHITE }; 289 const SkScalar pos[] = { 0, 1 }; 290 SkPaint paint; 291 paint.setShader(SkGradientShader::MakeLinear(pts, colors, pos, 2, SkShader::kClamp_TileMode)); 292 293 surface->getCanvas()->drawPaint(paint); 294} 295 296// A linear gradient interval can, due to numerical imprecision (likely in the divide) 297// finish an interval with the final fx not landing outside of [p0...p1]. 298// The old code had an assert which this test triggered. 299// We now explicitly clamp the resulting fx value. 300static void test_linear_fuzz(skiatest::Reporter* reporter) { 301 auto surface(SkSurface::MakeRasterN32Premul(1300, 630)); 302 303 const SkPoint pts[] = {{ 179.5f, -179.5f }, { 1074.5f, 715.5f }}; 304 const SkColor colors[] = { SK_ColorBLACK, SK_ColorWHITE, SK_ColorBLACK, SK_ColorWHITE }; 305 const SkScalar pos[] = {0, 0.200000003f, 0.800000012f, 1 }; 306 307 SkPaint paint; 308 paint.setShader(SkGradientShader::MakeLinear(pts, colors, pos, 4, SkShader::kClamp_TileMode)); 309 310 SkRect r = {0, 83, 1254, 620}; 311 surface->getCanvas()->drawRect(r, paint); 312} 313 314// https://bugs.chromium.org/p/skia/issues/detail?id=5023 315// We should still shade pixels for which the radius is exactly 0. 316static void test_two_point_conical_zero_radius(skiatest::Reporter* reporter) { 317 auto surface(SkSurface::MakeRasterN32Premul(5, 5)); 318 surface->getCanvas()->clear(SK_ColorRED); 319 320 const SkColor colors[] = { SK_ColorGREEN, SK_ColorBLUE }; 321 SkPaint p; 322 p.setShader(SkGradientShader::MakeTwoPointConical( 323 SkPoint::Make(2.5f, 2.5f), 0, 324 SkPoint::Make(3.0f, 3.0f), 10, 325 colors, nullptr, SK_ARRAY_COUNT(colors), SkShader::kClamp_TileMode)); 326 surface->getCanvas()->drawPaint(p); 327 328 // r == 0 for the center pixel. 329 // verify that we draw it (no red bleed) 330 SkPMColor centerPMColor; 331 surface->readPixels(SkImageInfo::MakeN32Premul(1, 1), ¢erPMColor, sizeof(SkPMColor), 2, 2); 332 REPORTER_ASSERT(reporter, SkGetPackedR32(centerPMColor) == 0); 333} 334 335// http://crbug.com/599458 336static void test_clamping_overflow(skiatest::Reporter*) { 337 SkPaint p; 338 const SkColor colors[] = { SK_ColorRED, SK_ColorGREEN }; 339 const SkPoint pts1[] = { SkPoint::Make(1001, 1000001), SkPoint::Make(1000.99f, 1000000) }; 340 341 p.setShader(SkGradientShader::MakeLinear(pts1, colors, nullptr, 2, SkShader::kClamp_TileMode)); 342 343 sk_sp<SkSurface> surface(SkSurface::MakeRasterN32Premul(50, 50)); 344 surface->getCanvas()->scale(100, 100); 345 surface->getCanvas()->drawPaint(p); 346 347 const SkPoint pts2[] = { SkPoint::Make(10000.99f, 1000000), SkPoint::Make(10001, 1000001) }; 348 p.setShader(SkGradientShader::MakeLinear(pts2, colors, nullptr, 2, SkShader::kClamp_TileMode)); 349 surface->getCanvas()->drawPaint(p); 350 351 // Passes if we don't trigger asserts. 352} 353 354// http://crbug.com/636194 355static void test_degenerate_linear(skiatest::Reporter*) { 356 SkPaint p; 357 const SkColor colors[] = { SK_ColorRED, SK_ColorGREEN }; 358 const SkPoint pts[] = { 359 SkPoint::Make(-46058024627067344430605278824628224.0f, 0), 360 SkPoint::Make(SK_ScalarMax, 0) 361 }; 362 363 p.setShader(SkGradientShader::MakeLinear(pts, colors, nullptr, 2, SkShader::kClamp_TileMode)); 364 sk_sp<SkSurface> surface(SkSurface::MakeRasterN32Premul(50, 50)); 365 surface->getCanvas()->drawPaint(p); 366 367 // Passes if we don't trigger asserts. 368} 369 370// "Interesting" fuzzer values. 371static void test_linear_fuzzer(skiatest::Reporter*) { 372 static const SkColor gColors0[] = { 0x30303030, 0x30303030 }; 373 static const SkColor gColors1[] = { 0x30303030, 0x30303030, 0x30303030 }; 374 375 static const SkScalar gPos1[] = { 0, 0, 1 }; 376 377 static const SkScalar gMatrix0[9] = 378 { 6.40969056e-10f, 0 , 6.40969056e-10f, 379 0 , 4.42539023e-39f, 6.40969056e-10f, 380 0 , 0 , 1 }; 381 static const SkScalar gMatrix1[9] = 382 { -2.75294113f , 6.40969056e-10f, 6.40969056e-10f, 383 6.40969056e-10f, 6.40969056e-10f, -3.32810161e+24f, 384 6.40969056e-10f, 6.40969056e-10f, 0 }; 385 386 static const struct { 387 SkPoint fPts[2]; 388 const SkColor* fColors; 389 const SkScalar* fPos; 390 int fCount; 391 SkShader::TileMode fTileMode; 392 uint32_t fFlags; 393 const SkScalar* fLocalMatrix; 394 const SkScalar* fGlobalMatrix; 395 } gConfigs[] = { 396 { 397 {{0, -2.752941f}, {0, 0}}, 398 gColors0, 399 nullptr, 400 SK_ARRAY_COUNT(gColors0), 401 SkShader::kClamp_TileMode, 402 0, 403 gMatrix0, 404 nullptr 405 }, 406 { 407 {{4.42539023e-39f, -4.42539023e-39f}, {9.78041162e-15f, 4.42539023e-39f}}, 408 gColors1, 409 gPos1, 410 SK_ARRAY_COUNT(gColors1), 411 SkShader::kClamp_TileMode, 412 0, 413 nullptr, 414 gMatrix1 415 }, 416 }; 417 418 static const uint32_t gForceFlags[] = { 0, SkLinearGradient::kForce4fContext_PrivateFlag }; 419 420 sk_sp<SkSurface> surface = SkSurface::MakeRasterN32Premul(100, 100); 421 SkCanvas* canvas = surface->getCanvas(); 422 SkPaint paint; 423 424 for (auto forceFlags : gForceFlags) { 425 for (const auto& config : gConfigs) { 426 SkAutoCanvasRestore acr(canvas, false); 427 SkTLazy<SkMatrix> localMatrix; 428 if (config.fLocalMatrix) { 429 localMatrix.init(); 430 localMatrix.get()->set9(config.fLocalMatrix); 431 } 432 433 paint.setShader(SkGradientShader::MakeLinear(config.fPts, 434 config.fColors, 435 config.fPos, 436 config.fCount, 437 config.fTileMode, 438 config.fFlags | forceFlags, 439 localMatrix.getMaybeNull())); 440 if (config.fGlobalMatrix) { 441 SkMatrix m; 442 m.set9(config.fGlobalMatrix); 443 canvas->save(); 444 canvas->concat(m); 445 } 446 447 canvas->drawPaint(paint); 448 } 449 } 450} 451 452 453DEF_TEST(Gradient, reporter) { 454 TestGradientShaders(reporter); 455 TestGradientOptimization(reporter); 456 TestConstantGradient(reporter); 457 test_big_grad(reporter); 458 test_nearly_vertical(reporter); 459 test_vertical(reporter); 460 test_linear_fuzz(reporter); 461 test_two_point_conical_zero_radius(reporter); 462 test_clamping_overflow(reporter); 463 test_degenerate_linear(reporter); 464 test_linear_fuzzer(reporter); 465} 466