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