blob: cbfa5cf0e4199f78673fb69ef9fe07353241cbd2 [file] [log] [blame]
senorblanco@chromium.org05054f12012-03-02 21:05:45 +00001/*
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"
djsollen@google.com64a0ec32012-06-12 15:17:27 +00009#include "SkBitmap.h"
senorblanco@chromium.org05054f12012-03-02 21:05:45 +000010#include "SkColorPriv.h"
djsollen@google.comc73dd5c2012-08-07 15:54:32 +000011#include "SkFlattenableBuffers.h"
12#include "SkRect.h"
bsalomon@google.comcf8fb1f2012-08-02 14:03:32 +000013#if SK_SUPPORT_GPU
senorblanco@chromium.org302cffb2012-08-01 20:16:34 +000014#include "GrContext.h"
15#include "GrTexture.h"
senorblanco@chromium.org84207c42012-08-22 20:51:19 +000016#include "GrGpu.h"
17#include "gl/GrGLProgramStage.h"
18#include "effects/Gr1DKernelEffect.h"
bsalomon@google.comcf8fb1f2012-08-02 14:03:32 +000019#endif
senorblanco@chromium.org05054f12012-03-02 21:05:45 +000020
21SkMorphologyImageFilter::SkMorphologyImageFilter(SkFlattenableReadBuffer& buffer)
22 : INHERITED(buffer) {
tomhudson@google.com75589252012-04-10 17:42:21 +000023 fRadius.fWidth = buffer.readInt();
24 fRadius.fHeight = buffer.readInt();
senorblanco@chromium.org05054f12012-03-02 21:05:45 +000025}
26
senorblanco@chromium.orgf1369ce2012-08-20 14:53:21 +000027SkMorphologyImageFilter::SkMorphologyImageFilter(int radiusX, int radiusY, SkImageFilter* input)
28 : INHERITED(input), fRadius(SkISize::Make(radiusX, radiusY)) {
senorblanco@chromium.org05054f12012-03-02 21:05:45 +000029}
30
31
djsollen@google.com54924242012-03-29 15:18:04 +000032void SkMorphologyImageFilter::flatten(SkFlattenableWriteBuffer& buffer) const {
senorblanco@chromium.org05054f12012-03-02 21:05:45 +000033 this->INHERITED::flatten(buffer);
tomhudson@google.com75589252012-04-10 17:42:21 +000034 buffer.writeInt(fRadius.fWidth);
35 buffer.writeInt(fRadius.fHeight);
senorblanco@chromium.org05054f12012-03-02 21:05:45 +000036}
37
38static void erode(const SkPMColor* src, SkPMColor* dst,
39 int radius, int width, int height,
40 int srcStrideX, int srcStrideY,
41 int dstStrideX, int dstStrideY)
42{
senorblanco@chromium.org56dd6302012-04-10 17:25:44 +000043 radius = SkMin32(radius, width - 1);
44 const SkPMColor* upperSrc = src + radius * srcStrideX;
senorblanco@chromium.org05054f12012-03-02 21:05:45 +000045 for (int x = 0; x < width; ++x) {
46 const SkPMColor* lp = src;
47 const SkPMColor* up = upperSrc;
48 SkPMColor* dptr = dst;
49 for (int y = 0; y < height; ++y) {
50 int minB = 255, minG = 255, minR = 255, minA = 255;
51 for (const SkPMColor* p = lp; p <= up; p += srcStrideX) {
52 int b = SkGetPackedB32(*p);
53 int g = SkGetPackedG32(*p);
54 int r = SkGetPackedR32(*p);
55 int a = SkGetPackedA32(*p);
56 if (b < minB) minB = b;
57 if (g < minG) minG = g;
58 if (r < minR) minR = r;
59 if (a < minA) minA = a;
60 }
61 *dptr = SkPackARGB32(minA, minR, minG, minB);
62 dptr += dstStrideY;
63 lp += srcStrideY;
64 up += srcStrideY;
65 }
66 if (x >= radius) src += srcStrideX;
67 if (x + radius < width - 1) upperSrc += srcStrideX;
68 dst += dstStrideX;
69 }
70}
71
72static void erodeX(const SkBitmap& src, SkBitmap* dst, int radiusX)
73{
74 erode(src.getAddr32(0, 0), dst->getAddr32(0, 0),
75 radiusX, src.width(), src.height(),
76 1, src.rowBytesAsPixels(), 1, dst->rowBytesAsPixels());
77}
78
79static void erodeY(const SkBitmap& src, SkBitmap* dst, int radiusY)
80{
81 erode(src.getAddr32(0, 0), dst->getAddr32(0, 0),
82 radiusY, src.height(), src.width(),
83 src.rowBytesAsPixels(), 1, dst->rowBytesAsPixels(), 1);
84}
85
86static void dilate(const SkPMColor* src, SkPMColor* dst,
87 int radius, int width, int height,
88 int srcStrideX, int srcStrideY,
89 int dstStrideX, int dstStrideY)
90{
senorblanco@chromium.org56dd6302012-04-10 17:25:44 +000091 radius = SkMin32(radius, width - 1);
92 const SkPMColor* upperSrc = src + radius * srcStrideX;
senorblanco@chromium.org05054f12012-03-02 21:05:45 +000093 for (int x = 0; x < width; ++x) {
94 const SkPMColor* lp = src;
95 const SkPMColor* up = upperSrc;
96 SkPMColor* dptr = dst;
97 for (int y = 0; y < height; ++y) {
98 int maxB = 0, maxG = 0, maxR = 0, maxA = 0;
99 for (const SkPMColor* p = lp; p <= up; p += srcStrideX) {
100 int b = SkGetPackedB32(*p);
101 int g = SkGetPackedG32(*p);
102 int r = SkGetPackedR32(*p);
103 int a = SkGetPackedA32(*p);
104 if (b > maxB) maxB = b;
105 if (g > maxG) maxG = g;
106 if (r > maxR) maxR = r;
107 if (a > maxA) maxA = a;
108 }
109 *dptr = SkPackARGB32(maxA, maxR, maxG, maxB);
110 dptr += dstStrideY;
111 lp += srcStrideY;
112 up += srcStrideY;
113 }
114 if (x >= radius) src += srcStrideX;
115 if (x + radius < width - 1) upperSrc += srcStrideX;
116 dst += dstStrideX;
117 }
118}
119
120static void dilateX(const SkBitmap& src, SkBitmap* dst, int radiusX)
121{
122 dilate(src.getAddr32(0, 0), dst->getAddr32(0, 0),
123 radiusX, src.width(), src.height(),
124 1, src.rowBytesAsPixels(), 1, dst->rowBytesAsPixels());
125}
126
127static void dilateY(const SkBitmap& src, SkBitmap* dst, int radiusY)
128{
129 dilate(src.getAddr32(0, 0), dst->getAddr32(0, 0),
130 radiusY, src.height(), src.width(),
131 src.rowBytesAsPixels(), 1, dst->rowBytesAsPixels(), 1);
132}
133
senorblanco@chromium.orgf1369ce2012-08-20 14:53:21 +0000134bool SkErodeImageFilter::onFilterImage(Proxy* proxy,
135 const SkBitmap& source, const SkMatrix& ctm,
136 SkBitmap* dst, SkIPoint* offset) {
137 SkBitmap src = this->getInputResult(proxy, source, ctm, offset);
senorblanco@chromium.org05054f12012-03-02 21:05:45 +0000138 if (src.config() != SkBitmap::kARGB_8888_Config) {
139 return false;
140 }
141
142 SkAutoLockPixels alp(src);
143 if (!src.getPixels()) {
144 return false;
145 }
146
147 dst->setConfig(src.config(), src.width(), src.height());
148 dst->allocPixels();
149
150 int width = radius().width();
151 int height = radius().height();
152
153 if (width < 0 || height < 0) {
154 return false;
155 }
156
157 if (width == 0 && height == 0) {
158 src.copyTo(dst, dst->config());
159 return true;
160 }
161
162 SkBitmap temp;
163 temp.setConfig(dst->config(), dst->width(), dst->height());
164 if (!temp.allocPixels()) {
165 return false;
166 }
167
168 if (width > 0 && height > 0) {
169 erodeX(src, &temp, width);
170 erodeY(temp, dst, height);
171 } else if (width > 0) {
172 erodeX(src, dst, width);
173 } else if (height > 0) {
174 erodeY(src, dst, height);
175 }
176 return true;
177}
178
senorblanco@chromium.orgf1369ce2012-08-20 14:53:21 +0000179bool SkDilateImageFilter::onFilterImage(Proxy* proxy,
180 const SkBitmap& source, const SkMatrix& ctm,
181 SkBitmap* dst, SkIPoint* offset) {
182 SkBitmap src = this->getInputResult(proxy, source, ctm, offset);
senorblanco@chromium.org05054f12012-03-02 21:05:45 +0000183 if (src.config() != SkBitmap::kARGB_8888_Config) {
184 return false;
185 }
186
187 SkAutoLockPixels alp(src);
188 if (!src.getPixels()) {
189 return false;
190 }
191
192 dst->setConfig(src.config(), src.width(), src.height());
193 dst->allocPixels();
194
195 int width = radius().width();
196 int height = radius().height();
197
198 if (width < 0 || height < 0) {
199 return false;
200 }
201
202 if (width == 0 && height == 0) {
203 src.copyTo(dst, dst->config());
204 return true;
205 }
206
207 SkBitmap temp;
208 temp.setConfig(dst->config(), dst->width(), dst->height());
209 if (!temp.allocPixels()) {
210 return false;
211 }
212
213 if (width > 0 && height > 0) {
214 dilateX(src, &temp, width);
215 dilateY(temp, dst, height);
216 } else if (width > 0) {
217 dilateX(src, dst, width);
218 } else if (height > 0) {
219 dilateY(src, dst, height);
220 }
221 return true;
222}
223
bsalomon@google.comcf8fb1f2012-08-02 14:03:32 +0000224#if SK_SUPPORT_GPU
senorblanco@chromium.org84207c42012-08-22 20:51:19 +0000225
226///////////////////////////////////////////////////////////////////////////////
227
228class GrGLMorphologyEffect;
229
230/**
231 * Morphology effects. Depending upon the type of morphology, either the
232 * component-wise min (Erode_Type) or max (Dilate_Type) of all pixels in the
233 * kernel is selected as the new color. The new color is modulated by the input
234 * color.
235 */
236class GrMorphologyEffect : public Gr1DKernelEffect {
237
238public:
239
240 enum MorphologyType {
241 kErode_MorphologyType,
242 kDilate_MorphologyType,
243 };
244
245 GrMorphologyEffect(GrTexture*, Direction, int radius, MorphologyType);
246 virtual ~GrMorphologyEffect();
247
248 MorphologyType type() const { return fType; }
249
250 static const char* Name() { return "Morphology"; }
251
252 typedef GrGLMorphologyEffect GLProgramStage;
253
254 virtual const GrProgramStageFactory& getFactory() const SK_OVERRIDE;
255 virtual bool isEqual(const GrCustomStage&) const SK_OVERRIDE;
256
257protected:
258
259 MorphologyType fType;
260
261private:
262 GR_DECLARE_CUSTOM_STAGE_TEST;
263
264 typedef Gr1DKernelEffect INHERITED;
265};
266
267///////////////////////////////////////////////////////////////////////////////
268
269class GrGLMorphologyEffect : public GrGLProgramStage {
270public:
271 GrGLMorphologyEffect (const GrProgramStageFactory& factory,
272 const GrCustomStage& stage);
273
274 virtual void setupVariables(GrGLShaderBuilder* builder) SK_OVERRIDE;
275 virtual void emitVS(GrGLShaderBuilder* state,
276 const char* vertexCoords) SK_OVERRIDE {};
277 virtual void emitFS(GrGLShaderBuilder* state,
278 const char* outputColor,
279 const char* inputColor,
280 const char* samplerName) SK_OVERRIDE;
281
282 static inline StageKey GenKey(const GrCustomStage& s, const GrGLCaps& caps);
283
284 virtual void setData(const GrGLUniformManager&,
285 const GrCustomStage&,
286 const GrRenderTarget*,
287 int stageNum) SK_OVERRIDE;
288
289private:
290 int width() const { return GrMorphologyEffect::WidthFromRadius(fRadius); }
291
292 int fRadius;
293 GrMorphologyEffect::MorphologyType fType;
294 GrGLUniformManager::UniformHandle fImageIncrementUni;
295
296 typedef GrGLProgramStage INHERITED;
297};
298
299GrGLMorphologyEffect::GrGLMorphologyEffect(const GrProgramStageFactory& factory,
300 const GrCustomStage& stage)
301 : GrGLProgramStage(factory)
302 , fImageIncrementUni(GrGLUniformManager::kInvalidUniformHandle) {
303 const GrMorphologyEffect& m = static_cast<const GrMorphologyEffect&>(stage);
304 fRadius = m.radius();
305 fType = m.type();
306}
307
308void GrGLMorphologyEffect::setupVariables(GrGLShaderBuilder* builder) {
309 fImageIncrementUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
310 kVec2f_GrSLType, "ImageIncrement");
311}
312
313void GrGLMorphologyEffect::emitFS(GrGLShaderBuilder* builder,
314 const char* outputColor,
315 const char* inputColor,
316 const char* samplerName) {
317 SkString* code = &builder->fFSCode;
318
319 const char* func;
320 switch (fType) {
321 case GrMorphologyEffect::kErode_MorphologyType:
322 code->appendf("\t\tvec4 value = vec4(1, 1, 1, 1);\n");
323 func = "min";
324 break;
325 case GrMorphologyEffect::kDilate_MorphologyType:
326 code->appendf("\t\tvec4 value = vec4(0, 0, 0, 0);\n");
327 func = "max";
328 break;
329 default:
330 GrCrash("Unexpected type");
331 func = ""; // suppress warning
332 break;
333 }
334 const char* imgInc = builder->getUniformCStr(fImageIncrementUni);
335
336 code->appendf("\t\tvec2 coord = %s - %d.0 * %s;\n",
337 builder->fSampleCoords.c_str(), fRadius, imgInc);
338 code->appendf("\t\tfor (int i = 0; i < %d; i++) {\n", this->width());
339 code->appendf("\t\t\tvalue = %s(value, ", func);
340 builder->emitTextureLookup(samplerName, "coord");
341 code->appendf(");\n");
342 code->appendf("\t\t\tcoord += %s;\n", imgInc);
343 code->appendf("\t\t}\n");
344 code->appendf("\t\t%s = value%s;\n", outputColor, builder->fModulate.c_str());
345}
346
347GrGLProgramStage::StageKey GrGLMorphologyEffect::GenKey(const GrCustomStage& s,
348 const GrGLCaps& caps) {
349 const GrMorphologyEffect& m = static_cast<const GrMorphologyEffect&>(s);
350 StageKey key = static_cast<StageKey>(m.radius());
351 key |= (m.type() << 8);
352 return key;
353}
354
355void GrGLMorphologyEffect::setData(const GrGLUniformManager& uman,
356 const GrCustomStage& data,
357 const GrRenderTarget*,
358 int stageNum) {
359 const Gr1DKernelEffect& kern =
360 static_cast<const Gr1DKernelEffect&>(data);
361 GrGLTexture& texture =
362 *static_cast<GrGLTexture*>(data.texture(0));
363 // the code we generated was for a specific kernel radius
364 GrAssert(kern.radius() == fRadius);
365 float imageIncrement[2] = { 0 };
366 switch (kern.direction()) {
367 case Gr1DKernelEffect::kX_Direction:
368 imageIncrement[0] = 1.0f / texture.width();
369 break;
370 case Gr1DKernelEffect::kY_Direction:
371 imageIncrement[1] = 1.0f / texture.height();
372 break;
373 default:
374 GrCrash("Unknown filter direction.");
375 }
376 uman.set2fv(fImageIncrementUni, 0, 1, imageIncrement);
377}
378
379///////////////////////////////////////////////////////////////////////////////
380
381GrMorphologyEffect::GrMorphologyEffect(GrTexture* texture,
382 Direction direction,
383 int radius,
384 MorphologyType type)
385 : Gr1DKernelEffect(texture, direction, radius)
386 , fType(type) {
387}
388
389GrMorphologyEffect::~GrMorphologyEffect() {
390}
391
392const GrProgramStageFactory& GrMorphologyEffect::getFactory() const {
393 return GrTProgramStageFactory<GrMorphologyEffect>::getInstance();
394}
395
396bool GrMorphologyEffect::isEqual(const GrCustomStage& sBase) const {
397 const GrMorphologyEffect& s =
398 static_cast<const GrMorphologyEffect&>(sBase);
399 return (INHERITED::isEqual(sBase) &&
400 this->radius() == s.radius() &&
401 this->direction() == s.direction() &&
402 this->type() == s.type());
403}
404
405///////////////////////////////////////////////////////////////////////////////
406
407GR_DEFINE_CUSTOM_STAGE_TEST(GrMorphologyEffect);
408
409GrCustomStage* GrMorphologyEffect::TestCreate(SkRandom* random,
410 GrContext* context,
411 GrTexture* textures[]) {
412 int texIdx = random->nextBool() ? GrCustomStageUnitTest::kSkiaPMTextureIdx :
413 GrCustomStageUnitTest::kAlphaTextureIdx;
414 Direction dir = random->nextBool() ? kX_Direction : kY_Direction;
415 static const int kMaxRadius = 10;
416 int radius = random->nextRangeU(1, kMaxRadius);
417 MorphologyType type = random->nextBool() ? GrMorphologyEffect::kErode_MorphologyType :
418 GrMorphologyEffect::kDilate_MorphologyType;
419
420 return SkNEW_ARGS(GrMorphologyEffect, (textures[texIdx], dir, radius, type));
421}
422
423namespace {
424
425void apply_morphology_pass(GrContext* context,
426 GrTexture* texture,
427 const SkRect& rect,
428 int radius,
429 GrMorphologyEffect::MorphologyType morphType,
430 Gr1DKernelEffect::Direction direction) {
431 GrMatrix sampleM;
432 sampleM.setIDiv(texture->width(), texture->height());
433 GrPaint paint;
434 paint.reset();
435 paint.textureSampler(0)->reset(sampleM);
436 paint.textureSampler(0)->setCustomStage(SkNEW_ARGS(GrMorphologyEffect, (texture, direction, radius, morphType)))->unref();
437 context->drawRect(paint, rect);
438}
439
440GrTexture* apply_morphology(GrTexture* srcTexture,
441 const GrRect& rect,
442 GrMorphologyEffect::MorphologyType morphType,
443 SkISize radius) {
444 GrContext* context = srcTexture->getContext();
445 srcTexture->ref();
446 GrContext::AutoMatrix avm(context, GrMatrix::I());
447 GrContext::AutoClip acs(context, GrRect::MakeWH(SkIntToScalar(srcTexture->width()),
448 SkIntToScalar(srcTexture->height())));
449 GrTextureDesc desc;
450 desc.fFlags = kRenderTarget_GrTextureFlagBit | kNoStencil_GrTextureFlagBit;
451 desc.fWidth = SkScalarCeilToInt(rect.width());
452 desc.fHeight = SkScalarCeilToInt(rect.height());
453 desc.fConfig = kRGBA_8888_GrPixelConfig;
454 if (radius.fWidth > 0) {
455 GrAutoScratchTexture ast(context, desc);
456 GrContext::AutoRenderTarget art(context, ast.texture()->asRenderTarget());
457 apply_morphology_pass(context, srcTexture, rect, radius.fWidth,
458 morphType, Gr1DKernelEffect::kX_Direction);
459 SkIRect clearRect = SkIRect::MakeXYWH(
460 SkScalarFloorToInt(rect.fLeft),
461 SkScalarFloorToInt(rect.fBottom),
462 SkScalarFloorToInt(rect.width()),
463 radius.fHeight);
464 context->clear(&clearRect, 0x0);
465 srcTexture->unref();
466 srcTexture = ast.detach();
467 }
468 if (radius.fHeight > 0) {
469 GrAutoScratchTexture ast(context, desc);
470 GrContext::AutoRenderTarget art(context, ast.texture()->asRenderTarget());
471 apply_morphology_pass(context, srcTexture, rect, radius.fHeight,
472 morphType, Gr1DKernelEffect::kY_Direction);
473 srcTexture->unref();
474 srcTexture = ast.detach();
475 }
476 return srcTexture;
477}
478
479};
480
481GrTexture* SkDilateImageFilter::onFilterImageGPU(GrTexture* src, const SkRect& rect) {
senorblanco@chromium.orgf1369ce2012-08-20 14:53:21 +0000482 SkAutoTUnref<GrTexture> input(this->getInputResultAsTexture(src, rect));
senorblanco@chromium.org84207c42012-08-22 20:51:19 +0000483 return apply_morphology(src, rect, GrMorphologyEffect::kDilate_MorphologyType, radius());
senorblanco@chromium.org05054f12012-03-02 21:05:45 +0000484}
485
senorblanco@chromium.org302cffb2012-08-01 20:16:34 +0000486GrTexture* SkErodeImageFilter::onFilterImageGPU(GrTexture* src, const SkRect& rect) {
senorblanco@chromium.orgf1369ce2012-08-20 14:53:21 +0000487 SkAutoTUnref<GrTexture> input(this->getInputResultAsTexture(src, rect));
senorblanco@chromium.org84207c42012-08-22 20:51:19 +0000488 return apply_morphology(src, rect, GrMorphologyEffect::kErode_MorphologyType, radius());
senorblanco@chromium.org05054f12012-03-02 21:05:45 +0000489}
490
senorblanco@chromium.org84207c42012-08-22 20:51:19 +0000491#endif
492
senorblanco@chromium.org05054f12012-03-02 21:05:45 +0000493SK_DEFINE_FLATTENABLE_REGISTRAR(SkDilateImageFilter)
494SK_DEFINE_FLATTENABLE_REGISTRAR(SkErodeImageFilter)