Home | History | Annotate | Download | only in effects
      1 /*
      2  * Copyright 2012 The Android Open Source Project
      3  *
      4  * Use of this source code is governed by a BSD-style license that can be
      5  * found in the LICENSE file.
      6  */
      7 
      8 #include "SkMorphologyImageFilter.h"
      9 #include "SkBitmap.h"
     10 #include "SkColorPriv.h"
     11 #include "SkReadBuffer.h"
     12 #include "SkWriteBuffer.h"
     13 #include "SkRect.h"
     14 #include "SkMorphology_opts.h"
     15 #if SK_SUPPORT_GPU
     16 #include "GrContext.h"
     17 #include "GrTexture.h"
     18 #include "GrTBackendEffectFactory.h"
     19 #include "gl/GrGLEffect.h"
     20 #include "effects/Gr1DKernelEffect.h"
     21 #endif
     22 
     23 SkMorphologyImageFilter::SkMorphologyImageFilter(SkReadBuffer& buffer)
     24   : INHERITED(1, buffer) {
     25     fRadius.fWidth = buffer.readInt();
     26     fRadius.fHeight = buffer.readInt();
     27     buffer.validate((fRadius.fWidth >= 0) &&
     28                     (fRadius.fHeight >= 0));
     29 }
     30 
     31 SkMorphologyImageFilter::SkMorphologyImageFilter(int radiusX,
     32                                                  int radiusY,
     33                                                  SkImageFilter* input,
     34                                                  const CropRect* cropRect)
     35     : INHERITED(input, cropRect), fRadius(SkISize::Make(radiusX, radiusY)) {
     36 }
     37 
     38 
     39 void SkMorphologyImageFilter::flatten(SkWriteBuffer& buffer) const {
     40     this->INHERITED::flatten(buffer);
     41     buffer.writeInt(fRadius.fWidth);
     42     buffer.writeInt(fRadius.fHeight);
     43 }
     44 
     45 enum MorphDirection {
     46     kX, kY
     47 };
     48 
     49 template<MorphDirection direction>
     50 static void erode(const SkPMColor* src, SkPMColor* dst,
     51                   int radius, int width, int height,
     52                   int srcStride, int dstStride)
     53 {
     54     const int srcStrideX = direction == kX ? 1 : srcStride;
     55     const int dstStrideX = direction == kX ? 1 : dstStride;
     56     const int srcStrideY = direction == kX ? srcStride : 1;
     57     const int dstStrideY = direction == kX ? dstStride : 1;
     58     radius = SkMin32(radius, width - 1);
     59     const SkPMColor* upperSrc = src + radius * srcStrideX;
     60     for (int x = 0; x < width; ++x) {
     61         const SkPMColor* lp = src;
     62         const SkPMColor* up = upperSrc;
     63         SkPMColor* dptr = dst;
     64         for (int y = 0; y < height; ++y) {
     65             int minB = 255, minG = 255, minR = 255, minA = 255;
     66             for (const SkPMColor* p = lp; p <= up; p += srcStrideX) {
     67                 int b = SkGetPackedB32(*p);
     68                 int g = SkGetPackedG32(*p);
     69                 int r = SkGetPackedR32(*p);
     70                 int a = SkGetPackedA32(*p);
     71                 if (b < minB) minB = b;
     72                 if (g < minG) minG = g;
     73                 if (r < minR) minR = r;
     74                 if (a < minA) minA = a;
     75             }
     76             *dptr = SkPackARGB32(minA, minR, minG, minB);
     77             dptr += dstStrideY;
     78             lp += srcStrideY;
     79             up += srcStrideY;
     80         }
     81         if (x >= radius) src += srcStrideX;
     82         if (x + radius < width - 1) upperSrc += srcStrideX;
     83         dst += dstStrideX;
     84     }
     85 }
     86 
     87 template<MorphDirection direction>
     88 static void dilate(const SkPMColor* src, SkPMColor* dst,
     89                    int radius, int width, int height,
     90                    int srcStride, int dstStride)
     91 {
     92     const int srcStrideX = direction == kX ? 1 : srcStride;
     93     const int dstStrideX = direction == kX ? 1 : dstStride;
     94     const int srcStrideY = direction == kX ? srcStride : 1;
     95     const int dstStrideY = direction == kX ? dstStride : 1;
     96     radius = SkMin32(radius, width - 1);
     97     const SkPMColor* upperSrc = src + radius * srcStrideX;
     98     for (int x = 0; x < width; ++x) {
     99         const SkPMColor* lp = src;
    100         const SkPMColor* up = upperSrc;
    101         SkPMColor* dptr = dst;
    102         for (int y = 0; y < height; ++y) {
    103             int maxB = 0, maxG = 0, maxR = 0, maxA = 0;
    104             for (const SkPMColor* p = lp; p <= up; p += srcStrideX) {
    105                 int b = SkGetPackedB32(*p);
    106                 int g = SkGetPackedG32(*p);
    107                 int r = SkGetPackedR32(*p);
    108                 int a = SkGetPackedA32(*p);
    109                 if (b > maxB) maxB = b;
    110                 if (g > maxG) maxG = g;
    111                 if (r > maxR) maxR = r;
    112                 if (a > maxA) maxA = a;
    113             }
    114             *dptr = SkPackARGB32(maxA, maxR, maxG, maxB);
    115             dptr += dstStrideY;
    116             lp += srcStrideY;
    117             up += srcStrideY;
    118         }
    119         if (x >= radius) src += srcStrideX;
    120         if (x + radius < width - 1) upperSrc += srcStrideX;
    121         dst += dstStrideX;
    122     }
    123 }
    124 
    125 static void callProcX(SkMorphologyImageFilter::Proc procX, const SkBitmap& src, SkBitmap* dst, int radiusX, const SkIRect& bounds)
    126 {
    127     procX(src.getAddr32(bounds.left(), bounds.top()), dst->getAddr32(0, 0),
    128           radiusX, bounds.width(), bounds.height(),
    129           src.rowBytesAsPixels(), dst->rowBytesAsPixels());
    130 }
    131 
    132 static void callProcY(SkMorphologyImageFilter::Proc procY, const SkBitmap& src, SkBitmap* dst, int radiusY, const SkIRect& bounds)
    133 {
    134     procY(src.getAddr32(bounds.left(), bounds.top()), dst->getAddr32(0, 0),
    135           radiusY, bounds.height(), bounds.width(),
    136           src.rowBytesAsPixels(), dst->rowBytesAsPixels());
    137 }
    138 
    139 bool SkMorphologyImageFilter::filterImageGeneric(SkMorphologyImageFilter::Proc procX,
    140                                                  SkMorphologyImageFilter::Proc procY,
    141                                                  Proxy* proxy,
    142                                                  const SkBitmap& source,
    143                                                  const Context& ctx,
    144                                                  SkBitmap* dst,
    145                                                  SkIPoint* offset) const {
    146     SkBitmap src = source;
    147     SkIPoint srcOffset = SkIPoint::Make(0, 0);
    148     if (getInput(0) && !getInput(0)->filterImage(proxy, source, ctx, &src, &srcOffset)) {
    149         return false;
    150     }
    151 
    152     if (src.colorType() != kN32_SkColorType) {
    153         return false;
    154     }
    155 
    156     SkIRect bounds;
    157     if (!this->applyCropRect(ctx, proxy, src, &srcOffset, &bounds, &src)) {
    158         return false;
    159     }
    160 
    161     SkAutoLockPixels alp(src);
    162     if (!src.getPixels()) {
    163         return false;
    164     }
    165 
    166     if (!dst->allocPixels(src.info().makeWH(bounds.width(), bounds.height()))) {
    167         return false;
    168     }
    169 
    170     SkVector radius = SkVector::Make(SkIntToScalar(this->radius().width()),
    171                                      SkIntToScalar(this->radius().height()));
    172     ctx.ctm().mapVectors(&radius, 1);
    173     int width = SkScalarFloorToInt(radius.fX);
    174     int height = SkScalarFloorToInt(radius.fY);
    175 
    176     if (width < 0 || height < 0) {
    177         return false;
    178     }
    179 
    180     SkIRect srcBounds = bounds;
    181     srcBounds.offset(-srcOffset);
    182 
    183     if (width == 0 && height == 0) {
    184         src.extractSubset(dst, srcBounds);
    185         offset->fX = bounds.left();
    186         offset->fY = bounds.top();
    187         return true;
    188     }
    189 
    190     SkBitmap temp;
    191     if (!temp.allocPixels(dst->info())) {
    192         return false;
    193     }
    194 
    195     if (width > 0 && height > 0) {
    196         callProcX(procX, src, &temp, width, srcBounds);
    197         SkIRect tmpBounds = SkIRect::MakeWH(srcBounds.width(), srcBounds.height());
    198         callProcY(procY, temp, dst, height, tmpBounds);
    199     } else if (width > 0) {
    200         callProcX(procX, src, dst, width, srcBounds);
    201     } else if (height > 0) {
    202         callProcY(procY, src, dst, height, srcBounds);
    203     }
    204     offset->fX = bounds.left();
    205     offset->fY = bounds.top();
    206     return true;
    207 }
    208 
    209 bool SkErodeImageFilter::onFilterImage(Proxy* proxy,
    210                                        const SkBitmap& source, const Context& ctx,
    211                                        SkBitmap* dst, SkIPoint* offset) const {
    212     Proc erodeXProc = SkMorphologyGetPlatformProc(kErodeX_SkMorphologyProcType);
    213     if (!erodeXProc) {
    214         erodeXProc = erode<kX>;
    215     }
    216     Proc erodeYProc = SkMorphologyGetPlatformProc(kErodeY_SkMorphologyProcType);
    217     if (!erodeYProc) {
    218         erodeYProc = erode<kY>;
    219     }
    220     return this->filterImageGeneric(erodeXProc, erodeYProc, proxy, source, ctx, dst, offset);
    221 }
    222 
    223 bool SkDilateImageFilter::onFilterImage(Proxy* proxy,
    224                                         const SkBitmap& source, const Context& ctx,
    225                                         SkBitmap* dst, SkIPoint* offset) const {
    226     Proc dilateXProc = SkMorphologyGetPlatformProc(kDilateX_SkMorphologyProcType);
    227     if (!dilateXProc) {
    228         dilateXProc = dilate<kX>;
    229     }
    230     Proc dilateYProc = SkMorphologyGetPlatformProc(kDilateY_SkMorphologyProcType);
    231     if (!dilateYProc) {
    232         dilateYProc = dilate<kY>;
    233     }
    234     return this->filterImageGeneric(dilateXProc, dilateYProc, proxy, source, ctx, dst, offset);
    235 }
    236 
    237 void SkMorphologyImageFilter::computeFastBounds(const SkRect& src, SkRect* dst) const {
    238     if (getInput(0)) {
    239         getInput(0)->computeFastBounds(src, dst);
    240     } else {
    241         *dst = src;
    242     }
    243     dst->outset(SkIntToScalar(fRadius.width()), SkIntToScalar(fRadius.height()));
    244 }
    245 
    246 bool SkMorphologyImageFilter::onFilterBounds(const SkIRect& src, const SkMatrix& ctm,
    247                                              SkIRect* dst) const {
    248     SkIRect bounds = src;
    249     if (getInput(0) && !getInput(0)->filterBounds(src, ctm, &bounds)) {
    250         return false;
    251     }
    252     SkVector radius = SkVector::Make(SkIntToScalar(this->radius().width()),
    253                                      SkIntToScalar(this->radius().height()));
    254     ctm.mapVectors(&radius, 1);
    255     bounds.outset(SkScalarCeilToInt(radius.x()), SkScalarCeilToInt(radius.y()));
    256     *dst = bounds;
    257     return true;
    258 }
    259 
    260 #if SK_SUPPORT_GPU
    261 
    262 ///////////////////////////////////////////////////////////////////////////////
    263 
    264 class GrGLMorphologyEffect;
    265 
    266 /**
    267  * Morphology effects. Depending upon the type of morphology, either the
    268  * component-wise min (Erode_Type) or max (Dilate_Type) of all pixels in the
    269  * kernel is selected as the new color. The new color is modulated by the input
    270  * color.
    271  */
    272 class GrMorphologyEffect : public Gr1DKernelEffect {
    273 
    274 public:
    275 
    276     enum MorphologyType {
    277         kErode_MorphologyType,
    278         kDilate_MorphologyType,
    279     };
    280 
    281     static GrEffectRef* Create(GrTexture* tex, Direction dir, int radius, MorphologyType type) {
    282         AutoEffectUnref effect(SkNEW_ARGS(GrMorphologyEffect, (tex, dir, radius, type)));
    283         return CreateEffectRef(effect);
    284     }
    285 
    286     virtual ~GrMorphologyEffect();
    287 
    288     MorphologyType type() const { return fType; }
    289 
    290     static const char* Name() { return "Morphology"; }
    291 
    292     typedef GrGLMorphologyEffect GLEffect;
    293 
    294     virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE;
    295     virtual void getConstantColorComponents(GrColor* color, uint32_t* validFlags) const SK_OVERRIDE;
    296 
    297 protected:
    298 
    299     MorphologyType fType;
    300 
    301 private:
    302     virtual bool onIsEqual(const GrEffect&) const SK_OVERRIDE;
    303 
    304     GrMorphologyEffect(GrTexture*, Direction, int radius, MorphologyType);
    305 
    306     GR_DECLARE_EFFECT_TEST;
    307 
    308     typedef Gr1DKernelEffect INHERITED;
    309 };
    310 
    311 ///////////////////////////////////////////////////////////////////////////////
    312 
    313 class GrGLMorphologyEffect : public GrGLEffect {
    314 public:
    315     GrGLMorphologyEffect (const GrBackendEffectFactory&, const GrDrawEffect&);
    316 
    317     virtual void emitCode(GrGLShaderBuilder*,
    318                           const GrDrawEffect&,
    319                           EffectKey,
    320                           const char* outputColor,
    321                           const char* inputColor,
    322                           const TransformedCoordsArray&,
    323                           const TextureSamplerArray&) SK_OVERRIDE;
    324 
    325     static inline EffectKey GenKey(const GrDrawEffect&, const GrGLCaps&);
    326 
    327     virtual void setData(const GrGLUniformManager&, const GrDrawEffect&) SK_OVERRIDE;
    328 
    329 private:
    330     int width() const { return GrMorphologyEffect::WidthFromRadius(fRadius); }
    331 
    332     int                                 fRadius;
    333     GrMorphologyEffect::MorphologyType  fType;
    334     GrGLUniformManager::UniformHandle   fImageIncrementUni;
    335 
    336     typedef GrGLEffect INHERITED;
    337 };
    338 
    339 GrGLMorphologyEffect::GrGLMorphologyEffect(const GrBackendEffectFactory& factory,
    340                                            const GrDrawEffect& drawEffect)
    341     : INHERITED(factory) {
    342     const GrMorphologyEffect& m = drawEffect.castEffect<GrMorphologyEffect>();
    343     fRadius = m.radius();
    344     fType = m.type();
    345 }
    346 
    347 void GrGLMorphologyEffect::emitCode(GrGLShaderBuilder* builder,
    348                                     const GrDrawEffect&,
    349                                     EffectKey key,
    350                                     const char* outputColor,
    351                                     const char* inputColor,
    352                                     const TransformedCoordsArray& coords,
    353                                     const TextureSamplerArray& samplers) {
    354     SkString coords2D = builder->ensureFSCoords2D(coords, 0);
    355     fImageIncrementUni = builder->addUniform(GrGLShaderBuilder::kFragment_Visibility,
    356                                              kVec2f_GrSLType, "ImageIncrement");
    357 
    358     const char* func;
    359     switch (fType) {
    360         case GrMorphologyEffect::kErode_MorphologyType:
    361             builder->fsCodeAppendf("\t\t%s = vec4(1, 1, 1, 1);\n", outputColor);
    362             func = "min";
    363             break;
    364         case GrMorphologyEffect::kDilate_MorphologyType:
    365             builder->fsCodeAppendf("\t\t%s = vec4(0, 0, 0, 0);\n", outputColor);
    366             func = "max";
    367             break;
    368         default:
    369             SkFAIL("Unexpected type");
    370             func = ""; // suppress warning
    371             break;
    372     }
    373     const char* imgInc = builder->getUniformCStr(fImageIncrementUni);
    374 
    375     builder->fsCodeAppendf("\t\tvec2 coord = %s - %d.0 * %s;\n", coords2D.c_str(), fRadius, imgInc);
    376     builder->fsCodeAppendf("\t\tfor (int i = 0; i < %d; i++) {\n", this->width());
    377     builder->fsCodeAppendf("\t\t\t%s = %s(%s, ", outputColor, func, outputColor);
    378     builder->fsAppendTextureLookup(samplers[0], "coord");
    379     builder->fsCodeAppend(");\n");
    380     builder->fsCodeAppendf("\t\t\tcoord += %s;\n", imgInc);
    381     builder->fsCodeAppend("\t\t}\n");
    382     SkString modulate;
    383     GrGLSLMulVarBy4f(&modulate, 2, outputColor, inputColor);
    384     builder->fsCodeAppend(modulate.c_str());
    385 }
    386 
    387 GrGLEffect::EffectKey GrGLMorphologyEffect::GenKey(const GrDrawEffect& drawEffect,
    388                                                    const GrGLCaps&) {
    389     const GrMorphologyEffect& m = drawEffect.castEffect<GrMorphologyEffect>();
    390     EffectKey key = static_cast<EffectKey>(m.radius());
    391     key |= (m.type() << 8);
    392     return key;
    393 }
    394 
    395 void GrGLMorphologyEffect::setData(const GrGLUniformManager& uman,
    396                                    const GrDrawEffect& drawEffect) {
    397     const Gr1DKernelEffect& kern = drawEffect.castEffect<Gr1DKernelEffect>();
    398     GrTexture& texture = *kern.texture(0);
    399     // the code we generated was for a specific kernel radius
    400     SkASSERT(kern.radius() == fRadius);
    401     float imageIncrement[2] = { 0 };
    402     switch (kern.direction()) {
    403         case Gr1DKernelEffect::kX_Direction:
    404             imageIncrement[0] = 1.0f / texture.width();
    405             break;
    406         case Gr1DKernelEffect::kY_Direction:
    407             imageIncrement[1] = 1.0f / texture.height();
    408             break;
    409         default:
    410             SkFAIL("Unknown filter direction.");
    411     }
    412     uman.set2fv(fImageIncrementUni, 1, imageIncrement);
    413 }
    414 
    415 ///////////////////////////////////////////////////////////////////////////////
    416 
    417 GrMorphologyEffect::GrMorphologyEffect(GrTexture* texture,
    418                                        Direction direction,
    419                                        int radius,
    420                                        MorphologyType type)
    421     : Gr1DKernelEffect(texture, direction, radius)
    422     , fType(type) {
    423 }
    424 
    425 GrMorphologyEffect::~GrMorphologyEffect() {
    426 }
    427 
    428 const GrBackendEffectFactory& GrMorphologyEffect::getFactory() const {
    429     return GrTBackendEffectFactory<GrMorphologyEffect>::getInstance();
    430 }
    431 
    432 bool GrMorphologyEffect::onIsEqual(const GrEffect& sBase) const {
    433     const GrMorphologyEffect& s = CastEffect<GrMorphologyEffect>(sBase);
    434     return (this->texture(0) == s.texture(0) &&
    435             this->radius() == s.radius() &&
    436             this->direction() == s.direction() &&
    437             this->type() == s.type());
    438 }
    439 
    440 void GrMorphologyEffect::getConstantColorComponents(GrColor* color, uint32_t* validFlags) const {
    441     // This is valid because the color components of the result of the kernel all come
    442     // exactly from existing values in the source texture.
    443     this->updateConstantColorComponentsForModulation(color, validFlags);
    444 }
    445 
    446 ///////////////////////////////////////////////////////////////////////////////
    447 
    448 GR_DEFINE_EFFECT_TEST(GrMorphologyEffect);
    449 
    450 GrEffectRef* GrMorphologyEffect::TestCreate(SkRandom* random,
    451                                             GrContext*,
    452                                             const GrDrawTargetCaps&,
    453                                             GrTexture* textures[]) {
    454     int texIdx = random->nextBool() ? GrEffectUnitTest::kSkiaPMTextureIdx :
    455                                       GrEffectUnitTest::kAlphaTextureIdx;
    456     Direction dir = random->nextBool() ? kX_Direction : kY_Direction;
    457     static const int kMaxRadius = 10;
    458     int radius = random->nextRangeU(1, kMaxRadius);
    459     MorphologyType type = random->nextBool() ? GrMorphologyEffect::kErode_MorphologyType :
    460                                                GrMorphologyEffect::kDilate_MorphologyType;
    461 
    462     return GrMorphologyEffect::Create(textures[texIdx], dir, radius, type);
    463 }
    464 
    465 namespace {
    466 
    467 void apply_morphology_pass(GrContext* context,
    468                            GrTexture* texture,
    469                            const SkIRect& srcRect,
    470                            const SkIRect& dstRect,
    471                            int radius,
    472                            GrMorphologyEffect::MorphologyType morphType,
    473                            Gr1DKernelEffect::Direction direction) {
    474     GrPaint paint;
    475     paint.addColorEffect(GrMorphologyEffect::Create(texture,
    476                                                     direction,
    477                                                     radius,
    478                                                     morphType))->unref();
    479     context->drawRectToRect(paint, SkRect::Make(dstRect), SkRect::Make(srcRect));
    480 }
    481 
    482 bool apply_morphology(const SkBitmap& input,
    483                       const SkIRect& rect,
    484                       GrMorphologyEffect::MorphologyType morphType,
    485                       SkISize radius,
    486                       SkBitmap* dst) {
    487     GrTexture* srcTexture = input.getTexture();
    488     SkASSERT(NULL != srcTexture);
    489     GrContext* context = srcTexture->getContext();
    490     srcTexture->ref();
    491     SkAutoTUnref<GrTexture> src(srcTexture);
    492 
    493     GrContext::AutoMatrix am;
    494     am.setIdentity(context);
    495 
    496     GrContext::AutoClip acs(context, SkRect::MakeWH(SkIntToScalar(srcTexture->width()),
    497                                                     SkIntToScalar(srcTexture->height())));
    498 
    499     SkIRect dstRect = SkIRect::MakeWH(rect.width(), rect.height());
    500     GrTextureDesc desc;
    501     desc.fFlags = kRenderTarget_GrTextureFlagBit | kNoStencil_GrTextureFlagBit;
    502     desc.fWidth = rect.width();
    503     desc.fHeight = rect.height();
    504     desc.fConfig = kSkia8888_GrPixelConfig;
    505     SkIRect srcRect = rect;
    506 
    507     if (radius.fWidth > 0) {
    508         GrAutoScratchTexture ast(context, desc);
    509         GrContext::AutoRenderTarget art(context, ast.texture()->asRenderTarget());
    510         apply_morphology_pass(context, src, srcRect, dstRect, radius.fWidth,
    511                               morphType, Gr1DKernelEffect::kX_Direction);
    512         SkIRect clearRect = SkIRect::MakeXYWH(dstRect.fLeft, dstRect.fBottom,
    513                                               dstRect.width(), radius.fHeight);
    514         context->clear(&clearRect, GrMorphologyEffect::kErode_MorphologyType == morphType ?
    515                                    SK_ColorWHITE :
    516                                    SK_ColorTRANSPARENT, false);
    517         src.reset(ast.detach());
    518         srcRect = dstRect;
    519     }
    520     if (radius.fHeight > 0) {
    521         GrAutoScratchTexture ast(context, desc);
    522         GrContext::AutoRenderTarget art(context, ast.texture()->asRenderTarget());
    523         apply_morphology_pass(context, src, srcRect, dstRect, radius.fHeight,
    524                               morphType, Gr1DKernelEffect::kY_Direction);
    525         src.reset(ast.detach());
    526     }
    527     SkImageFilter::WrapTexture(src, rect.width(), rect.height(), dst);
    528     return true;
    529 }
    530 
    531 };
    532 
    533 bool SkMorphologyImageFilter::filterImageGPUGeneric(bool dilate,
    534                                                     Proxy* proxy,
    535                                                     const SkBitmap& src,
    536                                                     const Context& ctx,
    537                                                     SkBitmap* result,
    538                                                     SkIPoint* offset) const {
    539     SkBitmap input = src;
    540     SkIPoint srcOffset = SkIPoint::Make(0, 0);
    541     if (getInput(0) && !getInput(0)->getInputResultGPU(proxy, src, ctx, &input, &srcOffset)) {
    542         return false;
    543     }
    544     SkIRect bounds;
    545     if (!this->applyCropRect(ctx, proxy, input, &srcOffset, &bounds, &input)) {
    546         return false;
    547     }
    548     SkVector radius = SkVector::Make(SkIntToScalar(this->radius().width()),
    549                                      SkIntToScalar(this->radius().height()));
    550     ctx.ctm().mapVectors(&radius, 1);
    551     int width = SkScalarFloorToInt(radius.fX);
    552     int height = SkScalarFloorToInt(radius.fY);
    553 
    554     if (width < 0 || height < 0) {
    555         return false;
    556     }
    557 
    558     SkIRect srcBounds = bounds;
    559     srcBounds.offset(-srcOffset);
    560     if (width == 0 && height == 0) {
    561         input.extractSubset(result, srcBounds);
    562         offset->fX = bounds.left();
    563         offset->fY = bounds.top();
    564         return true;
    565     }
    566 
    567     GrMorphologyEffect::MorphologyType type = dilate ? GrMorphologyEffect::kDilate_MorphologyType : GrMorphologyEffect::kErode_MorphologyType;
    568     if (!apply_morphology(input, srcBounds, type,
    569                           SkISize::Make(width, height), result)) {
    570         return false;
    571     }
    572     offset->fX = bounds.left();
    573     offset->fY = bounds.top();
    574     return true;
    575 }
    576 
    577 bool SkDilateImageFilter::filterImageGPU(Proxy* proxy, const SkBitmap& src, const Context& ctx,
    578                                          SkBitmap* result, SkIPoint* offset) const {
    579     return this->filterImageGPUGeneric(true, proxy, src, ctx, result, offset);
    580 }
    581 
    582 bool SkErodeImageFilter::filterImageGPU(Proxy* proxy, const SkBitmap& src, const Context& ctx,
    583                                         SkBitmap* result, SkIPoint* offset) const {
    584     return this->filterImageGPUGeneric(false, proxy, src, ctx, result, offset);
    585 }
    586 
    587 #endif
    588