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 "include/core/SkBitmap.h" 9#include "include/core/SkBlendMode.h" 10#include "include/core/SkCanvas.h" 11#include "include/core/SkClipOp.h" 12#include "include/core/SkColor.h" 13#include "include/core/SkDocument.h" 14#include "include/core/SkFlattenable.h" 15#include "include/core/SkImageFilter.h" 16#include "include/core/SkImageInfo.h" 17#include "include/core/SkMatrix.h" 18#include "include/core/SkPaint.h" 19#include "include/core/SkPath.h" 20#include "include/core/SkPictureRecorder.h" 21#include "include/core/SkPixmap.h" 22#include "include/core/SkPoint.h" 23#include "include/core/SkRect.h" 24#include "include/core/SkRefCnt.h" 25#include "include/core/SkRegion.h" 26#include "include/core/SkScalar.h" 27#include "include/core/SkShader.h" 28#include "include/core/SkSize.h" 29#include "include/core/SkStream.h" 30#include "include/core/SkString.h" 31#include "include/core/SkSurface.h" 32#include "include/core/SkTypes.h" 33#include "include/core/SkVertices.h" 34#include "include/docs/SkPDFDocument.h" 35#include "include/effects/SkImageFilters.h" 36#include "include/private/SkMalloc.h" 37#include "include/private/SkTemplates.h" 38#include "include/utils/SkNWayCanvas.h" 39#include "include/utils/SkPaintFilterCanvas.h" 40#include "src/core/SkBigPicture.h" 41#include "src/core/SkImageFilter_Base.h" 42#include "src/core/SkRecord.h" 43#include "src/core/SkSpecialImage.h" 44#include "src/utils/SkCanvasStack.h" 45#include "tests/Test.h" 46 47#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK 48#include "include/core/SkColorSpace.h" 49#include "include/private/SkColorData.h" 50#endif 51 52#include <memory> 53#include <utility> 54 55class SkReadBuffer; 56 57struct ClipRectVisitor { 58 skiatest::Reporter* r; 59 60 template <typename T> 61 SkRect operator()(const T&) { 62 REPORTER_ASSERT(r, false, "unexpected record"); 63 return {1,1,0,0}; 64 } 65 66 SkRect operator()(const SkRecords::ClipRect& op) { 67 return op.rect; 68 } 69}; 70 71DEF_TEST(canvas_unsorted_clip, r) { 72 // Test that sorted and unsorted clip rects are forwarded 73 // to picture subclasses and/or devices sorted. 74 // 75 // We can't just test this with an SkCanvas on stack and 76 // SkCanvas::getLocalClipBounds(), as that only tests the raster device, 77 // which sorts these rects itself. 78 for (SkRect clip : {SkRect{0,0,5,5}, SkRect{5,5,0,0}}) { 79 SkPictureRecorder rec; 80 rec.beginRecording({0,0,10,10}) 81 ->clipRect(clip); 82 sk_sp<SkPicture> pic = rec.finishRecordingAsPicture(); 83 84 auto bp = (const SkBigPicture*)pic.get(); 85 const SkRecord* record = bp->record(); 86 87 REPORTER_ASSERT(r, record->count() == 1); 88 REPORTER_ASSERT(r, record->visit(0, ClipRectVisitor{r}) 89 .isSorted()); 90 } 91} 92 93DEF_TEST(canvas_clipbounds, reporter) { 94 SkCanvas canvas(10, 10); 95 SkIRect irect, irect2; 96 SkRect rect, rect2; 97 98 irect = canvas.getDeviceClipBounds(); 99 REPORTER_ASSERT(reporter, irect == SkIRect::MakeWH(10, 10)); 100 REPORTER_ASSERT(reporter, canvas.getDeviceClipBounds(&irect2)); 101 REPORTER_ASSERT(reporter, irect == irect2); 102 103 // local bounds are always too big today -- can we trim them? 104 rect = canvas.getLocalClipBounds(); 105 REPORTER_ASSERT(reporter, rect.contains(SkRect::MakeWH(10, 10))); 106 REPORTER_ASSERT(reporter, canvas.getLocalClipBounds(&rect2)); 107 REPORTER_ASSERT(reporter, rect == rect2); 108 109 canvas.clipRect(SkRect::MakeEmpty()); 110 111 irect = canvas.getDeviceClipBounds(); 112 REPORTER_ASSERT(reporter, irect == SkIRect::MakeEmpty()); 113 REPORTER_ASSERT(reporter, !canvas.getDeviceClipBounds(&irect2)); 114 REPORTER_ASSERT(reporter, irect == irect2); 115 116 rect = canvas.getLocalClipBounds(); 117 REPORTER_ASSERT(reporter, rect == SkRect::MakeEmpty()); 118 REPORTER_ASSERT(reporter, !canvas.getLocalClipBounds(&rect2)); 119 REPORTER_ASSERT(reporter, rect == rect2); 120 121 // Test for wacky sizes that we (historically) have guarded against 122 { 123 SkCanvas c(-10, -20); 124 REPORTER_ASSERT(reporter, c.getBaseLayerSize() == SkISize::MakeEmpty()); 125 126 SkPictureRecorder().beginRecording({ 5, 5, 4, 4 }); 127 } 128} 129 130// Will call proc with multiple styles of canvas (recording, raster, pdf) 131template <typename F> static void multi_canvas_driver(int w, int h, F proc) { 132 proc(SkPictureRecorder().beginRecording(SkRect::MakeIWH(w, h))); 133 134 SkNullWStream stream; 135 if (auto doc = SkPDF::MakeDocument(&stream)) { 136 proc(doc->beginPage(SkIntToScalar(w), SkIntToScalar(h))); 137 } 138 139 proc(SkSurface::MakeRasterN32Premul(w, h, nullptr)->getCanvas()); 140} 141 142const SkIRect gBaseRestrictedR = { 0, 0, 10, 10 }; 143 144static void test_restriction(skiatest::Reporter* reporter, SkCanvas* canvas) { 145 REPORTER_ASSERT(reporter, canvas->getDeviceClipBounds() == gBaseRestrictedR); 146 147 const SkIRect restrictionR = { 2, 2, 8, 8 }; 148 canvas->androidFramework_setDeviceClipRestriction(restrictionR); 149 REPORTER_ASSERT(reporter, canvas->getDeviceClipBounds() == restrictionR); 150 151 const SkIRect clipR = { 4, 4, 6, 6 }; 152 canvas->clipRect(SkRect::Make(clipR), SkClipOp::kIntersect); 153 REPORTER_ASSERT(reporter, canvas->getDeviceClipBounds() == clipR); 154} 155 156/** 157 * Clip restriction logic exists in the canvas itself, and in various kinds of devices. 158 * 159 * This test explicitly tries to exercise that variety: 160 * - picture : empty device but exercises canvas itself 161 * - pdf : uses SkClipStack in its device (as does SVG and GPU) 162 * - raster : uses SkRasterClip in its device 163 */ 164DEF_TEST(canvas_clip_restriction, reporter) { 165 multi_canvas_driver(gBaseRestrictedR.width(), gBaseRestrictedR.height(), 166 [reporter](SkCanvas* canvas) { test_restriction(reporter, canvas); }); 167} 168 169DEF_TEST(canvas_empty_clip, reporter) { 170 multi_canvas_driver(50, 50, [reporter](SkCanvas* canvas) { 171 canvas->save(); 172 canvas->clipRect({0, 0, 20, 40 }); 173 REPORTER_ASSERT(reporter, !canvas->isClipEmpty()); 174 canvas->clipRect({30, 0, 50, 40 }); 175 REPORTER_ASSERT(reporter, canvas->isClipEmpty()); 176 }); 177} 178 179DEF_TEST(CanvasNewRasterTest, reporter) { 180 SkImageInfo info = SkImageInfo::MakeN32Premul(10, 10); 181 const size_t minRowBytes = info.minRowBytes(); 182 const size_t size = info.computeByteSize(minRowBytes); 183 SkAutoTMalloc<SkPMColor> storage(size); 184 SkPMColor* baseAddr = storage.get(); 185 sk_bzero(baseAddr, size); 186 187 std::unique_ptr<SkCanvas> canvas = SkCanvas::MakeRasterDirect(info, baseAddr, minRowBytes); 188 REPORTER_ASSERT(reporter, canvas); 189 190 SkPixmap pmap; 191 const SkPMColor* addr = canvas->peekPixels(&pmap) ? pmap.addr32() : nullptr; 192 REPORTER_ASSERT(reporter, addr); 193 REPORTER_ASSERT(reporter, info == pmap.info()); 194 REPORTER_ASSERT(reporter, minRowBytes == pmap.rowBytes()); 195 for (int y = 0; y < info.height(); ++y) { 196 for (int x = 0; x < info.width(); ++x) { 197 REPORTER_ASSERT(reporter, 0 == addr[x]); 198 } 199 addr = (const SkPMColor*)((const char*)addr + pmap.rowBytes()); 200 } 201 202 // unaligned rowBytes 203 REPORTER_ASSERT(reporter, nullptr == SkCanvas::MakeRasterDirect(info, baseAddr, 204 minRowBytes + 1)); 205 206 // now try a deliberately bad info 207 info = info.makeWH(-1, info.height()); 208 REPORTER_ASSERT(reporter, nullptr == SkCanvas::MakeRasterDirect(info, baseAddr, minRowBytes)); 209 210 // too big 211 info = info.makeWH(1 << 30, 1 << 30); 212 REPORTER_ASSERT(reporter, nullptr == SkCanvas::MakeRasterDirect(info, baseAddr, minRowBytes)); 213 214 // not a valid pixel type 215 info = SkImageInfo::Make(10, 10, kUnknown_SkColorType, info.alphaType()); 216 REPORTER_ASSERT(reporter, nullptr == SkCanvas::MakeRasterDirect(info, baseAddr, minRowBytes)); 217 218 // We should not succeed with a zero-sized valid info 219 info = SkImageInfo::MakeN32Premul(0, 0); 220 canvas = SkCanvas::MakeRasterDirect(info, baseAddr, minRowBytes); 221 REPORTER_ASSERT(reporter, nullptr == canvas); 222} 223 224static SkPath make_path_from_rect(SkRect r) { 225 SkPath path; 226 path.addRect(r); 227 return path; 228} 229 230static SkRegion make_region_from_irect(SkIRect r) { 231 SkRegion region; 232 region.setRect(r); 233 return region; 234} 235 236static SkBitmap make_n32_bitmap(int w, int h, SkColor c = SK_ColorWHITE) { 237 SkBitmap bm; 238 bm.allocN32Pixels(w, h); 239 bm.eraseColor(c); 240 return bm; 241} 242 243// Constants used by test steps 244static constexpr SkRect kRect = {0, 0, 2, 1}; 245static constexpr SkColor kColor = 0x01020304; 246static constexpr int kWidth = 2; 247static constexpr int kHeight = 2; 248 249using CanvasTest = void (*)(SkCanvas*, skiatest::Reporter*); 250 251static CanvasTest kCanvasTests[] = { 252 [](SkCanvas* c, skiatest::Reporter* r) { 253 c->translate(SkIntToScalar(1), SkIntToScalar(2)); 254 }, 255 [](SkCanvas* c, skiatest::Reporter* r) { 256 c->scale(SkIntToScalar(1), SkIntToScalar(2)); 257 }, 258 [](SkCanvas* c, skiatest::Reporter* r) { 259 c->rotate(SkIntToScalar(1)); 260 }, 261 [](SkCanvas* c, skiatest::Reporter* r) { 262 c->skew(SkIntToScalar(1), SkIntToScalar(2)); 263 }, 264 [](SkCanvas* c, skiatest::Reporter* r) { 265 c->concat(SkMatrix::Scale(2, 3)); 266 }, 267 [](SkCanvas* c, skiatest::Reporter* r) { 268 c->setMatrix(SkMatrix::Scale(2, 3)); 269 }, 270 [](SkCanvas* c, skiatest::Reporter* r) { 271 c->clipRect(kRect); 272 }, 273 [](SkCanvas* c, skiatest::Reporter* r) { 274 c->clipPath(make_path_from_rect(SkRect{0, 0, 2, 1})); 275 }, 276 [](SkCanvas* c, skiatest::Reporter* r) { 277 c->clipRegion(make_region_from_irect(SkIRect{0, 0, 2, 1})); 278 }, 279 [](SkCanvas* c, skiatest::Reporter* r) { 280 c->clear(kColor); 281 }, 282 [](SkCanvas* c, skiatest::Reporter* r) { 283 int saveCount = c->getSaveCount(); 284 c->save(); 285 c->translate(SkIntToScalar(1), SkIntToScalar(2)); 286 c->clipRegion(make_region_from_irect(SkIRect{0, 0, 2, 1})); 287 c->restore(); 288 REPORTER_ASSERT(r, c->getSaveCount() == saveCount); 289 REPORTER_ASSERT(r, c->getTotalMatrix().isIdentity()); 290 //REPORTER_ASSERT(reporter, c->getTotalClip() != kTestRegion); 291 }, 292 [](SkCanvas* c, skiatest::Reporter* r) { 293 int saveCount = c->getSaveCount(); 294 c->saveLayer(nullptr, nullptr); 295 c->restore(); 296 REPORTER_ASSERT(r, c->getSaveCount() == saveCount); 297 }, 298 [](SkCanvas* c, skiatest::Reporter* r) { 299 int saveCount = c->getSaveCount(); 300 c->saveLayer(&kRect, nullptr); 301 c->restore(); 302 REPORTER_ASSERT(r, c->getSaveCount() == saveCount); 303 }, 304 [](SkCanvas* c, skiatest::Reporter* r) { 305 int saveCount = c->getSaveCount(); 306 SkPaint p; 307 c->saveLayer(nullptr, &p); 308 c->restore(); 309 REPORTER_ASSERT(r, c->getSaveCount() == saveCount); 310 }, 311 [](SkCanvas* c, skiatest::Reporter* r) { 312 // This test exercises a functionality in SkPicture that leads to the 313 // recording of restore offset placeholders. This test will trigger an 314 // assertion at playback time if the placeholders are not properly 315 // filled when the recording ends. 316 c->clipRect(kRect); 317 c->clipRegion(make_region_from_irect(SkIRect{0, 0, 2, 1})); 318 }, 319 [](SkCanvas* c, skiatest::Reporter* r) { 320 // exercise fix for http://code.google.com/p/skia/issues/detail?id=560 321 // ('SkPathStroker::lineTo() fails for line with length SK_ScalarNearlyZero') 322 SkPaint paint; 323 paint.setStrokeWidth(SkIntToScalar(1)); 324 paint.setStyle(SkPaint::kStroke_Style); 325 SkPath path; 326 path.moveTo(SkPoint{ 0, 0 }); 327 path.lineTo(SkPoint{ 0, SK_ScalarNearlyZero }); 328 path.lineTo(SkPoint{ SkIntToScalar(1), 0 }); 329 path.lineTo(SkPoint{ SkIntToScalar(1), SK_ScalarNearlyZero/2 }); 330 // test nearly zero length path 331 c->drawPath(path, paint); 332 }, 333 [](SkCanvas* c, skiatest::Reporter* r) { 334 SkPictureRecorder recorder; 335 SkCanvas* testCanvas = recorder.beginRecording(SkIntToScalar(kWidth), 336 SkIntToScalar(kHeight)); 337 testCanvas->scale(SkIntToScalar(2), SkIntToScalar(1)); 338 testCanvas->clipRect(kRect); 339 testCanvas->drawRect(kRect, SkPaint()); 340 c->drawPicture(recorder.finishRecordingAsPicture()); 341 }, 342 [](SkCanvas* c, skiatest::Reporter* r) { 343 int baseSaveCount = c->getSaveCount(); 344 int n = c->save(); 345 REPORTER_ASSERT(r, baseSaveCount == n); 346 REPORTER_ASSERT(r, baseSaveCount + 1 == c->getSaveCount()); 347 c->save(); 348 c->save(); 349 REPORTER_ASSERT(r, baseSaveCount + 3 == c->getSaveCount()); 350 c->restoreToCount(baseSaveCount + 1); 351 REPORTER_ASSERT(r, baseSaveCount + 1 == c->getSaveCount()); 352 353 // should this pin to 1, or be a no-op, or crash? 354 c->restoreToCount(0); 355 REPORTER_ASSERT(r, 1 == c->getSaveCount()); 356 }, 357 [](SkCanvas* c, skiatest::Reporter* r) { 358 // This test step challenges the TestDeferredCanvasStateConsistency 359 // test cases because the opaque paint can trigger an optimization 360 // that discards previously recorded commands. The challenge is to maintain 361 // correct clip and matrix stack state. 362 c->resetMatrix(); 363 c->rotate(SkIntToScalar(30)); 364 c->save(); 365 c->translate(SkIntToScalar(2), SkIntToScalar(1)); 366 c->save(); 367 c->scale(SkIntToScalar(3), SkIntToScalar(3)); 368 SkPaint paint; 369 paint.setColor(0xFFFFFFFF); 370 c->drawPaint(paint); 371 c->restore(); 372 c->restore(); 373 }, 374 [](SkCanvas* c, skiatest::Reporter* r) { 375 // This test step challenges the TestDeferredCanvasStateConsistency 376 // test case because the canvas flush on a deferred canvas will 377 // reset the recording session. The challenge is to maintain correct 378 // clip and matrix stack state on the playback canvas. 379 c->resetMatrix(); 380 c->rotate(SkIntToScalar(30)); 381 c->save(); 382 c->translate(SkIntToScalar(2), SkIntToScalar(1)); 383 c->save(); 384 c->scale(SkIntToScalar(3), SkIntToScalar(3)); 385 c->drawRect(kRect, SkPaint()); 386 c->flush(); 387 c->restore(); 388 c->restore(); 389 }, 390 [](SkCanvas* c, skiatest::Reporter* r) { 391 SkPoint pts[4]; 392 pts[0].set(0, 0); 393 pts[1].set(SkIntToScalar(kWidth), 0); 394 pts[2].set(SkIntToScalar(kWidth), SkIntToScalar(kHeight)); 395 pts[3].set(0, SkIntToScalar(kHeight)); 396 SkPaint paint; 397 SkBitmap bitmap(make_n32_bitmap(kWidth, kHeight, 0x05060708)); 398 paint.setShader(bitmap.makeShader(SkSamplingOptions())); 399 c->drawVertices( 400 SkVertices::MakeCopy(SkVertices::kTriangleFan_VertexMode, 4, pts, pts, nullptr), 401 SkBlendMode::kModulate, paint); 402 } 403}; 404 405DEF_TEST(Canvas_bitmap, reporter) { 406 for (const CanvasTest& test : kCanvasTests) { 407 SkBitmap referenceStore = make_n32_bitmap(kWidth, kHeight); 408 SkCanvas referenceCanvas(referenceStore); 409 test(&referenceCanvas, reporter); 410 } 411} 412 413DEF_TEST(Canvas_pdf, reporter) { 414 for (const CanvasTest& test : kCanvasTests) { 415 SkNullWStream outStream; 416 if (auto doc = SkPDF::MakeDocument(&outStream)) { 417 SkCanvas* canvas = doc->beginPage(SkIntToScalar(kWidth), 418 SkIntToScalar(kHeight)); 419 REPORTER_ASSERT(reporter, canvas); 420 test(canvas, reporter); 421 } 422 } 423} 424 425DEF_TEST(Canvas_SaveState, reporter) { 426 SkCanvas canvas(10, 10); 427 REPORTER_ASSERT(reporter, 1 == canvas.getSaveCount()); 428 429 int n = canvas.save(); 430 REPORTER_ASSERT(reporter, 1 == n); 431 REPORTER_ASSERT(reporter, 2 == canvas.getSaveCount()); 432 433 n = canvas.saveLayer(nullptr, nullptr); 434 REPORTER_ASSERT(reporter, 2 == n); 435 REPORTER_ASSERT(reporter, 3 == canvas.getSaveCount()); 436 437 canvas.restore(); 438 REPORTER_ASSERT(reporter, 2 == canvas.getSaveCount()); 439 canvas.restore(); 440 REPORTER_ASSERT(reporter, 1 == canvas.getSaveCount()); 441} 442 443DEF_TEST(Canvas_ClipEmptyPath, reporter) { 444 SkCanvas canvas(10, 10); 445 canvas.save(); 446 SkPath path; 447 canvas.clipPath(path); 448 canvas.restore(); 449 canvas.save(); 450 path.moveTo(5, 5); 451 canvas.clipPath(path); 452 canvas.restore(); 453 canvas.save(); 454 path.moveTo(7, 7); 455 canvas.clipPath(path); // should not assert here 456 canvas.restore(); 457} 458 459namespace { 460 461class MockFilterCanvas : public SkPaintFilterCanvas { 462public: 463 MockFilterCanvas(SkCanvas* canvas) : INHERITED(canvas) { } 464 465protected: 466 bool onFilter(SkPaint&) const override { return true; } 467 468private: 469 using INHERITED = SkPaintFilterCanvas; 470}; 471 472} // anonymous namespace 473 474// SkPaintFilterCanvas should inherit the initial target canvas state. 475DEF_TEST(PaintFilterCanvas_ConsistentState, reporter) { 476 SkCanvas canvas(100, 100); 477 canvas.clipRect(SkRect::MakeXYWH(12.7f, 12.7f, 75, 75)); 478 canvas.scale(0.5f, 0.75f); 479 480 MockFilterCanvas filterCanvas(&canvas); 481 REPORTER_ASSERT(reporter, canvas.getTotalMatrix() == filterCanvas.getTotalMatrix()); 482 REPORTER_ASSERT(reporter, canvas.getLocalClipBounds() == filterCanvas.getLocalClipBounds()); 483 484 filterCanvas.clipRect(SkRect::MakeXYWH(30.5f, 30.7f, 100, 100)); 485 filterCanvas.scale(0.75f, 0.5f); 486 REPORTER_ASSERT(reporter, canvas.getTotalMatrix() == filterCanvas.getTotalMatrix()); 487 REPORTER_ASSERT(reporter, filterCanvas.getLocalClipBounds().contains(canvas.getLocalClipBounds())); 488} 489 490/////////////////////////////////////////////////////////////////////////////////////////////////// 491 492namespace { 493 494// Subclass that takes a bool*, which it updates in its construct (true) and destructor (false) 495// to allow the caller to know how long the object is alive. 496class LifeLineCanvas : public SkCanvas { 497 bool* fLifeLine; 498public: 499 LifeLineCanvas(int w, int h, bool* lifeline) : SkCanvas(w, h), fLifeLine(lifeline) { 500 *fLifeLine = true; 501 } 502 ~LifeLineCanvas() override { 503 *fLifeLine = false; 504 } 505}; 506 507} // namespace 508 509// Check that NWayCanvas does NOT try to manage the lifetime of its sub-canvases 510DEF_TEST(NWayCanvas, r) { 511 const int w = 10; 512 const int h = 10; 513 bool life[2]; 514 { 515 LifeLineCanvas c0(w, h, &life[0]); 516 REPORTER_ASSERT(r, life[0]); 517 } 518 REPORTER_ASSERT(r, !life[0]); 519 520 521 std::unique_ptr<SkCanvas> c0 = std::unique_ptr<SkCanvas>(new LifeLineCanvas(w, h, &life[0])); 522 std::unique_ptr<SkCanvas> c1 = std::unique_ptr<SkCanvas>(new LifeLineCanvas(w, h, &life[1])); 523 REPORTER_ASSERT(r, life[0]); 524 REPORTER_ASSERT(r, life[1]); 525 526 { 527 SkNWayCanvas nway(w, h); 528 nway.addCanvas(c0.get()); 529 nway.addCanvas(c1.get()); 530 REPORTER_ASSERT(r, life[0]); 531 REPORTER_ASSERT(r, life[1]); 532 } 533 // Now assert that the death of the nway has NOT also killed the sub-canvases 534 REPORTER_ASSERT(r, life[0]); 535 REPORTER_ASSERT(r, life[1]); 536} 537 538// Check that CanvasStack DOES manage the lifetime of its sub-canvases 539DEF_TEST(CanvasStack, r) { 540 const int w = 10; 541 const int h = 10; 542 bool life[2]; 543 std::unique_ptr<SkCanvas> c0 = std::unique_ptr<SkCanvas>(new LifeLineCanvas(w, h, &life[0])); 544 std::unique_ptr<SkCanvas> c1 = std::unique_ptr<SkCanvas>(new LifeLineCanvas(w, h, &life[1])); 545 REPORTER_ASSERT(r, life[0]); 546 REPORTER_ASSERT(r, life[1]); 547 548 { 549 SkCanvasStack stack(w, h); 550 stack.pushCanvas(std::move(c0), {0,0}); 551 stack.pushCanvas(std::move(c1), {0,0}); 552 REPORTER_ASSERT(r, life[0]); 553 REPORTER_ASSERT(r, life[1]); 554 } 555 // Now assert that the death of the canvasstack has also killed the sub-canvases 556 REPORTER_ASSERT(r, !life[0]); 557 REPORTER_ASSERT(r, !life[1]); 558} 559 560static void test_cliptype(SkCanvas* canvas, skiatest::Reporter* r) { 561 REPORTER_ASSERT(r, !canvas->isClipEmpty()); 562 REPORTER_ASSERT(r, canvas->isClipRect()); 563 564 canvas->save(); 565 canvas->clipRect({0, 0, 0, 0}); 566 REPORTER_ASSERT(r, canvas->isClipEmpty()); 567 REPORTER_ASSERT(r, !canvas->isClipRect()); 568 canvas->restore(); 569 570 canvas->save(); 571 canvas->clipRect({2, 2, 6, 6}); 572 REPORTER_ASSERT(r, !canvas->isClipEmpty()); 573 REPORTER_ASSERT(r, canvas->isClipRect()); 574 canvas->restore(); 575 576 canvas->save(); 577 canvas->clipRect({2, 2, 6, 6}, SkClipOp::kDifference); // punch a hole in the clip 578 REPORTER_ASSERT(r, !canvas->isClipEmpty()); 579 REPORTER_ASSERT(r, !canvas->isClipRect()); 580 canvas->restore(); 581 582 REPORTER_ASSERT(r, !canvas->isClipEmpty()); 583 REPORTER_ASSERT(r, canvas->isClipRect()); 584} 585 586DEF_TEST(CanvasClipType, r) { 587 // test rasterclip backend 588 test_cliptype(SkSurface::MakeRasterN32Premul(10, 10)->getCanvas(), r); 589 590 // test clipstack backend 591 SkDynamicMemoryWStream stream; 592 if (auto doc = SkPDF::MakeDocument(&stream)) { 593 test_cliptype(doc->beginPage(100, 100), r); 594 } 595} 596 597#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK 598DEF_TEST(Canvas_LegacyColorBehavior, r) { 599 sk_sp<SkColorSpace> cs = SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, 600 SkNamedGamut::kAdobeRGB); 601 602 // Make a Adobe RGB bitmap. 603 SkBitmap bitmap; 604 bitmap.allocPixels(SkImageInfo::MakeN32(1, 1, kOpaque_SkAlphaType, cs)); 605 bitmap.eraseColor(0xFF000000); 606 607 // Wrap it in a legacy canvas. Test that the canvas behaves like a legacy canvas. 608 SkCanvas canvas(bitmap, SkCanvas::ColorBehavior::kLegacy); 609 REPORTER_ASSERT(r, !canvas.imageInfo().colorSpace()); 610 SkPaint p; 611 p.setColor(SK_ColorRED); 612 canvas.drawIRect(SkIRect::MakeWH(1, 1), p); 613 REPORTER_ASSERT(r, SK_ColorRED == SkSwizzle_BGRA_to_PMColor(*bitmap.getAddr32(0, 0))); 614} 615#endif 616 617namespace { 618 619class ZeroBoundsImageFilter : public SkImageFilter_Base { 620public: 621 static sk_sp<SkImageFilter> Make() { return sk_sp<SkImageFilter>(new ZeroBoundsImageFilter); } 622 623protected: 624 sk_sp<SkSpecialImage> onFilterImage(const Context&, SkIPoint*) const override { 625 return nullptr; 626 } 627 SkIRect onFilterNodeBounds(const SkIRect&, const SkMatrix&, 628 MapDirection, const SkIRect* inputRect) const override { 629 return SkIRect::MakeEmpty(); 630 } 631 632private: 633 SK_FLATTENABLE_HOOKS(ZeroBoundsImageFilter) 634 635 ZeroBoundsImageFilter() : INHERITED(nullptr, 0, nullptr) {} 636 637 using INHERITED = SkImageFilter_Base; 638}; 639 640sk_sp<SkFlattenable> ZeroBoundsImageFilter::CreateProc(SkReadBuffer& buffer) { 641 SkDEBUGFAIL("Should never get here"); 642 return nullptr; 643} 644 645} // anonymous namespace 646 647DEF_TEST(Canvas_SaveLayerWithNullBoundsAndZeroBoundsImageFilter, r) { 648 SkCanvas canvas(10, 10); 649 SkPaint p; 650 p.setImageFilter(ZeroBoundsImageFilter::Make()); 651 // This should not fail any assert. 652 canvas.saveLayer(nullptr, &p); 653 REPORTER_ASSERT(r, canvas.getDeviceClipBounds().isEmpty()); 654 canvas.restore(); 655} 656 657// Test that we don't crash/assert when building a canvas with degenerate coordintes 658// (esp. big ones, that might invoke tiling). 659DEF_TEST(Canvas_degenerate_dimension, reporter) { 660 // Need a paint that will sneak us past the quickReject in SkCanvas, so we can test the 661 // raster code further downstream. 662 SkPaint paint; 663 paint.setImageFilter(SkImageFilters::Shader(SkShaders::Color(SK_ColorBLACK), nullptr)); 664 REPORTER_ASSERT(reporter, !paint.canComputeFastBounds()); 665 666 const int big = 100 * 1024; // big enough to definitely trigger tiling 667 const SkISize sizes[] {SkISize{0, big}, {big, 0}, {0, 0}}; 668 for (SkISize size : sizes) { 669 SkBitmap bm; 670 bm.setInfo(SkImageInfo::MakeN32Premul(size.width(), size.height())); 671 SkCanvas canvas(bm); 672 canvas.drawRect({0, 0, 100, 90*1024}, paint); 673 } 674} 675 676DEF_TEST(Canvas_ClippedOutImageFilter, reporter) { 677 SkCanvas canvas(100, 100); 678 679 SkPaint p; 680 p.setColor(SK_ColorGREEN); 681 p.setImageFilter(SkImageFilters::Blur(3.0f, 3.0f, nullptr, nullptr)); 682 683 SkRect blurredRect = SkRect::MakeXYWH(60, 10, 30, 30); 684 685 SkMatrix invM; 686 invM.setRotate(-45); 687 invM.mapRect(&blurredRect); 688 689 const SkRect clipRect = SkRect::MakeXYWH(0, 50, 50, 50); 690 691 canvas.clipRect(clipRect); 692 693 canvas.rotate(45); 694 const SkMatrix preCTM = canvas.getTotalMatrix(); 695 canvas.drawRect(blurredRect, p); 696 const SkMatrix postCTM = canvas.getTotalMatrix(); 697 REPORTER_ASSERT(reporter, preCTM == postCTM); 698} 699 700DEF_TEST(canvas_markctm, reporter) { 701 SkCanvas canvas(10, 10); 702 703 SkM44 m; 704 const char* id_a = "a"; 705 const char* id_b = "b"; 706 707 REPORTER_ASSERT(reporter, !canvas.findMarkedCTM(id_a, nullptr)); 708 REPORTER_ASSERT(reporter, !canvas.findMarkedCTM(id_b, nullptr)); 709 710 // remember the starting state 711 SkM44 b = canvas.getLocalToDevice(); 712 canvas.markCTM(id_b); 713 714 // test add 715 canvas.concat(SkM44::Scale(2, 4, 6)); 716 SkM44 a = canvas.getLocalToDevice(); 717 canvas.markCTM(id_a); 718 REPORTER_ASSERT(reporter, canvas.findMarkedCTM(id_a, &m) && m == a); 719 720 // test replace 721 canvas.translate(1, 2); 722 SkM44 a1 = canvas.getLocalToDevice(); 723 SkASSERT(a != a1); 724 canvas.markCTM(id_a); 725 REPORTER_ASSERT(reporter, canvas.findMarkedCTM(id_a, &m) && m == a1); 726 727 // test nested 728 canvas.save(); 729 // no change 730 REPORTER_ASSERT(reporter, canvas.findMarkedCTM(id_b, &m) && m == b); 731 REPORTER_ASSERT(reporter, canvas.findMarkedCTM(id_a, &m) && m == a1); 732 canvas.translate(2, 3); 733 SkM44 a2 = canvas.getLocalToDevice(); 734 SkASSERT(a2 != a1); 735 canvas.markCTM(id_a); 736 // found the new one 737 REPORTER_ASSERT(reporter, canvas.findMarkedCTM(id_a, &m) && m == a2); 738 canvas.restore(); 739 // found the previous one 740 REPORTER_ASSERT(reporter, canvas.findMarkedCTM(id_a, &m) && m == a1); 741} 742 743DEF_TEST(canvas_savelayer_destructor, reporter) { 744 // What should happen in our destructor if we have unbalanced saveLayers? 745 746 SkPMColor pixels[16]; 747 const SkImageInfo info = SkImageInfo::MakeN32Premul(4, 4); 748 SkPixmap pm(info, pixels, 4 * sizeof(SkPMColor)); 749 750 // check all of the pixel values in pm 751 auto check_pixels = [&](SkColor expected) { 752 const SkPMColor pmc = SkPreMultiplyColor(expected); 753 for (int y = 0; y < pm.info().height(); ++y) { 754 for (int x = 0; x < pm.info().width(); ++x) { 755 if (*pm.addr32(x, y) != pmc) { 756 ERRORF(reporter, "check_pixels_failed"); 757 return; 758 } 759 } 760 } 761 }; 762 763 auto do_test = [&](int saveCount, int restoreCount) { 764 SkASSERT(restoreCount <= saveCount); 765 766 auto surf = SkSurface::MakeRasterDirect(pm); 767 auto canvas = surf->getCanvas(); 768 769 canvas->clear(SK_ColorRED); 770 check_pixels(SK_ColorRED); 771 772 for (int i = 0; i < saveCount; ++i) { 773 canvas->saveLayer(nullptr, nullptr); 774 } 775 776 canvas->clear(SK_ColorBLUE); 777 // so far, we still expect to see the red, since the blue was drawn in a layer 778 check_pixels(SK_ColorRED); 779 780 for (int i = 0; i < restoreCount; ++i) { 781 canvas->restore(); 782 } 783 // by returning, we are implicitly deleting the surface, and its associated canvas 784 }; 785 786 do_test(1, 1); 787 // since we called restore, we expect to see now see blue 788 check_pixels(SK_ColorBLUE); 789 790 // Now repeat that, but delete the canvas before we restore it 791 do_test(1, 0); 792 // We don't blit the unbalanced saveLayers, so we expect to see red (not the layer's blue) 793 check_pixels(SK_ColorRED); 794 795 // Finally, test with multiple unbalanced saveLayers. This led to a crash in an earlier 796 // implementation (crbug.com/1238731) 797 do_test(2, 0); 798 check_pixels(SK_ColorRED); 799} 800