--- /dev/null
+/*
+ * Copyright 2011 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 "SkCanvas.h"
+#include "SkColorFilter.h"
+#include "SkColorPriv.h"
+#include "SkShader.h"
+
+#include "SkBlurImageFilter.h"
+#include "SkColorFilterImageFilter.h"
+#include "SkTestImageFilters.h"
+
+///////////////////////////////////////////////////////////////////////////////
+
+static void draw_paint(SkCanvas* canvas, const SkRect& r, SkImageFilter* imf) {
+ SkPaint paint;
+ paint.setImageFilter(imf);
+ paint.setColor(SK_ColorBLACK);
+ canvas->save();
+ canvas->clipRect(r);
+ canvas->drawPaint(paint);
+ canvas->restore();
+}
+
+static void draw_path(SkCanvas* canvas, const SkRect& r, SkImageFilter* imf) {
+ SkPaint paint;
+ paint.setColor(SK_ColorMAGENTA);
+ paint.setImageFilter(imf);
+ paint.setAntiAlias(true);
+ canvas->save();
+ canvas->clipRect(r);
+ canvas->drawCircle(r.centerX(), r.centerY(), r.width()*2/5, paint);
+ canvas->restore();
+}
+
+static void draw_text(SkCanvas* canvas, const SkRect& r, SkImageFilter* imf) {
+ SkPaint paint;
+ paint.setImageFilter(imf);
+ paint.setColor(SK_ColorGREEN);
+ paint.setAntiAlias(true);
+ paint.setTextSize(r.height()/2);
+ paint.setTextAlign(SkPaint::kCenter_Align);
+ canvas->save();
+ canvas->clipRect(r);
+ canvas->drawText("Text", 4, r.centerX(), r.centerY(), paint);
+ canvas->restore();
+}
+
+static void draw_bitmap(SkCanvas* canvas, const SkRect& r, SkImageFilter* imf) {
+ SkPaint paint;
+
+ SkIRect bounds;
+ r.roundOut(&bounds);
+
+ SkBitmap bm;
+ bm.setConfig(SkBitmap::kARGB_8888_Config, bounds.width(), bounds.height());
+ bm.allocPixels();
+ bm.eraseColor(SK_ColorTRANSPARENT);
+ SkCanvas c(bm);
+ draw_path(&c, r, NULL);
+
+ paint.setImageFilter(imf);
+ canvas->save();
+ canvas->clipRect(r);
+ canvas->drawBitmap(bm, 0, 0, &paint);
+ canvas->restore();
+}
+
+static void draw_sprite(SkCanvas* canvas, const SkRect& r, SkImageFilter* imf) {
+ SkPaint paint;
+
+ SkIRect bounds;
+ r.roundOut(&bounds);
+
+ SkBitmap bm;
+ bm.setConfig(SkBitmap::kARGB_8888_Config, bounds.width(), bounds.height());
+ bm.allocPixels();
+ bm.eraseColor(SK_ColorRED);
+ SkCanvas c(bm);
+
+ SkIRect cropRect = SkIRect::MakeXYWH(10, 10, 44, 44);
+ paint.setColor(SK_ColorGREEN);
+ c.drawRect(SkRect::Make(cropRect), paint);
+
+ paint.setImageFilter(imf);
+ SkPoint loc = { r.fLeft, r.fTop };
+ canvas->getTotalMatrix().mapPoints(&loc, 1);
+ canvas->drawSprite(bm,
+ SkScalarRoundToInt(loc.fX), SkScalarRoundToInt(loc.fY),
+ &paint);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+class ImageFiltersCroppedGM : public skiagm::GM {
+public:
+ ImageFiltersCroppedGM () {}
+
+protected:
+
+ virtual SkString onShortName() {
+ return SkString("imagefilterscropped");
+ }
+
+ virtual SkISize onISize() { return SkISize::Make(700, 460); }
+
+ void draw_frame(SkCanvas* canvas, const SkRect& r) {
+ SkPaint paint;
+ paint.setStyle(SkPaint::kStroke_Style);
+ paint.setColor(SK_ColorRED);
+ canvas->drawRect(r, paint);
+ }
+
+ virtual uint32_t onGetFlags() const {
+ // Because of the use of drawSprite, this test is excluded
+ // from scaled replay tests because drawSprite ignores the
+ // reciprocal scale that is applied at record time, which is
+ // the intended behavior of drawSprite.
+ return kSkipScaledReplay_Flag;
+ }
+
+ virtual void onDraw(SkCanvas* canvas) {
+ void (*drawProc[])(SkCanvas*, const SkRect&, SkImageFilter*) = {
+ draw_sprite, draw_bitmap, draw_path, draw_paint, draw_text
+ };
+
+ SkColorFilter* cf = SkColorFilter::CreateModeFilter(SK_ColorRED,
+ SkXfermode::kSrcIn_Mode);
+ SkIRect cropRect = SkIRect::MakeXYWH(10, 10, 44, 44);
+ SkIRect bogusRect = SkIRect::MakeXYWH(-100, -100, 10, 10);
+
+ SkImageFilter* filters[] = {
+ NULL,
+ SkColorFilterImageFilter::Create(cf, NULL, &cropRect),
+ new SkBlurImageFilter(8.0f, 0.0f, NULL, &cropRect),
+ new SkBlurImageFilter(0.0f, 8.0f, NULL, &cropRect),
+ new SkBlurImageFilter(8.0f, 8.0f, NULL, &cropRect),
+ new SkBlurImageFilter(8.0f, 8.0f, NULL, &bogusRect),
+ SkColorFilterImageFilter::Create(cf, NULL, &bogusRect),
+ };
+ cf->unref();
+
+ SkRect r = SkRect::MakeWH(SkIntToScalar(64), SkIntToScalar(64));
+ SkScalar MARGIN = SkIntToScalar(16);
+ SkScalar DX = r.width() + MARGIN;
+ SkScalar DY = r.height() + MARGIN;
+
+ canvas->translate(MARGIN, MARGIN);
+ for (size_t i = 0; i < SK_ARRAY_COUNT(filters); ++i) {
+ canvas->save();
+ for (size_t j = 0; j < SK_ARRAY_COUNT(drawProc); ++j) {
+ drawProc[j](canvas, r, filters[i]);
+ canvas->translate(0, DY);
+ }
+ canvas->restore();
+ canvas->translate(DX, 0);
+ }
+
+ for(size_t j = 0; j < SK_ARRAY_COUNT(filters); ++j) {
+ SkSafeUnref(filters[j]);
+ }
+ }
+
+private:
+ typedef GM INHERITED;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+static skiagm::GM* MyFactory(void*) { return new ImageFiltersCroppedGM; }
+static skiagm::GMRegistry reg(MyFactory);
'../gm/lighting.cpp',
'../gm/image.cpp',
'../gm/imagefiltersbase.cpp',
+ '../gm/imagefilterscropped.cpp',
'../gm/imagefiltersgraph.cpp',
'../gm/internal_links.cpp',
'../gm/lcdtext.cpp',
'../tests/HashCacheTest.cpp',
'../tests/ImageCacheTest.cpp',
'../tests/ImageDecodingTest.cpp',
+ '../tests/ImageFilterTest.cpp',
'../tests/InfRectTest.cpp',
'../tests/LListTest.cpp',
'../tests/LayerDrawLooperTest.cpp',
#define SkImageFilter_DEFINED
#include "SkFlattenable.h"
+#include "SkRect.h"
class SkBitmap;
class SkColorFilter;
class SkDevice;
class SkMatrix;
struct SkIPoint;
-struct SkIRect;
class SkShader;
class GrEffectRef;
class GrTexture;
return fInputs[i];
}
+ /**
+ * Returns the crop rectangle of this filter. This is set at construction
+ * time, and determines which pixels from the input image will
+ * be processed. The size of this rectangle should be used as the size
+ * of the destination image. The origin of this rect should be used to
+ * offset access to the input images, and should also be added to the
+ * "offset" parameter in onFilterImage and filterImageGPU(). (The latter
+ * ensures that the resulting buffer is drawn in the correct location.)
+ */
+ const SkIRect& cropRect() const { return fCropRect; }
+
protected:
- SkImageFilter(int inputCount, SkImageFilter** inputs);
+ SkImageFilter(int inputCount, SkImageFilter** inputs, const SkIRect* cropRect = NULL);
// Convenience constructor for 1-input filters.
- explicit SkImageFilter(SkImageFilter* input);
+ explicit SkImageFilter(SkImageFilter* input, const SkIRect* cropRect = NULL);
// Convenience constructor for 2-input filters.
- SkImageFilter(SkImageFilter* input1, SkImageFilter* input2);
+ SkImageFilter(SkImageFilter* input1, SkImageFilter* input2, const SkIRect* cropRect = NULL);
virtual ~SkImageFilter();
// Default impl copies src into dst and returns true
virtual bool onFilterBounds(const SkIRect&, const SkMatrix&, SkIRect*);
+ // Sets rect to the intersection of rect and the crop rect. If there
+ // is no overlap, returns false and leaves rect unchanged.
+ bool applyCropRect(SkIRect* rect) const;
+
private:
typedef SkFlattenable INHERITED;
int fInputCount;
SkImageFilter** fInputs;
+ SkIRect fCropRect;
};
#endif
return r;
}
+ static SkIRect SK_WARN_UNUSED_RESULT MakeLargest() {
+ SkIRect r;
+ r.setLargest();
+ return r;
+ }
+
static SkIRect SK_WARN_UNUSED_RESULT MakeWH(int32_t w, int32_t h) {
SkIRect r;
r.set(0, 0, w, h);
*/
bool isEmpty() const { return fLeft >= fRight || fTop >= fBottom; }
+ bool isLargest() const { return SK_MinS32 == fLeft &&
+ SK_MinS32 == fTop &&
+ SK_MaxS32 == fRight &&
+ SK_MaxS32 == fBottom; }
+
friend bool operator==(const SkIRect& a, const SkIRect& b) {
return !memcmp(&a, &b, sizeof(a));
}
class SK_API SkBlurImageFilter : public SkImageFilter {
public:
- SkBlurImageFilter(SkScalar sigmaX, SkScalar sigmaY, SkImageFilter* input = NULL);
+ SkBlurImageFilter(SkScalar sigmaX,
+ SkScalar sigmaY,
+ SkImageFilter* input = NULL,
+ const SkIRect* cropRect = NULL);
SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkBlurImageFilter)
class SK_API SkColorFilterImageFilter : public SkImageFilter {
public:
- static SkColorFilterImageFilter* Create(SkColorFilter* cf, SkImageFilter* input = NULL);
+ static SkColorFilterImageFilter* Create(SkColorFilter* cf,
+ SkImageFilter* input = NULL,
+ const SkIRect* cropRect = NULL);
virtual ~SkColorFilterImageFilter();
SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkColorFilterImageFilter)
virtual bool asColorFilter(SkColorFilter**) const SK_OVERRIDE;
private:
- SkColorFilterImageFilter(SkColorFilter* cf, SkImageFilter* input);
+ SkColorFilterImageFilter(SkColorFilter* cf,
+ SkImageFilter* input,
+ const SkIRect* cropRect = NULL);
SkColorFilter* fColorFilter;
typedef SkImageFilter INHERITED;
SK_DEFINE_INST_COUNT(SkImageFilter)
-SkImageFilter::SkImageFilter(int inputCount, SkImageFilter** inputs)
- : fInputCount(inputCount), fInputs(new SkImageFilter*[inputCount]) {
+SkImageFilter::SkImageFilter(int inputCount, SkImageFilter** inputs, const SkIRect* cropRect)
+ : fInputCount(inputCount),
+ fInputs(new SkImageFilter*[inputCount]),
+ fCropRect(cropRect ? *cropRect : SkIRect::MakeLargest()) {
for (int i = 0; i < inputCount; ++i) {
fInputs[i] = inputs[i];
SkSafeRef(fInputs[i]);
}
}
-SkImageFilter::SkImageFilter(SkImageFilter* input)
- : fInputCount(1), fInputs(new SkImageFilter*[1]) {
+SkImageFilter::SkImageFilter(SkImageFilter* input, const SkIRect* cropRect)
+ : fInputCount(1),
+ fInputs(new SkImageFilter*[1]),
+ fCropRect(cropRect ? *cropRect : SkIRect::MakeLargest()) {
fInputs[0] = input;
SkSafeRef(fInputs[0]);
}
-SkImageFilter::SkImageFilter(SkImageFilter* input1, SkImageFilter* input2)
- : fInputCount(2), fInputs(new SkImageFilter*[2]) {
+SkImageFilter::SkImageFilter(SkImageFilter* input1, SkImageFilter* input2, const SkIRect* cropRect)
+ : fInputCount(2), fInputs(new SkImageFilter*[2]),
+ fCropRect(cropRect ? *cropRect : SkIRect::MakeLargest()) {
fInputs[0] = input1;
fInputs[1] = input2;
SkSafeRef(fInputs[0]);
fInputs[i] = NULL;
}
}
+ buffer.readIRect(&fCropRect);
}
void SkImageFilter::flatten(SkFlattenableWriteBuffer& buffer) const {
buffer.writeFlattenable(input);
}
}
+ buffer.writeIRect(fCropRect);
}
bool SkImageFilter::filterImage(Proxy* proxy, const SkBitmap& src,
#endif
}
+bool SkImageFilter::applyCropRect(SkIRect* rect) const {
+ return rect->intersect(fCropRect);
+}
+
bool SkImageFilter::onFilterBounds(const SkIRect& src, const SkMatrix& ctm,
SkIRect* dst) {
*dst = src;
fSigma.fHeight = buffer.readScalar();
}
-SkBlurImageFilter::SkBlurImageFilter(SkScalar sigmaX, SkScalar sigmaY, SkImageFilter* input)
- : INHERITED(input), fSigma(SkSize::Make(sigmaX, sigmaY)) {
+SkBlurImageFilter::SkBlurImageFilter(SkScalar sigmaX,
+ SkScalar sigmaY,
+ SkImageFilter* input,
+ const SkIRect* cropRect)
+ : INHERITED(input, cropRect), fSigma(SkSize::Make(sigmaX, sigmaY)) {
SkASSERT(sigmaX >= 0 && sigmaY >= 0);
}
}
static void boxBlurX(const SkBitmap& src, SkBitmap* dst, int kernelSize,
- int leftOffset, int rightOffset)
+ int leftOffset, int rightOffset, const SkIRect& bounds)
{
- int width = src.width(), height = src.height();
+ int width = bounds.width(), height = bounds.height();
int rightBorder = SkMin32(rightOffset + 1, width);
for (int y = 0; y < height; ++y) {
int sumA = 0, sumR = 0, sumG = 0, sumB = 0;
- SkPMColor* p = src.getAddr32(0, y);
+ SkPMColor* p = src.getAddr32(bounds.fLeft, y + bounds.fTop);
for (int i = 0; i < rightBorder; ++i) {
sumA += SkGetPackedA32(*p);
sumR += SkGetPackedR32(*p);
p++;
}
- const SkColor* sptr = src.getAddr32(0, y);
+ const SkColor* sptr = src.getAddr32(bounds.fLeft, bounds.fTop + y);
SkColor* dptr = dst->getAddr32(0, y);
for (int x = 0; x < width; ++x) {
*dptr = SkPackARGB32(sumA / kernelSize,
}
static void boxBlurY(const SkBitmap& src, SkBitmap* dst, int kernelSize,
- int topOffset, int bottomOffset)
+ int topOffset, int bottomOffset, const SkIRect& bounds)
{
- int width = src.width(), height = src.height();
+ int width = bounds.width(), height = bounds.height();
int bottomBorder = SkMin32(bottomOffset + 1, height);
int srcStride = src.rowBytesAsPixels();
int dstStride = dst->rowBytesAsPixels();
for (int x = 0; x < width; ++x) {
int sumA = 0, sumR = 0, sumG = 0, sumB = 0;
- SkColor* p = src.getAddr32(x, 0);
+ SkColor* p = src.getAddr32(bounds.fLeft + x, bounds.fTop);
for (int i = 0; i < bottomBorder; ++i) {
sumA += SkGetPackedA32(*p);
sumR += SkGetPackedR32(*p);
p += srcStride;
}
- const SkColor* sptr = src.getAddr32(x, 0);
+ const SkColor* sptr = src.getAddr32(bounds.fLeft + x, bounds.fTop);
SkColor* dptr = dst->getAddr32(x, 0);
for (int y = 0; y < height; ++y) {
*dptr = SkPackARGB32(sumA / kernelSize,
return false;
}
- dst->setConfig(src.config(), src.width(), src.height());
+ SkIRect srcBounds, dstBounds;
+ src.getBounds(&srcBounds);
+ if (!this->applyCropRect(&srcBounds)) {
+ return false;
+ }
+
+ dst->setConfig(src.config(), srcBounds.width(), srcBounds.height());
+ dst->getBounds(&dstBounds);
dst->allocPixels();
int kernelSizeX, kernelSizeX3, lowOffsetX, highOffsetX;
int kernelSizeY, kernelSizeY3, lowOffsetY, highOffsetY;
}
if (kernelSizeX > 0 && kernelSizeY > 0) {
- boxBlurX(src, &temp, kernelSizeX, lowOffsetX, highOffsetX);
- boxBlurY(temp, dst, kernelSizeY, lowOffsetY, highOffsetY);
- boxBlurX(*dst, &temp, kernelSizeX, highOffsetX, lowOffsetX);
- boxBlurY(temp, dst, kernelSizeY, highOffsetY, lowOffsetY);
- boxBlurX(*dst, &temp, kernelSizeX3, highOffsetX, highOffsetX);
- boxBlurY(temp, dst, kernelSizeY3, highOffsetY, highOffsetY);
+ boxBlurX(src, &temp, kernelSizeX, lowOffsetX, highOffsetX, srcBounds);
+ boxBlurY(temp, dst, kernelSizeY, lowOffsetY, highOffsetY, dstBounds);
+ boxBlurX(*dst, &temp, kernelSizeX, highOffsetX, lowOffsetX, dstBounds);
+ boxBlurY(temp, dst, kernelSizeY, highOffsetY, lowOffsetY, dstBounds);
+ boxBlurX(*dst, &temp, kernelSizeX3, highOffsetX, highOffsetX, dstBounds);
+ boxBlurY(temp, dst, kernelSizeY3, highOffsetY, highOffsetY, dstBounds);
} else if (kernelSizeX > 0) {
- boxBlurX(src, dst, kernelSizeX, lowOffsetX, highOffsetX);
- boxBlurX(*dst, &temp, kernelSizeX, highOffsetX, lowOffsetX);
- boxBlurX(temp, dst, kernelSizeX3, highOffsetX, highOffsetX);
+ boxBlurX(src, dst, kernelSizeX, lowOffsetX, highOffsetX, srcBounds);
+ boxBlurX(*dst, &temp, kernelSizeX, highOffsetX, lowOffsetX, dstBounds);
+ boxBlurX(temp, dst, kernelSizeX3, highOffsetX, highOffsetX, dstBounds);
} else if (kernelSizeY > 0) {
- boxBlurY(src, dst, kernelSizeY, lowOffsetY, highOffsetY);
- boxBlurY(*dst, &temp, kernelSizeY, highOffsetY, lowOffsetY);
- boxBlurY(temp, dst, kernelSizeY3, highOffsetY, highOffsetY);
+ boxBlurY(src, dst, kernelSizeY, lowOffsetY, highOffsetY, srcBounds);
+ boxBlurY(*dst, &temp, kernelSizeY, highOffsetY, lowOffsetY, dstBounds);
+ boxBlurY(temp, dst, kernelSizeY3, highOffsetY, highOffsetY, dstBounds);
}
+ offset->fX += srcBounds.fLeft;
+ offset->fY += srcBounds.fTop;
return true;
}
return false;
}
GrTexture* source = input.getTexture();
- SkRect rect;
+ SkIRect rect;
src.getBounds(&rect);
+ if (!this->applyCropRect(&rect)) {
+ return false;
+ }
SkAutoTUnref<GrTexture> tex(SkGpuBlurUtils::GaussianBlur(source->getContext(),
- source, false, rect,
- fSigma.width(), fSigma.height()));
- return SkImageFilterUtils::WrapTexture(tex, src.width(), src.height(), result);
+ source,
+ false,
+ SkRect::Make(rect),
+ true,
+ fSigma.width(),
+ fSigma.height()));
+ offset->fX += rect.fLeft;
+ offset->fY += rect.fTop;
+ return SkImageFilterUtils::WrapTexture(tex, rect.width(), rect.height(), result);
#else
SkDEBUGFAIL("Should not call in GPU-less build");
return false;
// gaussianBlur. Otherwise, we need to save it for later compositing.
bool isNormalBlur = (SkBlurMaskFilter::kNormal_BlurStyle == fBlurStyle);
*result = SkGpuBlurUtils::GaussianBlur(context, src, isNormalBlur && canOverwriteSrc,
- clipRect, sigma, sigma);
+ clipRect, false, sigma, sigma);
if (NULL == *result) {
return false;
}
};
SkColorFilterImageFilter* SkColorFilterImageFilter::Create(SkColorFilter* cf,
- SkImageFilter* input) {
+ SkImageFilter* input, const SkIRect* cropRect) {
SkASSERT(cf);
SkScalar colorMatrix[20], inputMatrix[20];
SkColorFilter* inputColorFilter;
SkScalar combinedMatrix[20];
mult_color_matrix(inputMatrix, colorMatrix, combinedMatrix);
SkAutoTUnref<SkColorFilter> newCF(SkNEW_ARGS(SkColorMatrixFilter, (combinedMatrix)));
- return SkNEW_ARGS(SkColorFilterImageFilter, (newCF, input->getInput(0)));
+ return SkNEW_ARGS(SkColorFilterImageFilter, (newCF, input->getInput(0), cropRect));
}
}
- return SkNEW_ARGS(SkColorFilterImageFilter, (cf, input));
+ return SkNEW_ARGS(SkColorFilterImageFilter, (cf, input, cropRect));
}
-SkColorFilterImageFilter::SkColorFilterImageFilter(SkColorFilter* cf, SkImageFilter* input) : INHERITED(input), fColorFilter(cf) {
+SkColorFilterImageFilter::SkColorFilterImageFilter(SkColorFilter* cf,
+ SkImageFilter* input, const SkIRect* cropRect)
+ : INHERITED(input, cropRect), fColorFilter(cf) {
SkASSERT(cf);
SkSafeRef(cf);
}
return false;
}
- SkAutoTUnref<SkDevice> device(proxy->createDevice(src.width(), src.height()));
+ SkIRect bounds;
+ src.getBounds(&bounds);
+ if (!this->applyCropRect(&bounds)) {
+ return false;
+ }
+
+ SkAutoTUnref<SkDevice> device(proxy->createDevice(bounds.width(), bounds.height()));
SkCanvas canvas(device.get());
SkPaint paint;
paint.setXfermodeMode(SkXfermode::kSrc_Mode);
paint.setColorFilter(fColorFilter);
- canvas.drawSprite(src, 0, 0, &paint);
+ canvas.drawSprite(src, -bounds.fLeft, -bounds.fTop, &paint);
*result = device.get()->accessBitmap(false);
+ loc->fX += bounds.fLeft;
+ loc->fY += bounds.fTop;
return true;
}
bool SkColorFilterImageFilter::asColorFilter(SkColorFilter** filter) const {
- if (filter) {
- *filter = fColorFilter;
- fColorFilter->ref();
+ if (cropRect().isLargest()) {
+ if (filter) {
+ *filter = fColorFilter;
+ fColorFilter->ref();
+ }
+ return true;
}
- return true;
+ return false;
}
#if SK_SUPPORT_GPU
#include "effects/GrConvolutionEffect.h"
+#include "effects/GrTextureDomainEffect.h"
#include "GrContext.h"
#endif
static void convolve_gaussian(GrContext* context,
GrTexture* texture,
- const SkRect& rect,
+ const SkRect& srcRect,
+ const SkRect& dstRect,
+ bool cropToSrcRect,
float sigma,
int radius,
Gr1DKernelEffect::Direction direction) {
GrPaint paint;
+ paint.reset();
+ float cropRect[4] = { 0.0f, 1.0f, 0.0f, 1.0f };
+ if (cropToSrcRect) {
+ if (direction == Gr1DKernelEffect::kX_Direction) {
+ cropRect[0] = SkScalarToFloat(srcRect.left()) / texture->width();
+ cropRect[1] = SkScalarToFloat(srcRect.right()) / texture->width();
+ } else {
+ cropRect[2] = SkScalarToFloat(srcRect.top()) / texture->height();
+ cropRect[3] = SkScalarToFloat(srcRect.bottom()) / texture->height();
+ }
+ }
SkAutoTUnref<GrEffectRef> conv(GrConvolutionEffect::CreateGaussian(texture,
direction,
radius,
- sigma));
+ sigma,
+ cropToSrcRect,
+ cropRect));
paint.addColorEffect(conv);
- context->drawRect(paint, rect);
+ context->drawRectToRect(paint, dstRect, srcRect);
}
GrTexture* GaussianBlur(GrContext* context,
GrTexture* srcTexture,
bool canClobberSrc,
const SkRect& rect,
+ bool cropToRect,
float sigmaX,
float sigmaY) {
GrAssert(NULL != context);
scale_rect(&srcRect, static_cast<float>(scaleFactorX),
static_cast<float>(scaleFactorY));
- GrContext::AutoClip acs(context, srcRect);
+ GrContext::AutoClip acs(context, SkRect::MakeWH(srcRect.width(), srcRect.height()));
GrAssert(kBGRA_8888_GrPixelConfig == srcTexture->config() ||
kRGBA_8888_GrPixelConfig == srcTexture->config() ||
matrix.setIDiv(srcTexture->width(), srcTexture->height());
context->setRenderTarget(dstTexture->asRenderTarget());
SkRect dstRect(srcRect);
+ if (cropToRect && i == 1) {
+ dstRect.offset(-dstRect.fLeft, -dstRect.fTop);
+ SkRect domain;
+ matrix.mapRect(&domain, rect);
+ domain.inset(i < scaleFactorX ? SK_ScalarHalf / srcTexture->width() : 0.0f,
+ i < scaleFactorY ? SK_ScalarHalf / srcTexture->height() : 0.0f);
+ SkAutoTUnref<GrEffectRef> effect(GrTextureDomainEffect::Create(
+ srcTexture,
+ matrix,
+ domain,
+ GrTextureDomainEffect::kDecal_WrapMode,
+ true));
+ paint.addColorEffect(effect);
+ } else {
+ GrTextureParams params(SkShader::kClamp_TileMode, true);
+ paint.addColorTextureEffect(srcTexture, matrix, params);
+ }
scale_rect(&dstRect, i < scaleFactorX ? 0.5f : 1.0f,
i < scaleFactorY ? 0.5f : 1.0f);
- GrTextureParams params(SkShader::kClamp_TileMode, true);
- paint.addColorTextureEffect(srcTexture, matrix, params);
context->drawRectToRect(paint, dstRect, srcRect);
srcRect = dstRect;
srcTexture = dstTexture;
context->clear(&clearRect, 0x0);
}
context->setRenderTarget(dstTexture->asRenderTarget());
- convolve_gaussian(context, srcTexture, srcRect, sigmaX, radiusX,
- Gr1DKernelEffect::kX_Direction);
+ SkRect dstRect = SkRect::MakeWH(srcRect.width(), srcRect.height());
+ convolve_gaussian(context, srcTexture, srcRect, dstRect, cropToRect,
+ sigmaX, radiusX, Gr1DKernelEffect::kX_Direction);
srcTexture = dstTexture;
+ srcRect = dstRect;
SkTSwap(dstTexture, tempTexture);
}
}
context->setRenderTarget(dstTexture->asRenderTarget());
- convolve_gaussian(context, srcTexture, srcRect, sigmaY, radiusY,
- Gr1DKernelEffect::kY_Direction);
+ SkRect dstRect = SkRect::MakeWH(srcRect.width(), srcRect.height());
+ convolve_gaussian(context, srcTexture, srcRect, dstRect, cropToRect,
+ sigmaY, radiusY, Gr1DKernelEffect::kY_Direction);
srcTexture = dstTexture;
+ srcRect = dstRect;
SkTSwap(dstTexture, tempTexture);
}
* @param canClobberSrc If true, srcTexture may be overwritten, and
* may be returned as the result.
* @param rect The destination rectangle.
+ * @param cropToRect If true, do not sample any pixels outside the
+ * source rect.
* @param sigmaX The blur's standard deviation in X.
* @param sigmaY The blur's standard deviation in Y.
* @return the blurred texture, which may be srcTexture reffed, or a
GrTexture* srcTexture,
bool canClobberSrc,
const SkRect& rect,
+ bool cropToRect,
float sigmaX,
float sigmaY);
#endif
SkAutoCachedTexture act(this, bitmap, NULL, &texture);
SkImageFilter* filter = paint.getImageFilter();
- SkIPoint offset = SkIPoint::Make(0, 0);
+ SkIPoint offset = SkIPoint::Make(left, top);
// This bitmap will own the filtered result as a texture.
SkBitmap filteredBitmap;
texture = (GrTexture*) filteredBitmap.getTexture();
w = filteredBitmap.width();
h = filteredBitmap.height();
+ } else {
+ return;
}
}
}
fContext->drawRectToRect(grPaint,
- SkRect::MakeXYWH(SkIntToScalar(left),
- SkIntToScalar(top),
- SkIntToScalar(w),
- SkIntToScalar(h)),
SkRect::MakeXYWH(SkIntToScalar(offset.fX),
SkIntToScalar(offset.fY),
+ SkIntToScalar(w),
+ SkIntToScalar(h)),
+ SkRect::MakeXYWH(0,
+ 0,
SK_Scalar1 * w / texture->width(),
SK_Scalar1 * h / texture->height()));
}
h = filteredBitmap.height();
x += offset.fX;
y += offset.fY;
+ } else {
+ return;
}
}
private:
int width() const { return Gr1DKernelEffect::WidthFromRadius(fRadius); }
+ bool useCropRect() const { return fUseCropRect; }
int fRadius;
+ bool fUseCropRect;
UniformHandle fKernelUni;
UniformHandle fImageIncrementUni;
+ UniformHandle fCropRectUni;
GrGLEffectMatrix fEffectMatrix;
typedef GrGLEffect INHERITED;
: INHERITED(factory)
, fKernelUni(kInvalidUniformHandle)
, fImageIncrementUni(kInvalidUniformHandle)
+ , fCropRectUni(kInvalidUniformHandle)
, fEffectMatrix(drawEffect.castEffect<GrConvolutionEffect>().coordsType()) {
const GrConvolutionEffect& c = drawEffect.castEffect<GrConvolutionEffect>();
fRadius = c.radius();
+ fUseCropRect = c.useCropRect();
}
void GrGLConvolutionEffect::emitCode(GrGLShaderBuilder* builder,
fEffectMatrix.emitCodeMakeFSCoords2D(builder, key, &coords);
fImageIncrementUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
kVec2f_GrSLType, "ImageIncrement");
+ if (this->useCropRect()) {
+ fCropRectUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
+ kVec4f_GrSLType, "CropRect");
+ }
fKernelUni = builder->addUniformArray(GrGLShaderBuilder::kFragment_ShaderType,
kFloat_GrSLType, "Kernel", this->width());
builder->fsCodeAppendf("\t\t%s = vec4(0, 0, 0, 0);\n", outputColor);
- int width = this ->width();
+ int width = this->width();
const GrGLShaderVar& kernel = builder->getUniformVariable(fKernelUni);
const char* imgInc = builder->getUniformCStr(fImageIncrementUni);
kernel.appendArrayAccess(index.c_str(), &kernelIndex);
builder->fsCodeAppendf("\t\t%s += ", outputColor);
builder->appendTextureLookup(GrGLShaderBuilder::kFragment_ShaderType, samplers[0], "coord");
+ if (this->useCropRect()) {
+ const char* cropRect = builder->getUniformCStr(fCropRectUni);
+ builder->fsCodeAppendf(" * float(coord.x >= %s.x && coord.x <= %s.y && coord.y >= %s.z && coord.y <= %s.w)",
+ cropRect, cropRect, cropRect, cropRect);
+ }
builder->fsCodeAppendf(" * %s;\n", kernelIndex.c_str());
builder->fsCodeAppendf("\t\tcoord += %s;\n", imgInc);
}
+
SkString modulate;
GrGLSLMulVarBy4f(&modulate, 2, outputColor, inputColor);
builder->fsCodeAppend(modulate.c_str());
// the code we generated was for a specific kernel radius
GrAssert(conv.radius() == fRadius);
float imageIncrement[2] = { 0 };
+ float ySign = texture.origin() != kTopLeft_GrSurfaceOrigin ? 1.0f : -1.0f;
switch (conv.direction()) {
case Gr1DKernelEffect::kX_Direction:
imageIncrement[0] = 1.0f / texture.width();
break;
case Gr1DKernelEffect::kY_Direction:
- imageIncrement[1] = 1.0f / texture.height();
+ imageIncrement[1] = ySign / texture.height();
break;
default:
GrCrash("Unknown filter direction.");
}
uman.set2fv(fImageIncrementUni, 0, 1, imageIncrement);
+ if (conv.useCropRect()) {
+ float c[4];
+ memcpy(c, conv.cropRect(), sizeof(c));
+ if (texture.origin() != kTopLeft_GrSurfaceOrigin) {
+ float tmp = 1.0f - c[2];
+ c[2] = 1.0f - c[3];
+ c[3] = tmp;
+ }
+ uman.set4fv(fCropRectUni, 0, 1, c);
+ }
uman.set1fv(fKernelUni, 0, this->width(), conv.kernel());
fEffectMatrix.setData(uman, conv.getMatrix(), drawEffect, conv.texture(0));
}
GrGLEffect::EffectKey GrGLConvolutionEffect::GenKey(const GrDrawEffect& drawEffect,
const GrGLCaps&) {
const GrConvolutionEffect& conv = drawEffect.castEffect<GrConvolutionEffect>();
- EffectKey key = conv.radius();
+ EffectKey key = conv.radius() << 1;
+ key |= conv.useCropRect() ? 0x1 : 0x0;
key <<= GrGLEffectMatrix::kKeyBits;
EffectKey matrixKey = GrGLEffectMatrix::GenKey(conv.getMatrix(),
drawEffect,
GrConvolutionEffect::GrConvolutionEffect(GrTexture* texture,
Direction direction,
int radius,
- const float* kernel)
- : Gr1DKernelEffect(texture, direction, radius) {
+ const float* kernel,
+ bool useCropRect,
+ float cropRect[4])
+ : Gr1DKernelEffect(texture, direction, radius), fUseCropRect(useCropRect) {
GrAssert(radius <= kMaxKernelRadius);
GrAssert(NULL != kernel);
int width = this->width();
for (int i = 0; i < width; i++) {
fKernel[i] = kernel[i];
}
+ memcpy(fCropRect, cropRect, sizeof(fCropRect));
}
GrConvolutionEffect::GrConvolutionEffect(GrTexture* texture,
Direction direction,
int radius,
- float gaussianSigma)
- : Gr1DKernelEffect(texture, direction, radius) {
+ float gaussianSigma,
+ bool useCropRect,
+ float cropRect[4])
+ : Gr1DKernelEffect(texture, direction, radius), fUseCropRect(useCropRect) {
GrAssert(radius <= kMaxKernelRadius);
int width = this->width();
for (int i = 0; i < width; ++i) {
fKernel[i] *= scale;
}
+ memcpy(fCropRect, cropRect, sizeof(fCropRect));
}
GrConvolutionEffect::~GrConvolutionEffect() {
return (this->texture(0) == s.texture(0) &&
this->radius() == s.radius() &&
this->direction() == s.direction() &&
+ 0 == memcmp(fCropRect, s.fCropRect, sizeof(fCropRect)) &&
0 == memcmp(fKernel, s.fKernel, this->width() * sizeof(float)));
}
Direction dir = random->nextBool() ? kX_Direction : kY_Direction;
int radius = random->nextRangeU(1, kMaxKernelRadius);
float kernel[kMaxKernelRadius];
+ float cropRect[4];
for (int i = 0; i < kMaxKernelRadius; ++i) {
kernel[i] = random->nextSScalar1();
}
+ for (int i = 0; i < 4; ++i) {
+ cropRect[i] = random->nextF();
+ }
- return GrConvolutionEffect::Create(textures[texIdx], dir, radius,kernel);
+ bool useCropRect = random->nextBool();
+ return GrConvolutionEffect::Create(textures[texIdx],
+ dir,
+ radius,
+ kernel,
+ useCropRect,
+ cropRect);
}
public:
/// Convolve with an arbitrary user-specified kernel
- static GrEffectRef* Create(GrTexture* tex, Direction dir, int halfWidth, const float* kernel) {
+ static GrEffectRef* Create(GrTexture* tex,
+ Direction dir,
+ int halfWidth,
+ const float* kernel,
+ bool useCropRect,
+ float cropRect[4]) {
AutoEffectUnref effect(SkNEW_ARGS(GrConvolutionEffect, (tex,
dir,
halfWidth,
- kernel)));
+ kernel,
+ useCropRect,
+ cropRect)));
return CreateEffectRef(effect);
}
static GrEffectRef* CreateGaussian(GrTexture* tex,
Direction dir,
int halfWidth,
- float gaussianSigma) {
+ float gaussianSigma,
+ bool useCropRect,
+ float cropRect[4]) {
AutoEffectUnref effect(SkNEW_ARGS(GrConvolutionEffect, (tex,
dir,
halfWidth,
- gaussianSigma)));
+ gaussianSigma,
+ useCropRect,
+ cropRect)));
return CreateEffectRef(effect);
}
const float* kernel() const { return fKernel; }
+ const float* cropRect() const { return fCropRect; }
+ bool useCropRect() const { return fUseCropRect; }
+
static const char* Name() { return "Convolution"; }
typedef GrGLConvolutionEffect GLEffect;
protected:
float fKernel[kMaxKernelWidth];
+ bool fUseCropRect;
+ float fCropRect[4];
private:
GrConvolutionEffect(GrTexture*, Direction,
- int halfWidth, const float* kernel);
+ int halfWidth,
+ const float* kernel,
+ bool useCropRect,
+ float cropRect[4]);
/// Convolve with a Gaussian kernel
GrConvolutionEffect(GrTexture*, Direction,
int halfWidth,
- float gaussianSigma);
+ float gaussianSigma,
+ bool useCropRect,
+ float cropRect[4]);
virtual bool onIsEqual(const GrEffect&) const SK_OVERRIDE;
--- /dev/null
+
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "Test.h"
+#include "SkColorMatrixFilter.h"
+#include "SkColorFilterImageFilter.h"
+#include "SkRect.h"
+
+class ImageFilterTest {
+public:
+
+ static SkImageFilter* make_scale(float amount, SkImageFilter* input = NULL) {
+ SkScalar s = SkFloatToScalar(amount);
+ SkScalar matrix[20] = { s, 0, 0, 0, 0,
+ 0, s, 0, 0, 0,
+ 0, 0, s, 0, 0,
+ 0, 0, 0, s, 0 };
+ SkAutoTUnref<SkColorFilter> filter(new SkColorMatrixFilter(matrix));
+ return SkColorFilterImageFilter::Create(filter, input);
+ }
+
+ static SkImageFilter* make_grayscale(SkImageFilter* input = NULL, const SkIRect* cropRect = NULL) {
+ SkScalar matrix[20];
+ memset(matrix, 0, 20 * sizeof(SkScalar));
+ matrix[0] = matrix[5] = matrix[10] = SkFloatToScalar(0.2126f);
+ matrix[1] = matrix[6] = matrix[11] = SkFloatToScalar(0.7152f);
+ matrix[2] = matrix[7] = matrix[12] = SkFloatToScalar(0.0722f);
+ matrix[18] = SkFloatToScalar(1.0f);
+ SkAutoTUnref<SkColorFilter> filter(new SkColorMatrixFilter(matrix));
+ return SkColorFilterImageFilter::Create(filter, input, cropRect);
+ }
+
+ static SkImageFilter* make_mode_blue(SkImageFilter* input = NULL) {
+ SkAutoTUnref<SkColorFilter> filter(
+ SkColorFilter::CreateModeFilter(SK_ColorBLUE, SkXfermode::kSrcIn_Mode));
+ return SkColorFilterImageFilter::Create(filter, input);
+ }
+
+ static void Test(skiatest::Reporter* reporter) {
+ {
+ // Check that two non-clipping color matrices concatenate into a single filter.
+ SkAutoTUnref<SkImageFilter> halfBrightness(make_scale(0.5f));
+ SkAutoTUnref<SkImageFilter> quarterBrightness(make_scale(0.5f, halfBrightness));
+ REPORTER_ASSERT(reporter, NULL == quarterBrightness->getInput(0));
+ }
+
+ {
+ // Check that a clipping color matrix followed by a grayscale does not concatenate into a single filter.
+ SkAutoTUnref<SkImageFilter> doubleBrightness(make_scale(2.0f));
+ SkAutoTUnref<SkImageFilter> halfBrightness(make_scale(0.5f, doubleBrightness));
+ REPORTER_ASSERT(reporter, NULL != halfBrightness->getInput(0));
+ }
+
+ {
+ // Check that a color filter image filter without a crop rect can be
+ // expressed as a color filter.
+ SkAutoTUnref<SkImageFilter> gray(make_grayscale());
+ REPORTER_ASSERT(reporter, true == gray->asColorFilter(NULL));
+ }
+
+ {
+ // Check that a color filter image filter with a crop rect cannot
+ // be expressed as a color filter.
+ SkIRect cropRect = SkIRect::MakeXYWH(0, 0, 100, 100);
+ SkAutoTUnref<SkImageFilter> grayWithCrop(make_grayscale(NULL, &cropRect));
+ REPORTER_ASSERT(reporter, false == grayWithCrop->asColorFilter(NULL));
+ }
+ }
+};
+
+
+#include "TestClassDef.h"
+DEFINE_TESTCLASS("ImageFilterTest", ImageFilterTestClass, ImageFilterTest::Test)