diff options
30 files changed, 320 insertions, 159 deletions
diff --git a/gm/matrixconvolution.cpp b/gm/matrixconvolution.cpp index 4af12e172d..adb7eceff0 100644 --- a/gm/matrixconvolution.cpp +++ b/gm/matrixconvolution.cpp @@ -92,9 +92,14 @@ protected: cropRect)); canvas->save(); canvas->translate(SkIntToScalar(x), SkIntToScalar(y)); - canvas->clipRect(SkRect::MakeWH(SkIntToScalar(fBitmap.width()), - SkIntToScalar(fBitmap.height()))); - canvas->drawBitmap(fBitmap, 0, 0, &paint); + const SkRect layerBounds = SkRect::MakeIWH(fBitmap.width(), fBitmap.height()); + canvas->clipRect(layerBounds); + // This GM is, in part, intended to display the wrapping behavior of the + // matrix image filter. The only (rational) way to achieve that for repeat mode + // is to create a tight layer. + canvas->saveLayer(layerBounds, &paint); + canvas->drawBitmap(fBitmap, 0, 0, nullptr); + canvas->restore(); canvas->restore(); } diff --git a/include/core/SkColorFilter.h b/include/core/SkColorFilter.h index 97c8220c6b..e8b09bae9c 100644 --- a/include/core/SkColorFilter.h +++ b/include/core/SkColorFilter.h @@ -135,7 +135,7 @@ public: #endif bool affectsTransparentBlack() const { - return this->filterColor(0) != 0; + return this->filterColor(SK_ColorTRANSPARENT) != SK_ColorTRANSPARENT; } virtual void toString(SkString* str) const = 0; diff --git a/include/core/SkImageFilter.h b/include/core/SkImageFilter.h index 2528009e24..70f5513843 100644 --- a/include/core/SkImageFilter.h +++ b/include/core/SkImageFilter.h @@ -155,8 +155,13 @@ public: * be exact, but should never be smaller than the real answer. The default * implementation recursively unions all input bounds, or returns the * source rect if no inputs. + * + * In kReverse mode, 'inputRect' is the device-space bounds of the input pixels. In kForward + * mode it should always be null. If 'inputRect' is null in kReverse mode the resulting + * answer may be incorrect. */ - SkIRect filterBounds(const SkIRect& src, const SkMatrix& ctm, MapDirection) const; + SkIRect filterBounds(const SkIRect& src, const SkMatrix& ctm, + MapDirection, const SkIRect* inputRect = nullptr) const; #if SK_SUPPORT_GPU static sk_sp<SkSpecialImage> DrawWithFP(GrContext* context, @@ -320,12 +325,16 @@ protected: * and returns the union of those results. If a derived class has special * recursion requirements (e.g., it has an input which does not participate * in bounds computation), it can be overridden here. + * In kReverse mode, 'inputRect' is the device-space bounds of the input pixels. In kForward + * mode it should always be null. If 'inputRect' is null in kReverse mode the resulting + * answer may be incorrect. * * Note that this function is *not* responsible for mapping the rect for * this node's filter bounds requirements (i.e., calling * onFilterNodeBounds()); that is handled by filterBounds(). */ - virtual SkIRect onFilterBounds(const SkIRect&, const SkMatrix&, MapDirection) const; + virtual SkIRect onFilterBounds(const SkIRect&, const SkMatrix& ctm, + MapDirection, const SkIRect* inputRect) const; /** * Performs a forwards or reverse mapping of the given rect to accommodate @@ -339,8 +348,12 @@ protected: * inverse of the other. For example, blurring expands the given rect * in both forward and reverse directions. Unlike * onFilterBounds(), this function is non-recursive. + * In kReverse mode, 'inputRect' will be the device space bounds of the input pixels. In + * kForward mode, 'inputRect' should always be null. If 'inputRect' is null in kReverse mode + * the resulting answer may be incorrect. */ - virtual SkIRect onFilterNodeBounds(const SkIRect&, const SkMatrix&, MapDirection) const; + virtual SkIRect onFilterNodeBounds(const SkIRect&, const SkMatrix& ctm, + MapDirection, const SkIRect* inputRect) const; // Helper function which invokes filter processing on the input at the // specified "index". If the input is null, it returns "src" and leaves @@ -416,6 +429,15 @@ protected: return sk_ref_sp(const_cast<SkImageFilter*>(this)); } + // If 'srcBounds' will sample outside the border of 'originalSrcBounds' (i.e., the sample + // will wrap around to the other side) we must preserve the far side of the src along that + // axis (e.g., if we will sample beyond the left edge of the src, the right side must be + // preserved for the repeat sampling to work). + static SkIRect DetermineRepeatedSrcBound(const SkIRect& srcBounds, + const SkIVector& filterOffset, + const SkISize& filterSize, + const SkIRect& originalSrcBounds); + private: // For makeColorSpace(). friend class SkColorSpaceXformer; diff --git a/include/effects/SkComposeImageFilter.h b/include/effects/SkComposeImageFilter.h index dd44df5d9c..0bfb7d8c5c 100644 --- a/include/effects/SkComposeImageFilter.h +++ b/include/effects/SkComposeImageFilter.h @@ -27,7 +27,8 @@ protected: sk_sp<SkSpecialImage> onFilterImage(SkSpecialImage* source, const Context&, SkIPoint* offset) const override; sk_sp<SkImageFilter> onMakeColorSpace(SkColorSpaceXformer*) const override; - SkIRect onFilterBounds(const SkIRect&, const SkMatrix&, MapDirection) const override; + SkIRect onFilterBounds(const SkIRect&, const SkMatrix& ctm, + MapDirection, const SkIRect* inputRect) const override; bool onCanHandleComplexCTM() const override { return true; } private: diff --git a/include/effects/SkDisplacementMapEffect.h b/include/effects/SkDisplacementMapEffect.h index af4d6fd10e..daf03d742c 100644 --- a/include/effects/SkDisplacementMapEffect.h +++ b/include/effects/SkDisplacementMapEffect.h @@ -35,10 +35,11 @@ public: SkRect computeFastBounds(const SkRect& src) const override; - virtual SkIRect onFilterBounds(const SkIRect& src, const SkMatrix&, - MapDirection) const override; + virtual SkIRect onFilterBounds(const SkIRect& src, const SkMatrix& ctm, + MapDirection, const SkIRect* inputRect) const override; sk_sp<SkImageFilter> onMakeColorSpace(SkColorSpaceXformer*) const override; - SkIRect onFilterNodeBounds(const SkIRect&, const SkMatrix&, MapDirection) const override; + SkIRect onFilterNodeBounds(const SkIRect&, const SkMatrix& ctm, + MapDirection, const SkIRect* inputRect) const override; void toString(SkString* str) const override; diff --git a/include/effects/SkDropShadowImageFilter.h b/include/effects/SkDropShadowImageFilter.h index f6b27db271..d1ee44c168 100644 --- a/include/effects/SkDropShadowImageFilter.h +++ b/include/effects/SkDropShadowImageFilter.h @@ -37,7 +37,8 @@ protected: sk_sp<SkSpecialImage> onFilterImage(SkSpecialImage* source, const Context&, SkIPoint* offset) const override; sk_sp<SkImageFilter> onMakeColorSpace(SkColorSpaceXformer*) const override; - SkIRect onFilterNodeBounds(const SkIRect& src, const SkMatrix&, MapDirection) const override; + SkIRect onFilterNodeBounds(const SkIRect& src, const SkMatrix& ctm, + MapDirection, const SkIRect* inputRect) const override; private: SkDropShadowImageFilter(SkScalar dx, SkScalar dy, SkScalar sigmaX, SkScalar sigmaY, SkColor, diff --git a/include/effects/SkImageSource.h b/include/effects/SkImageSource.h index 25b169c20d..6ef855ab93 100644 --- a/include/effects/SkImageSource.h +++ b/include/effects/SkImageSource.h @@ -31,7 +31,8 @@ protected: SkIPoint* offset) const override; sk_sp<SkImageFilter> onMakeColorSpace(SkColorSpaceXformer*) const override; - SkIRect onFilterNodeBounds(const SkIRect&, const SkMatrix&, MapDirection) const override; + SkIRect onFilterNodeBounds(const SkIRect&, const SkMatrix& ctm, + MapDirection, const SkIRect* inputRect) const override; private: explicit SkImageSource(sk_sp<SkImage>); diff --git a/include/effects/SkMatrixConvolutionImageFilter.h b/include/effects/SkMatrixConvolutionImageFilter.h index 94ea716842..5361fef928 100644 --- a/include/effects/SkMatrixConvolutionImageFilter.h +++ b/include/effects/SkMatrixConvolutionImageFilter.h @@ -85,7 +85,8 @@ protected: sk_sp<SkSpecialImage> onFilterImage(SkSpecialImage* source, const Context&, SkIPoint* offset) const override; sk_sp<SkImageFilter> onMakeColorSpace(SkColorSpaceXformer*) const override; - SkIRect onFilterNodeBounds(const SkIRect&, const SkMatrix&, MapDirection) const override; + SkIRect onFilterNodeBounds(const SkIRect&, const SkMatrix& ctm, + MapDirection, const SkIRect* inputRect) const override; bool affectsTransparentBlack() const override; private: @@ -100,19 +101,23 @@ private: template <class PixelFetcher, bool convolveAlpha> void filterPixels(const SkBitmap& src, SkBitmap* result, + SkIVector& offset, const SkIRect& rect, const SkIRect& bounds) const; template <class PixelFetcher> void filterPixels(const SkBitmap& src, SkBitmap* result, + SkIVector& offset, const SkIRect& rect, const SkIRect& bounds) const; void filterInteriorPixels(const SkBitmap& src, SkBitmap* result, + SkIVector& offset, const SkIRect& rect, const SkIRect& bounds) const; void filterBorderPixels(const SkBitmap& src, SkBitmap* result, + SkIVector& offset, const SkIRect& rect, const SkIRect& bounds) const; diff --git a/include/effects/SkMorphologyImageFilter.h b/include/effects/SkMorphologyImageFilter.h index b75fe26d8a..d88a31dc65 100644 --- a/include/effects/SkMorphologyImageFilter.h +++ b/include/effects/SkMorphologyImageFilter.h @@ -16,7 +16,8 @@ class SK_API SkMorphologyImageFilter : public SkImageFilter { public: SkRect computeFastBounds(const SkRect& src) const override; - SkIRect onFilterNodeBounds(const SkIRect& src, const SkMatrix&, MapDirection) const override; + SkIRect onFilterNodeBounds(const SkIRect& src, const SkMatrix& ctm, + MapDirection, const SkIRect* inputRect) const override; /** * All morphology procs have the same signature: src is the source buffer, dst the diff --git a/include/effects/SkOffsetImageFilter.h b/include/effects/SkOffsetImageFilter.h index 9356c70fd2..2d7b14f652 100644 --- a/include/effects/SkOffsetImageFilter.h +++ b/include/effects/SkOffsetImageFilter.h @@ -27,7 +27,8 @@ protected: sk_sp<SkSpecialImage> onFilterImage(SkSpecialImage* source, const Context&, SkIPoint* offset) const override; sk_sp<SkImageFilter> onMakeColorSpace(SkColorSpaceXformer*) const override; - SkIRect onFilterNodeBounds(const SkIRect&, const SkMatrix&, MapDirection) const override; + SkIRect onFilterNodeBounds(const SkIRect&, const SkMatrix& ctm, + MapDirection, const SkIRect* inputRect) const override; private: SkOffsetImageFilter(SkScalar dx, SkScalar dy, sk_sp<SkImageFilter> input, const CropRect*); diff --git a/include/effects/SkTileImageFilter.h b/include/effects/SkTileImageFilter.h index f37982bbb9..f62d8a02d4 100644 --- a/include/effects/SkTileImageFilter.h +++ b/include/effects/SkTileImageFilter.h @@ -21,8 +21,10 @@ public: const SkRect& dst, sk_sp<SkImageFilter> input); - SkIRect onFilterBounds(const SkIRect& src, const SkMatrix&, MapDirection) const override; - SkIRect onFilterNodeBounds(const SkIRect&, const SkMatrix&, MapDirection) const override; + SkIRect onFilterBounds(const SkIRect& src, const SkMatrix& ctm, + MapDirection, const SkIRect* inputRect) const override; + SkIRect onFilterNodeBounds(const SkIRect&, const SkMatrix& ctm, + MapDirection, const SkIRect* inputRect) const override; SkRect computeFastBounds(const SkRect& src) const override; void toString(SkString* str) const override; diff --git a/src/core/SkBlurImageFilter.cpp b/src/core/SkBlurImageFilter.cpp index aa566a49f6..a53d061b37 100644 --- a/src/core/SkBlurImageFilter.cpp +++ b/src/core/SkBlurImageFilter.cpp @@ -49,7 +49,8 @@ protected: sk_sp<SkSpecialImage> onFilterImage(SkSpecialImage* source, const Context&, SkIPoint* offset) const override; sk_sp<SkImageFilter> onMakeColorSpace(SkColorSpaceXformer*) const override; - SkIRect onFilterNodeBounds(const SkIRect& src, const SkMatrix&, MapDirection) const override; + SkIRect onFilterNodeBounds(const SkIRect& src, const SkMatrix& ctm, + MapDirection, const SkIRect* inputRect) const override; private: typedef SkImageFilter INHERITED; @@ -692,7 +693,7 @@ SkRect SkBlurImageFilterImpl::computeFastBounds(const SkRect& src) const { } SkIRect SkBlurImageFilterImpl::onFilterNodeBounds(const SkIRect& src, const SkMatrix& ctm, - MapDirection) const { + MapDirection, const SkIRect* inputRect) const { SkVector sigma = map_sigma(fSigma, ctm); return src.makeOutset(SkScalarCeilToInt(sigma.x() * 3), SkScalarCeilToInt(sigma.y() * 3)); } diff --git a/src/core/SkCanvas.cpp b/src/core/SkCanvas.cpp index a467cac060..00bbdf418e 100644 --- a/src/core/SkCanvas.cpp +++ b/src/core/SkCanvas.cpp @@ -879,24 +879,42 @@ bool SkCanvas::clipRectBounds(const SkRect* bounds, SaveLayerFlags saveLayerFlag const SkMatrix& ctm = fMCRec->fMatrix; // this->getTotalMatrix() - if (imageFilter) { - clipBounds = imageFilter->filterBounds(clipBounds, ctm, - SkImageFilter::kReverse_MapDirection); - if (bounds && !imageFilter->canComputeFastBounds()) { - bounds = nullptr; - } + if (imageFilter && bounds && !imageFilter->canComputeFastBounds()) { + // If the image filter DAG affects transparent black then we will need to render + // out to the clip bounds + bounds = nullptr; } - SkIRect ir; + + SkIRect inputSaveLayerBounds; if (bounds) { SkRect r; ctm.mapRect(&r, *bounds); - r.roundOut(&ir); + r.roundOut(&inputSaveLayerBounds); } else { // no user bounds, so just use the clip - ir = clipBounds; + inputSaveLayerBounds = clipBounds; + } + + if (imageFilter) { + // expand the clip bounds by the image filter DAG to include extra content that might + // be required by the image filters. + clipBounds = imageFilter->filterBounds(clipBounds, ctm, + SkImageFilter::kReverse_MapDirection, + &inputSaveLayerBounds); + } + + SkIRect clippedSaveLayerBounds; + if (bounds) { + // For better or for worse, user bounds currently act as a hard clip on the layer's + // extent (i.e., they implement the CSS filter-effects 'filter region' feature). + clippedSaveLayerBounds = inputSaveLayerBounds; + } else { + // If there are no user bounds, we don't want to artificially restrict the resulting + // layer bounds, so allow the expanded clip bounds free reign. + clippedSaveLayerBounds = clipBounds; } // early exit if the layer's bounds are clipped out - if (!ir.intersect(clipBounds)) { + if (!clippedSaveLayerBounds.intersect(clipBounds)) { if (BoundsAffectsClip(saveLayerFlags)) { fMCRec->fTopLayer->fDevice->clipRegion(SkRegion(), SkClipOp::kIntersect); // empty fMCRec->fRasterClip.setEmpty(); @@ -904,17 +922,18 @@ bool SkCanvas::clipRectBounds(const SkRect* bounds, SaveLayerFlags saveLayerFlag } return false; } - SkASSERT(!ir.isEmpty()); + SkASSERT(!clippedSaveLayerBounds.isEmpty()); if (BoundsAffectsClip(saveLayerFlags)) { // Simplify the current clips since they will be applied properly during restore() - fMCRec->fRasterClip.setRect(ir); - fDeviceClipBounds = qr_clip_bounds(ir); + fMCRec->fRasterClip.setRect(clippedSaveLayerBounds); + fDeviceClipBounds = qr_clip_bounds(clippedSaveLayerBounds); } if (intersection) { - *intersection = ir; + *intersection = clippedSaveLayerBounds; } + return true; } diff --git a/src/core/SkImageFilter.cpp b/src/core/SkImageFilter.cpp index a9447212d3..59715d99e9 100644 --- a/src/core/SkImageFilter.cpp +++ b/src/core/SkImageFilter.cpp @@ -223,13 +223,14 @@ sk_sp<SkSpecialImage> SkImageFilter::filterImage(SkSpecialImage* src, const Cont } SkIRect SkImageFilter::filterBounds(const SkIRect& src, const SkMatrix& ctm, - MapDirection direction) const { + MapDirection direction, const SkIRect* inputRect) const { if (kReverse_MapDirection == direction) { - SkIRect bounds = this->onFilterNodeBounds(src, ctm, direction); - return this->onFilterBounds(bounds, ctm, direction); + SkIRect bounds = this->onFilterNodeBounds(src, ctm, direction, inputRect); + return this->onFilterBounds(bounds, ctm, direction, &bounds); } else { - SkIRect bounds = this->onFilterBounds(src, ctm, direction); - bounds = this->onFilterNodeBounds(bounds, ctm, direction); + SkASSERT(!inputRect); + SkIRect bounds = this->onFilterBounds(src, ctm, direction, nullptr); + bounds = this->onFilterNodeBounds(bounds, ctm, direction, nullptr); SkIRect dst; this->getCropRect().applyTo(bounds, ctm, this->affectsTransparentBlack(), &dst); return dst; @@ -327,7 +328,7 @@ bool SkImageFilter::canHandleComplexCTM() const { bool SkImageFilter::applyCropRect(const Context& ctx, const SkIRect& srcBounds, SkIRect* dstBounds) const { - SkIRect tmpDst = this->onFilterNodeBounds(srcBounds, ctx.ctm(), kForward_MapDirection); + SkIRect tmpDst = this->onFilterNodeBounds(srcBounds, ctx.ctm(), kForward_MapDirection, nullptr); fCropRect.applyTo(tmpDst, ctx.ctm(), this->affectsTransparentBlack(), dstBounds); // Intersect against the clip bounds, in case the crop rect has // grown the bounds beyond the original clip. This can happen for @@ -431,7 +432,7 @@ sk_sp<SkSpecialImage> SkImageFilter::applyCropRectAndPad(const Context& ctx, } SkIRect SkImageFilter::onFilterBounds(const SkIRect& src, const SkMatrix& ctm, - MapDirection direction) const { + MapDirection dir, const SkIRect* inputRect) const { if (this->countInputs() < 1) { return src; } @@ -439,7 +440,7 @@ SkIRect SkImageFilter::onFilterBounds(const SkIRect& src, const SkMatrix& ctm, SkIRect totalBounds; for (int i = 0; i < this->countInputs(); ++i) { SkImageFilter* filter = this->getInput(i); - SkIRect rect = filter ? filter->filterBounds(src, ctm, direction) : src; + SkIRect rect = filter ? filter->filterBounds(src, ctm, dir, inputRect) : src; if (0 == i) { totalBounds = rect; } else { @@ -450,14 +451,16 @@ SkIRect SkImageFilter::onFilterBounds(const SkIRect& src, const SkMatrix& ctm, return totalBounds; } -SkIRect SkImageFilter::onFilterNodeBounds(const SkIRect& src, const SkMatrix&, MapDirection) const { +SkIRect SkImageFilter::onFilterNodeBounds(const SkIRect& src, const SkMatrix&, + MapDirection, const SkIRect*) const { return src; } SkImageFilter::Context SkImageFilter::mapContext(const Context& ctx) const { SkIRect clipBounds = this->onFilterNodeBounds(ctx.clipBounds(), ctx.ctm(), - MapDirection::kReverse_MapDirection); + MapDirection::kReverse_MapDirection, + &ctx.clipBounds()); return Context(ctx.ctm(), clipBounds, ctx.cache(), ctx.outputProperties()); } @@ -494,3 +497,26 @@ sk_sp<SkSpecialImage> SkImageFilter::filterInput(int index, void SkImageFilter::PurgeCache() { SkImageFilterCache::Get()->purge(); } + +// In repeat mode, when we are going to sample off one edge of the srcBounds we require the +// opposite side be preserved. +SkIRect SkImageFilter::DetermineRepeatedSrcBound(const SkIRect& srcBounds, + const SkIVector& filterOffset, + const SkISize& filterSize, + const SkIRect& originalSrcBounds) { + SkIRect tmp = srcBounds; + tmp.fRight = Sk32_sat_add(tmp.fRight, filterSize.fWidth); + tmp.fBottom = Sk32_sat_add(tmp.fBottom, filterSize.fHeight); + tmp.offset(-filterOffset.fX, -filterOffset.fY); + + if (tmp.fLeft < originalSrcBounds.fLeft || tmp.fRight > originalSrcBounds.fRight) { + tmp.fLeft = originalSrcBounds.fLeft; + tmp.fRight = originalSrcBounds.fRight; + } + if (tmp.fTop < originalSrcBounds.fTop || tmp.fBottom > originalSrcBounds.fBottom) { + tmp.fTop = originalSrcBounds.fTop; + tmp.fBottom = originalSrcBounds.fBottom; + } + + return tmp; +} diff --git a/src/core/SkLocalMatrixImageFilter.cpp b/src/core/SkLocalMatrixImageFilter.cpp index a882849637..07db7aff77 100644 --- a/src/core/SkLocalMatrixImageFilter.cpp +++ b/src/core/SkLocalMatrixImageFilter.cpp @@ -52,9 +52,9 @@ sk_sp<SkSpecialImage> SkLocalMatrixImageFilter::onFilterImage(SkSpecialImage* so return this->filterInput(0, source, localCtx, offset); } -SkIRect SkLocalMatrixImageFilter::onFilterBounds(const SkIRect& src, const SkMatrix& matrix, - MapDirection direction) const { - return this->getInput(0)->filterBounds(src, SkMatrix::Concat(matrix, fLocalM), direction); +SkIRect SkLocalMatrixImageFilter::onFilterBounds(const SkIRect& src, const SkMatrix& ctm, + MapDirection dir, const SkIRect* inputRect) const { + return this->getInput(0)->filterBounds(src, SkMatrix::Concat(ctm, fLocalM), dir, inputRect); } sk_sp<SkImageFilter> SkLocalMatrixImageFilter::onMakeColorSpace(SkColorSpaceXformer* xformer) diff --git a/src/core/SkLocalMatrixImageFilter.h b/src/core/SkLocalMatrixImageFilter.h index 951d157111..89a7e9d145 100644 --- a/src/core/SkLocalMatrixImageFilter.h +++ b/src/core/SkLocalMatrixImageFilter.h @@ -26,7 +26,8 @@ protected: sk_sp<SkSpecialImage> onFilterImage(SkSpecialImage* source, const Context&, SkIPoint* offset) const override; sk_sp<SkImageFilter> onMakeColorSpace(SkColorSpaceXformer*) const override; - SkIRect onFilterBounds(const SkIRect& src, const SkMatrix&, MapDirection) const override; + SkIRect onFilterBounds(const SkIRect& src, const SkMatrix& ctm, + MapDirection, const SkIRect* inputRect) const override; private: SkLocalMatrixImageFilter(const SkMatrix& localM, sk_sp<SkImageFilter> input); diff --git a/src/core/SkMatrixImageFilter.cpp b/src/core/SkMatrixImageFilter.cpp index f9b53348b1..47b0d4245e 100644 --- a/src/core/SkMatrixImageFilter.cpp +++ b/src/core/SkMatrixImageFilter.cpp @@ -114,12 +114,12 @@ SkRect SkMatrixImageFilter::computeFastBounds(const SkRect& src) const { } SkIRect SkMatrixImageFilter::onFilterNodeBounds(const SkIRect& src, const SkMatrix& ctm, - MapDirection direction) const { + MapDirection dir, const SkIRect* inputRect) const { SkMatrix matrix; if (!ctm.invert(&matrix)) { return src; } - if (kForward_MapDirection == direction) { + if (kForward_MapDirection == dir) { matrix.postConcat(fTransform); } else { SkMatrix transformInverse; @@ -134,7 +134,7 @@ SkIRect SkMatrixImageFilter::onFilterNodeBounds(const SkIRect& src, const SkMatr SkIRect result = floatBounds.roundOut(); #ifndef SK_IGNORE_MATRIX_IMAGE_FILTER_FIX - if (kReverse_MapDirection == direction && kNone_SkFilterQuality != fFilterQuality) { + if (kReverse_MapDirection == dir && kNone_SkFilterQuality != fFilterQuality) { // When filtering we might need some pixels in the source that might be otherwise // clipped off. result.outset(1, 1); diff --git a/src/core/SkMatrixImageFilter.h b/src/core/SkMatrixImageFilter.h index 445e739195..b43df9166d 100644 --- a/src/core/SkMatrixImageFilter.h +++ b/src/core/SkMatrixImageFilter.h @@ -43,7 +43,8 @@ protected: sk_sp<SkSpecialImage> onFilterImage(SkSpecialImage* source, const Context&, SkIPoint* offset) const override; sk_sp<SkImageFilter> onMakeColorSpace(SkColorSpaceXformer*) const override; - SkIRect onFilterNodeBounds(const SkIRect& src, const SkMatrix&, MapDirection) const override; + SkIRect onFilterNodeBounds(const SkIRect& src, const SkMatrix& ctm, + MapDirection, const SkIRect* inputRect) const override; private: SkMatrix fTransform; diff --git a/src/effects/SkArithmeticImageFilter.cpp b/src/effects/SkArithmeticImageFilter.cpp index 984ed80a19..c022ec955e 100644 --- a/src/effects/SkArithmeticImageFilter.cpp +++ b/src/effects/SkArithmeticImageFilter.cpp @@ -44,7 +44,8 @@ protected: sk_sp<SkSpecialImage> onFilterImage(SkSpecialImage* source, const Context&, SkIPoint* offset) const override; - SkIRect onFilterBounds(const SkIRect&, const SkMatrix&, MapDirection) const override; + SkIRect onFilterBounds(const SkIRect&, const SkMatrix& ctm, + MapDirection, const SkIRect* inputRect) const override; #if SK_SUPPORT_GPU sk_sp<SkSpecialImage> filterImageGPU(SkSpecialImage* source, @@ -214,17 +215,18 @@ sk_sp<SkSpecialImage> ArithmeticImageFilterImpl::onFilterImage(SkSpecialImage* s SkIRect ArithmeticImageFilterImpl::onFilterBounds(const SkIRect& src, const SkMatrix& ctm, - MapDirection direction) const { - if (kReverse_MapDirection == direction) { - return SkImageFilter::onFilterBounds(src, ctm, direction); + MapDirection dir, + const SkIRect* inputRect) const { + if (kReverse_MapDirection == dir) { + return SkImageFilter::onFilterBounds(src, ctm, dir, inputRect); } 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; + auto i2 = this->getInput(0) ? this->getInput(0)->filterBounds(src, ctm, dir, nullptr) : src; + auto i1 = this->getInput(1) ? this->getInput(1)->filterBounds(src, ctm, dir, nullptr) : src; // Arithmetic with non-zero k4 may influence the complete filter primitive // region. [k4 > 0 => result(0,0) = k4 => result(i1,i2) >= k4] diff --git a/src/effects/SkComposeImageFilter.cpp b/src/effects/SkComposeImageFilter.cpp index 7769f6d845..51cec9c671 100644 --- a/src/effects/SkComposeImageFilter.cpp +++ b/src/effects/SkComposeImageFilter.cpp @@ -39,7 +39,7 @@ sk_sp<SkSpecialImage> SkComposeImageFilter::onFilterImage(SkSpecialImage* source // filter requires as input. This matters if the outer filter moves pixels. SkIRect innerClipBounds; innerClipBounds = this->getInput(0)->filterBounds(ctx.clipBounds(), ctx.ctm(), - kReverse_MapDirection); + kReverse_MapDirection, &ctx.clipBounds()); Context innerContext(ctx.ctm(), innerClipBounds, ctx.cache(), ctx.outputProperties()); SkIPoint innerOffset = SkIPoint::Make(0, 0); sk_sp<SkSpecialImage> inner(this->filterInput(1, source, innerContext, &innerOffset)); @@ -75,11 +75,12 @@ sk_sp<SkImageFilter> SkComposeImageFilter::onMakeColorSpace(SkColorSpaceXformer* } SkIRect SkComposeImageFilter::onFilterBounds(const SkIRect& src, const SkMatrix& ctm, - MapDirection direction) const { + MapDirection dir, const SkIRect* inputRect) const { SkImageFilter* outer = this->getInput(0); SkImageFilter* inner = this->getInput(1); - return outer->filterBounds(inner->filterBounds(src, ctm, direction), ctm, direction); + const SkIRect innerRect = inner->filterBounds(src, ctm, dir, inputRect); + return outer->filterBounds(innerRect, ctm, dir, &innerRect); } sk_sp<SkFlattenable> SkComposeImageFilter::CreateProc(SkReadBuffer& buffer) { diff --git a/src/effects/SkDisplacementMapEffect.cpp b/src/effects/SkDisplacementMapEffect.cpp index fe425c3cc3..d40e521b0a 100644 --- a/src/effects/SkDisplacementMapEffect.cpp +++ b/src/effects/SkDisplacementMapEffect.cpp @@ -388,7 +388,7 @@ SkRect SkDisplacementMapEffect::computeFastBounds(const SkRect& src) const { } SkIRect SkDisplacementMapEffect::onFilterNodeBounds(const SkIRect& src, const SkMatrix& ctm, - MapDirection) const { + MapDirection, const SkIRect* inputRect) const { SkVector scale = SkVector::Make(fScale, fScale); ctm.mapVectors(&scale, 1); return src.makeOutset(SkScalarCeilToInt(SkScalarAbs(scale.fX) * SK_ScalarHalf), @@ -396,10 +396,10 @@ SkIRect SkDisplacementMapEffect::onFilterNodeBounds(const SkIRect& src, const Sk } SkIRect SkDisplacementMapEffect::onFilterBounds(const SkIRect& src, const SkMatrix& ctm, - MapDirection direction) const { + MapDirection dir, const SkIRect* inputRect) const { // Recurse only into color input. if (this->getColorInput()) { - return this->getColorInput()->filterBounds(src, ctm, direction); + return this->getColorInput()->filterBounds(src, ctm, dir, inputRect); } return src; } diff --git a/src/effects/SkDropShadowImageFilter.cpp b/src/effects/SkDropShadowImageFilter.cpp index 9cdcace4c3..7532894bce 100644 --- a/src/effects/SkDropShadowImageFilter.cpp +++ b/src/effects/SkDropShadowImageFilter.cpp @@ -140,9 +140,9 @@ SkRect SkDropShadowImageFilter::computeFastBounds(const SkRect& src) const { } SkIRect SkDropShadowImageFilter::onFilterNodeBounds(const SkIRect& src, const SkMatrix& ctm, - MapDirection direction) const { + MapDirection dir, const SkIRect* inputRect) const { SkVector offsetVec = SkVector::Make(fDx, fDy); - if (kReverse_MapDirection == direction) { + if (kReverse_MapDirection == dir) { offsetVec.negate(); } ctm.mapVectors(&offsetVec, 1); diff --git a/src/effects/SkImageSource.cpp b/src/effects/SkImageSource.cpp index 167ef4b71b..0f48fb0c97 100644 --- a/src/effects/SkImageSource.cpp +++ b/src/effects/SkImageSource.cpp @@ -147,9 +147,9 @@ SkRect SkImageSource::computeFastBounds(const SkRect& src) const { } SkIRect SkImageSource::onFilterNodeBounds(const SkIRect& src, const SkMatrix& ctm, - MapDirection direction) const { + MapDirection direction, const SkIRect* inputRect) const { if (kReverse_MapDirection == direction) { - return SkImageFilter::onFilterNodeBounds(src, ctm, direction); + return SkImageFilter::onFilterNodeBounds(src, ctm, direction, inputRect); } SkRect dstRect = fDstRect; diff --git a/src/effects/SkMatrixConvolutionImageFilter.cpp b/src/effects/SkMatrixConvolutionImageFilter.cpp index 9412b604ab..10f417bea4 100644 --- a/src/effects/SkMatrixConvolutionImageFilter.cpp +++ b/src/effects/SkMatrixConvolutionImageFilter.cpp @@ -169,6 +169,7 @@ public: template<class PixelFetcher, bool convolveAlpha> void SkMatrixConvolutionImageFilter::filterPixels(const SkBitmap& src, SkBitmap* result, + SkIVector& offset, const SkIRect& r, const SkIRect& bounds) const { SkIRect rect(r); @@ -176,7 +177,7 @@ void SkMatrixConvolutionImageFilter::filterPixels(const SkBitmap& src, return; } for (int y = rect.fTop; y < rect.fBottom; ++y) { - SkPMColor* dptr = result->getAddr32(rect.fLeft - bounds.fLeft, y - bounds.fTop); + SkPMColor* dptr = result->getAddr32(rect.fLeft - offset.fX, y - offset.fY); for (int x = rect.fLeft; x < rect.fRight; ++x) { SkScalar sumA = 0, sumR = 0, sumG = 0, sumB = 0; for (int cy = 0; cy < fKernelSize.fHeight; cy++) { @@ -213,35 +214,38 @@ void SkMatrixConvolutionImageFilter::filterPixels(const SkBitmap& src, template<class PixelFetcher> void SkMatrixConvolutionImageFilter::filterPixels(const SkBitmap& src, SkBitmap* result, + SkIVector& offset, const SkIRect& rect, const SkIRect& bounds) const { if (fConvolveAlpha) { - filterPixels<PixelFetcher, true>(src, result, rect, bounds); + filterPixels<PixelFetcher, true>(src, result, offset, rect, bounds); } else { - filterPixels<PixelFetcher, false>(src, result, rect, bounds); + filterPixels<PixelFetcher, false>(src, result, offset, rect, bounds); } } void SkMatrixConvolutionImageFilter::filterInteriorPixels(const SkBitmap& src, SkBitmap* result, + SkIVector& offset, const SkIRect& rect, const SkIRect& bounds) const { - filterPixels<UncheckedPixelFetcher>(src, result, rect, bounds); + filterPixels<UncheckedPixelFetcher>(src, result, offset, rect, bounds); } void SkMatrixConvolutionImageFilter::filterBorderPixels(const SkBitmap& src, SkBitmap* result, + SkIVector& offset, const SkIRect& rect, - const SkIRect& bounds) const { + const SkIRect& srcBounds) const { switch (fTileMode) { case kClamp_TileMode: - filterPixels<ClampPixelFetcher>(src, result, rect, bounds); + filterPixels<ClampPixelFetcher>(src, result, offset, rect, srcBounds); break; case kRepeat_TileMode: - filterPixels<RepeatPixelFetcher>(src, result, rect, bounds); + filterPixels<RepeatPixelFetcher>(src, result, offset, rect, srcBounds); break; case kClampToBlack_TileMode: - filterPixels<ClampToBlackPixelFetcher>(src, result, rect, bounds); + filterPixels<ClampToBlackPixelFetcher>(src, result, offset, rect, srcBounds); break; } } @@ -301,6 +305,21 @@ sk_sp<SkSpecialImage> SkMatrixConvolutionImageFilter::onFilterImage(SkSpecialIma return nullptr; } + const SkIRect originalSrcBounds = SkIRect::MakeXYWH(inputOffset.fX, inputOffset.fY, + input->width(), input->height()); + + SkIRect srcBounds = this->onFilterNodeBounds(dstBounds, ctx.ctm(), kReverse_MapDirection, + &originalSrcBounds); + + if (kRepeat_TileMode == fTileMode) { + srcBounds = DetermineRepeatedSrcBound(srcBounds, fKernelOffset, + fKernelSize, originalSrcBounds); + } else { + if (!srcBounds.intersect(dstBounds)) { + return nullptr; + } + } + #if SK_SUPPORT_GPU // Note: if the kernel is too big, the GPU path falls back to SW if (source->isTextureBacked() && @@ -319,9 +338,10 @@ sk_sp<SkSpecialImage> SkMatrixConvolutionImageFilter::onFilterImage(SkSpecialIma offset->fX = dstBounds.left(); offset->fY = dstBounds.top(); dstBounds.offset(-inputOffset); + srcBounds.offset(-inputOffset); auto fp = GrMatrixConvolutionEffect::Make(std::move(inputProxy), - dstBounds, + srcBounds, fKernelSize, fKernel, fGain, @@ -366,6 +386,8 @@ sk_sp<SkSpecialImage> SkMatrixConvolutionImageFilter::onFilterImage(SkSpecialIma offset->fX = dstBounds.fLeft; offset->fY = dstBounds.fTop; dstBounds.offset(-inputOffset); + srcBounds.offset(-inputOffset); + SkIRect interior = SkIRect::MakeXYWH(dstBounds.left() + fKernelOffset.fX, dstBounds.top() + fKernelOffset.fY, dstBounds.width() - fKernelSize.fWidth + 1, @@ -378,11 +400,15 @@ sk_sp<SkSpecialImage> SkMatrixConvolutionImageFilter::onFilterImage(SkSpecialIma interior.left(), interior.bottom()); SkIRect right = SkIRect::MakeLTRB(interior.right(), interior.top(), dstBounds.right(), interior.bottom()); - this->filterBorderPixels(inputBM, &dst, top, dstBounds); - this->filterBorderPixels(inputBM, &dst, left, dstBounds); - this->filterInteriorPixels(inputBM, &dst, interior, dstBounds); - this->filterBorderPixels(inputBM, &dst, right, dstBounds); - this->filterBorderPixels(inputBM, &dst, bottom, dstBounds); + + SkIVector dstContentOffset = { offset->fX - inputOffset.fX, offset->fY - inputOffset.fY }; + + this->filterBorderPixels(inputBM, &dst, dstContentOffset, top, srcBounds); + this->filterBorderPixels(inputBM, &dst, dstContentOffset, left, srcBounds); + this->filterInteriorPixels(inputBM, &dst, dstContentOffset, interior, srcBounds); + this->filterBorderPixels(inputBM, &dst, dstContentOffset, right, srcBounds); + this->filterBorderPixels(inputBM, &dst, dstContentOffset, bottom, srcBounds); + return SkSpecialImage::MakeFromRaster(SkIRect::MakeWH(dstBounds.width(), dstBounds.height()), dst); } @@ -401,12 +427,18 @@ const { } SkIRect SkMatrixConvolutionImageFilter::onFilterNodeBounds(const SkIRect& src, const SkMatrix& ctm, - MapDirection direction) const { + MapDirection dir, + const SkIRect* inputRect) const { + if (kReverse_MapDirection == dir && kRepeat_TileMode == fTileMode && inputRect) { + SkASSERT(inputRect); + return DetermineRepeatedSrcBound(src, fKernelOffset, fKernelSize, *inputRect); + } + SkIRect dst = src; int w = fKernelSize.width() - 1, h = fKernelSize.height() - 1; dst.fRight = Sk32_sat_add(dst.fRight, w); dst.fBottom = Sk32_sat_add(dst.fBottom, h); - if (kReverse_MapDirection == direction) { + if (kReverse_MapDirection == dir) { dst.offset(-fKernelOffset); } else { dst.offset(fKernelOffset - SkIPoint::Make(w, h)); @@ -415,9 +447,14 @@ SkIRect SkMatrixConvolutionImageFilter::onFilterNodeBounds(const SkIRect& src, c } bool SkMatrixConvolutionImageFilter::affectsTransparentBlack() const { - // Because the kernel is applied in device-space, we have no idea what + // It seems that the only rational way for repeat sample mode to work is if the caller + // explicitly restricts the input in which case the input range is explicitly known and + // specified. + // TODO: is seems that this should be true for clamp mode too. + + // For the other modes, because the kernel is applied in device-space, we have no idea what // pixels it will affect in object-space. - return true; + return kRepeat_TileMode != fTileMode; } void SkMatrixConvolutionImageFilter::toString(SkString* str) const { diff --git a/src/effects/SkMorphologyImageFilter.cpp b/src/effects/SkMorphologyImageFilter.cpp index d985562c62..e421fdd28a 100644 --- a/src/effects/SkMorphologyImageFilter.cpp +++ b/src/effects/SkMorphologyImageFilter.cpp @@ -92,7 +92,7 @@ SkRect SkMorphologyImageFilter::computeFastBounds(const SkRect& src) const { } SkIRect SkMorphologyImageFilter::onFilterNodeBounds(const SkIRect& src, const SkMatrix& ctm, - MapDirection) const { + MapDirection, const SkIRect* inputRect) const { SkVector radius = SkVector::Make(SkIntToScalar(this->radius().width()), SkIntToScalar(this->radius().height())); ctm.mapVectors(&radius, 1); diff --git a/src/effects/SkOffsetImageFilter.cpp b/src/effects/SkOffsetImageFilter.cpp index 6d4d96ad7d..5b1533b371 100644 --- a/src/effects/SkOffsetImageFilter.cpp +++ b/src/effects/SkOffsetImageFilter.cpp @@ -49,8 +49,8 @@ sk_sp<SkSpecialImage> SkOffsetImageFilter::onFilterImage(SkSpecialImage* source, return input; } else { SkIRect bounds; - SkIRect srcBounds = SkIRect::MakeWH(input->width(), input->height()); - srcBounds.offset(srcOffset); + const SkIRect srcBounds = SkIRect::MakeXYWH(srcOffset.fX, srcOffset.fY, + input->width(), input->height()); if (!this->applyCropRect(ctx, srcBounds, &bounds)) { return nullptr; } @@ -97,9 +97,9 @@ SkRect SkOffsetImageFilter::computeFastBounds(const SkRect& src) const { } SkIRect SkOffsetImageFilter::onFilterNodeBounds(const SkIRect& src, const SkMatrix& ctm, - MapDirection direction) const { + MapDirection dir, const SkIRect* inputRect) const { SkIPoint vec = map_offset_vector(ctm, fOffset); - if (kReverse_MapDirection == direction) { + if (kReverse_MapDirection == dir) { SkPointPriv::Negate(vec); } diff --git a/src/effects/SkTileImageFilter.cpp b/src/effects/SkTileImageFilter.cpp index fdd66c4da0..1aac5814c5 100644 --- a/src/effects/SkTileImageFilter.cpp +++ b/src/effects/SkTileImageFilter.cpp @@ -128,13 +128,14 @@ sk_sp<SkImageFilter> SkTileImageFilter::onMakeColorSpace(SkColorSpaceXformer* xf } SkIRect SkTileImageFilter::onFilterNodeBounds(const SkIRect& src, const SkMatrix& ctm, - MapDirection direction) const { - SkRect rect = kReverse_MapDirection == direction ? fSrcRect : fDstRect; + MapDirection dir, const SkIRect* inputRect) const { + SkRect rect = kReverse_MapDirection == dir ? fSrcRect : fDstRect; ctm.mapRect(&rect); return rect.roundOut(); } -SkIRect SkTileImageFilter::onFilterBounds(const SkIRect& src, const SkMatrix&, MapDirection) const { +SkIRect SkTileImageFilter::onFilterBounds(const SkIRect& src, const SkMatrix&, + MapDirection, const SkIRect* inputRect) const { // Don't recurse into inputs. return src; } diff --git a/src/effects/SkXfermodeImageFilter.cpp b/src/effects/SkXfermodeImageFilter.cpp index 587784a391..9123818c84 100644 --- a/src/effects/SkXfermodeImageFilter.cpp +++ b/src/effects/SkXfermodeImageFilter.cpp @@ -42,7 +42,8 @@ protected: SkIPoint* offset) const override; sk_sp<SkImageFilter> onMakeColorSpace(SkColorSpaceXformer*) const override; - SkIRect onFilterBounds(const SkIRect&, const SkMatrix&, MapDirection) const override; + SkIRect onFilterBounds(const SkIRect&, const SkMatrix& ctm, + MapDirection, const SkIRect* inputRect) const override; #if SK_SUPPORT_GPU sk_sp<SkSpecialImage> filterImageGPU(SkSpecialImage* source, @@ -179,17 +180,19 @@ sk_sp<SkSpecialImage> SkXfermodeImageFilter_Base::onFilterImage(SkSpecialImage* SkIRect SkXfermodeImageFilter_Base::onFilterBounds(const SkIRect& src, const SkMatrix& ctm, - MapDirection direction) const { - if (kReverse_MapDirection == direction) { - return SkImageFilter::onFilterBounds(src, ctm, direction); + MapDirection dir, + const SkIRect* inputRect) const { + if (kReverse_MapDirection == dir) { + return SkImageFilter::onFilterBounds(src, ctm, dir, inputRect); } + SkASSERT(!inputRect); SkASSERT(2 == this->countInputs()); auto getBackground = [&]() { - return this->getInput(0) ? this->getInput(0)->filterBounds(src, ctm, direction) : src; + return this->getInput(0) ? this->getInput(0)->filterBounds(src, ctm, dir, inputRect) : src; }; auto getForeground = [&]() { - return this->getInput(1) ? this->getInput(1)->filterBounds(src, ctm, direction) : src; + return this->getInput(1) ? this->getInput(1)->filterBounds(src, ctm, dir, inputRect) : src; }; switch (fMode) { case SkBlendMode::kClear: diff --git a/tests/CanvasTest.cpp b/tests/CanvasTest.cpp index d61d195982..aee1d405be 100644 --- a/tests/CanvasTest.cpp +++ b/tests/CanvasTest.cpp @@ -846,7 +846,8 @@ protected: return nullptr; } sk_sp<SkImageFilter> onMakeColorSpace(SkColorSpaceXformer*) const override { return nullptr; } - SkIRect onFilterNodeBounds(const SkIRect&, const SkMatrix&, MapDirection) const override { + SkIRect onFilterNodeBounds(const SkIRect&, const SkMatrix&, + MapDirection, const SkIRect* inputRect) const override { return SkIRect::MakeEmpty(); } diff --git a/tests/ImageFilterTest.cpp b/tests/ImageFilterTest.cpp index 556c3f040d..8e46715fbb 100644 --- a/tests/ImageFilterTest.cpp +++ b/tests/ImageFilterTest.cpp @@ -150,7 +150,6 @@ public: FilterList(sk_sp<SkImageFilter> input, const SkImageFilter::CropRect* cropRect = nullptr) { SkPoint3 location = SkPoint3::Make(0, 0, SK_Scalar1); const SkScalar five = SkIntToScalar(5); - { sk_sp<SkColorFilter> cf(SkColorFilter::MakeModeFilter(SK_ColorRED, SkBlendMode::kSrcIn)); @@ -158,7 +157,6 @@ public: this->addFilter("color filter", SkColorFilterImageFilter::Make(std::move(cf), input, cropRect)); } - { sk_sp<SkImage> gradientImage(SkImage::MakeFromBitmap(make_gradient_circle(64, 64))); sk_sp<SkImageFilter> gradientSource(SkImageSource::Make(std::move(gradientImage))); @@ -169,7 +167,6 @@ public: 20.0f, std::move(gradientSource), input, cropRect)); } - this->addFilter("blur", SkBlurImageFilter::Make(SK_Scalar1, SK_Scalar1, input, @@ -193,13 +190,14 @@ public: const SkISize kernelSize = SkISize::Make(3, 3); const SkScalar gain = SK_Scalar1, bias = 0; + // This filter needs a saveLayer bc it is in repeat mode this->addFilter("matrix convolution", - SkMatrixConvolutionImageFilter::Make( - kernelSize, kernel, gain, bias, SkIPoint::Make(1, 1), - SkMatrixConvolutionImageFilter::kRepeat_TileMode, false, - input, cropRect)); + SkMatrixConvolutionImageFilter::Make( + kernelSize, kernel, gain, bias, SkIPoint::Make(1, 1), + SkMatrixConvolutionImageFilter::kRepeat_TileMode, false, + input, cropRect), + true); } - this->addFilter("merge", SkMergeImageFilter::Make(input, input, cropRect)); { @@ -273,18 +271,21 @@ public: int count() const { return fFilters.count(); } SkImageFilter* getFilter(int index) const { return fFilters[index].fFilter.get(); } const char* getName(int index) const { return fFilters[index].fName; } + bool needsSaveLayer(int index) const { return fFilters[index].fNeedsSaveLayer; } private: struct Filter { - Filter() : fName(nullptr) {} - Filter(const char* name, sk_sp<SkImageFilter> filter) + Filter() : fName(nullptr), fNeedsSaveLayer(false) {} + Filter(const char* name, sk_sp<SkImageFilter> filter, bool needsSaveLayer) : fName(name) - , fFilter(std::move(filter)) { + , fFilter(std::move(filter)) + , fNeedsSaveLayer(needsSaveLayer) { } const char* fName; sk_sp<SkImageFilter> fFilter; + bool fNeedsSaveLayer; }; - void addFilter(const char* name, sk_sp<SkImageFilter> filter) { - fFilters.push_back(Filter(name, std::move(filter))); + void addFilter(const char* name, sk_sp<SkImageFilter> filter, bool needsSaveLayer = false) { + fFilters.push_back(Filter(name, std::move(filter), needsSaveLayer)); } SkTArray<Filter> fFilters; @@ -305,7 +306,8 @@ private: } sk_sp<SkImageFilter> onMakeColorSpace(SkColorSpaceXformer*) const override { return nullptr; } - SkIRect onFilterBounds(const SkIRect&, const SkMatrix&, MapDirection) const override { + SkIRect onFilterBounds(const SkIRect&, const SkMatrix&, + MapDirection, const SkIRect*) const override { return fBounds; } @@ -743,34 +745,51 @@ DEF_TEST(ImageFilterDrawTiled, reporter) { tiledResult.allocN32Pixels(width, height); SkCanvas tiledCanvas(tiledResult); SkCanvas untiledCanvas(untiledResult); - int tileSize = 8; + const int tileSize = 8; + + SkPaint textPaint; + textPaint.setTextSize(SkIntToScalar(height)); + textPaint.setColor(SK_ColorWHITE); + + const char* text = "ABC"; + const SkScalar yPos = SkIntToScalar(height); for (int scale = 1; scale <= 2; ++scale) { for (int i = 0; i < filters.count(); ++i) { - tiledCanvas.clear(0); - untiledCanvas.clear(0); - SkPaint paint; - paint.setImageFilter(sk_ref_sp(filters.getFilter(i))); - paint.setTextSize(SkIntToScalar(height)); - paint.setColor(SK_ColorWHITE); - SkString str; - const char* text = "ABC"; - SkScalar ypos = SkIntToScalar(height); + SkPaint combinedPaint; + combinedPaint.setTextSize(SkIntToScalar(height)); + combinedPaint.setColor(SK_ColorWHITE); + combinedPaint.setImageFilter(sk_ref_sp(filters.getFilter(i))); + + untiledCanvas.clear(SK_ColorTRANSPARENT); untiledCanvas.save(); untiledCanvas.scale(SkIntToScalar(scale), SkIntToScalar(scale)); - untiledCanvas.drawString(text, 0, ypos, paint); + untiledCanvas.drawString(text, 0, yPos, combinedPaint); untiledCanvas.restore(); + untiledCanvas.flush(); + + tiledCanvas.clear(SK_ColorTRANSPARENT); for (int y = 0; y < height; y += tileSize) { for (int x = 0; x < width; x += tileSize) { tiledCanvas.save(); - tiledCanvas.clipRect(SkRect::Make(SkIRect::MakeXYWH(x, y, tileSize, tileSize))); - tiledCanvas.scale(SkIntToScalar(scale), SkIntToScalar(scale)); - tiledCanvas.drawString(text, 0, ypos, paint); + const SkRect clipRect = SkRect::MakeXYWH(x, y, tileSize, tileSize); + tiledCanvas.clipRect(clipRect); + if (filters.needsSaveLayer(i)) { + const SkRect layerBounds = SkRect::MakeWH(width, height); + tiledCanvas.saveLayer(&layerBounds, &combinedPaint); + tiledCanvas.scale(SkIntToScalar(scale), SkIntToScalar(scale)); + tiledCanvas.drawString(text, 0, yPos, textPaint); + tiledCanvas.restore(); + } else { + tiledCanvas.scale(SkIntToScalar(scale), SkIntToScalar(scale)); + tiledCanvas.drawString(text, 0, yPos, combinedPaint); + } + tiledCanvas.restore(); } } - untiledCanvas.flush(); tiledCanvas.flush(); + if (!sk_tool_utils::equal_pixels(untiledResult, tiledResult, 1)) { REPORTER_ASSERT(reporter, false, filters.getName(i)); break; @@ -850,7 +869,8 @@ DEF_TEST(ImageFilterBlurThenShadowBounds, reporter) { SkIRect bounds = SkIRect::MakeXYWH(0, 0, 100, 100); SkIRect expectedBounds = SkIRect::MakeXYWH(-133, -133, 236, 236); - bounds = filter2->filterBounds(bounds, SkMatrix::I(), SkImageFilter::kReverse_MapDirection); + bounds = filter2->filterBounds(bounds, SkMatrix::I(), + SkImageFilter::kReverse_MapDirection, &bounds); REPORTER_ASSERT(reporter, bounds == expectedBounds); } @@ -861,7 +881,8 @@ DEF_TEST(ImageFilterShadowThenBlurBounds, reporter) { SkIRect bounds = SkIRect::MakeXYWH(0, 0, 100, 100); SkIRect expectedBounds = SkIRect::MakeXYWH(-133, -133, 236, 236); - bounds = filter2->filterBounds(bounds, SkMatrix::I(), SkImageFilter::kReverse_MapDirection); + bounds = filter2->filterBounds(bounds, SkMatrix::I(), + SkImageFilter::kReverse_MapDirection, &bounds); REPORTER_ASSERT(reporter, bounds == expectedBounds); } @@ -872,7 +893,8 @@ DEF_TEST(ImageFilterDilateThenBlurBounds, reporter) { SkIRect bounds = SkIRect::MakeXYWH(0, 0, 100, 100); SkIRect expectedBounds = SkIRect::MakeXYWH(-132, -132, 234, 234); - bounds = filter2->filterBounds(bounds, SkMatrix::I(), SkImageFilter::kReverse_MapDirection); + bounds = filter2->filterBounds(bounds, SkMatrix::I(), + SkImageFilter::kReverse_MapDirection, &bounds); REPORTER_ASSERT(reporter, bounds == expectedBounds); } @@ -891,20 +913,20 @@ DEF_TEST(ImageFilterScaledBlurRadius, reporter) { SkIRect expectedBlurBounds = SkIRect::MakeLTRB(-6, -6, 206, 206); SkIRect blurBounds = blur->filterBounds( - bounds, scaleMatrix, SkImageFilter::kForward_MapDirection); + bounds, scaleMatrix, SkImageFilter::kForward_MapDirection, nullptr); REPORTER_ASSERT(reporter, blurBounds == expectedBlurBounds); SkIRect reverseBlurBounds = blur->filterBounds( - bounds, scaleMatrix, SkImageFilter::kReverse_MapDirection); + bounds, scaleMatrix, SkImageFilter::kReverse_MapDirection, &bounds); REPORTER_ASSERT(reporter, reverseBlurBounds == expectedBlurBounds); SkIRect expectedShadowBounds = SkIRect::MakeLTRB(0, 0, 460, 460); SkIRect shadowBounds = dropShadow->filterBounds( - bounds, scaleMatrix, SkImageFilter::kForward_MapDirection); + bounds, scaleMatrix, SkImageFilter::kForward_MapDirection, nullptr); REPORTER_ASSERT(reporter, shadowBounds == expectedShadowBounds); SkIRect expectedReverseShadowBounds = SkIRect::MakeLTRB(-260, -260, 200, 200); SkIRect reverseShadowBounds = dropShadow->filterBounds( - bounds, scaleMatrix, SkImageFilter::kReverse_MapDirection); + bounds, scaleMatrix, SkImageFilter::kReverse_MapDirection, &bounds); REPORTER_ASSERT(reporter, reverseShadowBounds == expectedReverseShadowBounds); } @@ -916,20 +938,20 @@ DEF_TEST(ImageFilterScaledBlurRadius, reporter) { SkIRect expectedBlurBounds = SkIRect::MakeLTRB(-3, -103, 103, 3); SkIRect blurBounds = blur->filterBounds( - bounds, scaleMatrix, SkImageFilter::kForward_MapDirection); + bounds, scaleMatrix, SkImageFilter::kForward_MapDirection, nullptr); REPORTER_ASSERT(reporter, blurBounds == expectedBlurBounds); SkIRect reverseBlurBounds = blur->filterBounds( - bounds, scaleMatrix, SkImageFilter::kReverse_MapDirection); + bounds, scaleMatrix, SkImageFilter::kReverse_MapDirection, &bounds); REPORTER_ASSERT(reporter, reverseBlurBounds == expectedBlurBounds); SkIRect expectedShadowBounds = SkIRect::MakeLTRB(0, -230, 230, 0); SkIRect shadowBounds = dropShadow->filterBounds( - bounds, scaleMatrix, SkImageFilter::kForward_MapDirection); + bounds, scaleMatrix, SkImageFilter::kForward_MapDirection, nullptr); REPORTER_ASSERT(reporter, shadowBounds == expectedShadowBounds); SkIRect expectedReverseShadowBounds = SkIRect::MakeLTRB(-130, -100, 100, 130); SkIRect reverseShadowBounds = dropShadow->filterBounds( - bounds, scaleMatrix, SkImageFilter::kReverse_MapDirection); + bounds, scaleMatrix, SkImageFilter::kReverse_MapDirection, &bounds); REPORTER_ASSERT(reporter, reverseShadowBounds == expectedReverseShadowBounds); } @@ -1919,8 +1941,8 @@ DEF_TEST(XfermodeImageFilterBounds, reporter) { for (int i = 0; i < kModeCount; ++i) { sk_sp<SkImageFilter> xfermode(SkXfermodeImageFilter::Make(static_cast<SkBlendMode>(i), background, foreground, nullptr)); - auto bounds = - xfermode->filterBounds(src, SkMatrix::I(), SkImageFilter::kForward_MapDirection); + auto bounds = xfermode->filterBounds(src, SkMatrix::I(), + SkImageFilter::kForward_MapDirection, nullptr); REPORTER_ASSERT(reporter, bounds == expectedBounds[i]); } @@ -1929,7 +1951,8 @@ DEF_TEST(XfermodeImageFilterBounds, reporter) { sk_sp<SkImageFilter> foreground2(new FixedBoundsImageFilter(SkIRect::MakeXYWH(40, 40, 50, 50))); sk_sp<SkImageFilter> xfermode(SkXfermodeImageFilter::Make( SkBlendMode::kSrcIn, std::move(background2), std::move(foreground2), nullptr)); - auto bounds = xfermode->filterBounds(src, SkMatrix::I(), SkImageFilter::kForward_MapDirection); + auto bounds = xfermode->filterBounds(src, SkMatrix::I(), + SkImageFilter::kForward_MapDirection, nullptr); REPORTER_ASSERT(reporter, bounds.isEmpty()); } @@ -1939,12 +1962,12 @@ DEF_TEST(OffsetImageFilterBounds, reporter) { SkIRect expectedForward = SkIRect::MakeXYWH(-50, -50, 100, 100); SkIRect boundsForward = offset->filterBounds(src, SkMatrix::I(), - SkImageFilter::kForward_MapDirection); + SkImageFilter::kForward_MapDirection, nullptr); REPORTER_ASSERT(reporter, boundsForward == expectedForward); SkIRect expectedReverse = SkIRect::MakeXYWH(50, 50, 100, 100); SkIRect boundsReverse = offset->filterBounds(src, SkMatrix::I(), - SkImageFilter::kReverse_MapDirection); + SkImageFilter::kReverse_MapDirection, &src); REPORTER_ASSERT(reporter, boundsReverse == expectedReverse); } @@ -1956,7 +1979,7 @@ static void test_arithmetic_bounds(skiatest::Reporter* reporter, float k1, float SkArithmeticImageFilter::Make(k1, k2, k3, k4, false, background, foreground, crop)); // The value of the input rect doesn't matter because we use inputs with fixed bounds. SkIRect bounds = arithmetic->filterBounds(SkIRect::MakeXYWH(11, 22, 33, 44), SkMatrix::I(), - SkImageFilter::kForward_MapDirection); + SkImageFilter::kForward_MapDirection, nullptr); REPORTER_ASSERT(reporter, expected == bounds); } @@ -2027,18 +2050,20 @@ DEF_TEST(ImageSourceBounds, reporter) { SkIRect input(SkIRect::MakeXYWH(10, 20, 30, 40)); REPORTER_ASSERT(reporter, imageBounds == source1->filterBounds(input, SkMatrix::I(), - SkImageFilter::kForward_MapDirection)); + SkImageFilter::kForward_MapDirection, + nullptr)); REPORTER_ASSERT(reporter, input == source1->filterBounds(input, SkMatrix::I(), - SkImageFilter::kReverse_MapDirection)); + SkImageFilter::kReverse_MapDirection, &input)); SkMatrix scale(SkMatrix::MakeScale(2)); SkIRect scaledBounds = SkIRect::MakeWH(128, 128); REPORTER_ASSERT(reporter, scaledBounds == source1->filterBounds(input, scale, - SkImageFilter::kForward_MapDirection)); - REPORTER_ASSERT( - reporter, - input == source1->filterBounds(input, scale, SkImageFilter::kReverse_MapDirection)); + SkImageFilter::kForward_MapDirection, + nullptr)); + REPORTER_ASSERT(reporter, input == source1->filterBounds(input, scale, + SkImageFilter::kReverse_MapDirection, + &input)); // Specified src and dst rects. SkRect src(SkRect::MakeXYWH(0.5, 0.5, 100.5, 100.5)); @@ -2046,16 +2071,19 @@ DEF_TEST(ImageSourceBounds, reporter) { sk_sp<SkImageFilter> source2(SkImageSource::Make(image, src, dst, kMedium_SkFilterQuality)); REPORTER_ASSERT(reporter, dst.roundOut() == source2->filterBounds(input, SkMatrix::I(), - SkImageFilter::kForward_MapDirection)); + SkImageFilter::kForward_MapDirection, + nullptr)); REPORTER_ASSERT(reporter, input == source2->filterBounds(input, SkMatrix::I(), - SkImageFilter::kReverse_MapDirection)); + SkImageFilter::kReverse_MapDirection, &input)); scale.mapRect(&dst); scale.mapRect(&src); REPORTER_ASSERT(reporter, dst.roundOut() == source2->filterBounds(input, scale, - SkImageFilter::kForward_MapDirection)); - REPORTER_ASSERT( - reporter, - input == source2->filterBounds(input, scale, SkImageFilter::kReverse_MapDirection)); + SkImageFilter::kForward_MapDirection, + nullptr)); + REPORTER_ASSERT(reporter, input == source2->filterBounds(input, scale, + SkImageFilter::kReverse_MapDirection, + &input)); } + |