From 0fa353c6c275fe79c2ba317a8bdf4cb87eef19a7 Mon Sep 17 00:00:00 2001 From: Xianzhu Wang Date: Fri, 25 Aug 2017 16:27:04 -0700 Subject: Optimize filterBounds() of SkArithmeticImageFilter/SkXfermodeImageFilter This brings the optimization in blink's FEComposit::MapRect() [1] into skia. Previously for these classes we used the default SkImageFilter:: onFilterBounds() which returns the union of the bounds of input filters. However, this was not optimized if some input filters don't contribute to the output. When we switch blink SPv2 paint invalidation from using blink's FilterOperations to cc/skia's filter classes, the non- optimization caused over-raster-invalidations. Now override SkImageFilter::onFilterBounds() in these classes to make their filterBounds() return the same results as the blink counterparts. Also fix a bug of SkArithmeticImageFilter when k4 is non-zero by overriding affectsTransparentBlack() to return true in the case, so that we will use the crop as the final bounds. [1] https://cs.chromium.org/chromium/src/third_party/WebKit/Source/platform/graphics/filters/FEComposite.cpp?l=115 Change-Id: I91d4cadc267e6262ee3f050a0ddac90154419775 Reviewed-on: https://skia-review.googlesource.com/38921 Reviewed-by: Mike Reed Commit-Queue: Stephen White --- src/effects/SkArithmeticImageFilter.cpp | 57 +++++++++++++++++++++++++++++++++ src/effects/SkXfermodeImageFilter.cpp | 45 ++++++++++++++++++++++++++ 2 files changed, 102 insertions(+) (limited to 'src') diff --git a/src/effects/SkArithmeticImageFilter.cpp b/src/effects/SkArithmeticImageFilter.cpp index dde38b78d5..8c0f1ef4c9 100644 --- a/src/effects/SkArithmeticImageFilter.cpp +++ b/src/effects/SkArithmeticImageFilter.cpp @@ -41,6 +41,8 @@ protected: sk_sp onFilterImage(SkSpecialImage* source, const Context&, SkIPoint* offset) const override; + SkIRect onFilterBounds(const SkIRect&, const SkMatrix&, MapDirection) const override; + #if SK_SUPPORT_GPU sk_sp filterImageGPU(SkSpecialImage* source, sk_sp background, @@ -64,6 +66,8 @@ protected: sk_sp onMakeColorSpace(SkColorSpaceXformer*) const override; private: + bool affectsTransparentBlack() const override { return !SkScalarNearlyZero(fK[3]); } + const float fK[4]; const bool fEnforcePMColor; @@ -202,6 +206,59 @@ sk_sp ArithmeticImageFilterImpl::onFilterImage(SkSpecialImage* s return surf->makeImageSnapshot(); } +SkIRect ArithmeticImageFilterImpl::onFilterBounds(const SkIRect& src, + const SkMatrix& ctm, + MapDirection direction) const { + if (kReverse_MapDirection == direction) { + return SkImageFilter::onFilterBounds(src, ctm, direction); + } + + SkASSERT(2 == this->countInputs()); + + // result(i1,i2) = k1*i1*i2 + k2*i1 + k3*i2 + k4 + // Note that background (getInput(0)) is i2, and foreground (getInput(1)) is i1. + auto i2 = this->getInput(0) ? this->getInput(0)->filterBounds(src, ctm, direction) : src; + auto i1 = this->getInput(1) ? this->getInput(1)->filterBounds(src, ctm, direction) : src; + + // Arithmetic with non-zero k4 may influence the complete filter primitive + // region. [k4 > 0 => result(0,0) = k4 => result(i1,i2) >= k4] + if (!SkScalarNearlyZero(fK[3])) { + i1.join(i2); + return i1; + } + + // If both K2 or K3 are non-zero, both i1 and i2 appear. + if (!SkScalarNearlyZero(fK[1]) && !SkScalarNearlyZero(fK[2])) { + i1.join(i2); + return i1; + } + + // If k2 is non-zero, output can be produced whenever i1 is non-transparent. + // [k3 = k4 = 0 => result(i1,i2) = k1*i1*i2 + k2*i1 = (k1*i2 + k2)*i1] + if (!SkScalarNearlyZero(fK[1])) { + return i1; + } + + // If k3 is non-zero, output can be produced whenever i2 is non-transparent. + // [k2 = k4 = 0 => result(i1,i2) = k1*i1*i2 + k3*i2 = (k1*i1 + k3)*i2] + if (!SkScalarNearlyZero(fK[2])) { + return i2; + } + + // If just k1 is non-zero, output will only be produce where both inputs + // are non-transparent. Use intersection. + // [k1 > 0 and k2 = k3 = k4 = 0 => result(i1,i2) = k1*i1*i2] + if (!SkScalarNearlyZero(fK[0])) { + if (!i1.intersect(i2)) { + return SkIRect::MakeEmpty(); + } + return i1; + } + + // [k1 = k2 = k3 = k4 = 0 => result(i1,i2) = 0] + return SkIRect::MakeEmpty(); +} + #if SK_SUPPORT_GPU namespace { diff --git a/src/effects/SkXfermodeImageFilter.cpp b/src/effects/SkXfermodeImageFilter.cpp index f7ce0a3027..ddc5d99b5e 100644 --- a/src/effects/SkXfermodeImageFilter.cpp +++ b/src/effects/SkXfermodeImageFilter.cpp @@ -40,6 +40,8 @@ protected: SkIPoint* offset) const override; sk_sp onMakeColorSpace(SkColorSpaceXformer*) const override; + SkIRect onFilterBounds(const SkIRect&, const SkMatrix&, MapDirection) const override; + #if SK_SUPPORT_GPU sk_sp filterImageGPU(SkSpecialImage* source, sk_sp background, @@ -173,6 +175,49 @@ sk_sp SkXfermodeImageFilter_Base::onFilterImage(SkSpecialImage* return surf->makeImageSnapshot(); } +SkIRect SkXfermodeImageFilter_Base::onFilterBounds(const SkIRect& src, + const SkMatrix& ctm, + MapDirection direction) const { + if (kReverse_MapDirection == direction) { + return SkImageFilter::onFilterBounds(src, ctm, direction); + } + + SkASSERT(2 == this->countInputs()); + auto getBackground = [&]() { + return this->getInput(0) ? this->getInput(0)->filterBounds(src, ctm, direction) : src; + }; + auto getForeground = [&]() { + return this->getInput(1) ? this->getInput(1)->filterBounds(src, ctm, direction) : src; + }; + switch (fMode) { + case SkBlendMode::kClear: + return SkIRect::MakeEmpty(); + + case SkBlendMode::kSrc: + case SkBlendMode::kDstATop: + return getForeground(); + + case SkBlendMode::kDst: + case SkBlendMode::kSrcATop: + return getBackground(); + + case SkBlendMode::kSrcIn: + case SkBlendMode::kDstIn: { + auto result = getBackground(); + if (!result.intersect(getForeground())) { + return SkIRect::MakeEmpty(); + } + return result; + } + + default: { + auto result = getBackground(); + result.join(getForeground()); + return result; + } + } +} + sk_sp SkXfermodeImageFilter_Base::onMakeColorSpace(SkColorSpaceXformer* xformer) const { SkASSERT(2 == this->countInputs()); -- cgit v1.2.3