Fix filter primitive bounds computations.

Make each filter responsible for expanding its destination
bounds. Previously, we were using a union of all
intermediate bounds sizes via join() calls in many image
filters' computeFastBounds(), due to the fact that those
filters could only produce bitmaps the same size as their
inputs. Now, we compute optimal bounds for each filter as
follows:

1) Pass the (unmodified) clip bounds to the root node
of the DAG in the first recursive call to onFilterImage()
as the Context's fClipBounds.

2) Reverse-map the clip: when recursing up the DAG in
filterInput[GPU](), apply filter-specific expansion to the
clip by calling calling onFilterNodeBounds(... kReverse).
This allows upstream nodes to have a clip that respects the
current node's requirements. This is done via helper
function mapContext().

3) Forward-map the source bitmap: just prior to applying
the crop rect in applyCropRect(), we determine the filter's
preferred bounds by mapping the source bitmap bounds
forwards via onFilterNodeBounds(..., kForward).

NOTE: GMs affected by this change:
fast_slow_blurimagefilter: fast and slow paths now produce the same result
spritebitmap: drawSprite() and drawBitmap() paths now produce the same result
filterfastbounds: fast bounds are optimized; all drop-shadow results now appear
apply-filter: snug and not-snug cases give same results
dropshadowimagefilter: drawSprite() results now show shadows
draw-with-filter: no artifacts on erode edges; blur edges no longer clipped
displacement, imagefiltersbase, imagefiltersclipped, imagefilterscropexpand, imagefiltersscaled, matriximagefilter,
resizeimagefilter, localmatriximagefilter, testimagefilters: fixed incorrect clipping
imagefilterstransformed, morphology: no artifacts on erode edges

BUG=skia:1062,skia:3194,skia:3939,skia:4337,skia:4526

Review URL: https://codereview.chromium.org/1308703007
This commit is contained in:
senorblanco 2015-12-09 10:11:43 -08:00 committed by Commit bot
parent 02046c50b2
commit db64af3b17
24 changed files with 209 additions and 102 deletions

View File

@ -346,6 +346,25 @@ protected:
// implementation recursively unions all input bounds, or returns false if
// no inputs.
virtual bool onFilterBounds(const SkIRect&, const SkMatrix&, SkIRect*) const;
enum MapDirection {
kForward_MapDirection,
kReverse_MapDirection
};
/**
* Performs a forwards or reverse mapping of the given rect to accommodate
* this filter's margin requirements. kForward_MapDirection is used to
* determine the destination pixels which would be touched by filtering
* the given given source rect (e.g., given source bitmap bounds,
* determine the optimal bounds of the filtered offscreen bitmap).
* kReverse_MapDirection is used to determine which pixels of the
* input(s) would be required to fill the given destination rect
* (e.g., clip bounds). NOTE: these operations may not be the
* inverse of the other. For example, blurring expands the given rect
* in both forward and reverse directions. Unlike
* onFilterBounds(), this function is non-recursive.
*/
virtual void onFilterNodeBounds(const SkIRect&, const SkMatrix&, SkIRect*, MapDirection) const;
// Helper function which invokes filter processing on the input at the
// specified "index". If the input is null, it leaves "result" and
@ -405,6 +424,14 @@ protected:
virtual bool asFragmentProcessor(GrFragmentProcessor**, GrTexture*, const SkMatrix&,
const SkIRect& bounds) const;
/**
* Creates a modified Context for use when recursing up the image filter DAG.
* The clip bounds are adjusted to accommodate any margins that this
* filter requires by calling this node's
* onFilterNodeBounds(..., kReverse_MapDirection).
*/
Context mapContext(const Context& ctx) const;
private:
friend class SkGraphics;
static void PurgeCache();

View File

@ -27,7 +27,8 @@ protected:
void flatten(SkWriteBuffer&) const override;
bool onFilterImage(Proxy*, const SkBitmap& src, const Context&, SkBitmap* result,
SkIPoint* offset) const override;
bool onFilterBounds(const SkIRect& src, const SkMatrix&, SkIRect* dst) const override;
void onFilterNodeBounds(const SkIRect& src, const SkMatrix&,
SkIRect* dst, MapDirection) const override;
bool canFilterImageGPU() const override { return true; }
bool filterImageGPU(Proxy* proxy, const SkBitmap& src, const Context& ctx, SkBitmap* result,
SkIPoint* offset) const override;

