Implement correct clipping for image filters.

Image filters in Skia currently clip the size of the the offscreen
bitmap used for filtering to the device clip bounds. This means that
any pixel-moving filter (e.g., blur) has edge artifacts at the clip
boundaries. This is problematic for tiling, where a single SkPicture
is played back with a clip set to the tile boundaries.

By implementing the onFilterBounds() traversal, and using it in
saveLayer() when a filter is present, we can clip the layer to the
expanded clip rect. Note that this requires that the traversal be
performed in reverse as compared to computeFastBounds().  (It's also
done in device space, unlike computeFastBounds()).

New test imagefiltersclipped tests pixel-moving filters when clipped
by various clip rects.
New test imageblurtiled tests tiled (compositor-style) rendering of
blurred text. There should be no artifacts at the tile boundaries.

BUG=337831
R=reed@google.com

Review URL: https://codereview.chromium.org/23011012

git-svn-id: http://skia.googlecode.com/svn/trunk@13323 2bbb7eff-a529-9590-31e7-b0007b416f81
This commit is contained in:
senorblanco@chromium.org 2014-02-05 17:51:22 +00:00
parent 495157b991
commit c4b12f19a4
25 changed files with 369 additions and 51 deletions

View File

@ -40,3 +40,17 @@ filltypespersp
# deprecated calling pattern.
# https://codereview.chromium.org/154163002/
extractbitmap
# Added by senorblanco as part of https://codereview.chromium.org/23011012/
colorfilterimagefilter
dropshadowimagefilter
imageblur
imageblur_large
imagefiltersbase
imagefilterscropped
imagefiltersgraph
imagefiltersscaled
morphology
offsetimagefilter
spritebitmap
xfermodeimagefilter

76
gm/imageblurtiled.cpp Normal file
View File

