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