View File

@ -40,6 +40,7 @@ public:
virtual bool onFilterBounds(const SkIRect& src, const SkMatrix&,
SkIRect* dst) const override;
void onFilterNodeBounds(const SkIRect&, const SkMatrix&, SkIRect*, MapDirection) const override;
#if SK_SUPPORT_GPU
bool canFilterImageGPU() const override { return true; }

View File

@ -35,7 +35,8 @@ protected:
void flatten(SkWriteBuffer&) const override;
bool onFilterImage(Proxy*, const SkBitmap& source, const Context&, SkBitmap* result,
SkIPoint* loc) const override;
bool onFilterBounds(const SkIRect& src, const SkMatrix&, SkIRect* dst) const override;
void onFilterNodeBounds(const SkIRect& src, const SkMatrix&,
SkIRect* dst, MapDirection) const override;
private:
SkDropShadowImageFilter(SkScalar dx, SkScalar dy, SkScalar sigmaX, SkScalar sigmaY, SkColor,

View File

@ -79,7 +79,7 @@ protected:
bool onFilterImage(Proxy*, const SkBitmap& src, const Context&,
SkBitmap* result, SkIPoint* loc) const override;
bool onFilterBounds(const SkIRect&, const SkMatrix&, SkIRect*) const override;
void onFilterNodeBounds(const SkIRect&, const SkMatrix&, SkIRect*, MapDirection) const override;
bool canComputeFastBounds() const override;
#if SK_SUPPORT_GPU

View File

@ -16,7 +16,8 @@
class SK_API SkMorphologyImageFilter : public SkImageFilter {
public:
void computeFastBounds(const SkRect& src, SkRect* dst) const override;
bool onFilterBounds(const SkIRect& src, const SkMatrix& ctm, SkIRect* dst) const override;
void onFilterNodeBounds(const SkIRect& src, const SkMatrix& ctm,
SkIRect* dst, MapDirection) const override;
/**
* All morphology procs have the same signature: src is the source buffer, dst the

View File

@ -30,7 +30,7 @@ protected:
void flatten(SkWriteBuffer&) const override;
bool onFilterImage(Proxy*, const SkBitmap& src, const Context&, SkBitmap* result,
SkIPoint* loc) const override;
bool onFilterBounds(const SkIRect&, const SkMatrix&, SkIRect*) const override;
void onFilterNodeBounds(const SkIRect&, const SkMatrix&, SkIRect*, MapDirection) const override;
private:
SkOffsetImageFilter(SkScalar dx, SkScalar dy, SkImageFilter* input, const CropRect*);

View File

@ -25,6 +25,7 @@ public:
SkBitmap* dst, SkIPoint* offset) const override;
bool onFilterBounds(const SkIRect& src, const SkMatrix&,
SkIRect* dst) const override;
void onFilterNodeBounds(const SkIRect&, const SkMatrix&, SkIRect*, MapDirection) const override;
void computeFastBounds(const SkRect& src, SkRect* dst) const override;
SK_TO_STRING_OVERRIDE()

View File

@ -40,8 +40,6 @@
#include "GrRenderTarget.h"
#endif
#define SK_SUPPORT_SRC_BOUNDS_BLOAT_FOR_IMAGEFILTERS
/*
* Return true if the drawing this rect would hit every pixels in the canvas.
*
@ -1084,6 +1082,10 @@ bool SkCanvas::clipRectBounds(const SkRect* bounds, SaveFlags flags,
} else {
bounds = nullptr;
}
#else
if (bounds && !imageFilter->canComputeFastBounds()) {
bounds = nullptr;
}
#endif
}
SkIRect ir;
@ -1370,7 +1372,11 @@ void SkCanvas::internalDrawDevice(SkBaseDevice* srcDev, int x, int y,
const SkBitmap& src = srcDev->accessBitmap(false);
SkMatrix matrix = *iter.fMatrix;
matrix.postTranslate(SkIntToScalar(-pos.x()), SkIntToScalar(-pos.y()));
#ifdef SK_SUPPORT_SRC_BOUNDS_BLOAT_FOR_IMAGEFILTERS
SkIRect clipBounds = SkIRect::MakeWH(srcDev->width(), srcDev->height());
#else
SkIRect clipBounds = iter.fClip->getBounds().makeOffset(-pos.x(), -pos.y());
#endif
SkAutoTUnref<SkImageFilter::Cache> cache(dstDev->getImageFilterCache());
SkImageFilter::Context ctx(matrix, clipBounds, cache.get(),
SkImageFilter::kApprox_SizeConstraint);

View File

@ -411,7 +411,11 @@ void SkBaseDevice::drawBitmapAsSprite(const SkDraw& draw, const SkBitmap& bitmap
SkIPoint offset = SkIPoint::Make(0, 0);
SkMatrix matrix = *draw.fMatrix;
matrix.postTranslate(SkIntToScalar(-x), SkIntToScalar(-y));
#ifdef SK_SUPPORT_SRC_BOUNDS_BLOAT_FOR_IMAGEFILTERS
const SkIRect clipBounds = bitmap.bounds();
#else
const SkIRect clipBounds = draw.fClip->getBounds().makeOffset(-x, -y);
#endif
SkAutoTUnref<SkImageFilter::Cache> cache(this->getImageFilterCache());
SkImageFilter::Context ctx(matrix, clipBounds, cache.get(),
SkImageFilter::kApprox_SizeConstraint);

View File

@ -277,7 +277,7 @@ bool SkImageFilter::filterInput(int index, Proxy* proxy, const SkBitmap& src,
}
Context ctx(origCtx.ctm(), origCtx.clipBounds(), origCtx.cache(), constraint);
return input->filterImage(proxy, src, ctx, result, offset);
return input->filterImage(proxy, src, this->mapContext(ctx), result, offset);
}
bool SkImageFilter::filterBounds(const SkIRect& src, const SkMatrix& ctm,
@ -405,7 +405,12 @@ bool SkImageFilter::applyCropRect(const Context& ctx, const SkBitmap& src,
}
src.getBounds(srcBounds);
srcBounds->offset(srcOffset);
return fCropRect.applyTo(*srcBounds, ctx, dstBounds) && srcBounds->intersect(*dstBounds);
#ifdef SK_SUPPORT_SRC_BOUNDS_BLOAT_FOR_IMAGEFILTERS
return fCropRect.applyTo(*srcBounds, ctx, dstBounds);
#else
this->onFilterNodeBounds(*srcBounds, ctx.ctm(), dstBounds, kForward_MapDirection);
return fCropRect.applyTo(*dstBounds, ctx, dstBounds);
#endif
}
bool SkImageFilter::applyCropRect(const Context& ctx, Proxy* proxy, const SkBitmap& src,
@ -413,7 +418,13 @@ bool SkImageFilter::applyCropRect(const Context& ctx, Proxy* proxy, const SkBitm
SkIRect srcBounds;
src.getBounds(&srcBounds);
srcBounds.offset(*srcOffset);
#ifdef SK_SUPPORT_SRC_BOUNDS_BLOAT_FOR_IMAGEFILTERS
if (!fCropRect.applyTo(srcBounds, ctx, bounds)) {
#else
SkIRect dstBounds;
this->onFilterNodeBounds(srcBounds, ctx.ctm(), &dstBounds, kForward_MapDirection);
if (!fCropRect.applyTo(dstBounds, ctx, bounds)) {
#endif
return false;
}
@ -441,26 +452,44 @@ bool SkImageFilter::onFilterBounds(const SkIRect& src, const SkMatrix& ctm,
return true;
}
SkIRect bounds;
SkIRect bounds, totalBounds;
this->onFilterNodeBounds(src, ctm, &bounds, kReverse_MapDirection);
for (int i = 0; i < fInputCount; ++i) {
SkImageFilter* filter = this->getInput(i);
SkIRect rect = src;
if (filter && !filter->filterBounds(src, ctm, &rect)) {
SkIRect rect = bounds;
if (filter && !filter->filterBounds(bounds, ctm, &rect)) {
return false;
}
if (0 == i) {
bounds = rect;
totalBounds = rect;
} else {
bounds.join(rect);
totalBounds.join(rect);
}
}
// don't modify dst until now, so we don't accidentally change it in the
// loop, but then return false on the next filter.
*dst = bounds;
*dst = totalBounds;
return true;
}
void SkImageFilter::onFilterNodeBounds(const SkIRect& src, const SkMatrix&,
SkIRect* dst, MapDirection) const {
*dst = src;
}
SkImageFilter::Context SkImageFilter::mapContext(const Context& ctx) const {
#ifdef SK_SUPPORT_SRC_BOUNDS_BLOAT_FOR_IMAGEFILTERS
return ctx;
#else
SkIRect clipBounds;
this->onFilterNodeBounds(ctx.clipBounds(), ctx.ctm(), &clipBounds,
MapDirection::kReverse_MapDirection);
return Context(ctx.ctm(), clipBounds, ctx.cache(), ctx.sizeConstraint());
#endif
}
bool SkImageFilter::asFragmentProcessor(GrFragmentProcessor**, GrTexture*,
const SkMatrix&, const SkIRect&) const {
return false;
@ -506,7 +535,7 @@ bool SkImageFilter::filterInputGPU(int index, SkImageFilter::Proxy* proxy,
}
Context ctx(origCtx.ctm(), origCtx.clipBounds(), origCtx.cache(), constraint);
if (input->filterImage(proxy, src, ctx, result, offset)) {
if (input->filterImage(proxy, src, this->mapContext(ctx), result, offset)) {
if (!result->getTexture()) {
const SkImageInfo info = result->info();
if (kUnknown_SkColorType == info.colorType()) {

View File

@ -97,30 +97,33 @@ void SkMatrixImageFilter::computeFastBounds(const SkRect& src, SkRect* dst) cons
getInput(0)->computeFastBounds(src, &bounds);
}
fTransform.mapRect(dst, bounds);
#ifdef SK_SUPPORT_SRC_BOUNDS_BLOAT_FOR_IMAGEFILTERS
dst->join(bounds); // Work around for skia:3194
#endif
}
bool SkMatrixImageFilter::onFilterBounds(const SkIRect& src, const SkMatrix& ctm,
SkIRect* dst) const {
SkMatrix transformInverse;
if (!fTransform.invert(&transformInverse)) {
return false;
}
void SkMatrixImageFilter::onFilterNodeBounds(const SkIRect& src, const SkMatrix& ctm,
SkIRect* dst, MapDirection direction) const {
SkMatrix matrix;
if (!ctm.invert(&matrix)) {
return false;
*dst = src;
return;
}
if (kForward_MapDirection == direction) {
matrix.postConcat(fTransform);
} else {
SkMatrix transformInverse;
if (!fTransform.invert(&transformInverse)) {
*dst = src;
return;
}
matrix.postConcat(transformInverse);
}
matrix.postConcat(transformInverse);
matrix.postConcat(ctm);
SkRect floatBounds;
matrix.mapRect(&floatBounds, SkRect::Make(src));
SkIRect bounds = floatBounds.roundOut();
if (getInput(0) && !getInput(0)->filterBounds(bounds, ctm, &bounds)) {
return false;
}
*dst = bounds;
return true;
}
#ifndef SK_IGNORE_TO_STRING

View File

@ -46,8 +46,8 @@ protected:
virtual bool onFilterImage(Proxy*, const SkBitmap& src, const Context&,
SkBitmap* result, SkIPoint* loc) const override;
virtual bool onFilterBounds(const SkIRect& src, const SkMatrix&,
SkIRect* dst) const override;
virtual void onFilterNodeBounds(const SkIRect& src, const SkMatrix&,
SkIRect* dst, MapDirection) const override;
private:
SkMatrix fTransform;

View File

@ -221,6 +221,7 @@ private:
int controlOps; // Number of control ops in this Save block, including the Save.
Bounds bounds; // Bounds of everything in the block.
const SkPaint* paint; // Unowned. If set, adjusts the bounds of all ops in this block.
SkMatrix ctm;
};
// Only Restore, SetMatrix, and Concat change the CTM.
@ -301,6 +302,7 @@ private:
sb.bounds =
PaintMayAffectTransparentBlack(paint) ? fCurrentClipBounds : Bounds::MakeEmpty();
sb.paint = paint;
sb.ctm = this->fCTM;
fSaveStack.push(sb);
this->pushControl();
@ -563,9 +565,15 @@ private:
bool adjustForSaveLayerPaints(SkRect* rect, int savesToIgnore = 0) const {
for (int i = fSaveStack.count() - 1 - savesToIgnore; i >= 0; i--) {
SkMatrix inverse;
if (!fSaveStack[i].ctm.invert(&inverse)) {
return false;
}
inverse.mapRect(rect);
if (!AdjustForPaint(fSaveStack[i].paint, rect)) {
return false;
}
fSaveStack[i].ctm.mapRect(rect);
}
return true;
}

View File

@ -82,7 +82,10 @@ bool SkBlurImageFilter::onFilterImage(Proxy* proxy,
}
SkIRect srcBounds, dstBounds;
if (!this->applyCropRect(ctx, src, srcOffset, &dstBounds, &srcBounds)) {
if (!this->applyCropRect(this->mapContext(ctx), src, srcOffset, &dstBounds, &srcBounds)) {
return false;
}
if (!srcBounds.intersect(dstBounds)) {
return false;
}
@ -184,17 +187,12 @@ void SkBlurImageFilter::computeFastBounds(const SkRect& src, SkRect* dst) const
SkScalarMul(fSigma.height(), SkIntToScalar(3)));
}
bool SkBlurImageFilter::onFilterBounds(const SkIRect& src, const SkMatrix& ctm,
SkIRect* dst) const {
SkIRect bounds = src;
void SkBlurImageFilter::onFilterNodeBounds(const SkIRect& src, const SkMatrix& ctm,
SkIRect* dst, MapDirection) const {
*dst = src;
SkVector sigma = mapSigma(fSigma, ctm);
bounds.outset(SkScalarCeilToInt(SkScalarMul(sigma.x(), SkIntToScalar(3))),
SkScalarCeilToInt(SkScalarMul(sigma.y(), SkIntToScalar(3))));
if (this->getInput(0) && !this->getInput(0)->filterBounds(bounds, ctm, &bounds)) {
return false;
}
*dst = bounds;
return true;
dst->outset(SkScalarCeilToInt(SkScalarMul(sigma.x(), SkIntToScalar(3))),
SkScalarCeilToInt(SkScalarMul(sigma.y(), SkIntToScalar(3))));
}
bool SkBlurImageFilter::filterImageGPU(Proxy* proxy, const SkBitmap& src, const Context& ctx,
@ -206,7 +204,10 @@ bool SkBlurImageFilter::filterImageGPU(Proxy* proxy, const SkBitmap& src, const
return false;
}
SkIRect srcBounds, dstBounds;
if (!this->applyCropRect(ctx, input, srcOffset, &dstBounds, &srcBounds)) {
if (!this->applyCropRect(this->mapContext(ctx), input, srcOffset, &dstBounds, &srcBounds)) {
return false;
}
if (!srcBounds.intersect(dstBounds)) {
return false;
}
GrTexture* source = input.getTexture();

View File

@ -35,7 +35,9 @@ bool SkComposeImageFilter::onFilterImage(Proxy* proxy,
SkMatrix outerMatrix(ctx.ctm());
outerMatrix.postTranslate(SkIntToScalar(-innerOffset.x()), SkIntToScalar(-innerOffset.y()));
Context outerContext(outerMatrix, ctx.clipBounds(), ctx.cache(), ctx.sizeConstraint());
SkIRect clipBounds = ctx.clipBounds();
clipBounds.offset(-innerOffset.x(), -innerOffset.y());
Context outerContext(outerMatrix, clipBounds, ctx.cache(), ctx.sizeConstraint());
if (!this->filterInput(0, proxy, tmp, outerContext, result, &outerOffset, false)) {
return false;
}

View File

@ -271,13 +271,19 @@ void SkDisplacementMapEffect::computeFastBounds(const SkRect& src, SkRect* dst)
dst->outset(fScale * SK_ScalarHalf, fScale * SK_ScalarHalf);
}
bool SkDisplacementMapEffect::onFilterBounds(const SkIRect& src, const SkMatrix& ctm,
SkIRect* dst) const {
SkIRect bounds = src;
void SkDisplacementMapEffect::onFilterNodeBounds(const SkIRect& src, const SkMatrix& ctm,
SkIRect* dst, MapDirection) const {
*dst = src;
SkVector scale = SkVector::Make(fScale, fScale);
ctm.mapVectors(&scale, 1);
bounds.outset(SkScalarCeilToInt(scale.fX * SK_ScalarHalf),
SkScalarCeilToInt(scale.fY * SK_ScalarHalf));
dst->outset(SkScalarCeilToInt(scale.fX * SK_ScalarHalf),
SkScalarCeilToInt(scale.fY * SK_ScalarHalf));
}
bool SkDisplacementMapEffect::onFilterBounds(const SkIRect& src, const SkMatrix& ctm,
SkIRect* dst) const {
SkIRect bounds;
this->onFilterNodeBounds(src, ctm, &bounds, kReverse_MapDirection);
if (this->getColorInput()) {
return this->getColorInput()->filterBounds(bounds, ctm, dst);
}

View File

@ -116,25 +116,23 @@ void SkDropShadowImageFilter::computeFastBounds(const SkRect& src, SkRect* dst)
}
}
bool SkDropShadowImageFilter::onFilterBounds(const SkIRect& src, const SkMatrix& ctm,
SkIRect* dst) const {
SkIRect bounds = src;
void SkDropShadowImageFilter::onFilterNodeBounds(const SkIRect& src, const SkMatrix& ctm,
SkIRect* dst, MapDirection direction) const {
*dst = src;
SkVector offsetVec = SkVector::Make(fDx, fDy);
if (kReverse_MapDirection == direction) {
offsetVec.negate();
}
ctm.mapVectors(&offsetVec, 1);
bounds.offset(-SkScalarCeilToInt(offsetVec.x()),
-SkScalarCeilToInt(offsetVec.y()));
dst->offset(SkScalarCeilToInt(offsetVec.x()),
SkScalarCeilToInt(offsetVec.y()));
SkVector sigma = SkVector::Make(fSigmaX, fSigmaY);
ctm.mapVectors(&sigma, 1);
bounds.outset(SkScalarCeilToInt(SkScalarMul(sigma.x(), SkIntToScalar(3))),
SkScalarCeilToInt(SkScalarMul(sigma.y(), SkIntToScalar(3))));
dst->outset(SkScalarCeilToInt(SkScalarMul(sigma.x(), SkIntToScalar(3))),
SkScalarCeilToInt(SkScalarMul(sigma.y(), SkIntToScalar(3))));
if (fShadowMode == kDrawShadowAndForeground_ShadowMode) {
bounds.join(src);
dst->join(src);
}
if (getInput(0) && !getInput(0)->filterBounds(bounds, ctm, &bounds)) {
return false;
}
*dst = bounds;
return true;
}
#ifndef SK_IGNORE_TO_STRING

View File

@ -280,7 +280,7 @@ bool SkMatrixConvolutionImageFilter::onFilterImage(Proxy* proxy,
}
SkIRect bounds;
if (!this->applyCropRect(ctx, proxy, src, &srcOffset, &bounds, &src)) {
if (!this->applyCropRect(this->mapContext(ctx), proxy, src, &srcOffset, &bounds, &src)) {
return false;
}
@ -322,17 +322,17 @@ bool SkMatrixConvolutionImageFilter::onFilterImage(Proxy* proxy,
return true;
}
bool SkMatrixConvolutionImageFilter::onFilterBounds(const SkIRect& src, const SkMatrix& ctm,
SkIRect* dst) const {
SkIRect bounds = src;
bounds.fRight += fKernelSize.width() - 1;
bounds.fBottom += fKernelSize.height() - 1;
bounds.offset(-fKernelOffset);
if (getInput(0) && !getInput(0)->filterBounds(bounds, ctm, &bounds)) {
return false;
void SkMatrixConvolutionImageFilter::onFilterNodeBounds(const SkIRect& src, const SkMatrix& ctm,
SkIRect* dst, MapDirection direction) const {
*dst = src;
int w = fKernelSize.width() - 1, h = fKernelSize.height() - 1;
dst->fRight += w;
dst->fBottom += h;
if (kReverse_MapDirection == direction) {
dst->offset(-fKernelOffset);
} else {
dst->offset(fKernelOffset - SkIPoint::Make(w, h));
}
*dst = bounds;
return true;
}
bool SkMatrixConvolutionImageFilter::canComputeFastBounds() const {

View File

@ -70,7 +70,7 @@ bool SkMorphologyImageFilter::filterImageGeneric(SkMorphologyImageFilter::Proc p
}
SkIRect bounds;
if (!this->applyCropRect(ctx, proxy, src, &srcOffset, &bounds, &src)) {
if (!this->applyCropRect(this->mapContext(ctx), proxy, src, &srcOffset, &bounds, &src)) {
return false;
}
@ -149,18 +149,13 @@ void SkMorphologyImageFilter::computeFastBounds(const SkRect& src, SkRect* dst)
dst->outset(SkIntToScalar(fRadius.width()), SkIntToScalar(fRadius.height()));
}
bool SkMorphologyImageFilter::onFilterBounds(const SkIRect& src, const SkMatrix& ctm,
SkIRect* dst) const {
SkIRect bounds = src;
void SkMorphologyImageFilter::onFilterNodeBounds(const SkIRect& src, const SkMatrix& ctm,
SkIRect* dst, MapDirection) const {
*dst = src;
SkVector radius = SkVector::Make(SkIntToScalar(this->radius().width()),
SkIntToScalar(this->radius().height()));
ctm.mapVectors(&radius, 1);
bounds.outset(SkScalarCeilToInt(radius.x()), SkScalarCeilToInt(radius.y()));
if (this->getInput(0) && !this->getInput(0)->filterBounds(bounds, ctm, &bounds)) {
return false;
}
*dst = bounds;
return true;
dst->outset(SkScalarCeilToInt(radius.x()), SkScalarCeilToInt(radius.y()));
}
SkFlattenable* SkErodeImageFilter::CreateProc(SkReadBuffer& buffer) {
@ -637,7 +632,7 @@ bool SkMorphologyImageFilter::filterImageGPUGeneric(bool dilate,
return false;
}
SkIRect bounds;
if (!this->applyCropRect(ctx, proxy, input, &srcOffset, &bounds, &input)) {
if (!this->applyCropRect(this->mapContext(ctx), proxy, input, &srcOffset, &bounds, &input)) {
return false;
}
SkVector radius = SkVector::Make(SkIntToScalar(this->radius().width()),

View File

@ -70,24 +70,28 @@ void SkOffsetImageFilter::computeFastBounds(const SkRect& src, SkRect* dst) cons
} else {
*dst = src;
}
#ifdef SK_SUPPORT_SRC_BOUNDS_BLOAT_FOR_IMAGEFILTERS
SkRect copy = *dst;
#endif
dst->offset(fOffset.fX, fOffset.fY);
#ifdef SK_SUPPORT_SRC_BOUNDS_BLOAT_FOR_IMAGEFILTERS
dst->join(copy);
#endif
}
bool SkOffsetImageFilter::onFilterBounds(const SkIRect& src, const SkMatrix& ctm,
SkIRect* dst) const {
void SkOffsetImageFilter::onFilterNodeBounds(const SkIRect& src, const SkMatrix& ctm,
SkIRect* dst, MapDirection direction) const {
SkVector vec;
ctm.mapVectors(&vec, &fOffset, 1);
SkIRect bounds = src;
bounds.offset(-SkScalarCeilToInt(vec.fX), -SkScalarCeilToInt(vec.fY));
bounds.join(src);
if (getInput(0)) {
return getInput(0)->filterBounds(bounds, ctm, dst);
if (kReverse_MapDirection == direction) {
vec.negate();
}
*dst = bounds;
return true;
*dst = src;
dst->offset(SkScalarCeilToInt(vec.fX), SkScalarCeilToInt(vec.fY));
#ifdef SK_SUPPORT_SRC_BOUNDS_BLOAT_FOR_IMAGEFILTERS
dst->join(src);
#endif
}
SkFlattenable* SkOffsetImageFilter::CreateProc(SkReadBuffer& buffer) {

View File

@ -81,21 +81,30 @@ bool SkTileImageFilter::onFilterImage(Proxy* proxy, const SkBitmap& src,
return true;
}
void SkTileImageFilter::onFilterNodeBounds(const SkIRect& src, const SkMatrix& ctm,
SkIRect* dst, MapDirection direction) const {
SkRect rect = kReverse_MapDirection == direction ? fSrcRect : fDstRect;
ctm.mapRect(&rect);
rect.roundOut(dst);
#ifdef SK_SUPPORT_SRC_BOUNDS_BLOAT_FOR_IMAGEFILTERS
dst->join(src);
#endif
}
bool SkTileImageFilter::onFilterBounds(const SkIRect& src, const SkMatrix& ctm,
SkIRect* dst) const {
SkRect srcRect;
ctm.mapRect(&srcRect, fSrcRect);
SkIRect srcIRect;
srcRect.roundOut(&srcIRect);
srcIRect.join(src);
*dst = srcIRect;
this->onFilterNodeBounds(src, ctm, dst, kReverse_MapDirection);
return true;
}
void SkTileImageFilter::computeFastBounds(const SkRect& src, SkRect* dst) const {
#ifdef SK_SUPPORT_SRC_BOUNDS_BLOAT_FOR_IMAGEFILTERS
// This is a workaround for skia:3194.
*dst = src;
dst->join(fDstRect);
#else
*dst = fDstRect;
#endif
}
SkFlattenable* SkTileImageFilter::CreateProc(SkReadBuffer& buffer) {

View File

@ -1164,7 +1164,11 @@ void SkGpuDevice::drawSprite(const SkDraw& draw, const SkBitmap& bitmap,
SkIPoint offset = SkIPoint::Make(0, 0);
SkMatrix matrix(*draw.fMatrix);
matrix.postTranslate(SkIntToScalar(-left), SkIntToScalar(-top));
#ifdef SK_SUPPORT_SRC_BOUNDS_BLOAT_FOR_IMAGEFILTERS
SkIRect clipBounds = SkIRect::MakeWH(bitmap.width(), bitmap.height());
#else
SkIRect clipBounds = draw.fClip->getBounds().makeOffset(-left, -top);
#endif
SkAutoTUnref<SkImageFilter::Cache> cache(getImageFilterCache());
// This cache is transient, and is freed (along with all its contained
// textures) when it goes out of scope.
@ -1327,7 +1331,11 @@ void SkGpuDevice::drawDevice(const SkDraw& draw, SkBaseDevice* device,
SkIPoint offset = SkIPoint::Make(0, 0);
SkMatrix matrix(*draw.fMatrix);
matrix.postTranslate(SkIntToScalar(-x), SkIntToScalar(-y));
#ifdef SK_SUPPORT_SRC_BOUNDS_BLOAT_FOR_IMAGEFILTERS
SkIRect clipBounds = SkIRect::MakeWH(devTex->width(), devTex->height());
#else
SkIRect clipBounds = draw.fClip->getBounds().makeOffset(-x, -y);
#endif
// This cache is transient, and is freed (along with all its contained
// textures) when it goes out of scope.
SkAutoTUnref<SkImageFilter::Cache> cache(getImageFilterCache());

View File

@ -1152,8 +1152,10 @@ DEF_TEST(ComposedImageFilterOffset, reporter) {
SkImageFilter::CropRect cropRect(SkRect::MakeXYWH(1, 0, 20, 20));
SkAutoTUnref<SkImageFilter> offsetFilter(SkOffsetImageFilter::Create(0, 0, nullptr, &cropRect));
SkAutoTUnref<SkImageFilter> blurFilter(makeBlur());
SkAutoTUnref<SkImageFilter> composedFilter(SkComposeImageFilter::Create(blurFilter, offsetFilter.get()));
SkAutoTUnref<SkImageFilter> blurFilter(SkBlurImageFilter::Create(SK_Scalar1, SK_Scalar1,
nullptr, &cropRect));
SkAutoTUnref<SkImageFilter> composedFilter(SkComposeImageFilter::Create(blurFilter,
offsetFilter.get()));
SkBitmap result;
SkIPoint offset;
SkImageFilter::Context ctx(SkMatrix::I(), SkIRect::MakeWH(100, 100), nullptr, SkImageFilter::kApprox_SizeConstraint);