@ -0,0 +1,76 @@
/*
* Copyright 2014 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "gm.h"
#include "SkBlurImageFilter.h"
#include "SkRandom.h"
#define WIDTH 640
#define HEIGHT 480
namespace skiagm {
class ImageBlurTiledGM : public GM {
public:
ImageBlurTiledGM(SkScalar sigmaX, SkScalar sigmaY)
: fSigmaX(sigmaX), fSigmaY(sigmaY) {
}
protected:
virtual SkString onShortName() {
return SkString("imageblurtiled");
}
virtual SkISize onISize() {
return make_isize(WIDTH, HEIGHT);
}
virtual void onDraw(SkCanvas* canvas) {
SkPaint paint;
paint.setImageFilter(new SkBlurImageFilter(fSigmaX, fSigmaY))->unref();
const SkScalar tile_size = SkIntToScalar(128);
SkRect bounds;
canvas->getClipBounds(&bounds);
for (SkScalar y = bounds.top(); y < bounds.bottom(); y += tile_size) {
for (SkScalar x = bounds.left(); x < bounds.right(); x += tile_size) {
canvas->save();
canvas->clipRect(SkRect::MakeXYWH(x, y, tile_size, tile_size));
canvas->saveLayer(NULL, &paint);
const char* str[] = {
"The quick",
"brown fox",
"jumped over",
"the lazy dog.",
};
SkPaint textPaint;
textPaint.setAntiAlias(true);
textPaint.setTextSize(SkIntToScalar(100));
int posY = 0;
for (unsigned i = 0; i < SK_ARRAY_COUNT(str); i++) {
posY += 100;
canvas->drawText(str[i], strlen(str[i]), SkIntToScalar(0),
SkIntToScalar(posY), textPaint);
}
canvas->restore();
canvas->restore();
}
}
}
private:
SkScalar fSigmaX;
SkScalar fSigmaY;
typedef GM INHERITED;
};
//////////////////////////////////////////////////////////////////////////////
static GM* MyFactory1(void*) { return new ImageBlurTiledGM(3.0f, 3.0f); }
static GMRegistry reg1(MyFactory1);
}

143
gm/imagefiltersclipped.cpp Normal file
View File

@ -0,0 +1,143 @@
/*
* Copyright 2014 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "gm.h"
#include "SkColor.h"
#include "SkBitmapSource.h"
#include "SkBlurImageFilter.h"
#include "SkDisplacementMapEffect.h"
#include "SkDropShadowImageFilter.h"
#include "SkGradientShader.h"
#include "SkMorphologyImageFilter.h"
#include "SkOffsetImageFilter.h"
#include "SkScalar.h"
namespace skiagm {
class ImageFiltersClippedGM : public GM {
public:
ImageFiltersClippedGM() : fInitialized(false) {
this->setBGColor(0x00000000);
}
protected:
virtual SkString onShortName() {
return SkString("imagefiltersclipped");
}
virtual SkISize onISize() {
return make_isize(860, 500);
}
void make_checkerboard() {
fCheckerboard.allocN32Pixels(64, 64);
SkBitmapDevice device(fCheckerboard);
SkCanvas canvas(&device);
canvas.clear(0x00000000);
SkPaint darkPaint;
darkPaint.setColor(0xFF404040);
SkPaint lightPaint;
lightPaint.setColor(0xFFA0A0A0);
for (int y = 0; y < 64; y += 16) {
for (int x = 0; x < 64; x += 16) {
canvas.save();
canvas.translate(SkIntToScalar(x), SkIntToScalar(y));
canvas.drawRect(SkRect::MakeXYWH(0, 0, 8, 8), darkPaint);
canvas.drawRect(SkRect::MakeXYWH(8, 0, 8, 8), lightPaint);
canvas.drawRect(SkRect::MakeXYWH(0, 8, 8, 8), lightPaint);
canvas.drawRect(SkRect::MakeXYWH(8, 8, 8, 8), darkPaint);
canvas.restore();
}
}
}
void make_gradient_circle(int width, int height) {
SkScalar x = SkIntToScalar(width / 2);
SkScalar y = SkIntToScalar(height / 2);
SkScalar radius = SkScalarMul(SkMinScalar(x, y), SkIntToScalar(4) / SkIntToScalar(5));
fGradientCircle.allocN32Pixels(width, height);
SkBitmapDevice device(fGradientCircle);
SkCanvas canvas(&device);
canvas.clear(0x00000000);
SkColor colors[2];
colors[0] = SK_ColorWHITE;
colors[1] = SK_ColorBLACK;
SkAutoTUnref<SkShader> shader(
SkGradientShader::CreateRadial(SkPoint::Make(x, y), radius, colors, NULL, 2,
SkShader::kClamp_TileMode)
);
SkPaint paint;
paint.setShader(shader);
canvas.drawCircle(x, y, radius, paint);
}
virtual void onDraw(SkCanvas* canvas) {
if (!fInitialized) {
this->make_checkerboard();
this->make_gradient_circle(64, 64);
fInitialized = true;
}
canvas->clear(0x00000000);
SkAutoTUnref<SkImageFilter> gradient(new SkBitmapSource(fGradientCircle));
SkAutoTUnref<SkImageFilter> checkerboard(new SkBitmapSource(fCheckerboard));
SkImageFilter* filters[] = {
new SkBlurImageFilter(SkIntToScalar(12), SkIntToScalar(12)),
new SkDropShadowImageFilter(SkIntToScalar(10), SkIntToScalar(10), SkIntToScalar(3),
SK_ColorGREEN),
new SkDisplacementMapEffect(SkDisplacementMapEffect::kR_ChannelSelectorType,
SkDisplacementMapEffect::kR_ChannelSelectorType,
SkIntToScalar(12),
gradient.get(),
checkerboard.get()),
new SkDilateImageFilter(2, 2, checkerboard.get()),
new SkErodeImageFilter(2, 2, checkerboard.get()),
new SkOffsetImageFilter(SkIntToScalar(-16), SkIntToScalar(32)),
};
SkRect r = SkRect::MakeWH(SkIntToScalar(64), SkIntToScalar(64));
SkScalar margin = SkIntToScalar(16);
SkRect bounds = r;
bounds.outset(margin, margin);
for (int xOffset = 0; xOffset < 80; xOffset += 16) {
canvas->save();
bounds.fLeft = SkIntToScalar(xOffset);
for (size_t i = 0; i < SK_ARRAY_COUNT(filters); ++i) {
SkPaint paint;
paint.setColor(SK_ColorWHITE);
paint.setImageFilter(filters[i]);
paint.setAntiAlias(true);
canvas->save();
canvas->clipRect(bounds);
if (i == 5) {
canvas->translate(SkIntToScalar(16), SkIntToScalar(-32));
}
canvas->drawCircle(r.centerX(), r.centerY(),
SkScalarDiv(r.width()*2, SkIntToScalar(5)), paint);
canvas->restore();
canvas->translate(r.width() + margin, 0);
}
canvas->restore();
canvas->translate(0, r.height() + margin);
}
}
private:
bool fInitialized;
SkBitmap fCheckerboard;
SkBitmap fGradientCircle;
typedef GM INHERITED;
};
//////////////////////////////////////////////////////////////////////////////
static GM* MyFactory(void*) { return new ImageFiltersClippedGM; }
static GMRegistry reg(MyFactory);
}

View File

@ -13,6 +13,7 @@
#include "SkDropShadowImageFilter.h"
#include "SkGradientShader.h"
#include "SkMorphologyImageFilter.h"
#include "SkOffsetImageFilter.h"
#include "SkScalar.h"
namespace skiagm {
@ -96,6 +97,7 @@ protected:
checkerboard.get()),
new SkDilateImageFilter(1, 1, checkerboard.get()),
new SkErodeImageFilter(1, 1, checkerboard.get()),
new SkOffsetImageFilter(SkIntToScalar(32), 0),
};
SkVector scales[] = {
@ -120,7 +122,9 @@ protected:
paint.setAntiAlias(true);
canvas->save();
canvas->scale(scales[j].fX, scales[j].fY);
canvas->clipRect(bounds);
if (5 == i) {
canvas->translate(SkIntToScalar(-32), 0);
}
canvas->drawCircle(r.centerX(), r.centerY(),
SkScalarDiv(r.width()*2, SkIntToScalar(5)), paint);
canvas->restore();

View File

@ -88,6 +88,7 @@
'../gm/hittestpath.cpp',
'../gm/imagealphathreshold.cpp',
'../gm/imageblur.cpp',
'../gm/imageblurtiled.cpp',
'../gm/imagemagnifier.cpp',
'../gm/inversepaths.cpp',
'../gm/lerpmode.cpp',
@ -95,6 +96,7 @@
'../gm/lumafilter.cpp',
'../gm/image.cpp',
'../gm/imagefiltersbase.cpp',
'../gm/imagefiltersclipped.cpp',
'../gm/imagefilterscropped.cpp',
'../gm/imagefiltersgraph.cpp',
'../gm/imagefiltersscaled.cpp',

View File

@ -1039,8 +1039,11 @@ protected:
// Clip rectangle bounds. Called internally by saveLayer.
// returns false if the entire rectangle is entirely clipped out
// If non-NULL, The imageFilter parameter will be used to expand the clip
// and offscreen bounds for any margin required by the filter DAG.
bool clipRectBounds(const SkRect* bounds, SaveFlags flags,
SkIRect* intersection);
SkIRect* intersection,
const SkImageFilter* imageFilter = NULL);
// Called by child classes that override clipPath and clipRRect to only
// track fast conservative clip bounds, rather than exact clips.

View File

@ -83,7 +83,7 @@ public:
* Given the src bounds of an image, this returns the bounds of the result
* image after the filter has been applied.
*/
bool filterBounds(const SkIRect& src, const SkMatrix& ctm, SkIRect* dst);
bool filterBounds(const SkIRect& src, const SkMatrix& ctm, SkIRect* dst) const;
/**
* Returns true if the filter can be processed on the GPU. This is most
@ -188,8 +188,14 @@ protected:
*/
virtual bool onFilterImage(Proxy*, const SkBitmap& src, const SkMatrix&,
SkBitmap* result, SkIPoint* offset);
// Default impl copies src into dst and returns true
virtual bool onFilterBounds(const SkIRect&, const SkMatrix&, SkIRect*);
// Given the bounds of the destination rect to be filled in device
// coordinates (first parameter), and the CTM, compute (conservatively)
// which rect of the source image would be required (third parameter).
// Used for clipping and temp-buffer allocations, so the result need not
// be exact, but should never be smaller than the real answer. The default
// implementation recursively unions all input bounds, or returns false if
// no inputs.
virtual bool onFilterBounds(const SkIRect&, const SkMatrix&, SkIRect*) const;
// Applies "matrix" to the crop rect, and sets "rect" to the intersection of
// "rect" and the transformed crop rect. If there is no overlap, returns

