| // Copyright 2013 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "flutter/flow/display_list_canvas.h" |
| #include "flutter/flow/layers/physical_shape_layer.h" |
| |
| #include "third_party/skia/include/core/SkColor.h" |
| #include "third_party/skia/include/core/SkImageInfo.h" |
| #include "third_party/skia/include/core/SkPath.h" |
| #include "third_party/skia/include/core/SkPictureRecorder.h" |
| #include "third_party/skia/include/core/SkRRect.h" |
| #include "third_party/skia/include/core/SkRSXform.h" |
| #include "third_party/skia/include/core/SkSurface.h" |
| #include "third_party/skia/include/core/SkTextBlob.h" |
| #include "third_party/skia/include/core/SkVertices.h" |
| #include "third_party/skia/include/effects/SkBlenders.h" |
| #include "third_party/skia/include/effects/SkDashPathEffect.h" |
| #include "third_party/skia/include/effects/SkDiscretePathEffect.h" |
| #include "third_party/skia/include/effects/SkGradientShader.h" |
| #include "third_party/skia/include/effects/SkImageFilters.h" |
| |
| #include <cmath> |
| |
| #include "gtest/gtest.h" |
| |
| namespace flutter { |
| namespace testing { |
| |
| constexpr int TestWidth = 200; |
| constexpr int TestHeight = 200; |
| constexpr int RenderWidth = 100; |
| constexpr int RenderHeight = 100; |
| constexpr int RenderHalfWidth = 50; |
| constexpr int RenderHalfHeight = 50; |
| constexpr int RenderLeft = (TestWidth - RenderWidth) / 2; |
| constexpr int RenderTop = (TestHeight - RenderHeight) / 2; |
| constexpr int RenderRight = RenderLeft + RenderWidth; |
| constexpr int RenderBottom = RenderTop + RenderHeight; |
| constexpr int RenderCenterX = (RenderLeft + RenderRight) / 2; |
| constexpr int RenderCenterY = (RenderTop + RenderBottom) / 2; |
| constexpr SkScalar RenderRadius = std::min(RenderWidth, RenderHeight) / 2.0; |
| constexpr SkScalar RenderCornerRadius = RenderRadius / 5.0; |
| |
| constexpr SkPoint TestCenter = SkPoint::Make(TestWidth / 2, TestHeight / 2); |
| constexpr SkRect TestBounds = SkRect::MakeWH(TestWidth, TestHeight); |
| constexpr SkRect RenderBounds = |
| SkRect::MakeLTRB(RenderLeft, RenderTop, RenderRight, RenderBottom); |
| |
| // The tests try 3 miter limit values, 0.0, 4.0 (the default), and 10.0 |
| // These values will allow us to construct a diamond that spans the |
| // width or height of the render box and still show the miter for 4.0 |
| // and 10.0. |
| // These values were discovered by drawing a diamond path in Skia fiddle |
| // and then playing with the cross-axis size until the miter was about |
| // as large as it could get before it got cut off. |
| |
| // The X offsets which will be used for tall vertical diamonds are |
| // expressed in terms of the rendering height to obtain the proper angle |
| constexpr SkScalar MiterExtremeDiamondOffsetX = RenderHeight * 0.04; |
| constexpr SkScalar Miter10DiamondOffsetX = RenderHeight * 0.051; |
| constexpr SkScalar Miter4DiamondOffsetX = RenderHeight * 0.14; |
| |
| // The Y offsets which will be used for long horizontal diamonds are |
| // expressed in terms of the rendering width to obtain the proper angle |
| constexpr SkScalar MiterExtremeDiamondOffsetY = RenderWidth * 0.04; |
| constexpr SkScalar Miter10DiamondOffsetY = RenderWidth * 0.051; |
| constexpr SkScalar Miter4DiamondOffsetY = RenderWidth * 0.14; |
| |
| // Render 3 vertical and horizontal diamonds each |
| // designed to break at the tested miter limits |
| // 0.0, 4.0 and 10.0 |
| constexpr SkScalar x_off_0 = RenderCenterX; |
| constexpr SkScalar x_off_l1 = RenderCenterX - Miter4DiamondOffsetX; |
| constexpr SkScalar x_off_l2 = x_off_l1 - Miter10DiamondOffsetX; |
| constexpr SkScalar x_off_l3 = x_off_l2 - Miter10DiamondOffsetX; |
| constexpr SkScalar x_off_r1 = RenderCenterX + Miter4DiamondOffsetX; |
| constexpr SkScalar x_off_r2 = x_off_r1 + MiterExtremeDiamondOffsetX; |
| constexpr SkScalar x_off_r3 = x_off_r2 + MiterExtremeDiamondOffsetX; |
| constexpr SkPoint VerticalMiterDiamondPoints[] = { |
| // Vertical diamonds: |
| // M10 M4 Mextreme |
| // /\ /|\ /\ top of RenderBounds |
| // / \ / | \ / \ to |
| // <----X--+--X----> RenderCenter |
| // \ / \ | / \ / to |
| // \/ \|/ \/ bottom of RenderBounds |
| // clang-format off |
| SkPoint::Make(x_off_l3, RenderCenterY), |
| SkPoint::Make(x_off_l2, RenderTop), |
| SkPoint::Make(x_off_l1, RenderCenterY), |
| SkPoint::Make(x_off_0, RenderTop), |
| SkPoint::Make(x_off_r1, RenderCenterY), |
| SkPoint::Make(x_off_r2, RenderTop), |
| SkPoint::Make(x_off_r3, RenderCenterY), |
| SkPoint::Make(x_off_r2, RenderBottom), |
| SkPoint::Make(x_off_r1, RenderCenterY), |
| SkPoint::Make(x_off_0, RenderBottom), |
| SkPoint::Make(x_off_l1, RenderCenterY), |
| SkPoint::Make(x_off_l2, RenderBottom), |
| SkPoint::Make(x_off_l3, RenderCenterY), |
| // clang-format on |
| }; |
| const int VerticalMiterDiamondPointCount = |
| sizeof(VerticalMiterDiamondPoints) / sizeof(VerticalMiterDiamondPoints[0]); |
| |
| constexpr SkScalar y_off_0 = RenderCenterY; |
| constexpr SkScalar y_off_u1 = RenderCenterY - Miter4DiamondOffsetY; |
| constexpr SkScalar y_off_u2 = y_off_u1 - Miter10DiamondOffsetY; |
| constexpr SkScalar y_off_u3 = y_off_u2 - Miter10DiamondOffsetY; |
| constexpr SkScalar y_off_d1 = RenderCenterY + Miter4DiamondOffsetY; |
| constexpr SkScalar y_off_d2 = y_off_d1 + MiterExtremeDiamondOffsetY; |
| constexpr SkScalar y_off_d3 = y_off_d2 + MiterExtremeDiamondOffsetY; |
| const SkPoint HorizontalMiterDiamondPoints[] = { |
| // Horizontal diamonds |
| // Same configuration as Vertical diamonds above but |
| // rotated 90 degrees |
| // clang-format off |
| SkPoint::Make(RenderCenterX, y_off_u3), |
| SkPoint::Make(RenderLeft, y_off_u2), |
| SkPoint::Make(RenderCenterX, y_off_u1), |
| SkPoint::Make(RenderLeft, y_off_0), |
| SkPoint::Make(RenderCenterX, y_off_d1), |
| SkPoint::Make(RenderLeft, y_off_d2), |
| SkPoint::Make(RenderCenterX, y_off_d3), |
| SkPoint::Make(RenderRight, y_off_d2), |
| SkPoint::Make(RenderCenterX, y_off_d1), |
| SkPoint::Make(RenderRight, y_off_0), |
| SkPoint::Make(RenderCenterX, y_off_u1), |
| SkPoint::Make(RenderRight, y_off_u2), |
| SkPoint::Make(RenderCenterX, y_off_u3), |
| // clang-format on |
| }; |
| const int HorizontalMiterDiamondPointCount = |
| (sizeof(HorizontalMiterDiamondPoints) / |
| sizeof(HorizontalMiterDiamondPoints[0])); |
| |
| // A class to specify how much tolerance to allow in bounds estimates. |
| // For some attributes, the machinery must make some conservative |
| // assumptions as to the extent of the bounds, but some of our test |
| // parameters do not produce bounds that expand by the full conservative |
| // estimates. This class provides a number of tweaks to apply to the |
| // pixel bounds to account for the conservative factors. |
| // |
| // An instance is passed along through the methods and if any test adds |
| // a paint attribute or other modifier that will cause a more conservative |
| // estimate for bounds, it can modify the factors here to account for it. |
| // Ideally, all tests will be executed with geometry that will trigger |
| // the conservative cases anyway and all attributes will be combined with |
| // other attributes that make their output more predictable, but in those |
| // cases where a given test sequence cannot really provide attributes to |
| // demonstrate the worst case scenario, they can modify these factors to |
| // avoid false bounds overflow notifications. |
| class BoundsTolerance { |
| public: |
| BoundsTolerance() : BoundsTolerance(0, 0, 1, 1, 0, 0, 0) {} |
| BoundsTolerance(SkScalar bounds_pad_x, |
| SkScalar bounds_pad_y, |
| SkScalar scale_x, |
| SkScalar scale_y, |
| SkScalar absolute_pad_x, |
| SkScalar absolute_pad_y, |
| SkScalar discrete_offset) |
| : bounds_pad_x_(bounds_pad_x), |
| bounds_pad_y_(bounds_pad_y), |
| scale_x_(scale_x), |
| scale_y_(scale_y), |
| absolute_pad_x_(absolute_pad_x), |
| absolute_pad_y_(absolute_pad_y), |
| discrete_offset_(discrete_offset) {} |
| |
| BoundsTolerance addBoundsPadding(SkScalar bounds_pad_x, |
| SkScalar bounds_pad_y) const { |
| return {bounds_pad_x_ + bounds_pad_x, |
| bounds_pad_y_ + bounds_pad_y, |
| scale_x_, |
| scale_y_, |
| absolute_pad_x_, |
| absolute_pad_y_, |
| discrete_offset_}; |
| } |
| |
| BoundsTolerance addScale(SkScalar scale_x, SkScalar scale_y) const { |
| return {bounds_pad_x_, // |
| bounds_pad_y_, // |
| scale_x_ * scale_x, // |
| scale_y_ * scale_y, // |
| absolute_pad_x_, // |
| absolute_pad_y_, // |
| discrete_offset_}; |
| } |
| |
| BoundsTolerance addAbsolutePadding(SkScalar absolute_pad_x, |
| SkScalar absolute_pad_y) const { |
| return {bounds_pad_x_, |
| bounds_pad_y_, |
| scale_x_, |
| scale_y_, |
| absolute_pad_x_ + absolute_pad_x, |
| absolute_pad_y_ + absolute_pad_y, |
| discrete_offset_}; |
| } |
| |
| BoundsTolerance addDiscreteOffset(SkScalar discrete_offset) const { |
| return {bounds_pad_x_, |
| bounds_pad_y_, |
| scale_x_, |
| scale_y_, |
| absolute_pad_x_, |
| absolute_pad_y_, |
| discrete_offset_ + discrete_offset}; |
| } |
| |
| bool overflows(SkISize pix_size, |
| int worst_bounds_pad_x, |
| int worst_bounds_pad_y) const { |
| int scaled_bounds_pad_x = |
| std::ceil((pix_size.width() + bounds_pad_x_) * scale_x_); |
| int allowed_width = scaled_bounds_pad_x + absolute_pad_x_; |
| int scaled_bounds_pad_y = |
| std::ceil((pix_size.height() + bounds_pad_y_) * scale_y_); |
| int allowed_height = scaled_bounds_pad_y + absolute_pad_y_; |
| int allowed_pad_x = allowed_width - pix_size.width(); |
| int allowed_pad_y = allowed_height - pix_size.height(); |
| if (worst_bounds_pad_x > allowed_pad_x || |
| worst_bounds_pad_y > allowed_pad_y) { |
| FML_LOG(ERROR) << "allowed pad: " // |
| << allowed_pad_x << ", " << allowed_pad_y; |
| } |
| return (worst_bounds_pad_x > allowed_pad_x || |
| worst_bounds_pad_y > allowed_pad_y); |
| } |
| |
| SkScalar discrete_offset() const { return discrete_offset_; } |
| |
| private: |
| SkScalar bounds_pad_x_; |
| SkScalar bounds_pad_y_; |
| SkScalar scale_x_; |
| SkScalar scale_y_; |
| SkScalar absolute_pad_x_; |
| SkScalar absolute_pad_y_; |
| |
| SkScalar discrete_offset_; |
| }; |
| |
| class CanvasCompareTester { |
| private: |
| // If a test is using any shadow operations then we cannot currently |
| // record those in an SkCanvas and play it back into a DisplayList |
| // because internally the operation gets encapsulated in a Skia |
| // ShadowRec which is not exposed by their headers. For operations |
| // that use shadows, we can perform a lot of tests, but not the tests |
| // that require SkCanvas->DisplayList transfers. |
| // See: https://bugs.chromium.org/p/skia/issues/detail?id=12125 |
| static bool TestingDrawShadows; |
| // The CPU renders nothing for drawVertices with a Blender. |
| // See: https://bugs.chromium.org/p/skia/issues/detail?id=12200 |
| static bool TestingDrawVertices; |
| // The CPU renders nothing for drawAtlas with a Blender. |
| // See: https://bugs.chromium.org/p/skia/issues/detail?id=12199 |
| static bool TestingDrawAtlas; |
| |
| public: |
| typedef const std::function<void(SkCanvas*, SkPaint&)> CvRenderer; |
| typedef const std::function<void(DisplayListBuilder&)> DlRenderer; |
| typedef const std::function<const BoundsTolerance(const BoundsTolerance&, |
| const SkPaint&, |
| const SkMatrix&)> |
| ToleranceAdjuster; |
| |
| static BoundsTolerance DefaultTolerance; |
| static const BoundsTolerance DefaultAdjuster(const BoundsTolerance& tolerance, |
| const SkPaint& paint, |
| const SkMatrix& matrix) { |
| return tolerance; |
| } |
| |
| // All of the tests should eventually use this method except for the |
| // tests that call |RenderNoAttributes| because they do not use the |
| // SkPaint object. |
| // But there are a couple of conditions beyond our control which require |
| // the use of one of the variant methods below (|RenderShadows|, |
| // |RenderVertices|, |RenderAtlas|). |
| static void RenderAll(CvRenderer& cv_renderer, |
| DlRenderer& dl_renderer, |
| ToleranceAdjuster& adjuster = DefaultAdjuster, |
| const BoundsTolerance& tolerance = DefaultTolerance) { |
| RenderNoAttributes(cv_renderer, dl_renderer, adjuster, tolerance); |
| RenderWithAttributes(cv_renderer, dl_renderer, adjuster, tolerance); |
| } |
| |
| // Used by the tests that render shadows to deal with a condition where |
| // we cannot recapture the shadow information from an SkCanvas stream |
| // due to the DrawShadowRec used by Skia is not properly exported. |
| // See: https://bugs.chromium.org/p/skia/issues/detail?id=12125 |
| static void RenderShadows( |
| CvRenderer& cv_renderer, |
| DlRenderer& dl_renderer, |
| ToleranceAdjuster& adjuster = DefaultAdjuster, |
| const BoundsTolerance& tolerance = DefaultTolerance) { |
| TestingDrawShadows = true; |
| RenderNoAttributes(cv_renderer, dl_renderer, adjuster, tolerance); |
| TestingDrawShadows = false; |
| } |
| |
| // Used by the tests that call drawVertices to avoid using an SkBlender |
| // during testing because the CPU renderer appears not to render anything. |
| // See: https://bugs.chromium.org/p/skia/issues/detail?id=12200 |
| static void RenderVertices(CvRenderer& cv_renderer, DlRenderer& dl_renderer) { |
| TestingDrawVertices = true; |
| RenderAll(cv_renderer, dl_renderer); |
| TestingDrawVertices = false; |
| } |
| |
| // Used by the tests that call drawAtlas to avoid using an SkBlender |
| // during testing because the CPU renderer appears not to render anything. |
| // See: https://bugs.chromium.org/p/skia/issues/detail?id=12199 |
| static void RenderAtlas(CvRenderer& cv_renderer, DlRenderer& dl_renderer) { |
| TestingDrawAtlas = true; |
| RenderAll(cv_renderer, dl_renderer); |
| TestingDrawAtlas = false; |
| } |
| |
| // Used by the tests that call a draw method that does not take a paint |
| // call. Those tests could use |RenderAll| but there would be a lot of |
| // wasted test runs that prepare an SkPaint that is never used. |
| static void RenderNoAttributes( |
| CvRenderer& cv_renderer, |
| DlRenderer& dl_renderer, |
| ToleranceAdjuster& adjuster = DefaultAdjuster, |
| const BoundsTolerance& tolerance = DefaultTolerance) { |
| RenderWith([=](SkCanvas*, SkPaint& p) {}, // |
| [=](DisplayListBuilder& d) {}, // |
| cv_renderer, dl_renderer, adjuster, tolerance, "Base Test"); |
| RenderWithTransforms(cv_renderer, dl_renderer, adjuster, tolerance); |
| RenderWithClips(cv_renderer, dl_renderer, adjuster, tolerance); |
| RenderWithSaveRestore(cv_renderer, dl_renderer, adjuster, tolerance); |
| } |
| |
| static void RenderWithSaveRestore(CvRenderer& cv_renderer, |
| DlRenderer& dl_renderer, |
| ToleranceAdjuster& adjuster, |
| const BoundsTolerance& tolerance) { |
| SkRect clip = SkRect::MakeLTRB(0, 0, 10, 10); |
| SkRect rect = SkRect::MakeLTRB(5, 5, 15, 15); |
| SkColor alpha_layer_color = SkColorSetARGB(0x7f, 0x00, 0xff, 0xff); |
| SkColor default_color = SkPaint().getColor(); |
| CvRenderer cv_restored = [=](SkCanvas* cv, SkPaint& p) { |
| // Draw more than one primitive to disable peephole optimizations |
| cv->drawRect(RenderBounds.makeOutset(5, 5), p); |
| cv_renderer(cv, p); |
| cv->restore(); |
| }; |
| DlRenderer dl_restored = [=](DisplayListBuilder& b) { |
| // Draw more than one primitive to disable peephole optimizations |
| b.drawRect(RenderBounds.makeOutset(5, 5)); |
| dl_renderer(b); |
| b.restore(); |
| }; |
| RenderWith( |
| [=](SkCanvas* cv, SkPaint& p) { |
| cv->save(); |
| cv->clipRect(clip, SkClipOp::kIntersect, false); |
| cv->drawRect(rect, p); |
| cv->restore(); |
| }, |
| [=](DisplayListBuilder& b) { |
| b.save(); |
| b.clipRect(clip, SkClipOp::kIntersect, false); |
| b.drawRect(rect); |
| b.restore(); |
| }, |
| cv_renderer, dl_renderer, adjuster, tolerance, |
| "With prior save/clip/restore"); |
| RenderWith( |
| [=](SkCanvas* cv, SkPaint& p) { // |
| cv->saveLayer(nullptr, nullptr); |
| }, |
| [=](DisplayListBuilder& b) { // |
| b.saveLayer(nullptr, false); |
| }, |
| cv_restored, dl_restored, adjuster, tolerance, |
| "saveLayer no paint, no bounds"); |
| RenderWith( |
| [=](SkCanvas* cv, SkPaint& p) { // |
| cv->saveLayer(RenderBounds, nullptr); |
| }, |
| [=](DisplayListBuilder& b) { // |
| b.saveLayer(&RenderBounds, false); |
| }, |
| cv_restored, dl_restored, adjuster, tolerance, |
| "saveLayer no paint, with bounds"); |
| RenderWith( |
| [=](SkCanvas* cv, SkPaint& p) { |
| SkPaint save_p; |
| save_p.setColor(alpha_layer_color); |
| cv->saveLayer(nullptr, &save_p); |
| }, |
| [=](DisplayListBuilder& b) { |
| b.setColor(alpha_layer_color); |
| b.saveLayer(nullptr, true); |
| b.setColor(default_color); |
| }, |
| cv_restored, dl_restored, adjuster, tolerance, |
| "saveLayer with alpha, no bounds"); |
| RenderWith( |
| [=](SkCanvas* cv, SkPaint& p) { |
| SkPaint save_p; |
| save_p.setColor(alpha_layer_color); |
| cv->saveLayer(RenderBounds, &save_p); |
| }, |
| [=](DisplayListBuilder& b) { |
| b.setColor(alpha_layer_color); |
| b.saveLayer(&RenderBounds, true); |
| b.setColor(default_color); |
| }, |
| cv_restored, dl_restored, adjuster, tolerance, |
| "saveLayer with alpha and bounds"); |
| |
| { |
| sk_sp<SkImageFilter> filter = |
| SkImageFilters::Blur(5.0, 5.0, SkTileMode::kDecal, nullptr, nullptr); |
| BoundsTolerance blur5Tolerance = tolerance.addBoundsPadding(4, 4); |
| { |
| RenderWith( |
| [=](SkCanvas* cv, SkPaint& p) { |
| SkPaint save_p; |
| save_p.setImageFilter(filter); |
| cv->saveLayer(nullptr, &save_p); |
| p.setStrokeWidth(5.0); |
| }, |
| [=](DisplayListBuilder& b) { |
| b.setImageFilter(filter); |
| b.saveLayer(nullptr, true); |
| b.setImageFilter(nullptr); |
| b.setStrokeWidth(5.0); |
| }, |
| cv_restored, dl_restored, adjuster, blur5Tolerance, |
| "saveLayer ImageFilter, no bounds"); |
| } |
| ASSERT_TRUE(filter->unique()) |
| << "saveLayer ImageFilter, no bounds Cleanup"; |
| { |
| RenderWith( |
| [=](SkCanvas* cv, SkPaint& p) { |
| SkPaint save_p; |
| save_p.setImageFilter(filter); |
| cv->saveLayer(RenderBounds, &save_p); |
| p.setStrokeWidth(5.0); |
| }, |
| [=](DisplayListBuilder& b) { |
| b.setImageFilter(filter); |
| b.saveLayer(&RenderBounds, true); |
| b.setImageFilter(nullptr); |
| b.setStrokeWidth(5.0); |
| }, |
| cv_restored, dl_restored, adjuster, blur5Tolerance, |
| "saveLayer ImageFilter and bounds"); |
| } |
| ASSERT_TRUE(filter->unique()) |
| << "saveLayer ImageFilter and bounds Cleanup"; |
| } |
| } |
| |
| static void RenderWithAttributes(CvRenderer& cv_renderer, |
| DlRenderer& dl_renderer, |
| ToleranceAdjuster& adjuster, |
| const BoundsTolerance& tolerance) { |
| RenderWith([=](SkCanvas*, SkPaint& p) {}, // |
| [=](DisplayListBuilder& d) {}, // |
| cv_renderer, dl_renderer, adjuster, tolerance, "Base Test"); |
| |
| RenderWith([=](SkCanvas*, SkPaint& p) { p.setAntiAlias(true); }, // |
| [=](DisplayListBuilder& b) { b.setAntiAlias(true); }, // |
| cv_renderer, dl_renderer, adjuster, tolerance, |
| "AntiAlias == True"); |
| RenderWith([=](SkCanvas*, SkPaint& p) { p.setAntiAlias(false); }, // |
| [=](DisplayListBuilder& b) { b.setAntiAlias(false); }, // |
| cv_renderer, dl_renderer, adjuster, tolerance, |
| "AntiAlias == False"); |
| |
| RenderWith([=](SkCanvas*, SkPaint& p) { p.setDither(true); }, // |
| [=](DisplayListBuilder& b) { b.setDither(true); }, // |
| cv_renderer, dl_renderer, adjuster, tolerance, "Dither == True"); |
| RenderWith([=](SkCanvas*, SkPaint& p) { p.setDither(false); }, // |
| [=](DisplayListBuilder& b) { b.setDither(false); }, // |
| cv_renderer, dl_renderer, adjuster, tolerance, "Dither = False"); |
| |
| RenderWith([=](SkCanvas*, SkPaint& p) { p.setColor(SK_ColorBLUE); }, // |
| [=](DisplayListBuilder& b) { b.setColor(SK_ColorBLUE); }, // |
| cv_renderer, dl_renderer, adjuster, tolerance, "Color == Blue"); |
| RenderWith([=](SkCanvas*, SkPaint& p) { p.setColor(SK_ColorGREEN); }, // |
| [=](DisplayListBuilder& b) { b.setColor(SK_ColorGREEN); }, // |
| cv_renderer, dl_renderer, adjuster, tolerance, "Color == Green"); |
| |
| RenderWithStrokes(cv_renderer, dl_renderer, adjuster, tolerance); |
| |
| { |
| // half opaque cyan |
| SkColor blendableColor = SkColorSetARGB(0x7f, 0x00, 0xff, 0xff); |
| SkColor bg = SK_ColorWHITE; |
| |
| RenderWith( |
| [=](SkCanvas*, SkPaint& p) { |
| p.setBlendMode(SkBlendMode::kSrcIn); |
| p.setColor(blendableColor); |
| }, |
| [=](DisplayListBuilder& b) { |
| b.setBlendMode(SkBlendMode::kSrcIn); |
| b.setColor(blendableColor); |
| }, |
| cv_renderer, dl_renderer, adjuster, tolerance, "Blend == SrcIn", &bg); |
| RenderWith( |
| [=](SkCanvas*, SkPaint& p) { |
| p.setBlendMode(SkBlendMode::kDstIn); |
| p.setColor(blendableColor); |
| }, |
| [=](DisplayListBuilder& b) { |
| b.setBlendMode(SkBlendMode::kDstIn); |
| b.setColor(blendableColor); |
| }, |
| cv_renderer, dl_renderer, adjuster, tolerance, "Blend == DstIn", &bg); |
| } |
| |
| if (!(TestingDrawAtlas || TestingDrawVertices)) { |
| sk_sp<SkBlender> blender = |
| SkBlenders::Arithmetic(0.25, 0.25, 0.25, 0.25, false); |
| { |
| RenderWith([=](SkCanvas*, SkPaint& p) { p.setBlender(blender); }, |
| [=](DisplayListBuilder& b) { b.setBlender(blender); }, |
| cv_renderer, dl_renderer, adjuster, tolerance, |
| "ImageFilter == Blender Arithmetic 0.25-false"); |
| } |
| ASSERT_TRUE(blender->unique()) << "Blender Cleanup"; |
| blender = SkBlenders::Arithmetic(0.25, 0.25, 0.25, 0.25, true); |
| { |
| RenderWith([=](SkCanvas*, SkPaint& p) { p.setBlender(blender); }, |
| [=](DisplayListBuilder& b) { b.setBlender(blender); }, |
| cv_renderer, dl_renderer, adjuster, tolerance, |
| "ImageFilter == Blender Arithmetic 0.25-true"); |
| } |
| ASSERT_TRUE(blender->unique()) << "Blender Cleanup"; |
| } |
| |
| { |
| sk_sp<SkImageFilter> filter = |
| SkImageFilters::Blur(5.0, 5.0, SkTileMode::kDecal, nullptr, nullptr); |
| BoundsTolerance blur5Tolerance = tolerance.addBoundsPadding(4, 4); |
| { |
| RenderWith( |
| [=](SkCanvas*, SkPaint& p) { |
| // Provide some non-trivial stroke size to get blurred |
| p.setStrokeWidth(5.0); |
| p.setImageFilter(filter); |
| }, |
| [=](DisplayListBuilder& b) { |
| // Provide some non-trivial stroke size to get blurred |
| b.setStrokeWidth(5.0); |
| b.setImageFilter(filter); |
| }, |
| cv_renderer, dl_renderer, adjuster, blur5Tolerance, |
| "ImageFilter == Decal Blur 5"); |
| } |
| ASSERT_TRUE(filter->unique()) << "ImageFilter Cleanup"; |
| filter = |
| SkImageFilters::Blur(5.0, 5.0, SkTileMode::kClamp, nullptr, nullptr); |
| { |
| RenderWith( |
| [=](SkCanvas*, SkPaint& p) { |
| // Provide some non-trivial stroke size to get blurred |
| p.setStrokeWidth(5.0); |
| p.setImageFilter(filter); |
| }, |
| [=](DisplayListBuilder& b) { |
| // Provide some non-trivial stroke size to get blurred |
| b.setStrokeWidth(5.0); |
| b.setImageFilter(filter); |
| }, |
| cv_renderer, dl_renderer, adjuster, blur5Tolerance, |
| "ImageFilter == Clamp Blur 5"); |
| } |
| ASSERT_TRUE(filter->unique()) << "ImageFilter Cleanup"; |
| } |
| |
| { |
| // clang-format off |
| constexpr float rotate_color_matrix[20] = { |
| 0, 1, 0, 0, 0, |
| 0, 0, 1, 0, 0, |
| 1, 0, 0, 0, 0, |
| 0, 0, 0, 1, 0, |
| }; |
| constexpr float invert_color_matrix[20] = { |
| -1.0, 0, 0, 1.0, 0, |
| 0, -1.0, 0, 1.0, 0, |
| 0, 0, -1.0, 1.0, 0, |
| 1.0, 1.0, 1.0, 1.0, 0, |
| }; |
| // clang-format on |
| sk_sp<SkColorFilter> filter = SkColorFilters::Matrix(rotate_color_matrix); |
| { |
| SkColor bg = SK_ColorWHITE; |
| RenderWith( |
| [=](SkCanvas*, SkPaint& p) { |
| p.setColor(SK_ColorYELLOW); |
| p.setColorFilter(filter); |
| }, |
| [=](DisplayListBuilder& b) { |
| b.setColor(SK_ColorYELLOW); |
| b.setColorFilter(filter); |
| }, |
| cv_renderer, dl_renderer, adjuster, tolerance, |
| "ColorFilter == RotateRGB", &bg); |
| } |
| ASSERT_TRUE(filter->unique()) << "ColorFilter == RotateRGB Cleanup"; |
| filter = SkColorFilters::Matrix(invert_color_matrix); |
| { |
| SkColor bg = SK_ColorWHITE; |
| RenderWith( |
| [=](SkCanvas*, SkPaint& p) { |
| p.setColor(SK_ColorYELLOW); |
| p.setColorFilter(filter); |
| }, |
| [=](DisplayListBuilder& b) { |
| b.setColor(SK_ColorYELLOW); |
| b.setInvertColors(true); |
| }, |
| cv_renderer, dl_renderer, adjuster, tolerance, |
| "ColorFilter == Invert", &bg); |
| } |
| ASSERT_TRUE(filter->unique()) << "ColorFilter == Invert Cleanup"; |
| } |
| |
| { |
| sk_sp<SkPathEffect> effect = SkDiscretePathEffect::Make(3, 5); |
| { |
| // Discrete path effects need a stroke width for drawPointsAsPoints |
| // to do something realistic |
| RenderWith( |
| [=](SkCanvas*, SkPaint& p) { |
| p.setStrokeWidth(5.0); |
| // A Discrete(3, 5) effect produces miters that are near |
| // maximal for a miter limit of 3.0. |
| p.setStrokeMiter(3.0); |
| p.setPathEffect(effect); |
| }, |
| [=](DisplayListBuilder& b) { |
| b.setStrokeWidth(5.0); |
| // A Discrete(3, 5) effect produces miters that are near |
| // maximal for a miter limit of 3.0. |
| b.setStrokeMiter(3.0); |
| b.setPathEffect(effect); |
| }, |
| cv_renderer, dl_renderer, adjuster, |
| tolerance |
| // register the discrete offset so adjusters can compensate |
| .addDiscreteOffset(5) |
| // the miters in the 3-5 discrete effect don't always fill |
| // their conservative bounds, so tolerate a couple of pixels |
| .addBoundsPadding(2, 2), |
| "PathEffect == Discrete-3-5"); |
| } |
| ASSERT_TRUE(effect->unique()) << "PathEffect == Discrete-3-5 Cleanup"; |
| effect = SkDiscretePathEffect::Make(2, 3); |
| { |
| RenderWith( |
| [=](SkCanvas*, SkPaint& p) { |
| p.setStrokeWidth(5.0); |
| // A Discrete(2, 3) effect produces miters that are near |
| // maximal for a miter limit of 2.5. |
| p.setStrokeMiter(2.5); |
| p.setPathEffect(effect); |
| }, |
| [=](DisplayListBuilder& b) { |
| b.setStrokeWidth(5.0); |
| // A Discrete(2, 3) effect produces miters that are near |
| // maximal for a miter limit of 2.5. |
| b.setStrokeMiter(2.5); |
| b.setPathEffect(effect); |
| }, |
| cv_renderer, dl_renderer, adjuster, |
| tolerance |
| // register the discrete offset so adjusters can compensate |
| .addDiscreteOffset(3) |
| // the miters in the 3-5 discrete effect don't always fill |
| // their conservative bounds, so tolerate a couple of pixels |
| .addBoundsPadding(2, 2), |
| "PathEffect == Discrete-2-3"); |
| } |
| ASSERT_TRUE(effect->unique()) << "PathEffect == Discrete-2-3 Cleanup"; |
| } |
| |
| { |
| sk_sp<SkMaskFilter> filter = |
| SkMaskFilter::MakeBlur(kNormal_SkBlurStyle, 5.0); |
| BoundsTolerance blur5Tolerance = tolerance.addBoundsPadding(4, 4); |
| { |
| RenderWith( |
| [=](SkCanvas*, SkPaint& p) { |
| // Provide some non-trivial stroke size to get blurred |
| p.setStrokeWidth(5.0); |
| p.setMaskFilter(filter); |
| }, |
| [=](DisplayListBuilder& b) { |
| // Provide some non-trivial stroke size to get blurred |
| b.setStrokeWidth(5.0); |
| b.setMaskFilter(filter); |
| }, |
| cv_renderer, dl_renderer, adjuster, blur5Tolerance, |
| "MaskFilter == Blur 5"); |
| } |
| ASSERT_TRUE(filter->unique()) << "MaskFilter == Blur 5 Cleanup"; |
| { |
| RenderWith( |
| [=](SkCanvas*, SkPaint& p) { |
| // Provide some non-trivial stroke size to get blurred |
| p.setStrokeWidth(5.0); |
| p.setMaskFilter(filter); |
| }, |
| [=](DisplayListBuilder& b) { |
| // Provide some non-trivial stroke size to get blurred |
| b.setStrokeWidth(5.0); |
| b.setMaskBlurFilter(kNormal_SkBlurStyle, 5.0); |
| }, |
| cv_renderer, dl_renderer, adjuster, blur5Tolerance, |
| "MaskFilter == Blur(Normal, 5.0)"); |
| } |
| ASSERT_TRUE(filter->unique()) |
| << "MaskFilter == Blur(Normal, 5.0) Cleanup"; |
| } |
| |
| { |
| SkPoint end_points[] = { |
| SkPoint::Make(RenderBounds.fLeft, RenderBounds.fTop), |
| SkPoint::Make(RenderBounds.fRight, RenderBounds.fBottom), |
| }; |
| SkColor colors[] = { |
| SK_ColorGREEN, |
| SK_ColorYELLOW, |
| SK_ColorBLUE, |
| }; |
| float stops[] = { |
| 0.0, |
| 0.5, |
| 1.0, |
| }; |
| sk_sp<SkShader> shader = SkGradientShader::MakeLinear( |
| end_points, colors, stops, 3, SkTileMode::kMirror, 0, nullptr); |
| { |
| RenderWith([=](SkCanvas*, SkPaint& p) { p.setShader(shader); }, |
| [=](DisplayListBuilder& b) { b.setShader(shader); }, |
| cv_renderer, dl_renderer, adjuster, tolerance, |
| "LinearGradient GYB"); |
| } |
| ASSERT_TRUE(shader->unique()) << "LinearGradient GYB Cleanup"; |
| } |
| } |
| |
| static void RenderWithStrokes(CvRenderer& cv_renderer, |
| DlRenderer& dl_renderer, |
| ToleranceAdjuster& adjuster, |
| const BoundsTolerance& tolerance_in) { |
| // The test cases were generated with geometry that will try to fill |
| // out the various miter limits used for testing, but they can be off |
| // by a couple of pixels so we will relax bounds testing for strokes by |
| // a couple of pixels. |
| BoundsTolerance tolerance = tolerance_in.addBoundsPadding(2, 2); |
| RenderWith( // |
| [=](SkCanvas*, SkPaint& p) { p.setStyle(SkPaint::kFill_Style); }, |
| [=](DisplayListBuilder& b) { b.setStyle(SkPaint::kFill_Style); }, |
| cv_renderer, dl_renderer, adjuster, tolerance, "Fill"); |
| RenderWith( |
| [=](SkCanvas*, SkPaint& p) { p.setStyle(SkPaint::kStroke_Style); }, |
| [=](DisplayListBuilder& b) { b.setStyle(SkPaint::kStroke_Style); }, |
| cv_renderer, dl_renderer, adjuster, tolerance, "Stroke + defaults"); |
| |
| RenderWith( |
| [=](SkCanvas*, SkPaint& p) { |
| p.setStyle(SkPaint::kFill_Style); |
| p.setStrokeWidth(10.0); |
| }, |
| [=](DisplayListBuilder& b) { |
| b.setStyle(SkPaint::kFill_Style); |
| b.setStrokeWidth(10.0); |
| }, |
| cv_renderer, dl_renderer, adjuster, tolerance, |
| "Fill + unnecessary StrokeWidth 10"); |
| |
| RenderWith( |
| [=](SkCanvas*, SkPaint& p) { |
| p.setStyle(SkPaint::kStroke_Style); |
| p.setStrokeWidth(10.0); |
| }, |
| [=](DisplayListBuilder& b) { |
| b.setStyle(SkPaint::kStroke_Style); |
| b.setStrokeWidth(10.0); |
| }, |
| cv_renderer, dl_renderer, adjuster, tolerance, "Stroke Width 10"); |
| RenderWith( |
| [=](SkCanvas*, SkPaint& p) { |
| p.setStyle(SkPaint::kStroke_Style); |
| p.setStrokeWidth(5.0); |
| }, |
| [=](DisplayListBuilder& b) { |
| b.setStyle(SkPaint::kStroke_Style); |
| b.setStrokeWidth(5.0); |
| }, |
| cv_renderer, dl_renderer, adjuster, tolerance, "Stroke Width 5"); |
| |
| RenderWith( |
| [=](SkCanvas*, SkPaint& p) { |
| p.setStyle(SkPaint::kStroke_Style); |
| p.setStrokeWidth(5.0); |
| p.setStrokeCap(SkPaint::kButt_Cap); |
| }, |
| [=](DisplayListBuilder& b) { |
| b.setStyle(SkPaint::kStroke_Style); |
| b.setStrokeWidth(5.0); |
| b.setStrokeCap(SkPaint::kButt_Cap); |
| }, |
| cv_renderer, dl_renderer, adjuster, tolerance, |
| "Stroke Width 5, Butt Cap"); |
| RenderWith( |
| [=](SkCanvas*, SkPaint& p) { |
| p.setStyle(SkPaint::kStroke_Style); |
| p.setStrokeWidth(5.0); |
| p.setStrokeCap(SkPaint::kRound_Cap); |
| }, |
| [=](DisplayListBuilder& b) { |
| b.setStyle(SkPaint::kStroke_Style); |
| b.setStrokeWidth(5.0); |
| b.setStrokeCap(SkPaint::kRound_Cap); |
| }, |
| cv_renderer, dl_renderer, adjuster, tolerance, |
| "Stroke Width 5, Round Cap"); |
| |
| RenderWith( |
| [=](SkCanvas*, SkPaint& p) { |
| p.setStyle(SkPaint::kStroke_Style); |
| p.setStrokeWidth(5.0); |
| p.setStrokeJoin(SkPaint::kBevel_Join); |
| }, |
| [=](DisplayListBuilder& b) { |
| b.setStyle(SkPaint::kStroke_Style); |
| b.setStrokeWidth(5.0); |
| b.setStrokeJoin(SkPaint::kBevel_Join); |
| }, |
| cv_renderer, dl_renderer, adjuster, tolerance, |
| "Stroke Width 5, Bevel Join"); |
| RenderWith( |
| [=](SkCanvas*, SkPaint& p) { |
| p.setStyle(SkPaint::kStroke_Style); |
| p.setStrokeWidth(5.0); |
| p.setStrokeJoin(SkPaint::kRound_Join); |
| }, |
| [=](DisplayListBuilder& b) { |
| b.setStyle(SkPaint::kStroke_Style); |
| b.setStrokeWidth(5.0); |
| b.setStrokeJoin(SkPaint::kRound_Join); |
| }, |
| cv_renderer, dl_renderer, adjuster, tolerance, |
| "Stroke Width 5, Round Join"); |
| |
| RenderWith( |
| [=](SkCanvas*, SkPaint& p) { |
| p.setStyle(SkPaint::kStroke_Style); |
| p.setStrokeWidth(5.0); |
| p.setStrokeMiter(10.0); |
| p.setStrokeJoin(SkPaint::kMiter_Join); |
| // AA helps fill in the peaks of the really thin miters better |
| // for bounds accuracy testing |
| p.setAntiAlias(true); |
| }, |
| [=](DisplayListBuilder& b) { |
| b.setStyle(SkPaint::kStroke_Style); |
| b.setStrokeWidth(5.0); |
| b.setStrokeMiter(10.0); |
| b.setStrokeJoin(SkPaint::kMiter_Join); |
| // AA helps fill in the peaks of the really thin miters better |
| // for bounds accuracy testing |
| b.setAntiAlias(true); |
| }, |
| cv_renderer, dl_renderer, adjuster, tolerance, |
| "Stroke Width 5, Miter 10"); |
| |
| RenderWith( |
| [=](SkCanvas*, SkPaint& p) { |
| p.setStyle(SkPaint::kStroke_Style); |
| p.setStrokeWidth(5.0); |
| p.setStrokeMiter(0.0); |
| p.setStrokeJoin(SkPaint::kMiter_Join); |
| }, |
| [=](DisplayListBuilder& b) { |
| b.setStyle(SkPaint::kStroke_Style); |
| b.setStrokeWidth(5.0); |
| b.setStrokeMiter(0.0); |
| b.setStrokeJoin(SkPaint::kMiter_Join); |
| }, |
| cv_renderer, dl_renderer, adjuster, tolerance, |
| "Stroke Width 5, Miter 0"); |
| |
| { |
| const SkScalar TestDashes1[] = {29.0, 2.0}; |
| const SkScalar TestDashes2[] = {17.0, 1.5}; |
| sk_sp<SkPathEffect> effect = SkDashPathEffect::Make(TestDashes1, 2, 0.0f); |
| { |
| RenderWith( |
| [=](SkCanvas*, SkPaint& p) { |
| // Need stroke style to see dashing properly |
| p.setStyle(SkPaint::kStroke_Style); |
| // Provide some non-trivial stroke size to get dashed |
| p.setStrokeWidth(5.0); |
| p.setPathEffect(effect); |
| }, |
| [=](DisplayListBuilder& b) { |
| // Need stroke style to see dashing properly |
| b.setStyle(SkPaint::kStroke_Style); |
| // Provide some non-trivial stroke size to get dashed |
| b.setStrokeWidth(5.0); |
| b.setPathEffect(effect); |
| }, |
| cv_renderer, dl_renderer, adjuster, tolerance, |
| "PathEffect == Dash-29-2"); |
| } |
| ASSERT_TRUE(effect->unique()) << "PathEffect == Dash-29-2 Cleanup"; |
| effect = SkDashPathEffect::Make(TestDashes2, 2, 0.0f); |
| { |
| RenderWith( |
| [=](SkCanvas*, SkPaint& p) { |
| // Need stroke style to see dashing properly |
| p.setStyle(SkPaint::kStroke_Style); |
| // Provide some non-trivial stroke size to get dashed |
| p.setStrokeWidth(5.0); |
| p.setPathEffect(effect); |
| }, |
| [=](DisplayListBuilder& b) { |
| // Need stroke style to see dashing properly |
| b.setStyle(SkPaint::kStroke_Style); |
| // Provide some non-trivial stroke size to get dashed |
| b.setStrokeWidth(5.0); |
| b.setPathEffect(effect); |
| }, |
| cv_renderer, dl_renderer, adjuster, tolerance, |
| "PathEffect == Dash-17-1.5"); |
| } |
| ASSERT_TRUE(effect->unique()) << "PathEffect == Dash-17-1.5 Cleanup"; |
| } |
| } |
| |
| static void RenderWithTransforms(CvRenderer& cv_renderer, |
| DlRenderer& dl_renderer, |
| ToleranceAdjuster& adjuster, |
| const BoundsTolerance& tolerance) { |
| // If there is bounds padding for some conservative bounds overestimate |
| // then that padding will be even more pronounced in rotated or skewed |
| // coordinate systems so we scale the padding by about 5% to compensate. |
| BoundsTolerance skewed_tolerance = tolerance.addScale(1.05, 1.05); |
| RenderWith([=](SkCanvas* c, SkPaint&) { c->translate(5, 10); }, // |
| [=](DisplayListBuilder& b) { b.translate(5, 10); }, // |
| cv_renderer, dl_renderer, adjuster, tolerance, |
| "Translate 5, 10"); |
| RenderWith([=](SkCanvas* c, SkPaint&) { c->scale(1.05, 1.05); }, // |
| [=](DisplayListBuilder& b) { b.scale(1.05, 1.05); }, // |
| cv_renderer, dl_renderer, adjuster, tolerance, // |
| "Scale +5%"); |
| RenderWith([=](SkCanvas* c, SkPaint&) { c->rotate(5); }, // |
| [=](DisplayListBuilder& b) { b.rotate(5); }, // |
| cv_renderer, dl_renderer, adjuster, skewed_tolerance, |
| "Rotate 5 degrees"); |
| RenderWith([=](SkCanvas* c, SkPaint&) { c->skew(0.05, 0.05); }, // |
| [=](DisplayListBuilder& b) { b.skew(0.05, 0.05); }, // |
| cv_renderer, dl_renderer, adjuster, skewed_tolerance, // |
| "Skew 5%"); |
| { |
| SkMatrix tx = SkMatrix::MakeAll(1.10, 0.10, 5, // |
| 0.05, 1.05, 10, // |
| 0, 0, 1); |
| RenderWith([=](SkCanvas* c, SkPaint&) { c->concat(tx); }, // |
| [=](DisplayListBuilder& b) { |
| b.transform2DAffine(tx[0], tx[1], tx[2], // |
| tx[3], tx[4], tx[5]); |
| }, // |
| cv_renderer, dl_renderer, adjuster, skewed_tolerance, |
| "Transform 2D Affine"); |
| } |
| { |
| SkM44 m44 = SkM44(1, 0, 0, RenderCenterX, // |
| 0, 1, 0, RenderCenterY, // |
| 0, 0, 1, 0, // |
| 0, 0, .001, 1); |
| m44.preConcat(SkM44::Rotate({1, 0, 0}, M_PI / 60)); // 3 degrees around X |
| m44.preConcat(SkM44::Rotate({0, 1, 0}, M_PI / 45)); // 4 degrees around Y |
| m44.preTranslate(-RenderCenterX, -RenderCenterY); |
| RenderWith([=](SkCanvas* c, SkPaint&) { c->concat(m44); }, // |
| [=](DisplayListBuilder& b) { |
| b.transformFullPerspective( |
| m44.rc(0, 0), m44.rc(0, 1), m44.rc(0, 2), m44.rc(0, 3), |
| m44.rc(1, 0), m44.rc(1, 1), m44.rc(1, 2), m44.rc(1, 3), |
| m44.rc(2, 0), m44.rc(2, 1), m44.rc(2, 2), m44.rc(2, 3), |
| m44.rc(3, 0), m44.rc(3, 1), m44.rc(3, 2), m44.rc(3, 3)); |
| }, // |
| cv_renderer, dl_renderer, adjuster, skewed_tolerance, |
| "Transform Full Perspective"); |
| } |
| } |
| |
| static void RenderWithClips(CvRenderer& cv_renderer, |
| DlRenderer& dl_renderer, |
| ToleranceAdjuster& diff_adjuster, |
| const BoundsTolerance& diff_tolerance) { |
| SkRect r_clip = RenderBounds.makeInset(15.5, 15.5); |
| // For kIntersect clips we can be really strict on tolerance |
| ToleranceAdjuster& intersect_adjuster = DefaultAdjuster; |
| BoundsTolerance& intersect_tolerance = DefaultTolerance; |
| RenderWith( |
| [=](SkCanvas* c, SkPaint&) { |
| c->clipRect(r_clip, SkClipOp::kIntersect, false); |
| }, |
| [=](DisplayListBuilder& b) { |
| b.clipRect(r_clip, SkClipOp::kIntersect, false); |
| }, |
| cv_renderer, dl_renderer, intersect_adjuster, intersect_tolerance, |
| "Hard ClipRect inset by 15.5"); |
| RenderWith( |
| [=](SkCanvas* c, SkPaint&) { |
| c->clipRect(r_clip, SkClipOp::kIntersect, true); |
| }, |
| [=](DisplayListBuilder& b) { |
| b.clipRect(r_clip, SkClipOp::kIntersect, true); |
| }, |
| cv_renderer, dl_renderer, intersect_adjuster, intersect_tolerance, |
| "AntiAlias ClipRect inset by 15.5"); |
| RenderWith( |
| [=](SkCanvas* c, SkPaint&) { |
| c->clipRect(r_clip, SkClipOp::kDifference, false); |
| }, |
| [=](DisplayListBuilder& b) { |
| b.clipRect(r_clip, SkClipOp::kDifference, false); |
| }, |
| cv_renderer, dl_renderer, diff_adjuster, diff_tolerance, |
| "Hard ClipRect Diff, inset by 15.5"); |
| SkRRect rr_clip = SkRRect::MakeRectXY(r_clip, 1.8, 2.7); |
| RenderWith( |
| [=](SkCanvas* c, SkPaint&) { |
| c->clipRRect(rr_clip, SkClipOp::kIntersect, false); |
| }, |
| [=](DisplayListBuilder& b) { |
| b.clipRRect(rr_clip, SkClipOp::kIntersect, false); |
| }, |
| cv_renderer, dl_renderer, intersect_adjuster, intersect_tolerance, |
| "Hard ClipRRect inset by 15.5"); |
| RenderWith( |
| [=](SkCanvas* c, SkPaint&) { |
| c->clipRRect(rr_clip, SkClipOp::kIntersect, true); |
| }, |
| [=](DisplayListBuilder& b) { |
| b.clipRRect(rr_clip, SkClipOp::kIntersect, true); |
| }, |
| cv_renderer, dl_renderer, intersect_adjuster, intersect_tolerance, |
| "AntiAlias ClipRRect inset by 15.5"); |
| RenderWith( |
| [=](SkCanvas* c, SkPaint&) { |
| c->clipRRect(rr_clip, SkClipOp::kDifference, false); |
| }, |
| [=](DisplayListBuilder& b) { |
| b.clipRRect(rr_clip, SkClipOp::kDifference, false); |
| }, |
| cv_renderer, dl_renderer, diff_adjuster, diff_tolerance, |
| "Hard ClipRRect Diff, inset by 15.5"); |
| SkPath path_clip = SkPath(); |
| path_clip.setFillType(SkPathFillType::kEvenOdd); |
| path_clip.addRect(r_clip); |
| path_clip.addCircle(RenderCenterX, RenderCenterY, 1.0); |
| RenderWith( |
| [=](SkCanvas* c, SkPaint&) { |
| c->clipPath(path_clip, SkClipOp::kIntersect, false); |
| }, |
| [=](DisplayListBuilder& b) { |
| b.clipPath(path_clip, SkClipOp::kIntersect, false); |
| }, |
| cv_renderer, dl_renderer, intersect_adjuster, intersect_tolerance, |
| "Hard ClipPath inset by 15.5"); |
| RenderWith( |
| [=](SkCanvas* c, SkPaint&) { |
| c->clipPath(path_clip, SkClipOp::kIntersect, true); |
| }, |
| [=](DisplayListBuilder& b) { |
| b.clipPath(path_clip, SkClipOp::kIntersect, true); |
| }, |
| cv_renderer, dl_renderer, intersect_adjuster, intersect_tolerance, |
| "AntiAlias ClipPath inset by 15.5"); |
| RenderWith( |
| [=](SkCanvas* c, SkPaint&) { |
| c->clipPath(path_clip, SkClipOp::kDifference, false); |
| }, |
| [=](DisplayListBuilder& b) { |
| b.clipPath(path_clip, SkClipOp::kDifference, false); |
| }, |
| cv_renderer, dl_renderer, diff_adjuster, diff_tolerance, |
| "Hard ClipPath Diff, inset by 15.5"); |
| } |
| |
| static sk_sp<SkPicture> getSkPicture(CvRenderer& cv_setup, |
| CvRenderer& cv_render) { |
| SkPictureRecorder recorder; |
| SkRTreeFactory rtree_factory; |
| SkCanvas* cv = recorder.beginRecording(TestBounds, &rtree_factory); |
| SkPaint p; |
| cv_setup(cv, p); |
| cv_render(cv, p); |
| return recorder.finishRecordingAsPicture(); |
| } |
| |
| static void RenderWith(CvRenderer& cv_setup, |
| DlRenderer& dl_setup, |
| CvRenderer& cv_render, |
| DlRenderer& dl_render, |
| ToleranceAdjuster& adjuster, |
| const BoundsTolerance& tolerance_in, |
| const std::string info, |
| const SkColor* bg = nullptr) { |
| // surface1 is direct rendering via SkCanvas to SkSurface |
| // DisplayList mechanisms are not involved in this operation |
| sk_sp<SkSurface> ref_surface = makeSurface(bg); |
| SkPaint paint1; |
| cv_setup(ref_surface->getCanvas(), paint1); |
| const BoundsTolerance tolerance = adjuster( |
| tolerance_in, paint1, ref_surface->getCanvas()->getTotalMatrix()); |
| cv_render(ref_surface->getCanvas(), paint1); |
| sk_sp<SkPicture> ref_picture = getSkPicture(cv_setup, cv_render); |
| SkRect ref_bounds = ref_picture->cullRect(); |
| SkPixmap ref_pixels; |
| ASSERT_TRUE(ref_surface->peekPixels(&ref_pixels)) << info; |
| ASSERT_EQ(ref_pixels.width(), TestWidth) << info; |
| ASSERT_EQ(ref_pixels.height(), TestHeight) << info; |
| ASSERT_EQ(ref_pixels.info().bytesPerPixel(), 4) << info; |
| checkPixels(&ref_pixels, ref_bounds, info + " (Skia reference)", bg); |
| |
| { |
| // This sequence plays the provided equivalently constructed |
| // DisplayList onto the SkCanvas of the surface |
| // DisplayList => direct rendering |
| sk_sp<SkSurface> test_surface = makeSurface(bg); |
| DisplayListBuilder builder(TestBounds); |
| dl_setup(builder); |
| dl_render(builder); |
| sk_sp<DisplayList> display_list = builder.Build(); |
| SkRect dl_bounds = display_list->bounds(); |
| if (!ref_bounds.roundOut().contains(dl_bounds)) { |
| FML_LOG(ERROR) << "For " << info; |
| FML_LOG(ERROR) << "ref: " // |
| << ref_bounds.fLeft << ", " << ref_bounds.fTop << " => " |
| << ref_bounds.fRight << ", " << ref_bounds.fBottom; |
| FML_LOG(ERROR) << "dl: " // |
| << dl_bounds.fLeft << ", " << dl_bounds.fTop << " => " |
| << dl_bounds.fRight << ", " << dl_bounds.fBottom; |
| if (!dl_bounds.contains(ref_bounds)) { |
| FML_LOG(ERROR) << "DisplayList bounds are too small!"; |
| } |
| if (!ref_bounds.roundOut().contains(dl_bounds.roundOut())) { |
| FML_LOG(ERROR) << "###### DisplayList bounds larger than reference!"; |
| } |
| } |
| |
| // This sometimes triggers, but when it triggers and I examine |
| // the ref_bounds, they are always unnecessarily large and |
| // since the pixel OOB tests in the compare method do not |
| // trigger, we will trust the DL bounds. |
| // EXPECT_TRUE(dl_bounds.contains(ref_bounds)) << info; |
| |
| EXPECT_EQ(display_list->op_count(), ref_picture->approximateOpCount()) |
| << info; |
| |
| display_list->RenderTo(test_surface->getCanvas()); |
| compareToReference(test_surface.get(), &ref_pixels, |
| info + " (DisplayList built directly -> surface)", |
| &dl_bounds, &tolerance, bg); |
| } |
| |
| // This test cannot work if the rendering is using shadows until |
| // we can access the Skia ShadowRec via public headers. |
| if (!TestingDrawShadows) { |
| // This sequence renders SkCanvas calls to a DisplayList and then |
| // plays them back on SkCanvas to SkSurface |
| // SkCanvas calls => DisplayList => rendering |
| sk_sp<SkSurface> test_surface = makeSurface(bg); |
| DisplayListCanvasRecorder dl_recorder(TestBounds); |
| SkPaint test_paint; |
| cv_setup(&dl_recorder, test_paint); |
| cv_render(&dl_recorder, test_paint); |
| dl_recorder.builder()->Build()->RenderTo(test_surface->getCanvas()); |
| compareToReference(test_surface.get(), &ref_pixels, |
| info + " (Skia calls -> DisplayList -> surface)", |
| nullptr, nullptr, nullptr); |
| } |
| |
| { |
| // This sequence renders the SkCanvas calls to an SkPictureRecorder and |
| // renders the DisplayList calls to a DisplayListBuilder and then |
| // renders both back under a transform (scale(2x)) to see if their |
| // rendering is affected differently by a change of matrix between |
| // recording time and rendering time. |
| const int TestWidth2 = TestWidth * 2; |
| const int TestHeight2 = TestHeight * 2; |
| const SkScalar TestScale = 2.0; |
| |
| SkPictureRecorder sk_recorder; |
| SkCanvas* ref_canvas = sk_recorder.beginRecording(TestBounds); |
| SkPaint ref_paint; |
| cv_setup(ref_canvas, ref_paint); |
| cv_render(ref_canvas, ref_paint); |
| sk_sp<SkPicture> ref_picture = sk_recorder.finishRecordingAsPicture(); |
| sk_sp<SkSurface> ref_surface2 = makeSurface(bg, TestWidth2, TestHeight2); |
| SkCanvas* ref_canvas2 = ref_surface2->getCanvas(); |
| ref_canvas2->scale(TestScale, TestScale); |
| ref_picture->playback(ref_canvas2); |
| SkPixmap ref_pixels2; |
| ASSERT_TRUE(ref_surface2->peekPixels(&ref_pixels2)) << info; |
| ASSERT_EQ(ref_pixels2.width(), TestWidth2) << info; |
| ASSERT_EQ(ref_pixels2.height(), TestHeight2) << info; |
| ASSERT_EQ(ref_pixels2.info().bytesPerPixel(), 4) << info; |
| |
| DisplayListBuilder builder(TestBounds); |
| dl_setup(builder); |
| dl_render(builder); |
| sk_sp<DisplayList> display_list = builder.Build(); |
| sk_sp<SkSurface> test_surface = makeSurface(bg, TestWidth2, TestHeight2); |
| SkCanvas* test_canvas = test_surface->getCanvas(); |
| test_canvas->scale(TestScale, TestScale); |
| display_list->RenderTo(test_canvas); |
| compareToReference(test_surface.get(), &ref_pixels2, |
| info + " (Both rendered scaled 2x)", nullptr, nullptr, |
| nullptr, TestWidth2, TestHeight2, false); |
| } |
| } |
| |
| static void checkPixels(SkPixmap* ref_pixels, |
| SkRect ref_bounds, |
| const std::string info, |
| const SkColor* bg) { |
| SkPMColor untouched = (bg) ? SkPreMultiplyColor(*bg) : 0; |
| int pixels_touched = 0; |
| int pixels_oob = 0; |
| SkIRect i_bounds = ref_bounds.roundOut(); |
| for (int y = 0; y < TestHeight; y++) { |
| const uint32_t* ref_row = ref_pixels->addr32(0, y); |
| for (int x = 0; x < TestWidth; x++) { |
| if (ref_row[x] != untouched) { |
| pixels_touched++; |
| if (!i_bounds.contains(x, y)) { |
| pixels_oob++; |
| } |
| } |
| } |
| } |
| ASSERT_EQ(pixels_oob, 0) << info; |
| ASSERT_GT(pixels_touched, 0) << info; |
| } |
| |
| static void compareToReference(SkSurface* test_surface, |
| SkPixmap* reference, |
| const std::string info, |
| SkRect* bounds, |
| const BoundsTolerance* tolerance, |
| const SkColor* bg, |
| int width = TestWidth, |
| int height = TestHeight, |
| bool printMismatches = false) { |
| SkPMColor untouched = (bg) ? SkPreMultiplyColor(*bg) : 0; |
| SkPixmap test_pixels; |
| ASSERT_TRUE(test_surface->peekPixels(&test_pixels)) << info; |
| ASSERT_EQ(test_pixels.width(), width) << info; |
| ASSERT_EQ(test_pixels.height(), height) << info; |
| ASSERT_EQ(test_pixels.info().bytesPerPixel(), 4) << info; |
| SkIRect i_bounds = |
| bounds ? bounds->roundOut() : SkIRect::MakeWH(width, height); |
| |
| int pixels_different = 0; |
| int pixels_oob = 0; |
| int minX = width; |
| int minY = height; |
| int maxX = 0; |
| int maxY = 0; |
| for (int y = 0; y < height; y++) { |
| const uint32_t* ref_row = reference->addr32(0, y); |
| const uint32_t* test_row = test_pixels.addr32(0, y); |
| for (int x = 0; x < width; x++) { |
| if (bounds && test_row[x] != untouched) { |
| if (minX > x) |
| minX = x; |
| if (minY > y) |
| minY = y; |
| if (maxX <= x) |
| maxX = x + 1; |
| if (maxY <= y) |
| maxY = y + 1; |
| if (!i_bounds.contains(x, y)) { |
| pixels_oob++; |
| } |
| } |
| if (test_row[x] != ref_row[x]) { |
| if (printMismatches) { |
| FML_LOG(ERROR) << "pix[" << x << ", " << y |
| << "] mismatch: " << std::hex << test_row[x] |
| << "(test) != (ref)" << ref_row[x] << std::dec; |
| } |
| pixels_different++; |
| } |
| } |
| } |
| if (pixels_oob > 0) { |
| FML_LOG(ERROR) << "pix bounds[" // |
| << minX << ", " << minY << " => " << maxX << ", " << maxY |
| << "]"; |
| FML_LOG(ERROR) << "dl_bounds[" // |
| << bounds->fLeft << ", " << bounds->fTop // |
| << " => " // |
| << bounds->fRight << ", " << bounds->fBottom // |
| << "]"; |
| } else if (bounds) { |
| showBoundsOverflow(info, i_bounds, tolerance, minX, minY, maxX, maxY); |
| } |
| ASSERT_EQ(pixels_oob, 0) << info; |
| ASSERT_EQ(pixels_different, 0) << info; |
| } |
| |
| static void showBoundsOverflow(std::string info, |
| SkIRect& bounds, |
| const BoundsTolerance* tolerance, |
| int pixLeft, |
| int pixTop, |
| int pixRight, |
| int pixBottom) { |
| int pad_left = std::max(0, pixLeft - bounds.fLeft); |
| int pad_top = std::max(0, pixTop - bounds.fTop); |
| int pad_right = std::max(0, bounds.fRight - pixRight); |
| int pad_bottom = std::max(0, bounds.fBottom - pixBottom); |
| int pixWidth = pixRight - pixLeft; |
| int pixHeight = pixBottom - pixTop; |
| SkISize pixSize = SkISize::Make(pixWidth, pixHeight); |
| int worst_pad_x = std::max(pad_left, pad_right); |
| int worst_pad_y = std::max(pad_top, pad_bottom); |
| if (tolerance->overflows(pixSize, worst_pad_x, worst_pad_y)) { |
| FML_LOG(ERROR) << "Overflow for " << info; |
| FML_LOG(ERROR) << "pix bounds[" // |
| << pixLeft << ", " << pixTop << " => " // |
| << pixRight << ", " << pixBottom // |
| << "]"; |
| FML_LOG(ERROR) << "dl_bounds[" // |
| << bounds.fLeft << ", " << bounds.fTop // |
| << " => " // |
| << bounds.fRight << ", " << bounds.fBottom // |
| << "]"; |
| FML_LOG(ERROR) << "Bounds overflowed by up to " // |
| << worst_pad_x << ", " << worst_pad_y // |
| << " (" << (worst_pad_x * 100.0 / pixWidth) // |
| << "%, " << (worst_pad_y * 100.0 / pixHeight) << "%)"; |
| int pix_area = pixSize.area(); |
| int dl_area = bounds.width() * bounds.height(); |
| FML_LOG(ERROR) << "Total overflow area: " << (dl_area - pix_area) // |
| << " (+" << (dl_area * 100.0 / pix_area - 100.0) << "%)"; |
| FML_LOG(ERROR); |
| } |
| } |
| |
| static sk_sp<SkSurface> makeSurface(const SkColor* bg, |
| int width = TestWidth, |
| int height = TestHeight) { |
| sk_sp<SkSurface> surface = SkSurface::MakeRasterN32Premul(width, height); |
| if (bg) { |
| surface->getCanvas()->drawColor(*bg); |
| } |
| return surface; |
| } |
| |
| static const sk_sp<SkImage> testImage; |
| static const sk_sp<SkImage> makeTestImage() { |
| sk_sp<SkSurface> surface = |
| SkSurface::MakeRasterN32Premul(RenderWidth, RenderHeight); |
| SkCanvas* canvas = surface->getCanvas(); |
| SkPaint p0, p1; |
| p0.setStyle(SkPaint::kFill_Style); |
| p0.setColor(SK_ColorGREEN); |
| p1.setStyle(SkPaint::kFill_Style); |
| p1.setColor(SK_ColorBLUE); |
| // Some pixels need some transparency for DstIn testing |
| p1.setAlpha(128); |
| int cbdim = 5; |
| for (int y = 0; y < RenderHeight; y += cbdim) { |
| for (int x = 0; x < RenderWidth; x += cbdim) { |
| SkPaint& cellp = ((x + y) & 1) == 0 ? p0 : p1; |
| canvas->drawRect(SkRect::MakeXYWH(x, y, cbdim, cbdim), cellp); |
| } |
| } |
| return surface->makeImageSnapshot(); |
| } |
| |
| static sk_sp<SkTextBlob> MakeTextBlob(std::string string, |
| SkScalar font_height) { |
| SkFont font(SkTypeface::MakeFromName("ahem", SkFontStyle::Normal()), |
| font_height); |
| return SkTextBlob::MakeFromText(string.c_str(), string.size(), font, |
| SkTextEncoding::kUTF8); |
| } |
| }; |
| |
| bool CanvasCompareTester::TestingDrawShadows = false; |
| bool CanvasCompareTester::TestingDrawVertices = false; |
| bool CanvasCompareTester::TestingDrawAtlas = false; |
| BoundsTolerance CanvasCompareTester::DefaultTolerance = |
| BoundsTolerance().addAbsolutePadding(1, 1); |
| |
| const sk_sp<SkImage> CanvasCompareTester::testImage = |
| CanvasCompareTester::makeTestImage(); |
| |
| TEST(DisplayListCanvas, DrawPaint) { |
| CanvasCompareTester::RenderAll( |
| [=](SkCanvas* canvas, SkPaint& paint) { // |
| canvas->drawPaint(paint); |
| }, |
| [=](DisplayListBuilder& builder) { // |
| builder.drawPaint(); |
| }); |
| } |
| |
| TEST(DisplayListCanvas, DrawColor) { |
| CanvasCompareTester::RenderNoAttributes( // |
| [=](SkCanvas* canvas, SkPaint& paint) { // |
| canvas->drawColor(SK_ColorMAGENTA); |
| }, |
| [=](DisplayListBuilder& builder) { // |
| builder.drawColor(SK_ColorMAGENTA, SkBlendMode::kSrcOver); |
| }); |
| } |
| |
| BoundsTolerance lineTolerance(const BoundsTolerance& tolerance, |
| const SkPaint& paint, |
| const SkMatrix& matrix, |
| bool is_horizontal, |
| bool is_vertical, |
| bool ignores_butt_cap) { |
| SkScalar adjust = 0.0; |
| SkScalar half_width = paint.getStrokeWidth() * 0.5f; |
| if (tolerance.discrete_offset() > 0) { |
| // When a discrete path effect is added, the bounds calculations must allow |
| // for miters in any direction, but a horizontal line will not have |
| // miters in the horizontal direction, similarly for vertical |
| // lines, and diagonal lines will have miters off at a "45 degree" angle |
| // that don't expand the bounds much at all. |
| // Also, the discrete offset will not move any points parallel with |
| // the line, so provide tolerance for both miters and offset. |
| adjust = half_width * paint.getStrokeMiter() + tolerance.discrete_offset(); |
| } |
| if (paint.getStrokeCap() == SkPaint::kButt_Cap && !ignores_butt_cap) { |
| adjust = std::max(adjust, half_width); |
| } |
| if (adjust == 0) { |
| return CanvasCompareTester::DefaultAdjuster(tolerance, paint, matrix); |
| } |
| SkScalar hTolerance; |
| SkScalar vTolerance; |
| if (is_horizontal) { |
| FML_DCHECK(!is_vertical); |
| hTolerance = adjust; |
| vTolerance = 0; |
| } else if (is_vertical) { |
| hTolerance = 0; |
| vTolerance = adjust; |
| } else { |
| // The perpendicular miters just do not impact the bounds of |
| // diagonal lines at all as they are aimed in the wrong direction |
| // to matter. So allow tolerance in both axes. |
| hTolerance = vTolerance = adjust; |
| } |
| BoundsTolerance new_tolerance = |
| tolerance.addBoundsPadding(hTolerance, vTolerance); |
| return CanvasCompareTester::DefaultAdjuster(new_tolerance, paint, matrix); |
| } |
| |
| // For drawing horizontal lines |
| BoundsTolerance hLineTolerance(const BoundsTolerance& tolerance, |
| const SkPaint& paint, |
| const SkMatrix& matrix) { |
| return lineTolerance(tolerance, paint, matrix, true, false, false); |
| } |
| |
| // For drawing vertical lines |
| BoundsTolerance vLineTolerance(const BoundsTolerance& tolerance, |
| const SkPaint& paint, |
| const SkMatrix& matrix) { |
| return lineTolerance(tolerance, paint, matrix, false, true, false); |
| } |
| |
| // For drawing diagonal lines |
| BoundsTolerance dLineTolerance(const BoundsTolerance& tolerance, |
| const SkPaint& paint, |
| const SkMatrix& matrix) { |
| return lineTolerance(tolerance, paint, matrix, false, false, false); |
| } |
| |
| // For drawing individual points (drawPoints(Point_Mode)) |
| BoundsTolerance pointsTolerance(const BoundsTolerance& tolerance, |
| const SkPaint& paint, |
| const SkMatrix& matrix) { |
| return lineTolerance(tolerance, paint, matrix, false, false, true); |
| } |
| |
| TEST(DisplayListCanvas, DrawDiagonalLines) { |
| SkPoint p1 = SkPoint::Make(RenderLeft, RenderTop); |
| SkPoint p2 = SkPoint::Make(RenderRight, RenderBottom); |
| SkPoint p3 = SkPoint::Make(RenderLeft, RenderBottom); |
| SkPoint p4 = SkPoint::Make(RenderRight, RenderTop); |
| |
| CanvasCompareTester::RenderAll( |
| [=](SkCanvas* canvas, SkPaint& paint) { // |
| // Skia requires kStroke style on horizontal and vertical |
| // lines to get the bounds correct. |
| // See https://bugs.chromium.org/p/skia/issues/detail?id=12446 |
| SkPaint p = paint; |
| p.setStyle(SkPaint::kStroke_Style); |
| canvas->drawLine(p1, p2, p); |
| canvas->drawLine(p3, p4, p); |
| }, |
| [=](DisplayListBuilder& builder) { // |
| builder.drawLine(p1, p2); |
| builder.drawLine(p3, p4); |
| }, |
| dLineTolerance); |
| } |
| |
| TEST(DisplayListCanvas, DrawHorizontalLine) { |
| SkPoint p1 = SkPoint::Make(RenderLeft, RenderCenterY); |
| SkPoint p2 = SkPoint::Make(RenderRight, RenderCenterY); |
| |
| CanvasCompareTester::RenderAll( |
| [=](SkCanvas* canvas, SkPaint& paint) { // |
| // Skia requires kStroke style on horizontal and vertical |
| // lines to get the bounds correct. |
| // See https://bugs.chromium.org/p/skia/issues/detail?id=12446 |
| SkPaint p = paint; |
| p.setStyle(SkPaint::kStroke_Style); |
| canvas->drawLine(p1, p2, p); |
| }, |
| [=](DisplayListBuilder& builder) { // |
| builder.drawLine(p1, p2); |
| }, |
| hLineTolerance); |
| } |
| |
| TEST(DisplayListCanvas, DrawVerticalLine) { |
| SkPoint p1 = SkPoint::Make(RenderCenterX, RenderTop); |
| SkPoint p2 = SkPoint::Make(RenderCenterY, RenderBottom); |
| |
| CanvasCompareTester::RenderAll( |
| [=](SkCanvas* canvas, SkPaint& paint) { // |
| // Skia requires kStroke style on horizontal and vertical |
| // lines to get the bounds correct. |
| // See https://bugs.chromium.org/p/skia/issues/detail?id=12446 |
| SkPaint p = paint; |
| p.setStyle(SkPaint::kStroke_Style); |
| canvas->drawLine(p1, p2, p); |
| }, |
| [=](DisplayListBuilder& builder) { // |
| builder.drawLine(p1, p2); |
| }, |
| vLineTolerance); |
| } |
| |
| TEST(DisplayListCanvas, DrawRect) { |
| CanvasCompareTester::RenderAll( |
| [=](SkCanvas* canvas, SkPaint& paint) { // |
| canvas->drawRect(RenderBounds, paint); |
| }, |
| [=](DisplayListBuilder& builder) { // |
| builder.drawRect(RenderBounds); |
| }); |
| } |
| |
| TEST(DisplayListCanvas, DrawOval) { |
| SkRect rect = RenderBounds.makeInset(0, 10); |
| |
| CanvasCompareTester::RenderAll( |
| [=](SkCanvas* canvas, SkPaint& paint) { // |
| canvas->drawOval(rect, paint); |
| }, |
| [=](DisplayListBuilder& builder) { // |
| builder.drawOval(rect); |
| }); |
| } |
| |
| TEST(DisplayListCanvas, DrawCircle) { |
| CanvasCompareTester::RenderAll( |
| [=](SkCanvas* canvas, SkPaint& paint) { // |
| canvas->drawCircle(TestCenter, RenderRadius, paint); |
| }, |
| [=](DisplayListBuilder& builder) { // |
| builder.drawCircle(TestCenter, RenderRadius); |
| }); |
| } |
| |
| TEST(DisplayListCanvas, DrawRRect) { |
| SkRRect rrect = |
| SkRRect::MakeRectXY(RenderBounds, RenderCornerRadius, RenderCornerRadius); |
| CanvasCompareTester::RenderAll( |
| [=](SkCanvas* canvas, SkPaint& paint) { // |
| canvas->drawRRect(rrect, paint); |
| }, |
| [=](DisplayListBuilder& builder) { // |
| builder.drawRRect(rrect); |
| }); |
| } |
| |
| TEST(DisplayListCanvas, DrawDRRect) { |
| SkRRect outer = |
| SkRRect::MakeRectXY(RenderBounds, RenderCornerRadius, RenderCornerRadius); |
| SkRect innerBounds = RenderBounds.makeInset(30.0, 30.0); |
| SkRRect inner = |
| SkRRect::MakeRectXY(innerBounds, RenderCornerRadius, RenderCornerRadius); |
| CanvasCompareTester::RenderAll( |
| [=](SkCanvas* canvas, SkPaint& paint) { // |
| canvas->drawDRRect(outer, inner, paint); |
| }, |
| [=](DisplayListBuilder& builder) { // |
| builder.drawDRRect(outer, inner); |
| }); |
| } |
| |
| TEST(DisplayListCanvas, DrawPath) { |
| SkPath path; |
| path.addRect(RenderBounds); |
| path.moveTo(VerticalMiterDiamondPoints[0]); |
| for (int i = 1; i < VerticalMiterDiamondPointCount; i++) { |
| path.lineTo(VerticalMiterDiamondPoints[i]); |
| } |
| path.close(); |
| path.moveTo(HorizontalMiterDiamondPoints[0]); |
| for (int i = 1; i < HorizontalMiterDiamondPointCount; i++) { |
| path.lineTo(HorizontalMiterDiamondPoints[i]); |
| } |
| path.close(); |
| CanvasCompareTester::RenderAll( |
| [=](SkCanvas* canvas, SkPaint& paint) { // |
| canvas->drawPath(path, paint); |
| }, |
| [=](DisplayListBuilder& builder) { // |
| builder.drawPath(path); |
| }); |
| } |
| |
| TEST(DisplayListCanvas, DrawArc) { |
| CanvasCompareTester::RenderAll( |
| [=](SkCanvas* canvas, SkPaint& paint) { // |
| canvas->drawArc(RenderBounds, 60, 330, false, paint); |
| }, |
| [=](DisplayListBuilder& builder) { // |
| builder.drawArc(RenderBounds, 60, 330, false); |
| }); |
| } |
| |
| TEST(DisplayListCanvas, DrawArcCenter) { |
| CanvasCompareTester::RenderAll( |
| [=](SkCanvas* canvas, SkPaint& paint) { // |
| canvas->drawArc(RenderBounds, 60, 330, true, paint); |
| }, |
| [=](DisplayListBuilder& builder) { // |
| builder.drawArc(RenderBounds, 60, 330, true); |
| }); |
| } |
| |
| TEST(DisplayListCanvas, DrawPointsAsPoints) { |
| // The +/- 16 points are designed to fall just inside the clips |
| // that are tested against so we avoid lots of undrawn pixels |
| // in the accumulated bounds. |
| const SkScalar x0 = RenderLeft; |
| const SkScalar x1 = RenderLeft + 16; |
| const SkScalar x2 = (RenderLeft + RenderCenterX) * 0.5; |
| const SkScalar x3 = RenderCenterX; |
| const SkScalar x4 = (RenderRight + RenderCenterX) * 0.5; |
| const SkScalar x5 = RenderRight - 16; |
| const SkScalar x6 = RenderRight; |
| |
| const SkScalar y0 = RenderTop; |
| const SkScalar y1 = RenderTop + 16; |
| const SkScalar y2 = (RenderTop + RenderCenterY) * 0.5; |
| const SkScalar y3 = RenderCenterY; |
| const SkScalar y4 = (RenderBottom + RenderCenterY) * 0.5; |
| const SkScalar y5 = RenderBottom - 16; |
| const SkScalar y6 = RenderBottom; |
| |
| // clang-format off |
| const SkPoint points[] = { |
| {x0, y0}, {x1, y0}, {x2, y0}, {x3, y0}, {x4, y0}, {x5, y0}, {x6, y0}, |
| {x0, y1}, {x1, y1}, {x2, y1}, {x3, y1}, {x4, y1}, {x5, y1}, {x6, y1}, |
| {x0, y2}, {x1, y2}, {x2, y2}, {x3, y2}, {x4, y2}, {x5, y2}, {x6, y2}, |
| {x0, y3}, {x1, y3}, {x2, y3}, {x3, y3}, {x4, y3}, {x5, y3}, {x6, y3}, |
| {x0, y4}, {x1, y4}, {x2, y4}, {x3, y4}, {x4, y4}, {x5, y4}, {x6, y4}, |
| {x0, y5}, {x1, y5}, {x2, y5}, {x3, y5}, {x4, y5}, {x5, y5}, {x6, y5}, |
| {x0, y6}, {x1, y6}, {x2, y6}, {x3, y6}, {x4, y6}, {x5, y6}, {x6, y6}, |
| }; |
| // clang-format on |
| const int count = sizeof(points) / sizeof(points[0]); |
| |
| CanvasCompareTester::RenderAll( |
| [=](SkCanvas* canvas, SkPaint& paint) { // |
| // Skia requires kStroke style on horizontal and vertical |
| // lines to get the bounds correct. |
| // See https://bugs.chromium.org/p/skia/issues/detail?id=12446 |
| SkPaint p = paint; |
| p.setStyle(SkPaint::kStroke_Style); |
| canvas->drawPoints(SkCanvas::kPoints_PointMode, count, points, p); |
| }, |
| [=](DisplayListBuilder& builder) { // |
| builder.drawPoints(SkCanvas::kPoints_PointMode, count, points); |
| }, |
| pointsTolerance); |
| } |
| |
| TEST(DisplayListCanvas, DrawPointsAsLines) { |
| const SkScalar x0 = RenderLeft + 1; |
| const SkScalar x1 = RenderLeft + 16; |
| const SkScalar x2 = RenderRight - 16; |
| const SkScalar x3 = RenderRight - 1; |
| |
| const SkScalar y0 = RenderTop; |
| const SkScalar y1 = RenderTop + 16; |
| const SkScalar y2 = RenderBottom - 16; |
| const SkScalar y3 = RenderBottom; |
| |
| // clang-format off |
| const SkPoint points[] = { |
| // Outer box |
| {x0, y0}, {x3, y0}, |
| {x3, y0}, {x3, y3}, |
| {x3, y3}, {x0, y3}, |
| {x0, y3}, {x0, y0}, |
| |
| // Diagonals |
| {x0, y0}, {x3, y3}, {x3, y0}, {x0, y3}, |
| |
| // Inner box |
| {x1, y1}, {x2, y1}, |
| {x2, y1}, {x2, y2}, |
| {x2, y2}, {x1, y2}, |
| {x1, y2}, {x1, y1}, |
| }; |
| // clang-format on |
| |
| const int count = sizeof(points) / sizeof(points[0]); |
| ASSERT_TRUE((count & 1) == 0); |
| CanvasCompareTester::RenderAll( |
| [=](SkCanvas* canvas, SkPaint& paint) { // |
| // Skia requires kStroke style on horizontal and vertical |
| // lines to get the bounds correct. |
| // See https://bugs.chromium.org/p/skia/issues/detail?id=12446 |
| SkPaint p = paint; |
| p.setStyle(SkPaint::kStroke_Style); |
| canvas->drawPoints(SkCanvas::kLines_PointMode, count, points, p); |
| }, |
| [=](DisplayListBuilder& builder) { // |
| builder.drawPoints(SkCanvas::kLines_PointMode, count, points); |
| }); |
| } |
| |
| TEST(DisplayListCanvas, DrawPointsAsPolygon) { |
| const SkPoint points1[] = { |
| // RenderBounds box with a diagonal |
| SkPoint::Make(RenderLeft, RenderTop), |
| SkPoint::Make(RenderRight, RenderTop), |
| SkPoint::Make(RenderRight, RenderBottom), |
| SkPoint::Make(RenderLeft, RenderBottom), |
| SkPoint::Make(RenderLeft, RenderTop), |
| SkPoint::Make(RenderRight, RenderBottom), |
| }; |
| const int count1 = sizeof(points1) / sizeof(points1[0]); |
| |
| CanvasCompareTester::RenderAll( |
| [=](SkCanvas* canvas, SkPaint& paint) { // |
| // Skia requires kStroke style on horizontal and vertical |
| // lines to get the bounds correct. |
| // See https://bugs.chromium.org/p/skia/issues/detail?id=12446 |
| SkPaint p = paint; |
| p.setStyle(SkPaint::kStroke_Style); |
| canvas->drawPoints(SkCanvas::kPolygon_PointMode, count1, points1, p); |
| }, |
| [=](DisplayListBuilder& builder) { // |
| builder.drawPoints(SkCanvas::kPolygon_PointMode, count1, points1); |
| }); |
| } |
| |
| TEST(DisplayListCanvas, DrawVerticesWithColors) { |
| // Cover as many sides of the box with only 6 vertices: |
| // +----------+ |
| // |xxxxxxxxxx| |
| // | xxxxxx| |
| // | xxx| |
| // |xxx | |
| // |xxxxxx | |
| // |xxxxxxxxxx| |
| // +----------| |
| const SkPoint pts[6] = { |
| // Upper-Right corner, full top, half right coverage |
| SkPoint::Make(RenderLeft, RenderTop), |
| SkPoint::Make(RenderRight, RenderTop), |
| SkPoint::Make(RenderRight, RenderCenterY), |
| // Lower-Left corner, full bottom, half left coverage |
| SkPoint::Make(RenderLeft, RenderBottom), |
| SkPoint::Make(RenderLeft, RenderCenterY), |
| SkPoint::Make(RenderRight, RenderBottom), |
| }; |
| const SkColor colors[6] = { |
| SK_ColorRED, SK_ColorBLUE, SK_ColorGREEN, |
| SK_ColorCYAN, SK_ColorYELLOW, SK_ColorMAGENTA, |
| }; |
| const sk_sp<SkVertices> vertices = SkVertices::MakeCopy( |
| SkVertices::kTriangles_VertexMode, 6, pts, nullptr, colors); |
| CanvasCompareTester::RenderVertices( |
| [=](SkCanvas* canvas, SkPaint& paint) { // |
| canvas->drawVertices(vertices.get(), SkBlendMode::kSrcOver, paint); |
| }, |
| [=](DisplayListBuilder& builder) { // |
| builder.drawVertices(vertices, SkBlendMode::kSrcOver); |
| }); |
| ASSERT_TRUE(vertices->unique()); |
| } |
| |
| TEST(DisplayListCanvas, DrawVerticesWithImage) { |
| // Cover as many sides of the box with only 6 vertices: |
| // +----------+ |
| // |xxxxxxxxxx| |
| // | xxxxxx| |
| // | xxx| |
| // |xxx | |
| // |xxxxxx | |
| // |xxxxxxxxxx| |
| // +----------| |
| const SkPoint pts[6] = { |
| // Upper-Right corner, full top, half right coverage |
| SkPoint::Make(RenderLeft, RenderTop), |
| SkPoint::Make(RenderRight, RenderTop), |
| SkPoint::Make(RenderRight, RenderCenterY), |
| // Lower-Left corner, full bottom, half left coverage |
| SkPoint::Make(RenderLeft, RenderBottom), |
| SkPoint::Make(RenderLeft, RenderCenterY), |
| SkPoint::Make(RenderRight, RenderBottom), |
| }; |
| const SkPoint tex[6] = { |
| SkPoint::Make(RenderWidth / 2.0, 0), |
| SkPoint::Make(0, RenderHeight), |
| SkPoint::Make(RenderWidth, RenderHeight), |
| SkPoint::Make(RenderWidth / 2, RenderHeight), |
| SkPoint::Make(0, 0), |
| SkPoint::Make(RenderWidth, 0), |
| }; |
| const sk_sp<SkVertices> vertices = SkVertices::MakeCopy( |
| SkVertices::kTriangles_VertexMode, 6, pts, tex, nullptr); |
| const sk_sp<SkShader> shader = CanvasCompareTester::testImage->makeShader( |
| SkTileMode::kRepeat, SkTileMode::kRepeat, SkSamplingOptions()); |
| CanvasCompareTester::RenderVertices( |
| [=](SkCanvas* canvas, SkPaint& paint) { // |
| paint.setShader(shader); |
| canvas->drawVertices(vertices.get(), SkBlendMode::kSrcOver, paint); |
| }, |
| [=](DisplayListBuilder& builder) { // |
| builder.setShader(shader); |
| builder.drawVertices(vertices, SkBlendMode::kSrcOver); |
| }); |
| ASSERT_TRUE(vertices->unique()); |
| ASSERT_TRUE(shader->unique()); |
| } |
| |
| TEST(DisplayListCanvas, DrawImageNearest) { |
| CanvasCompareTester::RenderAll( |
| [=](SkCanvas* canvas, SkPaint& paint) { // |
| canvas->drawImage(CanvasCompareTester::testImage, RenderLeft, RenderTop, |
| DisplayList::NearestSampling, &paint); |
| }, |
| [=](DisplayListBuilder& builder) { // |
| builder.drawImage(CanvasCompareTester::testImage, |
| SkPoint::Make(RenderLeft, RenderTop), |
| DisplayList::NearestSampling, true); |
| }); |
| } |
| |
| TEST(DisplayListCanvas, DrawImageNearestNoPaint) { |
| CanvasCompareTester::RenderAll( |
| [=](SkCanvas* canvas, SkPaint& paint) { // |
| canvas->drawImage(CanvasCompareTester::testImage, RenderLeft, RenderTop, |
| DisplayList::NearestSampling, nullptr); |
| }, |
| [=](DisplayListBuilder& builder) { // |
| builder.drawImage(CanvasCompareTester::testImage, |
| SkPoint::Make(RenderLeft, RenderTop), |
| DisplayList::NearestSampling, false); |
| }); |
| } |
| |
| TEST(DisplayListCanvas, DrawImageLinear) { |
| CanvasCompareTester::RenderAll( |
| [=](SkCanvas* canvas, SkPaint& paint) { // |
| canvas->drawImage(CanvasCompareTester::testImage, RenderLeft, RenderTop, |
| DisplayList::LinearSampling, &paint); |
| }, |
| [=](DisplayListBuilder& builder) { // |
| builder.drawImage(CanvasCompareTester::testImage, |
| SkPoint::Make(RenderLeft, RenderTop), |
| DisplayList::LinearSampling, true); |
| }); |
| } |
| |
| TEST(DisplayListCanvas, DrawImageRectNearest) { |
| SkRect src = SkRect::MakeIWH(RenderWidth, RenderHeight).makeInset(5, 5); |
| SkRect dst = RenderBounds.makeInset(15.5, 10.5); |
| CanvasCompareTester::RenderAll( |
| [=](SkCanvas* canvas, SkPaint& paint) { // |
| canvas->drawImageRect(CanvasCompareTester::testImage, src, dst, |
| DisplayList::NearestSampling, &paint, |
| SkCanvas::kFast_SrcRectConstraint); |
| }, |
| [=](DisplayListBuilder& builder) { // |
| builder.drawImageRect(CanvasCompareTester::testImage, src, dst, |
| DisplayList::NearestSampling, true); |
| }); |
| } |
| |
| TEST(DisplayListCanvas, DrawImageRectNearestNoPaint) { |
| SkRect src = SkRect::MakeIWH(RenderWidth, RenderHeight).makeInset(5, 5); |
| SkRect dst = RenderBounds.makeInset(15.5, 10.5); |
| CanvasCompareTester::RenderAll( |
| [=](SkCanvas* canvas, SkPaint& paint) { // |
| canvas->drawImageRect(CanvasCompareTester::testImage, src, dst, |
| DisplayList::NearestSampling, nullptr, |
| SkCanvas::kFast_SrcRectConstraint); |
| }, |
| [=](DisplayListBuilder& builder) { // |
| builder.drawImageRect(CanvasCompareTester::testImage, src, dst, |
| DisplayList::NearestSampling, false); |
| }); |
| } |
| |
| TEST(DisplayListCanvas, DrawImageRectLinear) { |
| SkRect src = SkRect::MakeIWH(RenderWidth, RenderHeight).makeInset(5, 5); |
| SkRect dst = RenderBounds.makeInset(15.5, 10.5); |
| CanvasCompareTester::RenderAll( |
| [=](SkCanvas* canvas, SkPaint& paint) { // |
| canvas->drawImageRect(CanvasCompareTester::testImage, src, dst, |
| DisplayList::LinearSampling, &paint, |
| SkCanvas::kFast_SrcRectConstraint); |
| }, |
| [=](DisplayListBuilder& builder) { // |
| builder.drawImageRect(CanvasCompareTester::testImage, src, dst, |
| DisplayList::LinearSampling, true); |
| }); |
| } |
| |
| TEST(DisplayListCanvas, DrawImageNineNearest) { |
| SkIRect src = SkIRect::MakeWH(RenderWidth, RenderHeight).makeInset(5, 5); |
| SkRect dst = RenderBounds.makeInset(15.5, 10.5); |
| CanvasCompareTester::RenderAll( |
| [=](SkCanvas* canvas, SkPaint& paint) { // |
| canvas->drawImageNine(CanvasCompareTester::testImage.get(), src, dst, |
| SkFilterMode::kNearest, &paint); |
| }, |
| [=](DisplayListBuilder& builder) { // |
| builder.drawImageNine(CanvasCompareTester::testImage, src, dst, |
| SkFilterMode::kNearest, true); |
| }); |
| } |
| |
| TEST(DisplayListCanvas, DrawImageNineNearestNoPaint) { |
| SkIRect src = SkIRect::MakeWH(RenderWidth, RenderHeight).makeInset(5, 5); |
| SkRect dst = RenderBounds.makeInset(15.5, 10.5); |
| CanvasCompareTester::RenderAll( |
| [=](SkCanvas* canvas, SkPaint& paint) { // |
| canvas->drawImageNine(CanvasCompareTester::testImage.get(), src, dst, |
| SkFilterMode::kNearest, nullptr); |
| }, |
| [=](DisplayListBuilder& builder) { // |
| builder.drawImageNine(CanvasCompareTester::testImage, src, dst, |
| SkFilterMode::kNearest, false); |
| }); |
| } |
| |
| TEST(DisplayListCanvas, DrawImageNineLinear) { |
| SkIRect src = SkIRect::MakeWH(RenderWidth, RenderHeight).makeInset(5, 5); |
| SkRect dst = RenderBounds.makeInset(15.5, 10.5); |
| CanvasCompareTester::RenderAll( |
| [=](SkCanvas* canvas, SkPaint& paint) { // |
| canvas->drawImageNine(CanvasCompareTester::testImage.get(), src, dst, |
| SkFilterMode::kLinear, &paint); |
| }, |
| [=](DisplayListBuilder& builder) { // |
| builder.drawImageNine(CanvasCompareTester::testImage, src, dst, |
| SkFilterMode::kLinear, true); |
| }); |
| } |
| |
| TEST(DisplayListCanvas, DrawImageLatticeNearest) { |
| const SkRect dst = RenderBounds.makeInset(15.5, 10.5); |
| const int divX[] = { |
| (RenderLeft + RenderCenterX) / 2, |
| RenderCenterX, |
| (RenderRight + RenderCenterX) / 2, |
| }; |
| const int divY[] = { |
| (RenderTop + RenderCenterY) / 2, |
| RenderCenterY, |
| (RenderBottom + RenderCenterY) / 2, |
| }; |
| SkCanvas::Lattice lattice = { |
| divX, divY, nullptr, 3, 3, nullptr, nullptr, |
| }; |
| CanvasCompareTester::RenderAll( |
| [=](SkCanvas* canvas, SkPaint& paint) { // |
| canvas->drawImageLattice(CanvasCompareTester::testImage.get(), lattice, |
| dst, SkFilterMode::kNearest, &paint); |
| }, |
| [=](DisplayListBuilder& builder) { // |
| builder.drawImageLattice(CanvasCompareTester::testImage, lattice, // |
| dst, SkFilterMode::kNearest, true); |
| }); |
| } |
| |
| TEST(DisplayListCanvas, DrawImageLatticeNearestNoPaint) { |
| const SkRect dst = RenderBounds.makeInset(15.5, 10.5); |
| const int divX[] = { |
| (RenderLeft + RenderCenterX) / 2, |
| RenderCenterX, |
| (RenderRight + RenderCenterX) / 2, |
| }; |
| const int divY[] = { |
| (RenderTop + RenderCenterY) / 2, |
| RenderCenterY, |
| (RenderBottom + RenderCenterY) / 2, |
| }; |
| SkCanvas::Lattice lattice = { |
| divX, divY, nullptr, 3, 3, nullptr, nullptr, |
| }; |
| CanvasCompareTester::RenderAll( |
| [=](SkCanvas* canvas, SkPaint& paint) { // |
| canvas->drawImageLattice(CanvasCompareTester::testImage.get(), lattice, |
| dst, SkFilterMode::kNearest, nullptr); |
| }, |
| [=](DisplayListBuilder& builder) { // |
| builder.drawImageLattice(CanvasCompareTester::testImage, lattice, // |
| dst, SkFilterMode::kNearest, false); |
| }); |
| } |
| |
| TEST(DisplayListCanvas, DrawImageLatticeLinear) { |
| const SkRect dst = RenderBounds.makeInset(15.5, 10.5); |
| const int divX[] = { |
| (RenderLeft + RenderCenterX) / 2, |
| RenderCenterX, |
| (RenderRight + RenderCenterX) / 2, |
| }; |
| const int divY[] = { |
| (RenderTop + RenderCenterY) / 2, |
| RenderCenterY, |
| (RenderBottom + RenderCenterY) / 2, |
| }; |
| SkCanvas::Lattice lattice = { |
| divX, divY, nullptr, 3, 3, nullptr, nullptr, |
| }; |
| CanvasCompareTester::RenderAll( |
| [=](SkCanvas* canvas, SkPaint& paint) { // |
| canvas->drawImageLattice(CanvasCompareTester::testImage.get(), lattice, |
| dst, SkFilterMode::kLinear, &paint); |
| }, |
| [=](DisplayListBuilder& builder) { // |
| builder.drawImageLattice(CanvasCompareTester::testImage, lattice, // |
| dst, SkFilterMode::kLinear, true); |
| }); |
| } |
| |
| TEST(DisplayListCanvas, DrawAtlasNearest) { |
| const SkRSXform xform[] = { |
| // clang-format off |
| { 1.2f, 0.0f, RenderLeft, RenderTop}, |
| { 0.0f, 1.2f, RenderRight, RenderTop}, |
| {-1.2f, 0.0f, RenderRight, RenderBottom}, |
| { 0.0f, -1.2f, RenderLeft, RenderBottom}, |
| // clang-format on |
| }; |
| const SkRect tex[] = { |
| // clang-format off |
| {0, 0, RenderHalfWidth, RenderHalfHeight}, |
| {RenderHalfWidth, 0, RenderWidth, RenderHalfHeight}, |
| {RenderHalfWidth, RenderHalfHeight, RenderWidth, RenderHeight}, |
| {0, RenderHalfHeight, RenderHalfWidth, RenderHeight}, |
| // clang-format on |
| }; |
| const SkColor colors[] = { |
| SK_ColorBLUE, |
| SK_ColorGREEN, |
| SK_ColorYELLOW, |
| SK_ColorMAGENTA, |
| }; |
| const sk_sp<SkImage> image = CanvasCompareTester::testImage; |
| CanvasCompareTester::RenderAtlas( |
| [=](SkCanvas* canvas, SkPaint& paint) { |
| canvas->drawAtlas(image.get(), xform, tex, colors, 4, |
| SkBlendMode::kSrcOver, DisplayList::NearestSampling, |
| nullptr, &paint); |
| }, |
| [=](DisplayListBuilder& builder) { |
| builder.drawAtlas(image, xform, tex, colors, 4, // |
| SkBlendMode::kSrcOver, DisplayList::NearestSampling, |
| nullptr, true); |
| }); |
| } |
| |
| TEST(DisplayListCanvas, DrawAtlasNearestNoPaint) { |
| const SkRSXform xform[] = { |
| // clang-format off |
| { 1.2f, 0.0f, RenderLeft, RenderTop}, |
| { 0.0f, 1.2f, RenderRight, RenderTop}, |
| {-1.2f, 0.0f, RenderRight, RenderBottom}, |
| { 0.0f, -1.2f, RenderLeft, RenderBottom}, |
| // clang-format on |
| }; |
| const SkRect tex[] = { |
| // clang-format off |
| {0, 0, RenderHalfWidth, RenderHalfHeight}, |
| {RenderHalfWidth, 0, RenderWidth, RenderHalfHeight}, |
| {RenderHalfWidth, RenderHalfHeight, RenderWidth, RenderHeight}, |
| {0, RenderHalfHeight, RenderHalfWidth, RenderHeight}, |
| // clang-format on |
| }; |
| const SkColor colors[] = { |
| SK_ColorBLUE, |
| SK_ColorGREEN, |
| SK_ColorYELLOW, |
| SK_ColorMAGENTA, |
| }; |
| const sk_sp<SkImage> image = CanvasCompareTester::testImage; |
| CanvasCompareTester::RenderAtlas( |
| [=](SkCanvas* canvas, SkPaint& paint) { |
| canvas->drawAtlas(image.get(), xform, tex, colors, 4, |
| SkBlendMode::kSrcOver, DisplayList::NearestSampling, |
| nullptr, nullptr); |
| }, |
| [=](DisplayListBuilder& builder) { |
| builder.drawAtlas(image, xform, tex, colors, 4, // |
| SkBlendMode::kSrcOver, DisplayList::NearestSampling, |
| nullptr, false); |
| }); |
| } |
| |
| TEST(DisplayListCanvas, DrawAtlasLinear) { |
| const SkRSXform xform[] = { |
| // clang-format off |
| { 1.2f, 0.0f, RenderLeft, RenderTop}, |
| { 0.0f, 1.2f, RenderRight, RenderTop}, |
| {-1.2f, 0.0f, RenderRight, RenderBottom}, |
| { 0.0f, -1.2f, RenderLeft, RenderBottom}, |
| // clang-format on |
| }; |
| const SkRect tex[] = { |
| // clang-format off |
| {0, 0, RenderHalfWidth, RenderHalfHeight}, |
| {RenderHalfWidth, 0, RenderWidth, RenderHalfHeight}, |
| {RenderHalfWidth, RenderHalfHeight, RenderWidth, RenderHeight}, |
| {0, RenderHalfHeight, RenderHalfWidth, RenderHeight}, |
| // clang-format on |
| }; |
| const SkColor colors[] = { |
| SK_ColorBLUE, |
| SK_ColorGREEN, |
| SK_ColorYELLOW, |
| SK_ColorMAGENTA, |
| }; |
| const sk_sp<SkImage> image = CanvasCompareTester::testImage; |
| CanvasCompareTester::RenderAtlas( |
| [=](SkCanvas* canvas, SkPaint& paint) { |
| canvas->drawAtlas(image.get(), xform, tex, colors, 2, // |
| SkBlendMode::kSrcOver, DisplayList::LinearSampling, |
| nullptr, &paint); |
| }, |
| [=](DisplayListBuilder& builder) { |
| builder.drawAtlas(image, xform, tex, colors, 2, // |
| SkBlendMode::kSrcOver, DisplayList::LinearSampling, |
| nullptr, true); |
| }); |
| } |
| |
| sk_sp<SkPicture> makeTestPicture() { |
| SkPictureRecorder recorder; |
| SkCanvas* cv = recorder.beginRecording(RenderBounds); |
| SkPaint p; |
| p.setStyle(SkPaint::kFill_Style); |
| SkScalar x_coords[] = { |
| RenderLeft, |
| RenderCenterX, |
| RenderRight, |
| }; |
| SkScalar y_coords[] = { |
| RenderTop, |
| RenderCenterY, |
| RenderBottom, |
| }; |
| SkColor colors[][2] = { |
| { |
| SK_ColorRED, |
| SK_ColorBLUE, |
| }, |
| { |
| SK_ColorGREEN, |
| SK_ColorYELLOW, |
| }, |
| }; |
| for (int j = 0; j < 2; j++) { |
| for (int i = 0; i < 2; i++) { |
| SkRect rect = { |
| x_coords[i], |
| y_coords[j], |
| x_coords[i + 1], |
| y_coords[j + 1], |
| }; |
| p.setColor(colors[i][j]); |
| cv->drawOval(rect, p); |
| } |
| } |
| return recorder.finishRecordingAsPicture(); |
| } |
| |
| TEST(DisplayListCanvas, DrawPicture) { |
| sk_sp<SkPicture> picture = makeTestPicture(); |
| CanvasCompareTester::RenderNoAttributes( |
| [=](SkCanvas* canvas, SkPaint& paint) { // |
| canvas->drawPicture(picture, nullptr, nullptr); |
| }, |
| [=](DisplayListBuilder& builder) { // |
| builder.drawPicture(picture, nullptr, false); |
| }); |
| } |
| |
| TEST(DisplayListCanvas, DrawPictureWithMatrix) { |
| sk_sp<SkPicture> picture = makeTestPicture(); |
| SkMatrix matrix = SkMatrix::Scale(0.95, 0.95); |
| CanvasCompareTester::RenderNoAttributes( |
| [=](SkCanvas* canvas, SkPaint& paint) { // |
| canvas->drawPicture(picture, &matrix, nullptr); |
| }, |
| [=](DisplayListBuilder& builder) { // |
| builder.drawPicture(picture, &matrix, false); |
| }); |
| } |
| |
| TEST(DisplayListCanvas, DrawPictureWithPaint) { |
| sk_sp<SkPicture> picture = makeTestPicture(); |
| CanvasCompareTester::RenderAll( |
| [=](SkCanvas* canvas, SkPaint& paint) { // |
| canvas->drawPicture(picture, nullptr, &paint); |
| }, |
| [=](DisplayListBuilder& builder) { // |
| builder.drawPicture(picture, nullptr, true); |
| }); |
| } |
| |
| TEST(DisplayListCanvas, DrawDisplayList) { |
| DisplayListBuilder builder; |
| builder.setStyle(SkPaint::kFill_Style); |
| builder.setColor(SK_ColorBLUE); |
| builder.drawOval(RenderBounds); |
| sk_sp<DisplayList> display_list = builder.Build(); |
| CanvasCompareTester::RenderNoAttributes( |
| [=](SkCanvas* canvas, SkPaint& paint) { // |
| display_list->RenderTo(canvas); |
| }, |
| [=](DisplayListBuilder& builder) { // |
| builder.drawDisplayList(display_list); |
| }); |
| } |
| |
| TEST(DisplayListCanvas, DrawTextBlob) { |
| // TODO(https://github.com/flutter/flutter/issues/82202): Remove once the |
| // performance overlay can use Fuchsia's font manager instead of the empty |
| // default. |
| #if defined(OS_FUCHSIA) |
| GTEST_SKIP() << "Rendering comparisons require a valid default font manager"; |
| #endif // OS_FUCHSIA |
| sk_sp<SkTextBlob> blob = |
| CanvasCompareTester::MakeTextBlob("Testing", RenderHeight * 0.33f); |
| SkScalar RenderY1_3 = RenderTop + RenderHeight * 0.33; |
| SkScalar RenderY2_3 = RenderTop + RenderHeight * 0.66; |
| CanvasCompareTester::RenderNoAttributes( |
| [=](SkCanvas* canvas, SkPaint& paint) { // |
| canvas->drawTextBlob(blob, RenderLeft, RenderY1_3, paint); |
| canvas->drawTextBlob(blob, RenderLeft, RenderY2_3, paint); |
| canvas->drawTextBlob(blob, RenderLeft, RenderBottom, paint); |
| }, |
| [=](DisplayListBuilder& builder) { // |
| builder.drawTextBlob(blob, RenderLeft, RenderY1_3); |
| builder.drawTextBlob(blob, RenderLeft, RenderY2_3); |
| builder.drawTextBlob(blob, RenderLeft, RenderBottom); |
| }, |
| CanvasCompareTester::DefaultAdjuster, |
| // From examining the bounds differential for the "Default" case, the |
| // SkTextBlob adds a padding of ~31 on the left, ~30 on the right, |
| // ~12 on top and ~8 on the bottom, so we add 32h & 13v allowed |
| // padding to the tolerance |
| CanvasCompareTester::DefaultTolerance.addBoundsPadding(32, 13)); |
| } |
| |
| const BoundsTolerance shadowTolerance(const BoundsTolerance& tolerance, |
| const SkPaint& paint, |
| const SkMatrix& matrix) { |
| // Shadow primitives could use just a little more horizontal bounds |
| // tolerance when drawn with a perspective transform. |
| return CanvasCompareTester::DefaultAdjuster( |
| matrix.hasPerspective() ? tolerance.addScale(1.04, 1.0) : tolerance, |
| paint, matrix); |
| } |
| |
| TEST(DisplayListCanvas, DrawShadow) { |
| SkPath path; |
| path.addRoundRect( |
| { |
| RenderLeft + 10, |
| RenderTop, |
| RenderRight - 10, |
| RenderBottom - 20, |
| }, |
| RenderCornerRadius, RenderCornerRadius); |
| const SkColor color = SK_ColorDKGRAY; |
| const SkScalar elevation = 5; |
| |
| CanvasCompareTester::RenderShadows( |
| [=](SkCanvas* canvas, SkPaint& paint) { // |
| PhysicalShapeLayer::DrawShadow(canvas, path, color, elevation, false, |
| 1.0); |
| }, |
| [=](DisplayListBuilder& builder) { // |
| builder.drawShadow(path, color, elevation, false, 1.0); |
| }, |
| shadowTolerance, |
| CanvasCompareTester::DefaultTolerance.addBoundsPadding(3, 3)); |
| } |
| |
| TEST(DisplayListCanvas, DrawShadowTransparentOccluder) { |
| SkPath path; |
| path.addRoundRect( |
| { |
| RenderLeft + 10, |
| RenderTop, |
| RenderRight - 10, |
| RenderBottom - 20, |
| }, |
| RenderCornerRadius, RenderCornerRadius); |
| const SkColor color = SK_ColorDKGRAY; |
| const SkScalar elevation = 5; |
| |
| CanvasCompareTester::RenderShadows( |
| [=](SkCanvas* canvas, SkPaint& paint) { // |
| PhysicalShapeLayer::DrawShadow(canvas, path, color, elevation, true, |
| 1.0); |
| }, |
| [=](DisplayListBuilder& builder) { // |
| builder.drawShadow(path, color, elevation, true, 1.0); |
| }, |
| shadowTolerance, |
| CanvasCompareTester::DefaultTolerance.addBoundsPadding(3, 3)); |
| } |
| |
| TEST(DisplayListCanvas, DrawShadowDpr) { |
| SkPath path; |
| path.addRoundRect( |
| { |
| RenderLeft + 10, |
| RenderTop, |
| RenderRight - 10, |
| RenderBottom - 20, |
| }, |
| RenderCornerRadius, RenderCornerRadius); |
| const SkColor color = SK_ColorDKGRAY; |
| const SkScalar elevation = 5; |
| |
| CanvasCompareTester::RenderShadows( |
| [=](SkCanvas* canvas, SkPaint& paint) { // |
| PhysicalShapeLayer::DrawShadow(canvas, path, color, elevation, false, |
| 1.5); |
| }, |
| [=](DisplayListBuilder& builder) { // |
| builder.drawShadow(path, color, elevation, false, 1.5); |
| }, |
| shadowTolerance, |
| CanvasCompareTester::DefaultTolerance.addBoundsPadding(3, 3)); |
| } |
| |
| } // namespace testing |
| } // namespace flutter |