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 "SkBigPicture.h" 9#include "SkBBoxHierarchy.h" 10#include "SkBlurImageFilter.h" 11#include "SkCanvas.h" 12#include "SkColorMatrixFilter.h" 13#include "SkColorPriv.h" 14#include "SkDashPathEffect.h" 15#include "SkData.h" 16#include "SkImageGenerator.h" 17#include "SkImageEncoder.h" 18#include "SkImageGenerator.h" 19#include "SkMD5.h" 20#include "SkPaint.h" 21#include "SkPicture.h" 22#include "SkPictureAnalyzer.h" 23#include "SkPictureRecorder.h" 24#include "SkPixelRef.h" 25#include "SkPixelSerializer.h" 26#include "SkMiniRecorder.h" 27#include "SkRRect.h" 28#include "SkRandom.h" 29#include "SkRecord.h" 30#include "SkShader.h" 31#include "SkStream.h" 32#include "sk_tool_utils.h" 33 34#include "Test.h" 35 36#include "SkLumaColorFilter.h" 37#include "SkColorFilterImageFilter.h" 38 39static void make_bm(SkBitmap* bm, int w, int h, SkColor color, bool immutable) { 40 bm->allocN32Pixels(w, h); 41 bm->eraseColor(color); 42 if (immutable) { 43 bm->setImmutable(); 44 } 45} 46 47// For a while willPlayBackBitmaps() ignored SkImages and just looked for SkBitmaps. 48static void test_images_are_found_by_willPlayBackBitmaps(skiatest::Reporter* reporter) { 49 // We just need _some_ SkImage 50 const SkPMColor pixel = 0; 51 const SkImageInfo info = SkImageInfo::MakeN32Premul(1, 1); 52 sk_sp<SkImage> image(SkImage::MakeRasterCopy(SkPixmap(info, &pixel, sizeof(pixel)))); 53 54 SkPictureRecorder recorder; 55 recorder.beginRecording(100,100)->drawImage(image, 0,0); 56 sk_sp<SkPicture> picture(recorder.finishRecordingAsPicture()); 57 58 REPORTER_ASSERT(reporter, picture->willPlayBackBitmaps()); 59} 60 61/* Hit a few SkPicture::Analysis cases not handled elsewhere. */ 62static void test_analysis(skiatest::Reporter* reporter) { 63 SkPictureRecorder recorder; 64 65 SkCanvas* canvas = recorder.beginRecording(100, 100); 66 { 67 canvas->drawRect(SkRect::MakeWH(10, 10), SkPaint ()); 68 } 69 sk_sp<SkPicture> picture(recorder.finishRecordingAsPicture()); 70 REPORTER_ASSERT(reporter, !picture->willPlayBackBitmaps()); 71 72 canvas = recorder.beginRecording(100, 100); 73 { 74 SkPaint paint; 75 // CreateBitmapShader is too smart for us; an empty (or 1x1) bitmap shader 76 // gets optimized into a non-bitmap form, so we create a 2x2 bitmap here. 77 SkBitmap bitmap; 78 bitmap.allocPixels(SkImageInfo::MakeN32Premul(2, 2)); 79 bitmap.eraseColor(SK_ColorBLUE); 80 *(bitmap.getAddr32(0, 0)) = SK_ColorGREEN; 81 paint.setShader(SkShader::MakeBitmapShader(bitmap, SkShader::kClamp_TileMode, 82 SkShader::kClamp_TileMode)); 83 REPORTER_ASSERT(reporter, paint.getShader()->isAImage()); 84 85 canvas->drawRect(SkRect::MakeWH(10, 10), paint); 86 } 87 REPORTER_ASSERT(reporter, recorder.finishRecordingAsPicture()->willPlayBackBitmaps()); 88} 89 90 91#ifdef SK_DEBUG 92// Ensure that deleting an empty SkPicture does not assert. Asserts only fire 93// in debug mode, so only run in debug mode. 94static void test_deleting_empty_picture() { 95 SkPictureRecorder recorder; 96 // Creates an SkPictureRecord 97 recorder.beginRecording(0, 0); 98 // Turns that into an SkPicture 99 sk_sp<SkPicture> picture(recorder.finishRecordingAsPicture()); 100 // Ceates a new SkPictureRecord 101 recorder.beginRecording(0, 0); 102} 103 104// Ensure that serializing an empty picture does not assert. Likewise only runs in debug mode. 105static void test_serializing_empty_picture() { 106 SkPictureRecorder recorder; 107 recorder.beginRecording(0, 0); 108 sk_sp<SkPicture> picture(recorder.finishRecordingAsPicture()); 109 SkDynamicMemoryWStream stream; 110 picture->serialize(&stream); 111} 112#endif 113 114static void rand_op(SkCanvas* canvas, SkRandom& rand) { 115 SkPaint paint; 116 SkRect rect = SkRect::MakeWH(50, 50); 117 118 SkScalar unit = rand.nextUScalar1(); 119 if (unit <= 0.3) { 120// SkDebugf("save\n"); 121 canvas->save(); 122 } else if (unit <= 0.6) { 123// SkDebugf("restore\n"); 124 canvas->restore(); 125 } else if (unit <= 0.9) { 126// SkDebugf("clip\n"); 127 canvas->clipRect(rect); 128 } else { 129// SkDebugf("draw\n"); 130 canvas->drawPaint(paint); 131 } 132} 133 134#if SK_SUPPORT_GPU 135 136static SkPath make_convex_path() { 137 SkPath path; 138 path.lineTo(100, 0); 139 path.lineTo(50, 100); 140 path.close(); 141 142 return path; 143} 144 145static SkPath make_concave_path() { 146 SkPath path; 147 path.lineTo(50, 50); 148 path.lineTo(100, 0); 149 path.lineTo(50, 100); 150 path.close(); 151 152 return path; 153} 154 155static void test_gpu_veto(skiatest::Reporter* reporter) { 156 SkPictureRecorder recorder; 157 158 SkCanvas* canvas = recorder.beginRecording(100, 100); 159 { 160 SkPath path; 161 path.moveTo(0, 0); 162 path.lineTo(50, 50); 163 164 SkScalar intervals[] = { 1.0f, 1.0f }; 165 sk_sp<SkPathEffect> dash(SkDashPathEffect::Make(intervals, 2, 0)); 166 167 SkPaint paint; 168 paint.setStyle(SkPaint::kStroke_Style); 169 paint.setPathEffect(dash); 170 171 for (int i = 0; i < 50; ++i) { 172 canvas->drawPath(path, paint); 173 } 174 } 175 sk_sp<SkPicture> picture(recorder.finishRecordingAsPicture()); 176 // path effects currently render an SkPicture undesireable for GPU rendering 177 178 const char *reason = nullptr; 179 REPORTER_ASSERT(reporter, 180 !SkPictureGpuAnalyzer(picture).suitableForGpuRasterization(&reason)); 181 REPORTER_ASSERT(reporter, reason); 182 183 canvas = recorder.beginRecording(100, 100); 184 { 185 SkPath path; 186 187 path.moveTo(0, 0); 188 path.lineTo(0, 50); 189 path.lineTo(25, 25); 190 path.lineTo(50, 50); 191 path.lineTo(50, 0); 192 path.close(); 193 REPORTER_ASSERT(reporter, !path.isConvex()); 194 195 SkPaint paint; 196 paint.setAntiAlias(true); 197 for (int i = 0; i < 50; ++i) { 198 canvas->drawPath(path, paint); 199 } 200 } 201 picture = recorder.finishRecordingAsPicture(); 202 // A lot of small AA concave paths should be fine for GPU rendering 203 REPORTER_ASSERT(reporter, SkPictureGpuAnalyzer(picture).suitableForGpuRasterization()); 204 205 canvas = recorder.beginRecording(100, 100); 206 { 207 SkPath path; 208 209 path.moveTo(0, 0); 210 path.lineTo(0, 100); 211 path.lineTo(50, 50); 212 path.lineTo(100, 100); 213 path.lineTo(100, 0); 214 path.close(); 215 REPORTER_ASSERT(reporter, !path.isConvex()); 216 217 SkPaint paint; 218 paint.setAntiAlias(true); 219 for (int i = 0; i < 50; ++i) { 220 canvas->drawPath(path, paint); 221 } 222 } 223 picture = recorder.finishRecordingAsPicture(); 224 // A lot of large AA concave paths currently render an SkPicture undesireable for GPU rendering 225 REPORTER_ASSERT(reporter, !SkPictureGpuAnalyzer(picture).suitableForGpuRasterization()); 226 227 canvas = recorder.beginRecording(100, 100); 228 { 229 SkPath path; 230 231 path.moveTo(0, 0); 232 path.lineTo(0, 50); 233 path.lineTo(25, 25); 234 path.lineTo(50, 50); 235 path.lineTo(50, 0); 236 path.close(); 237 REPORTER_ASSERT(reporter, !path.isConvex()); 238 239 SkPaint paint; 240 paint.setAntiAlias(true); 241 paint.setStyle(SkPaint::kStroke_Style); 242 paint.setStrokeWidth(0); 243 for (int i = 0; i < 50; ++i) { 244 canvas->drawPath(path, paint); 245 } 246 } 247 picture = recorder.finishRecordingAsPicture(); 248 // hairline stroked AA concave paths are fine for GPU rendering 249 REPORTER_ASSERT(reporter, SkPictureGpuAnalyzer(picture).suitableForGpuRasterization()); 250 251 canvas = recorder.beginRecording(100, 100); 252 { 253 SkPaint paint; 254 SkScalar intervals [] = { 10, 20 }; 255 paint.setPathEffect(SkDashPathEffect::Make(intervals, 2, 25)); 256 257 SkPoint points [2] = { { 0, 0 }, { 100, 0 } }; 258 259 for (int i = 0; i < 50; ++i) { 260 canvas->drawPoints(SkCanvas::kLines_PointMode, 2, points, paint); 261 } 262 } 263 picture = recorder.finishRecordingAsPicture(); 264 // fast-path dashed effects are fine for GPU rendering ... 265 REPORTER_ASSERT(reporter, SkPictureGpuAnalyzer(picture).suitableForGpuRasterization()); 266 267 canvas = recorder.beginRecording(100, 100); 268 { 269 SkPaint paint; 270 SkScalar intervals [] = { 10, 20 }; 271 paint.setPathEffect(SkDashPathEffect::Make(intervals, 2, 25)); 272 273 for (int i = 0; i < 50; ++i) { 274 canvas->drawRect(SkRect::MakeWH(10, 10), paint); 275 } 276 } 277 picture = recorder.finishRecordingAsPicture(); 278 // ... but only when applied to drawPoint() calls 279 REPORTER_ASSERT(reporter, !SkPictureGpuAnalyzer(picture).suitableForGpuRasterization()); 280 281 canvas = recorder.beginRecording(100, 100); 282 { 283 const SkPath convexClip = make_convex_path(); 284 const SkPath concaveClip = make_concave_path(); 285 286 for (int i = 0; i < 50; ++i) { 287 canvas->clipPath(convexClip); 288 canvas->clipPath(concaveClip); 289 canvas->clipPath(convexClip, kIntersect_SkClipOp, true); 290 canvas->drawRect(SkRect::MakeWH(100, 100), SkPaint()); 291 } 292 } 293 picture = recorder.finishRecordingAsPicture(); 294 // Convex clips and non-AA concave clips are fine on the GPU. 295 REPORTER_ASSERT(reporter, SkPictureGpuAnalyzer(picture).suitableForGpuRasterization()); 296 297 canvas = recorder.beginRecording(100, 100); 298 { 299 const SkPath concaveClip = make_concave_path(); 300 for (int i = 0; i < 50; ++i) { 301 canvas->clipPath(concaveClip, kIntersect_SkClipOp, true); 302 canvas->drawRect(SkRect::MakeWH(100, 100), SkPaint()); 303 } 304 } 305 picture = recorder.finishRecordingAsPicture(); 306 // ... but AA concave clips are not. 307 REPORTER_ASSERT(reporter, !SkPictureGpuAnalyzer(picture).suitableForGpuRasterization()); 308 309 // Nest the previous picture inside a new one. 310 canvas = recorder.beginRecording(100, 100); 311 { 312 canvas->drawPicture(picture); 313 } 314 picture = recorder.finishRecordingAsPicture(); 315 REPORTER_ASSERT(reporter, !SkPictureGpuAnalyzer(picture).suitableForGpuRasterization()); 316} 317 318#endif // SK_SUPPORT_GPU 319 320static void set_canvas_to_save_count_4(SkCanvas* canvas) { 321 canvas->restoreToCount(1); 322 canvas->save(); 323 canvas->save(); 324 canvas->save(); 325} 326 327/** 328 * A canvas that records the number of saves, saveLayers and restores. 329 */ 330class SaveCountingCanvas : public SkCanvas { 331public: 332 SaveCountingCanvas(int width, int height) 333 : INHERITED(width, height) 334 , fSaveCount(0) 335 , fSaveLayerCount(0) 336 , fRestoreCount(0){ 337 } 338 339 SaveLayerStrategy getSaveLayerStrategy(const SaveLayerRec& rec) override { 340 ++fSaveLayerCount; 341 return this->INHERITED::getSaveLayerStrategy(rec); 342 } 343 344 void willSave() override { 345 ++fSaveCount; 346 this->INHERITED::willSave(); 347 } 348 349 void willRestore() override { 350 ++fRestoreCount; 351 this->INHERITED::willRestore(); 352 } 353 354 unsigned int getSaveCount() const { return fSaveCount; } 355 unsigned int getSaveLayerCount() const { return fSaveLayerCount; } 356 unsigned int getRestoreCount() const { return fRestoreCount; } 357 358private: 359 unsigned int fSaveCount; 360 unsigned int fSaveLayerCount; 361 unsigned int fRestoreCount; 362 363 typedef SkCanvas INHERITED; 364}; 365 366void check_save_state(skiatest::Reporter* reporter, SkPicture* picture, 367 unsigned int numSaves, unsigned int numSaveLayers, 368 unsigned int numRestores) { 369 SaveCountingCanvas canvas(SkScalarCeilToInt(picture->cullRect().width()), 370 SkScalarCeilToInt(picture->cullRect().height())); 371 372 picture->playback(&canvas); 373 374 // Optimizations may have removed these, 375 // so expect to have seen no more than num{Saves,SaveLayers,Restores}. 376 REPORTER_ASSERT(reporter, numSaves >= canvas.getSaveCount()); 377 REPORTER_ASSERT(reporter, numSaveLayers >= canvas.getSaveLayerCount()); 378 REPORTER_ASSERT(reporter, numRestores >= canvas.getRestoreCount()); 379} 380 381// This class exists so SkPicture can friend it and give it access to 382// the 'partialReplay' method. 383class SkPictureRecorderReplayTester { 384public: 385 static sk_sp<SkPicture> Copy(SkPictureRecorder* recorder) { 386 SkPictureRecorder recorder2; 387 388 SkCanvas* canvas = recorder2.beginRecording(10, 10); 389 390 recorder->partialReplay(canvas); 391 392 return recorder2.finishRecordingAsPicture(); 393 } 394}; 395 396static void create_imbalance(SkCanvas* canvas) { 397 SkRect clipRect = SkRect::MakeWH(2, 2); 398 SkRect drawRect = SkRect::MakeWH(10, 10); 399 canvas->save(); 400 canvas->clipRect(clipRect, kReplace_SkClipOp); 401 canvas->translate(1.0f, 1.0f); 402 SkPaint p; 403 p.setColor(SK_ColorGREEN); 404 canvas->drawRect(drawRect, p); 405 // no restore 406} 407 408// This tests that replaying a potentially unbalanced picture into a canvas 409// doesn't affect the canvas' save count or matrix/clip state. 410static void check_balance(skiatest::Reporter* reporter, SkPicture* picture) { 411 SkBitmap bm; 412 bm.allocN32Pixels(4, 3); 413 SkCanvas canvas(bm); 414 415 int beforeSaveCount = canvas.getSaveCount(); 416 417 SkMatrix beforeMatrix = canvas.getTotalMatrix(); 418 419 SkRect beforeClip = canvas.getLocalClipBounds(); 420 421 canvas.drawPicture(picture); 422 423 REPORTER_ASSERT(reporter, beforeSaveCount == canvas.getSaveCount()); 424 REPORTER_ASSERT(reporter, beforeMatrix == canvas.getTotalMatrix()); 425 426 SkRect afterClip = canvas.getLocalClipBounds(); 427 428 REPORTER_ASSERT(reporter, afterClip == beforeClip); 429} 430 431// Test out SkPictureRecorder::partialReplay 432DEF_TEST(PictureRecorder_replay, reporter) { 433 // check save/saveLayer state 434 { 435 SkPictureRecorder recorder; 436 437 SkCanvas* canvas = recorder.beginRecording(10, 10); 438 439 canvas->saveLayer(nullptr, nullptr); 440 441 sk_sp<SkPicture> copy(SkPictureRecorderReplayTester::Copy(&recorder)); 442 443 // The extra save and restore comes from the Copy process. 444 check_save_state(reporter, copy.get(), 2, 1, 3); 445 446 canvas->saveLayer(nullptr, nullptr); 447 448 sk_sp<SkPicture> final(recorder.finishRecordingAsPicture()); 449 450 check_save_state(reporter, final.get(), 1, 2, 3); 451 452 // The copy shouldn't pick up any operations added after it was made 453 check_save_state(reporter, copy.get(), 2, 1, 3); 454 } 455 456 // (partially) check leakage of draw ops 457 { 458 SkPictureRecorder recorder; 459 460 SkCanvas* canvas = recorder.beginRecording(10, 10); 461 462 SkRect r = SkRect::MakeWH(5, 5); 463 SkPaint p; 464 465 canvas->drawRect(r, p); 466 467 sk_sp<SkPicture> copy(SkPictureRecorderReplayTester::Copy(&recorder)); 468 469 REPORTER_ASSERT(reporter, !copy->willPlayBackBitmaps()); 470 471 SkBitmap bm; 472 make_bm(&bm, 10, 10, SK_ColorRED, true); 473 474 r.offset(5.0f, 5.0f); 475 canvas->drawBitmapRect(bm, r, nullptr); 476 477 sk_sp<SkPicture> final(recorder.finishRecordingAsPicture()); 478 REPORTER_ASSERT(reporter, final->willPlayBackBitmaps()); 479 480 REPORTER_ASSERT(reporter, copy->uniqueID() != final->uniqueID()); 481 482 // The snapshot shouldn't pick up any operations added after it was made 483 REPORTER_ASSERT(reporter, !copy->willPlayBackBitmaps()); 484 } 485 486 // Recreate the Android partialReplay test case 487 { 488 SkPictureRecorder recorder; 489 490 SkCanvas* canvas = recorder.beginRecording(4, 3, nullptr, 0); 491 create_imbalance(canvas); 492 493 int expectedSaveCount = canvas->getSaveCount(); 494 495 sk_sp<SkPicture> copy(SkPictureRecorderReplayTester::Copy(&recorder)); 496 check_balance(reporter, copy.get()); 497 498 REPORTER_ASSERT(reporter, expectedSaveCount = canvas->getSaveCount()); 499 500 // End the recording of source to test the picture finalization 501 // process isn't complicated by the partialReplay step 502 sk_sp<SkPicture> final(recorder.finishRecordingAsPicture()); 503 } 504} 505 506static void test_unbalanced_save_restores(skiatest::Reporter* reporter) { 507 SkCanvas testCanvas(100, 100); 508 set_canvas_to_save_count_4(&testCanvas); 509 510 REPORTER_ASSERT(reporter, 4 == testCanvas.getSaveCount()); 511 512 SkPaint paint; 513 SkRect rect = SkRect::MakeLTRB(-10000000, -10000000, 10000000, 10000000); 514 515 SkPictureRecorder recorder; 516 517 { 518 // Create picture with 2 unbalanced saves 519 SkCanvas* canvas = recorder.beginRecording(100, 100); 520 canvas->save(); 521 canvas->translate(10, 10); 522 canvas->drawRect(rect, paint); 523 canvas->save(); 524 canvas->translate(10, 10); 525 canvas->drawRect(rect, paint); 526 sk_sp<SkPicture> extraSavePicture(recorder.finishRecordingAsPicture()); 527 528 testCanvas.drawPicture(extraSavePicture); 529 REPORTER_ASSERT(reporter, 4 == testCanvas.getSaveCount()); 530 } 531 532 set_canvas_to_save_count_4(&testCanvas); 533 534 { 535 // Create picture with 2 unbalanced restores 536 SkCanvas* canvas = recorder.beginRecording(100, 100); 537 canvas->save(); 538 canvas->translate(10, 10); 539 canvas->drawRect(rect, paint); 540 canvas->save(); 541 canvas->translate(10, 10); 542 canvas->drawRect(rect, paint); 543 canvas->restore(); 544 canvas->restore(); 545 canvas->restore(); 546 canvas->restore(); 547 sk_sp<SkPicture> extraRestorePicture(recorder.finishRecordingAsPicture()); 548 549 testCanvas.drawPicture(extraRestorePicture); 550 REPORTER_ASSERT(reporter, 4 == testCanvas.getSaveCount()); 551 } 552 553 set_canvas_to_save_count_4(&testCanvas); 554 555 { 556 SkCanvas* canvas = recorder.beginRecording(100, 100); 557 canvas->translate(10, 10); 558 canvas->drawRect(rect, paint); 559 sk_sp<SkPicture> noSavePicture(recorder.finishRecordingAsPicture()); 560 561 testCanvas.drawPicture(noSavePicture); 562 REPORTER_ASSERT(reporter, 4 == testCanvas.getSaveCount()); 563 REPORTER_ASSERT(reporter, testCanvas.getTotalMatrix().isIdentity()); 564 } 565} 566 567static void test_peephole() { 568 SkRandom rand; 569 570 SkPictureRecorder recorder; 571 572 for (int j = 0; j < 100; j++) { 573 SkRandom rand2(rand); // remember the seed 574 575 SkCanvas* canvas = recorder.beginRecording(100, 100); 576 577 for (int i = 0; i < 1000; ++i) { 578 rand_op(canvas, rand); 579 } 580 sk_sp<SkPicture> picture(recorder.finishRecordingAsPicture()); 581 582 rand = rand2; 583 } 584 585 { 586 SkCanvas* canvas = recorder.beginRecording(100, 100); 587 SkRect rect = SkRect::MakeWH(50, 50); 588 589 for (int i = 0; i < 100; ++i) { 590 canvas->save(); 591 } 592 while (canvas->getSaveCount() > 1) { 593 canvas->clipRect(rect); 594 canvas->restore(); 595 } 596 sk_sp<SkPicture> picture(recorder.finishRecordingAsPicture()); 597 } 598} 599 600#ifndef SK_DEBUG 601// Only test this is in release mode. We deliberately crash in debug mode, since a valid caller 602// should never do this. 603static void test_bad_bitmap() { 604 // This bitmap has a width and height but no pixels. As a result, attempting to record it will 605 // fail. 606 SkBitmap bm; 607 bm.setInfo(SkImageInfo::MakeN32Premul(100, 100)); 608 SkPictureRecorder recorder; 609 SkCanvas* recordingCanvas = recorder.beginRecording(100, 100); 610 recordingCanvas->drawBitmap(bm, 0, 0); 611 sk_sp<SkPicture> picture(recorder.finishRecordingAsPicture()); 612 613 SkCanvas canvas; 614 canvas.drawPicture(picture); 615} 616#endif 617 618static void test_clip_bound_opt(skiatest::Reporter* reporter) { 619 // Test for crbug.com/229011 620 SkRect rect1 = SkRect::MakeXYWH(SkIntToScalar(4), SkIntToScalar(4), 621 SkIntToScalar(2), SkIntToScalar(2)); 622 SkRect rect2 = SkRect::MakeXYWH(SkIntToScalar(7), SkIntToScalar(7), 623 SkIntToScalar(1), SkIntToScalar(1)); 624 SkRect rect3 = SkRect::MakeXYWH(SkIntToScalar(6), SkIntToScalar(6), 625 SkIntToScalar(1), SkIntToScalar(1)); 626 627 SkPath invPath; 628 invPath.addOval(rect1); 629 invPath.setFillType(SkPath::kInverseEvenOdd_FillType); 630 SkPath path; 631 path.addOval(rect2); 632 SkPath path2; 633 path2.addOval(rect3); 634 SkIRect clipBounds; 635 SkPictureRecorder recorder; 636 637 // Testing conservative-raster-clip that is enabled by PictureRecord 638 { 639 SkCanvas* canvas = recorder.beginRecording(10, 10); 640 canvas->clipPath(invPath); 641 clipBounds = canvas->getDeviceClipBounds(); 642 REPORTER_ASSERT(reporter, 0 == clipBounds.fLeft); 643 REPORTER_ASSERT(reporter, 0 == clipBounds.fTop); 644 REPORTER_ASSERT(reporter, 10 == clipBounds.fBottom); 645 REPORTER_ASSERT(reporter, 10 == clipBounds.fRight); 646 } 647 { 648 SkCanvas* canvas = recorder.beginRecording(10, 10); 649 canvas->clipPath(path); 650 canvas->clipPath(invPath); 651 clipBounds = canvas->getDeviceClipBounds(); 652 REPORTER_ASSERT(reporter, 7 == clipBounds.fLeft); 653 REPORTER_ASSERT(reporter, 7 == clipBounds.fTop); 654 REPORTER_ASSERT(reporter, 8 == clipBounds.fBottom); 655 REPORTER_ASSERT(reporter, 8 == clipBounds.fRight); 656 } 657 { 658 SkCanvas* canvas = recorder.beginRecording(10, 10); 659 canvas->clipPath(path); 660 canvas->clipPath(invPath, kUnion_SkClipOp); 661 clipBounds = canvas->getDeviceClipBounds(); 662 REPORTER_ASSERT(reporter, 0 == clipBounds.fLeft); 663 REPORTER_ASSERT(reporter, 0 == clipBounds.fTop); 664 REPORTER_ASSERT(reporter, 10 == clipBounds.fBottom); 665 REPORTER_ASSERT(reporter, 10 == clipBounds.fRight); 666 } 667 { 668 SkCanvas* canvas = recorder.beginRecording(10, 10); 669 canvas->clipPath(path, kDifference_SkClipOp); 670 clipBounds = canvas->getDeviceClipBounds(); 671 REPORTER_ASSERT(reporter, 0 == clipBounds.fLeft); 672 REPORTER_ASSERT(reporter, 0 == clipBounds.fTop); 673 REPORTER_ASSERT(reporter, 10 == clipBounds.fBottom); 674 REPORTER_ASSERT(reporter, 10 == clipBounds.fRight); 675 } 676 { 677 SkCanvas* canvas = recorder.beginRecording(10, 10); 678 canvas->clipPath(path, kReverseDifference_SkClipOp); 679 clipBounds = canvas->getDeviceClipBounds(); 680 // True clip is actually empty in this case, but the best 681 // determination we can make using only bounds as input is that the 682 // clip is included in the bounds of 'path'. 683 REPORTER_ASSERT(reporter, 7 == clipBounds.fLeft); 684 REPORTER_ASSERT(reporter, 7 == clipBounds.fTop); 685 REPORTER_ASSERT(reporter, 8 == clipBounds.fBottom); 686 REPORTER_ASSERT(reporter, 8 == clipBounds.fRight); 687 } 688 { 689 SkCanvas* canvas = recorder.beginRecording(10, 10); 690 canvas->clipPath(path, kIntersect_SkClipOp); 691 canvas->clipPath(path2, kXOR_SkClipOp); 692 clipBounds = canvas->getDeviceClipBounds(); 693 REPORTER_ASSERT(reporter, 6 == clipBounds.fLeft); 694 REPORTER_ASSERT(reporter, 6 == clipBounds.fTop); 695 REPORTER_ASSERT(reporter, 8 == clipBounds.fBottom); 696 REPORTER_ASSERT(reporter, 8 == clipBounds.fRight); 697 } 698} 699 700static void test_cull_rect_reset(skiatest::Reporter* reporter) { 701 SkPictureRecorder recorder; 702 SkRect bounds = SkRect::MakeWH(10, 10); 703 SkRTreeFactory factory; 704 SkCanvas* canvas = recorder.beginRecording(bounds, &factory); 705 bounds = SkRect::MakeWH(100, 100); 706 SkPaint paint; 707 canvas->drawRect(bounds, paint); 708 canvas->drawRect(bounds, paint); 709 sk_sp<SkPicture> p(recorder.finishRecordingAsPictureWithCull(bounds)); 710 const SkBigPicture* picture = p->asSkBigPicture(); 711 REPORTER_ASSERT(reporter, picture); 712 713 SkRect finalCullRect = picture->cullRect(); 714 REPORTER_ASSERT(reporter, 0 == finalCullRect.fLeft); 715 REPORTER_ASSERT(reporter, 0 == finalCullRect.fTop); 716 REPORTER_ASSERT(reporter, 100 == finalCullRect.fBottom); 717 REPORTER_ASSERT(reporter, 100 == finalCullRect.fRight); 718 719 const SkBBoxHierarchy* pictureBBH = picture->bbh(); 720 SkRect bbhCullRect = pictureBBH->getRootBound(); 721 REPORTER_ASSERT(reporter, 0 == bbhCullRect.fLeft); 722 REPORTER_ASSERT(reporter, 0 == bbhCullRect.fTop); 723 REPORTER_ASSERT(reporter, 100 == bbhCullRect.fBottom); 724 REPORTER_ASSERT(reporter, 100 == bbhCullRect.fRight); 725} 726 727 728/** 729 * A canvas that records the number of clip commands. 730 */ 731class ClipCountingCanvas : public SkCanvas { 732public: 733 ClipCountingCanvas(int width, int height) 734 : INHERITED(width, height) 735 , fClipCount(0){ 736 } 737 738 void onClipRect(const SkRect& r, SkClipOp op, ClipEdgeStyle edgeStyle) override { 739 fClipCount += 1; 740 this->INHERITED::onClipRect(r, op, edgeStyle); 741 } 742 743 void onClipRRect(const SkRRect& rrect, SkClipOp op, ClipEdgeStyle edgeStyle)override { 744 fClipCount += 1; 745 this->INHERITED::onClipRRect(rrect, op, edgeStyle); 746 } 747 748 void onClipPath(const SkPath& path, SkClipOp op, ClipEdgeStyle edgeStyle) override { 749 fClipCount += 1; 750 this->INHERITED::onClipPath(path, op, edgeStyle); 751 } 752 753 void onClipRegion(const SkRegion& deviceRgn, SkClipOp op) override { 754 fClipCount += 1; 755 this->INHERITED::onClipRegion(deviceRgn, op); 756 } 757 758 unsigned getClipCount() const { return fClipCount; } 759 760private: 761 unsigned fClipCount; 762 763 typedef SkCanvas INHERITED; 764}; 765 766static void test_clip_expansion(skiatest::Reporter* reporter) { 767 SkPictureRecorder recorder; 768 SkCanvas* canvas = recorder.beginRecording(10, 10); 769 770 canvas->clipRect(SkRect::MakeEmpty(), kReplace_SkClipOp); 771 // The following expanding clip should not be skipped. 772 canvas->clipRect(SkRect::MakeXYWH(4, 4, 3, 3), kUnion_SkClipOp); 773 // Draw something so the optimizer doesn't just fold the world. 774 SkPaint p; 775 p.setColor(SK_ColorBLUE); 776 canvas->drawPaint(p); 777 sk_sp<SkPicture> picture(recorder.finishRecordingAsPicture()); 778 779 ClipCountingCanvas testCanvas(10, 10); 780 picture->playback(&testCanvas); 781 782 // Both clips should be present on playback. 783 REPORTER_ASSERT(reporter, testCanvas.getClipCount() == 2); 784} 785 786static void test_hierarchical(skiatest::Reporter* reporter) { 787 SkBitmap bm; 788 make_bm(&bm, 10, 10, SK_ColorRED, true); 789 790 SkPictureRecorder recorder; 791 792 recorder.beginRecording(10, 10); 793 sk_sp<SkPicture> childPlain(recorder.finishRecordingAsPicture()); 794 REPORTER_ASSERT(reporter, !childPlain->willPlayBackBitmaps()); // 0 795 796 recorder.beginRecording(10, 10)->drawBitmap(bm, 0, 0); 797 sk_sp<SkPicture> childWithBitmap(recorder.finishRecordingAsPicture()); 798 REPORTER_ASSERT(reporter, childWithBitmap->willPlayBackBitmaps()); // 1 799 800 { 801 SkCanvas* canvas = recorder.beginRecording(10, 10); 802 canvas->drawPicture(childPlain); 803 sk_sp<SkPicture> parentPP(recorder.finishRecordingAsPicture()); 804 REPORTER_ASSERT(reporter, !parentPP->willPlayBackBitmaps()); // 0 805 } 806 { 807 SkCanvas* canvas = recorder.beginRecording(10, 10); 808 canvas->drawPicture(childWithBitmap); 809 sk_sp<SkPicture> parentPWB(recorder.finishRecordingAsPicture()); 810 REPORTER_ASSERT(reporter, parentPWB->willPlayBackBitmaps()); // 1 811 } 812 { 813 SkCanvas* canvas = recorder.beginRecording(10, 10); 814 canvas->drawBitmap(bm, 0, 0); 815 canvas->drawPicture(childPlain); 816 sk_sp<SkPicture> parentWBP(recorder.finishRecordingAsPicture()); 817 REPORTER_ASSERT(reporter, parentWBP->willPlayBackBitmaps()); // 1 818 } 819 { 820 SkCanvas* canvas = recorder.beginRecording(10, 10); 821 canvas->drawBitmap(bm, 0, 0); 822 canvas->drawPicture(childWithBitmap); 823 sk_sp<SkPicture> parentWBWB(recorder.finishRecordingAsPicture()); 824 REPORTER_ASSERT(reporter, parentWBWB->willPlayBackBitmaps()); // 2 825 } 826} 827 828static void test_gen_id(skiatest::Reporter* reporter) { 829 830 SkPictureRecorder recorder; 831 recorder.beginRecording(0, 0); 832 sk_sp<SkPicture> empty(recorder.finishRecordingAsPicture()); 833 834 // Empty pictures should still have a valid ID 835 REPORTER_ASSERT(reporter, empty->uniqueID() != SK_InvalidGenID); 836 837 SkCanvas* canvas = recorder.beginRecording(1, 1); 838 canvas->drawColor(SK_ColorWHITE); 839 sk_sp<SkPicture> hasData(recorder.finishRecordingAsPicture()); 840 // picture should have a non-zero id after recording 841 REPORTER_ASSERT(reporter, hasData->uniqueID() != SK_InvalidGenID); 842 843 // both pictures should have different ids 844 REPORTER_ASSERT(reporter, hasData->uniqueID() != empty->uniqueID()); 845} 846 847static void test_typeface(skiatest::Reporter* reporter) { 848 SkPictureRecorder recorder; 849 SkCanvas* canvas = recorder.beginRecording(10, 10); 850 SkPaint paint; 851 paint.setTypeface(SkTypeface::MakeFromName("Arial", 852 SkFontStyle::FromOldStyle(SkTypeface::kItalic))); 853 canvas->drawString("Q", 0, 10, paint); 854 sk_sp<SkPicture> picture(recorder.finishRecordingAsPicture()); 855 SkDynamicMemoryWStream stream; 856 picture->serialize(&stream); 857} 858 859DEF_TEST(Picture, reporter) { 860 test_typeface(reporter); 861#ifdef SK_DEBUG 862 test_deleting_empty_picture(); 863 test_serializing_empty_picture(); 864#else 865 test_bad_bitmap(); 866#endif 867 test_unbalanced_save_restores(reporter); 868 test_peephole(); 869#if SK_SUPPORT_GPU 870 test_gpu_veto(reporter); 871#endif 872 test_images_are_found_by_willPlayBackBitmaps(reporter); 873 test_analysis(reporter); 874 test_clip_bound_opt(reporter); 875 test_clip_expansion(reporter); 876 test_hierarchical(reporter); 877 test_gen_id(reporter); 878 test_cull_rect_reset(reporter); 879} 880 881static void draw_bitmaps(const SkBitmap bitmap, SkCanvas* canvas) { 882 const SkPaint paint; 883 const SkRect rect = { 5.0f, 5.0f, 8.0f, 8.0f }; 884 const SkIRect irect = { 2, 2, 3, 3 }; 885 int divs[] = { 2, 3 }; 886 SkCanvas::Lattice lattice; 887 lattice.fXCount = lattice.fYCount = 2; 888 lattice.fXDivs = lattice.fYDivs = divs; 889 890 // Don't care what these record, as long as they're legal. 891 canvas->drawBitmap(bitmap, 0.0f, 0.0f, &paint); 892 canvas->drawBitmapRect(bitmap, rect, rect, &paint, SkCanvas::kStrict_SrcRectConstraint); 893 canvas->drawBitmapNine(bitmap, irect, rect, &paint); 894 canvas->drawBitmap(bitmap, 1, 1); // drawSprite 895 canvas->drawBitmapLattice(bitmap, lattice, rect, &paint); 896} 897 898static void test_draw_bitmaps(SkCanvas* canvas) { 899 SkBitmap empty; 900 draw_bitmaps(empty, canvas); 901 empty.setInfo(SkImageInfo::MakeN32Premul(10, 10)); 902 draw_bitmaps(empty, canvas); 903} 904 905DEF_TEST(Picture_EmptyBitmap, r) { 906 SkPictureRecorder recorder; 907 test_draw_bitmaps(recorder.beginRecording(10, 10)); 908 sk_sp<SkPicture> picture(recorder.finishRecordingAsPicture()); 909} 910 911DEF_TEST(Canvas_EmptyBitmap, r) { 912 SkBitmap dst; 913 dst.allocN32Pixels(10, 10); 914 SkCanvas canvas(dst); 915 916 test_draw_bitmaps(&canvas); 917} 918 919DEF_TEST(DontOptimizeSaveLayerDrawDrawRestore, reporter) { 920 // This test is from crbug.com/344987. 921 // The commands are: 922 // saveLayer with paint that modifies alpha 923 // drawBitmapRect 924 // drawBitmapRect 925 // restore 926 // The bug was that this structure was modified so that: 927 // - The saveLayer and restore were eliminated 928 // - The alpha was only applied to the first drawBitmapRectToRect 929 930 // This test draws blue and red squares inside a 50% transparent 931 // layer. Both colours should show up muted. 932 // When the bug is present, the red square (the second bitmap) 933 // shows upwith full opacity. 934 935 SkBitmap blueBM; 936 make_bm(&blueBM, 100, 100, SkColorSetARGB(255, 0, 0, 255), true); 937 SkBitmap redBM; 938 make_bm(&redBM, 100, 100, SkColorSetARGB(255, 255, 0, 0), true); 939 SkPaint semiTransparent; 940 semiTransparent.setAlpha(0x80); 941 942 SkPictureRecorder recorder; 943 SkCanvas* canvas = recorder.beginRecording(100, 100); 944 canvas->drawColor(0); 945 946 canvas->saveLayer(0, &semiTransparent); 947 canvas->drawBitmap(blueBM, 25, 25); 948 canvas->drawBitmap(redBM, 50, 50); 949 canvas->restore(); 950 951 sk_sp<SkPicture> picture(recorder.finishRecordingAsPicture()); 952 953 // Now replay the picture back on another canvas 954 // and check a couple of its pixels. 955 SkBitmap replayBM; 956 make_bm(&replayBM, 100, 100, SK_ColorBLACK, false); 957 SkCanvas replayCanvas(replayBM); 958 picture->playback(&replayCanvas); 959 replayCanvas.flush(); 960 961 // With the bug present, at (55, 55) we would get a fully opaque red 962 // intead of a dark red. 963 REPORTER_ASSERT(reporter, replayBM.getColor(30, 30) == 0xff000080); 964 REPORTER_ASSERT(reporter, replayBM.getColor(55, 55) == 0xff800000); 965} 966 967struct CountingBBH : public SkBBoxHierarchy { 968 mutable int searchCalls; 969 SkRect rootBound; 970 971 CountingBBH(const SkRect& bound) : searchCalls(0), rootBound(bound) {} 972 973 void search(const SkRect& query, SkTDArray<int>* results) const override { 974 this->searchCalls++; 975 } 976 977 void insert(const SkRect[], int) override {} 978 virtual size_t bytesUsed() const override { return 0; } 979 SkRect getRootBound() const override { return rootBound; } 980}; 981 982class SpoonFedBBHFactory : public SkBBHFactory { 983public: 984 explicit SpoonFedBBHFactory(SkBBoxHierarchy* bbh) : fBBH(bbh) {} 985 SkBBoxHierarchy* operator()(const SkRect&) const override { 986 return SkRef(fBBH); 987 } 988private: 989 SkBBoxHierarchy* fBBH; 990}; 991 992// When the canvas clip covers the full picture, we don't need to call the BBH. 993DEF_TEST(Picture_SkipBBH, r) { 994 SkRect bound = SkRect::MakeWH(320, 240); 995 CountingBBH bbh(bound); 996 SpoonFedBBHFactory factory(&bbh); 997 998 SkPictureRecorder recorder; 999 SkCanvas* c = recorder.beginRecording(bound, &factory); 1000 // Record a few ops so we don't hit a small- or empty- picture optimization. 1001 c->drawRect(bound, SkPaint()); 1002 c->drawRect(bound, SkPaint()); 1003 sk_sp<SkPicture> picture(recorder.finishRecordingAsPicture()); 1004 1005 SkCanvas big(640, 480), small(300, 200); 1006 1007 picture->playback(&big); 1008 REPORTER_ASSERT(r, bbh.searchCalls == 0); 1009 1010 picture->playback(&small); 1011 REPORTER_ASSERT(r, bbh.searchCalls == 1); 1012} 1013 1014DEF_TEST(Picture_BitmapLeak, r) { 1015 SkBitmap mut, immut; 1016 mut.allocN32Pixels(300, 200); 1017 immut.allocN32Pixels(300, 200); 1018 immut.setImmutable(); 1019 SkASSERT(!mut.isImmutable()); 1020 SkASSERT(immut.isImmutable()); 1021 1022 // No one can hold a ref on our pixels yet. 1023 REPORTER_ASSERT(r, mut.pixelRef()->unique()); 1024 REPORTER_ASSERT(r, immut.pixelRef()->unique()); 1025 1026 sk_sp<SkPicture> pic; 1027 { 1028 // we want the recorder to go out of scope before our subsequent checks, so we 1029 // place it inside local braces. 1030 SkPictureRecorder rec; 1031 SkCanvas* canvas = rec.beginRecording(1920, 1200); 1032 canvas->drawBitmap(mut, 0, 0); 1033 canvas->drawBitmap(immut, 800, 600); 1034 pic = rec.finishRecordingAsPicture(); 1035 } 1036 1037 // The picture shares the immutable pixels but copies the mutable ones. 1038 REPORTER_ASSERT(r, mut.pixelRef()->unique()); 1039 REPORTER_ASSERT(r, !immut.pixelRef()->unique()); 1040 1041 // When the picture goes away, it's just our bitmaps holding the refs. 1042 pic = nullptr; 1043 REPORTER_ASSERT(r, mut.pixelRef()->unique()); 1044 REPORTER_ASSERT(r, immut.pixelRef()->unique()); 1045} 1046 1047// getRecordingCanvas() should return a SkCanvas when recording, null when not recording. 1048DEF_TEST(Picture_getRecordingCanvas, r) { 1049 SkPictureRecorder rec; 1050 REPORTER_ASSERT(r, !rec.getRecordingCanvas()); 1051 for (int i = 0; i < 3; i++) { 1052 rec.beginRecording(100, 100); 1053 REPORTER_ASSERT(r, rec.getRecordingCanvas()); 1054 rec.finishRecordingAsPicture(); 1055 REPORTER_ASSERT(r, !rec.getRecordingCanvas()); 1056 } 1057} 1058 1059DEF_TEST(MiniRecorderLeftHanging, r) { 1060 // Any shader or other ref-counted effect will do just fine here. 1061 SkPaint paint; 1062 paint.setShader(SkShader::MakeColorShader(SK_ColorRED)); 1063 1064 SkMiniRecorder rec; 1065 REPORTER_ASSERT(r, rec.drawRect(SkRect::MakeWH(20,30), paint)); 1066 // Don't call rec.detachPicture(). Test succeeds by not asserting or leaking the shader. 1067} 1068 1069DEF_TEST(Picture_preserveCullRect, r) { 1070 SkPictureRecorder recorder; 1071 1072 SkCanvas* c = recorder.beginRecording(SkRect::MakeLTRB(1, 2, 3, 4)); 1073 c->clear(SK_ColorCYAN); 1074 1075 sk_sp<SkPicture> picture(recorder.finishRecordingAsPicture()); 1076 SkDynamicMemoryWStream wstream; 1077 picture->serialize(&wstream); 1078 1079 std::unique_ptr<SkStream> rstream(wstream.detachAsStream()); 1080 sk_sp<SkPicture> deserializedPicture(SkPicture::MakeFromStream(rstream.get())); 1081 1082 REPORTER_ASSERT(r, deserializedPicture != nullptr); 1083 REPORTER_ASSERT(r, deserializedPicture->cullRect().left() == 1); 1084 REPORTER_ASSERT(r, deserializedPicture->cullRect().top() == 2); 1085 REPORTER_ASSERT(r, deserializedPicture->cullRect().right() == 3); 1086 REPORTER_ASSERT(r, deserializedPicture->cullRect().bottom() == 4); 1087} 1088 1089#if SK_SUPPORT_GPU 1090 1091DEF_TEST(PictureGpuAnalyzer, r) { 1092 SkPictureRecorder recorder; 1093 1094 { 1095 SkCanvas* canvas = recorder.beginRecording(10, 10); 1096 SkPaint paint; 1097 SkScalar intervals [] = { 10, 20 }; 1098 paint.setPathEffect(SkDashPathEffect::Make(intervals, 2, 25)); 1099 1100 for (int i = 0; i < 50; ++i) { 1101 canvas->drawRect(SkRect::MakeWH(10, 10), paint); 1102 } 1103 } 1104 sk_sp<SkPicture> vetoPicture(recorder.finishRecordingAsPicture()); 1105 1106 SkPictureGpuAnalyzer analyzer; 1107 REPORTER_ASSERT(r, analyzer.suitableForGpuRasterization()); 1108 1109 analyzer.analyzePicture(vetoPicture.get()); 1110 REPORTER_ASSERT(r, !analyzer.suitableForGpuRasterization()); 1111 1112 analyzer.reset(); 1113 REPORTER_ASSERT(r, analyzer.suitableForGpuRasterization()); 1114 1115 recorder.beginRecording(10, 10)->drawPicture(vetoPicture); 1116 sk_sp<SkPicture> nestedVetoPicture(recorder.finishRecordingAsPicture()); 1117 1118 analyzer.analyzePicture(nestedVetoPicture.get()); 1119 REPORTER_ASSERT(r, !analyzer.suitableForGpuRasterization()); 1120 1121 analyzer.reset(); 1122 1123 const SkPath convexClip = make_convex_path(); 1124 const SkPath concaveClip = make_concave_path(); 1125 for (int i = 0; i < 50; ++i) { 1126 analyzer.analyzeClipPath(convexClip, kIntersect_SkClipOp, false); 1127 analyzer.analyzeClipPath(convexClip, kIntersect_SkClipOp, true); 1128 analyzer.analyzeClipPath(concaveClip, kIntersect_SkClipOp, false); 1129 } 1130 REPORTER_ASSERT(r, analyzer.suitableForGpuRasterization()); 1131 1132 for (int i = 0; i < 50; ++i) { 1133 analyzer.analyzeClipPath(concaveClip, kIntersect_SkClipOp, true); 1134 } 1135 REPORTER_ASSERT(r, !analyzer.suitableForGpuRasterization()); 1136} 1137 1138#endif // SK_SUPPORT_GPU 1139 1140// If we record bounded ops into a picture with a big cull and calculate the 1141// bounds of those ops, we should trim down the picture cull to the ops' bounds. 1142// If we're not using an SkBBH, we shouldn't change it. 1143DEF_TEST(Picture_UpdatedCull_1, r) { 1144 // Testing 1 draw exercises SkMiniPicture. 1145 SkRTreeFactory factory; 1146 SkPictureRecorder recorder; 1147 1148 auto canvas = recorder.beginRecording(SkRect::MakeLargest(), &factory); 1149 canvas->drawRect(SkRect::MakeWH(20,20), SkPaint{}); 1150 auto pic = recorder.finishRecordingAsPicture(); 1151 REPORTER_ASSERT(r, pic->cullRect() == SkRect::MakeWH(20,20)); 1152 1153 canvas = recorder.beginRecording(SkRect::MakeLargest()); 1154 canvas->drawRect(SkRect::MakeWH(20,20), SkPaint{}); 1155 pic = recorder.finishRecordingAsPicture(); 1156 REPORTER_ASSERT(r, pic->cullRect() == SkRect::MakeLargest()); 1157} 1158DEF_TEST(Picture_UpdatedCull_2, r) { 1159 // Testing >1 draw exercises SkBigPicture. 1160 SkRTreeFactory factory; 1161 SkPictureRecorder recorder; 1162 1163 auto canvas = recorder.beginRecording(SkRect::MakeLargest(), &factory); 1164 canvas->drawRect(SkRect::MakeWH(20,20), SkPaint{}); 1165 canvas->drawRect(SkRect::MakeWH(10,40), SkPaint{}); 1166 auto pic = recorder.finishRecordingAsPicture(); 1167 REPORTER_ASSERT(r, pic->cullRect() == SkRect::MakeWH(20,40)); 1168 1169 canvas = recorder.beginRecording(SkRect::MakeLargest()); 1170 canvas->drawRect(SkRect::MakeWH(20,20), SkPaint{}); 1171 canvas->drawRect(SkRect::MakeWH(10,40), SkPaint{}); 1172 pic = recorder.finishRecordingAsPicture(); 1173 REPORTER_ASSERT(r, pic->cullRect() == SkRect::MakeLargest()); 1174} 1175