View File

@ -24,6 +24,7 @@ protected:
virtual void flatten(SkWriteBuffer&) const SK_OVERRIDE;
virtual bool onFilterImage(Proxy*, const SkBitmap& src, const SkMatrix&,
SkBitmap* result, SkIPoint* offset) SK_OVERRIDE;
virtual bool onFilterBounds(const SkIRect& src, const SkMatrix& ctm, SkIRect* dst) const SK_OVERRIDE;
private:
SkBitmap fBitmap;

View File

@ -27,6 +27,8 @@ protected:
virtual bool onFilterImage(Proxy*, const SkBitmap& src, const SkMatrix&,
SkBitmap* result, SkIPoint* offset) SK_OVERRIDE;
virtual bool onFilterBounds(const SkIRect& src, const SkMatrix&,
SkIRect* dst) const SK_OVERRIDE;
bool canFilterImageGPU() const SK_OVERRIDE { return true; }
virtual bool filterImageGPU(Proxy* proxy, const SkBitmap& src, const SkMatrix& ctm,

View File

@ -22,7 +22,7 @@ protected:
virtual bool onFilterImage(Proxy*, const SkBitmap& src, const SkMatrix&,
SkBitmap* result, SkIPoint* loc) SK_OVERRIDE;
virtual bool onFilterBounds(const SkIRect&, const SkMatrix&, SkIRect*) SK_OVERRIDE;
virtual bool onFilterBounds(const SkIRect&, const SkMatrix&, SkIRect*) const SK_OVERRIDE;
private:
typedef SkImageFilter INHERITED;

View File

@ -39,6 +39,9 @@ public:
SkIPoint* offset) SK_OVERRIDE;
virtual void computeFastBounds(const SkRect& src, SkRect* dst) const SK_OVERRIDE;
virtual bool onFilterBounds(const SkIRect& src, const SkMatrix&,
SkIRect* dst) const SK_OVERRIDE;
#if SK_SUPPORT_GPU
virtual bool canFilterImageGPU() const SK_OVERRIDE { return true; }
virtual bool filterImageGPU(Proxy* proxy, const SkBitmap& src, const SkMatrix& ctm,

View File

@ -20,6 +20,10 @@ protected:
explicit SkDropShadowImageFilter(SkReadBuffer&);
virtual void flatten(SkWriteBuffer&) const SK_OVERRIDE;
virtual bool onFilterImage(Proxy*, const SkBitmap& source, const SkMatrix&, SkBitmap* result, SkIPoint* loc) SK_OVERRIDE;
virtual bool onFilterBounds(const SkIRect& src, const SkMatrix&,
SkIRect* dst) const SK_OVERRIDE;
private:
SkScalar fDx, fDy, fSigmaX, fSigmaY;

View File

@ -30,8 +30,6 @@ protected:
virtual bool onFilterImage(Proxy*, const SkBitmap& src, const SkMatrix&,
SkBitmap* result, SkIPoint* loc) SK_OVERRIDE;
virtual bool onFilterBounds(const SkIRect&, const SkMatrix&, SkIRect*) SK_OVERRIDE;
private:
uint8_t* fModes; // SkXfermode::Mode

View File

@ -17,6 +17,7 @@ class SK_API SkMorphologyImageFilter : public SkImageFilter {
public:
SkMorphologyImageFilter(int radiusX, int radiusY, SkImageFilter* input, const CropRect* cropRect);
virtual void computeFastBounds(const SkRect& src, SkRect* dst) const SK_OVERRIDE;
virtual bool onFilterBounds(const SkIRect& src, const SkMatrix& ctm, SkIRect* dst) const SK_OVERRIDE;
/**
* All morphology procs have the same signature: src is the source buffer, dst the

View File

@ -26,7 +26,7 @@ protected:
virtual bool onFilterImage(Proxy*, const SkBitmap& src, const SkMatrix&,
SkBitmap* result, SkIPoint* loc) SK_OVERRIDE;
virtual bool onFilterBounds(const SkIRect&, const SkMatrix&, SkIRect*) SK_OVERRIDE;
virtual bool onFilterBounds(const SkIRect&, const SkMatrix&, SkIRect*) const SK_OVERRIDE;
private:
SkVector fOffset;

View File

@ -791,11 +791,18 @@ static bool bounds_affects_clip(SkCanvas::SaveFlags flags) {
}
bool SkCanvas::clipRectBounds(const SkRect* bounds, SaveFlags flags,
SkIRect* intersection) {
SkIRect* intersection, const SkImageFilter* imageFilter) {
SkIRect clipBounds;
SkRegion::Op op = SkRegion::kIntersect_Op;
if (!this->getClipDeviceBounds(&clipBounds)) {
return false;
}
if (imageFilter) {
imageFilter->filterBounds(clipBounds, *fMCRec->fMatrix, &clipBounds);
// Filters may grow the bounds beyond the device bounds.
op = SkRegion::kReplace_Op;
}
SkIRect ir;
if (NULL != bounds) {
SkRect r;
@ -813,11 +820,11 @@ bool SkCanvas::clipRectBounds(const SkRect* bounds, SaveFlags flags,
ir = clipBounds;
}
fClipStack.clipDevRect(ir, SkRegion::kIntersect_Op);
fClipStack.clipDevRect(ir, op);
// early exit if the clip is now empty
if (bounds_affects_clip(flags) &&
!fMCRec->fRasterClip->op(ir, SkRegion::kIntersect_Op)) {
!fMCRec->fRasterClip->op(ir, op)) {
return false;
}
@ -861,7 +868,7 @@ int SkCanvas::internalSaveLayer(const SkRect* bounds, const SkPaint* paint,
fDeviceCMDirty = true;
SkIRect ir;
if (!this->clipRectBounds(bounds, flags, &ir)) {
if (!this->clipRectBounds(bounds, flags, &ir, paint ? paint->getImageFilter() : NULL)) {
return count;
}

View File

@ -106,7 +106,7 @@ bool SkImageFilter::filterImage(Proxy* proxy, const SkBitmap& src,
}
bool SkImageFilter::filterBounds(const SkIRect& src, const SkMatrix& ctm,
SkIRect* dst) {
SkIRect* dst) const {
SkASSERT(&src);
SkASSERT(dst);
return this->onFilterBounds(src, ctm, dst);
@ -210,8 +210,28 @@ bool SkImageFilter::applyCropRect(SkIRect* rect, const SkMatrix& matrix) const {
}
bool SkImageFilter::onFilterBounds(const SkIRect& src, const SkMatrix& ctm,
SkIRect* dst) {
*dst = src;
SkIRect* dst) const {
if (fInputCount < 1) {
return false;
}
SkIRect bounds;
for (int i = 0; i < fInputCount; ++i) {
SkImageFilter* filter = this->getInput(i);
SkIRect rect = src;
if (filter && !filter->filterBounds(src, ctm, &rect)) {
return false;
}
if (0 == i) {
bounds = rect;
} else {
bounds.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;
return true;
}

View File

@ -83,3 +83,9 @@ bool SkBitmapSource::onFilterImage(Proxy* proxy, const SkBitmap&, const SkMatrix
void SkBitmapSource::computeFastBounds(const SkRect&, SkRect* dst) const {
*dst = fDstRect;
}
bool SkBitmapSource::onFilterBounds(const SkIRect& src, const SkMatrix& ctm,
SkIRect* dst) const {
*dst = src;
return true;
}

View File

@ -236,6 +236,21 @@ void SkBlurImageFilter::computeFastBounds(const SkRect& src, SkRect* dst) const
dst->outset(SkScalarMul(fSigma.width(), SkIntToScalar(3)),
SkScalarMul(fSigma.height(), SkIntToScalar(3)));
}
bool SkBlurImageFilter::onFilterBounds(const SkIRect& src, const SkMatrix& ctm,
SkIRect* dst) const {
SkIRect bounds = src;
if (getInput(0) && !getInput(0)->filterBounds(src, ctm, &bounds)) {
return false;
}
SkVector sigma, localSigma = SkVector::Make(fSigma.width(), fSigma.height());
ctm.mapVectors(&sigma, &localSigma, 1);
bounds.outset(SkScalarCeilToInt(SkScalarMul(sigma.x(), SkIntToScalar(3))),
SkScalarCeilToInt(SkScalarMul(sigma.y(), SkIntToScalar(3))));
*dst = bounds;
return true;
}
bool SkBlurImageFilter::filterImageGPU(Proxy* proxy, const SkBitmap& src, const SkMatrix& ctm,
SkBitmap* result, SkIPoint* offset) {
#if SK_SUPPORT_GPU

View File

@ -36,7 +36,7 @@ bool SkComposeImageFilter::onFilterImage(Proxy* proxy,
bool SkComposeImageFilter::onFilterBounds(const SkIRect& src,
const SkMatrix& ctm,
SkIRect* dst) {
SkIRect* dst) const {
SkImageFilter* outer = getInput(0);
SkImageFilter* inner = getInput(1);

View File

@ -255,6 +255,15 @@ void SkDisplacementMapEffect::computeFastBounds(const SkRect& src, SkRect* dst)
}
}
bool SkDisplacementMapEffect::onFilterBounds(const SkIRect& src, const SkMatrix& ctm,
SkIRect* dst) const {
if (getColorInput()) {
return getColorInput()->filterBounds(src, ctm, dst);
}
*dst = src;
return true;
}
///////////////////////////////////////////////////////////////////////////////
#if SK_SUPPORT_GPU

View File

@ -112,3 +112,22 @@ void SkDropShadowImageFilter::computeFastBounds(const SkRect& src, SkRect* dst)
SkScalarMul(fSigmaY, SkIntToScalar(3)));
dst->join(shadowBounds);
}
bool SkDropShadowImageFilter::onFilterBounds(const SkIRect& src, const SkMatrix& ctm,
SkIRect* dst) const {
SkIRect bounds = src;
if (getInput(0) && !getInput(0)->filterBounds(src, ctm, &bounds)) {
return false;
}
SkVector offsetVec, localOffsetVec = SkVector::Make(fDx, fDy);
ctm.mapVectors(&offsetVec, &localOffsetVec, 1);
bounds.offset(-SkScalarCeilToInt(offsetVec.x()),
-SkScalarCeilToInt(offsetVec.y()));
SkVector sigma, localSigma = SkVector::Make(fSigmaX, fSigmaY);
ctm.mapVectors(&sigma, &localSigma, 1);
bounds.outset(SkScalarCeilToInt(SkScalarMul(sigma.x(), SkIntToScalar(3))),
SkScalarCeilToInt(SkScalarMul(sigma.y(), SkIntToScalar(3))));
bounds.join(src);
*dst = bounds;
return true;
}

View File

@ -65,38 +65,6 @@ SkMergeImageFilter::~SkMergeImageFilter() {
}
}
bool SkMergeImageFilter::onFilterBounds(const SkIRect& src, const SkMatrix& ctm,
SkIRect* dst) {
if (countInputs() < 1) {
return false;
}
SkIRect totalBounds;
int inputCount = countInputs();
for (int i = 0; i < inputCount; ++i) {
SkImageFilter* filter = getInput(i);
SkIRect r;
if (filter) {
if (!filter->filterBounds(src, ctm, &r)) {
return false;
}
} else {
r = src;
}
if (0 == i) {
totalBounds = r;
} else {
totalBounds.join(r);
}
}
// 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 = totalBounds;
return true;
}
bool SkMergeImageFilter::onFilterImage(Proxy* proxy, const SkBitmap& src,
const SkMatrix& ctm,
SkBitmap* result, SkIPoint* offset) {

View File

@ -249,6 +249,20 @@ 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;
if (getInput(0) && !getInput(0)->filterBounds(src, ctm, &bounds)) {
return false;
}
SkVector radius = SkVector::Make(SkIntToScalar(this->radius().width()),
SkIntToScalar(this->radius().height()));
ctm.mapVectors(&radius, 1);
bounds.outset(SkScalarCeilToInt(radius.x()), SkScalarCeilToInt(radius.y()));
*dst = bounds;
return true;
}
#if SK_SUPPORT_GPU
///////////////////////////////////////////////////////////////////////////////

View File

@ -72,16 +72,19 @@ void SkOffsetImageFilter::computeFastBounds(const SkRect& src, SkRect* dst) cons
} else {
*dst = src;
}
SkRect copy = *dst;
dst->offset(fOffset.fX, fOffset.fY);
dst->join(copy);
}
bool SkOffsetImageFilter::onFilterBounds(const SkIRect& src, const SkMatrix& ctm,
SkIRect* dst) {
SkIRect* dst) const {
SkVector vec;
ctm.mapVectors(&vec, &fOffset, 1);
*dst = src;
dst->offset(SkScalarRoundToInt(vec.fX), SkScalarRoundToInt(vec.fY));
dst->offset(-SkScalarCeilToInt(vec.fX), -SkScalarCeilToInt(vec.fY));
dst->join(src);
return true;
}