| /* | 
 |  * Copyright 2014 Google Inc. | 
 |  * | 
 |  * Use of this source code is governed by a BSD-style license that can be | 
 |  * found in the LICENSE file. | 
 |  */ | 
 |  | 
 | #include "include/core/SkBlendMode.h" | 
 | #include "include/core/SkCanvas.h" | 
 | #include "include/core/SkColor.h" | 
 | #include "include/core/SkColorFilter.h" | 
 | #include "include/core/SkImageFilter.h" | 
 | #include "include/core/SkImageInfo.h" | 
 | #include "include/core/SkPaint.h" | 
 | #include "include/core/SkPicture.h" | 
 | #include "include/core/SkPictureRecorder.h" | 
 | #include "include/core/SkRect.h" | 
 | #include "include/core/SkRefCnt.h" | 
 | #include "include/core/SkScalar.h" | 
 | #include "include/core/SkSurface.h" | 
 | #include "include/effects/SkImageFilters.h" | 
 | #include "src/core/SkRecord.h" | 
 | #include "src/core/SkRecordOpts.h" | 
 | #include "src/core/SkRecorder.h" | 
 | #include "src/core/SkRecords.h" | 
 | #include "tests/RecordTestUtils.h" | 
 | #include "tests/Test.h" | 
 |  | 
 | #include <array> | 
 | #include <cstddef> | 
 |  | 
 | static const int W = 1920, H = 1080; | 
 |  | 
 | DEF_TEST(RecordOpts_NoopDraw, r) { | 
 |     SkRecord record; | 
 |     SkRecorder recorder(&record, W, H); | 
 |  | 
 |     recorder.drawRect(SkRect::MakeWH(200, 200), SkPaint()); | 
 |     recorder.drawRect(SkRect::MakeWH(300, 300), SkPaint()); | 
 |     recorder.drawRect(SkRect::MakeWH(100, 100), SkPaint()); | 
 |  | 
 |     record.replace<SkRecords::NoOp>(1);  // NoOps should be allowed. | 
 |  | 
 |     SkRecordNoopSaveRestores(&record); | 
 |  | 
 |     REPORTER_ASSERT(r, 2 == count_instances_of_type<SkRecords::DrawRect>(record)); | 
 | } | 
 |  | 
 | DEF_TEST(RecordOpts_SingleNoopSaveRestore, r) { | 
 |     SkRecord record; | 
 |     SkRecorder recorder(&record, W, H); | 
 |  | 
 |     recorder.save(); | 
 |         recorder.clipRect(SkRect::MakeWH(200, 200)); | 
 |     recorder.restore(); | 
 |  | 
 |     SkRecordNoopSaveRestores(&record); | 
 |     for (int i = 0; i < 3; i++) { | 
 |         assert_type<SkRecords::NoOp>(r, record, i); | 
 |     } | 
 | } | 
 |  | 
 | DEF_TEST(RecordOpts_NoopSaveRestores, r) { | 
 |     SkRecord record; | 
 |     SkRecorder recorder(&record, W, H); | 
 |  | 
 |     // The second pass will clean up this pair after the first pass noops all the innards. | 
 |     recorder.save(); | 
 |         // A simple pointless pair of save/restore. | 
 |         recorder.save(); | 
 |         recorder.restore(); | 
 |  | 
 |         // As long as we don't draw in there, everything is a noop. | 
 |         recorder.save(); | 
 |             recorder.clipRect(SkRect::MakeWH(200, 200)); | 
 |             recorder.clipRect(SkRect::MakeWH(100, 100)); | 
 |         recorder.restore(); | 
 |     recorder.restore(); | 
 |  | 
 |     SkRecordNoopSaveRestores(&record); | 
 |     for (int index = 0; index < record.count(); index++) { | 
 |         assert_type<SkRecords::NoOp>(r, record, index); | 
 |     } | 
 | } | 
 |  | 
 | DEF_TEST(RecordOpts_SaveSaveLayerRestoreRestore, r) { | 
 |     SkRecord record; | 
 |     SkRecorder recorder(&record, W, H); | 
 |  | 
 |     // A previous bug NoOp'd away the first 3 commands. | 
 |     recorder.save(); | 
 |         recorder.saveLayer(nullptr, nullptr); | 
 |         recorder.restore(); | 
 |     recorder.restore(); | 
 |  | 
 |     SkRecordNoopSaveRestores(&record); | 
 |     switch (record.count()) { | 
 |         case 4: | 
 |             assert_type<SkRecords::Save>     (r, record, 0); | 
 |             assert_type<SkRecords::SaveLayer>(r, record, 1); | 
 |             assert_type<SkRecords::Restore>  (r, record, 2); | 
 |             assert_type<SkRecords::Restore>  (r, record, 3); | 
 |             break; | 
 |         case 2: | 
 |             assert_type<SkRecords::SaveLayer>(r, record, 0); | 
 |             assert_type<SkRecords::Restore>  (r, record, 1); | 
 |             break; | 
 |         case 0: | 
 |             break; | 
 |         default: | 
 |             REPORTER_ASSERT(r, false); | 
 |     } | 
 | } | 
 |  | 
 | #ifndef SK_BUILD_FOR_ANDROID_FRAMEWORK | 
 | static void assert_savelayer_restore(skiatest::Reporter* r, | 
 |                                      SkRecord* record, | 
 |                                      int i, | 
 |                                      bool shouldBeNoOped) { | 
 |     SkRecordNoopSaveLayerDrawRestores(record); | 
 |     if (shouldBeNoOped) { | 
 |         assert_type<SkRecords::NoOp>(r, *record, i); | 
 |         assert_type<SkRecords::NoOp>(r, *record, i+1); | 
 |     } else { | 
 |         assert_type<SkRecords::SaveLayer>(r, *record, i); | 
 |         assert_type<SkRecords::Restore>(r, *record, i+1); | 
 |     } | 
 | } | 
 |  | 
 | static void assert_savelayer_draw_restore(skiatest::Reporter* r, | 
 |                                           SkRecord* record, | 
 |                                           int i, | 
 |                                           bool shouldBeNoOped) { | 
 |     SkRecordNoopSaveLayerDrawRestores(record); | 
 |     if (shouldBeNoOped) { | 
 |         assert_type<SkRecords::NoOp>(r, *record, i); | 
 |         assert_type<SkRecords::NoOp>(r, *record, i+2); | 
 |     } else { | 
 |         assert_type<SkRecords::SaveLayer>(r, *record, i); | 
 |         assert_type<SkRecords::Restore>(r, *record, i+2); | 
 |     } | 
 | } | 
 |  | 
 | DEF_TEST(RecordOpts_NoopSaveLayerDrawRestore, r) { | 
 |     SkRecord record; | 
 |     SkRecorder recorder(&record, W, H); | 
 |  | 
 |     SkRect bounds = SkRect::MakeWH(100, 200); | 
 |     SkRect   draw = SkRect::MakeWH(50, 60); | 
 |  | 
 |     SkPaint alphaOnlyLayerPaint, translucentLayerPaint, xfermodeLayerPaint; | 
 |     alphaOnlyLayerPaint.setColor(0x03000000);  // Only alpha. | 
 |     translucentLayerPaint.setColor(0x03040506);  // Not only alpha. | 
 |     xfermodeLayerPaint.setBlendMode(SkBlendMode::kDstIn);  // Any effect will do. | 
 |  | 
 |     SkPaint opaqueDrawPaint, translucentDrawPaint; | 
 |     opaqueDrawPaint.setColor(0xFF020202);  // Opaque. | 
 |     translucentDrawPaint.setColor(0x0F020202);  // Not opaque. | 
 |  | 
 |     // SaveLayer/Restore removed: No paint = no point. | 
 |     recorder.saveLayer(nullptr, nullptr); | 
 |         recorder.drawRect(draw, opaqueDrawPaint); | 
 |     recorder.restore(); | 
 |     assert_savelayer_draw_restore(r, &record, 0, true); | 
 |  | 
 |     // Bounds don't matter. | 
 |     recorder.saveLayer(&bounds, nullptr); | 
 |         recorder.drawRect(draw, opaqueDrawPaint); | 
 |     recorder.restore(); | 
 |     assert_savelayer_draw_restore(r, &record, 3, true); | 
 |  | 
 |     // TODO(mtklein): test case with null draw paint | 
 |  | 
 |     // No change: layer paint isn't alpha-only. | 
 |     recorder.saveLayer(nullptr, &translucentLayerPaint); | 
 |         recorder.drawRect(draw, opaqueDrawPaint); | 
 |     recorder.restore(); | 
 |     assert_savelayer_draw_restore(r, &record, 6, false); | 
 |  | 
 |     // No change: layer paint has an effect. | 
 |     recorder.saveLayer(nullptr, &xfermodeLayerPaint); | 
 |         recorder.drawRect(draw, opaqueDrawPaint); | 
 |     recorder.restore(); | 
 |     assert_savelayer_draw_restore(r, &record, 9, false); | 
 |  | 
 |     // SaveLayer/Restore removed: we can fold in the alpha! | 
 |     recorder.saveLayer(nullptr, &alphaOnlyLayerPaint); | 
 |         recorder.drawRect(draw, translucentDrawPaint); | 
 |     recorder.restore(); | 
 |     assert_savelayer_draw_restore(r, &record, 12, true); | 
 |  | 
 |     // SaveLayer/Restore removed: we can fold in the alpha! | 
 |     recorder.saveLayer(nullptr, &alphaOnlyLayerPaint); | 
 |         recorder.drawRect(draw, opaqueDrawPaint); | 
 |     recorder.restore(); | 
 |     assert_savelayer_draw_restore(r, &record, 15, true); | 
 |  | 
 |     const SkRecords::DrawRect* drawRect = assert_type<SkRecords::DrawRect>(r, record, 16); | 
 |     REPORTER_ASSERT(r, drawRect != nullptr); | 
 |     REPORTER_ASSERT(r, drawRect->paint.getColor() == 0x03020202); | 
 |  | 
 |     // saveLayer w/ backdrop should NOT go away | 
 |     sk_sp<SkImageFilter> filter(SkImageFilters::Blur(3, 3, nullptr)); | 
 |     recorder.saveLayer({ nullptr, nullptr, filter.get(), 0}); | 
 |         recorder.drawRect(draw, opaqueDrawPaint); | 
 |     recorder.restore(); | 
 |     assert_savelayer_draw_restore(r, &record, 18, false); | 
 | } | 
 | #endif | 
 |  | 
 | static void assert_merge_svg_opacity_and_filter_layers(skiatest::Reporter* r, | 
 |                                                        SkRecord* record, | 
 |                                                        int i, | 
 |                                                        bool shouldBeNoOped) { | 
 |     SkRecordMergeSvgOpacityAndFilterLayers(record); | 
 |     if (shouldBeNoOped) { | 
 |         assert_type<SkRecords::NoOp>(r, *record, i); | 
 |         assert_type<SkRecords::NoOp>(r, *record, i + 6); | 
 |     } else { | 
 |         assert_type<SkRecords::SaveLayer>(r, *record, i); | 
 |         assert_type<SkRecords::Restore>(r, *record, i + 6); | 
 |     } | 
 | } | 
 |  | 
 | DEF_TEST(RecordOpts_MergeSvgOpacityAndFilterLayers, r) { | 
 |     SkRecord record; | 
 |     SkRecorder recorder(&record, W, H); | 
 |  | 
 |     SkRect bounds = SkRect::MakeWH(SkIntToScalar(100), SkIntToScalar(200)); | 
 |     SkRect clip = SkRect::MakeWH(SkIntToScalar(50), SkIntToScalar(60)); | 
 |  | 
 |     SkPaint alphaOnlyLayerPaint; | 
 |     alphaOnlyLayerPaint.setColor(0x03000000);  // Only alpha. | 
 |     SkPaint translucentLayerPaint; | 
 |     translucentLayerPaint.setColor(0x03040506);  // Not only alpha. | 
 |     SkPaint xfermodePaint; | 
 |     xfermodePaint.setBlendMode(SkBlendMode::kDstIn); | 
 |     SkPaint colorFilterPaint; | 
 |     colorFilterPaint.setColorFilter( | 
 |         SkColorFilters::Blend(SK_ColorLTGRAY, SkBlendMode::kSrcIn)); | 
 |  | 
 |     SkPaint opaqueFilterLayerPaint; | 
 |     opaqueFilterLayerPaint.setColor(0xFF020202);  // Opaque. | 
 |     SkPaint translucentFilterLayerPaint; | 
 |     translucentFilterLayerPaint.setColor(0x0F020202);  // Not opaque. | 
 |     sk_sp<SkPicture> shape; | 
 |     { | 
 |         SkPictureRecorder picRecorder; | 
 |         SkCanvas* canvas = picRecorder.beginRecording(SkIntToScalar(100), SkIntToScalar(100)); | 
 |         SkPaint shapePaint; | 
 |         shapePaint.setColor(SK_ColorWHITE); | 
 |         canvas->drawRect(SkRect::MakeWH(SkIntToScalar(50), SkIntToScalar(50)), shapePaint); | 
 |         shape = picRecorder.finishRecordingAsPicture(); | 
 |     } | 
 |     translucentFilterLayerPaint.setImageFilter(SkImageFilters::Picture(shape)); | 
 |  | 
 |     int index = 0; | 
 |  | 
 |     { | 
 |         sk_sp<SkImageFilter> filter(SkImageFilters::Blur(3, 3, nullptr)); | 
 |         // first (null) should be optimized, 2nd should not | 
 |         SkImageFilter* filters[] = { nullptr, filter.get() }; | 
 |  | 
 |         // Any combination of these should cause the pattern to be optimized. | 
 |         SkRect* firstBounds[] = { nullptr, &bounds }; | 
 |         SkPaint* firstPaints[] = { nullptr, &alphaOnlyLayerPaint }; | 
 |         SkRect* secondBounds[] = { nullptr, &bounds }; | 
 |         SkPaint* secondPaints[] = { &opaqueFilterLayerPaint, &translucentFilterLayerPaint }; | 
 |  | 
 |         for (auto outerF : filters) { | 
 |             bool outerNoOped = !outerF; | 
 |             for (auto innerF : filters) { | 
 |                 for (size_t i = 0; i < std::size(firstBounds); ++ i) { | 
 |                     for (size_t j = 0; j < std::size(firstPaints); ++j) { | 
 |                         for (size_t k = 0; k < std::size(secondBounds); ++k) { | 
 |                             for (size_t m = 0; m < std::size(secondPaints); ++m) { | 
 |                                 bool innerNoOped = !secondBounds[k] && !secondPaints[m] && !innerF; | 
 |  | 
 |                                 recorder.saveLayer({firstBounds[i], firstPaints[j], outerF, 0}); | 
 |                                 recorder.save(); | 
 |                                 recorder.clipRect(clip); | 
 |                                 recorder.saveLayer({secondBounds[k], secondPaints[m], innerF, 0}); | 
 |                                 recorder.restore(); | 
 |                                 recorder.restore(); | 
 |                                 recorder.restore(); | 
 |                                 assert_merge_svg_opacity_and_filter_layers(r, &record, index, | 
 |                                                                            outerNoOped); | 
 |                             #ifndef SK_BUILD_FOR_ANDROID_FRAMEWORK | 
 |                                 assert_savelayer_restore(r, &record, index + 3, innerNoOped); | 
 |                             #endif | 
 |                                 index += 7; | 
 |                             } | 
 |                         } | 
 |                     } | 
 |                 } | 
 |             } | 
 |         } | 
 |     } | 
 |  | 
 |     // These should cause the pattern to stay unoptimized: | 
 |     struct { | 
 |         SkPaint* firstPaint; | 
 |         SkPaint* secondPaint; | 
 |     } noChangeTests[] = { | 
 |         // No change: nullptr filter layer paint not implemented. | 
 |         { &alphaOnlyLayerPaint, nullptr }, | 
 |         // No change: layer paint is not alpha-only. | 
 |         { &translucentLayerPaint, &opaqueFilterLayerPaint }, | 
 |         // No change: layer paint has an xfereffect. | 
 |         { &xfermodePaint, &opaqueFilterLayerPaint }, | 
 |         // No change: filter layer paint has an xfereffect. | 
 |         { &alphaOnlyLayerPaint, &xfermodePaint }, | 
 |         // No change: layer paint has a color filter. | 
 |         { &colorFilterPaint, &opaqueFilterLayerPaint }, | 
 |         // No change: filter layer paint has a color filter (until the optimization accounts for | 
 |         // constant color draws that can filter the color). | 
 |         { &alphaOnlyLayerPaint, &colorFilterPaint } | 
 |     }; | 
 |  | 
 |     for (size_t i = 0; i < std::size(noChangeTests); ++i) { | 
 |         recorder.saveLayer(nullptr, noChangeTests[i].firstPaint); | 
 |         recorder.save(); | 
 |         recorder.clipRect(clip); | 
 |         recorder.saveLayer(nullptr, noChangeTests[i].secondPaint); | 
 |         recorder.restore(); | 
 |         recorder.restore(); | 
 |         recorder.restore(); | 
 |         assert_merge_svg_opacity_and_filter_layers(r, &record, index, false); | 
 |         index += 7; | 
 |     } | 
 |  | 
 |     // Test the folded alpha value. | 
 |     recorder.saveLayer(nullptr, &alphaOnlyLayerPaint); | 
 |     recorder.save(); | 
 |     recorder.clipRect(clip); | 
 |     recorder.saveLayer(nullptr, &opaqueFilterLayerPaint); | 
 |     recorder.restore(); | 
 |     recorder.restore(); | 
 |     recorder.restore(); | 
 |     assert_merge_svg_opacity_and_filter_layers(r, &record, index, true); | 
 |  | 
 |     const SkRecords::SaveLayer* saveLayer = assert_type<SkRecords::SaveLayer>(r, record, index + 3); | 
 |     REPORTER_ASSERT(r, saveLayer != nullptr); | 
 |     REPORTER_ASSERT(r, saveLayer->paint->getColor() == 0x03020202); | 
 |  | 
 |     index += 7; | 
 |  | 
 |     // Test that currently we do not fold alphas for patterns without the clip. This is just not | 
 |     // implemented. | 
 |     recorder.saveLayer(nullptr, &alphaOnlyLayerPaint); | 
 |     recorder.saveLayer(nullptr, &opaqueFilterLayerPaint); | 
 |     recorder.restore(); | 
 |     recorder.restore(); | 
 |     SkRecordMergeSvgOpacityAndFilterLayers(&record); | 
 |     assert_type<SkRecords::SaveLayer>(r, record, index); | 
 |     assert_type<SkRecords::SaveLayer>(r, record, index + 1); | 
 |     assert_type<SkRecords::Restore>(r, record, index + 2); | 
 |     assert_type<SkRecords::Restore>(r, record, index + 3); | 
 |     index += 4; | 
 | } | 
 |  | 
 | static void do_draw(SkCanvas* canvas, SkColor color, bool doLayer) { | 
 |     canvas->drawColor(SK_ColorWHITE); | 
 |  | 
 |     SkPaint p; | 
 |     p.setColor(color); | 
 |  | 
 |     if (doLayer) { | 
 |         canvas->saveLayer(nullptr, nullptr); | 
 |         p.setBlendMode(SkBlendMode::kSrc); | 
 |         canvas->drawPaint(p); | 
 |         canvas->restore(); | 
 |     } else { | 
 |         canvas->drawPaint(p); | 
 |     } | 
 | } | 
 |  | 
 | static bool is_equal(SkSurface* a, SkSurface* b) { | 
 |     const SkImageInfo info = SkImageInfo::MakeN32Premul(1, 1); | 
 |     SkPMColor ca, cb; | 
 |     a->readPixels(info, &ca, sizeof(SkPMColor), 0, 0); | 
 |     b->readPixels(info, &cb, sizeof(SkPMColor), 0, 0); | 
 |     return ca == cb; | 
 | } | 
 |  | 
 | // Test drawing w/ and w/o a simple layer (no bounds or paint), so see that drawing ops | 
 | // that *should* draw the same in fact do. | 
 | // | 
 | // Perform this test twice : once directly, and once via a picture | 
 | // | 
 | static void do_savelayer_srcmode(skiatest::Reporter* r, SkColor color) { | 
 |     for (int doPicture = 0; doPicture <= 1; ++doPicture) { | 
 |         sk_sp<SkSurface> surf0 = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(10, 10)); | 
 |         sk_sp<SkSurface> surf1 = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(10, 10)); | 
 |         SkCanvas* c0 = surf0->getCanvas(); | 
 |         SkCanvas* c1 = surf1->getCanvas(); | 
 |  | 
 |         SkPictureRecorder rec0, rec1; | 
 |         if (doPicture) { | 
 |             c0 = rec0.beginRecording(10, 10); | 
 |             c1 = rec1.beginRecording(10, 10); | 
 |         } | 
 |  | 
 |         do_draw(c0, color, false); | 
 |         do_draw(c1, color, true); | 
 |  | 
 |         if (doPicture) { | 
 |             surf0->getCanvas()->drawPicture(rec0.finishRecordingAsPicture()); | 
 |             surf1->getCanvas()->drawPicture(rec1.finishRecordingAsPicture()); | 
 |         } | 
 |  | 
 |         // we replicate the assert so we can see which line is reported if there is a failure | 
 |         if (doPicture) { | 
 |             REPORTER_ASSERT(r, is_equal(surf0.get(), surf1.get())); | 
 |         } else { | 
 |             REPORTER_ASSERT(r, is_equal(surf0.get(), surf1.get())); | 
 |         } | 
 |     } | 
 | } | 
 |  | 
 | DEF_TEST(savelayer_srcmode_opaque, r) { | 
 |     do_savelayer_srcmode(r, SK_ColorRED); | 
 | } | 
 |  | 
 | DEF_TEST(savelayer_srcmode_alpha, r) { | 
 |     do_savelayer_srcmode(r, 0x80FF0000); | 
 | } | 
 |  |