initial impl of SkImageFilters : virtual signature will change!
authorreed@google.com <reed@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81>
Tue, 20 Dec 2011 16:19:00 +0000 (16:19 +0000)
committerreed@google.com <reed@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81>
Tue, 20 Dec 2011 16:19:00 +0000 (16:19 +0000)
Do not invest too much in other subclasses until this API solidifies.

git-svn-id: http://skia.googlecode.com/svn/trunk@2903 2bbb7eff-a529-9590-31e7-b0007b416f81

gm/testimagefilters.cpp [new file with mode: 0644]
gyp/effects.gyp
gyp/gmslides.gypi
include/core/SkImageFilter.h
include/effects/SkTestImageFilters.h [new file with mode: 0755]
src/core/SkDevice.cpp
src/core/SkPaint.cpp
src/effects/SkTestImageFilters.cpp [new file with mode: 0755]

diff --git a/gm/testimagefilters.cpp b/gm/testimagefilters.cpp
new file mode 100644 (file)
index 0000000..e484e1b
--- /dev/null
@@ -0,0 +1,117 @@
+/*
+ * 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 "SkTestImageFilters.h"
+
+#define FILTER_WIDTH    SkIntToScalar(150)
+#define FILTER_HEIGHT   SkIntToScalar(200)
+
+static SkImageFilter* make0() { return new SkDownSampleImageFilter(SK_Scalar1 / 5); }
+static SkImageFilter* make1() { return new SkOffsetImageFilter(SkIntToScalar(16), SkIntToScalar(16)); }
+static SkImageFilter* make2() {
+    SkColorFilter* cf = SkColorFilter::CreateModeFilter(SK_ColorBLUE,
+                                                        SkXfermode::kSrcIn_Mode);
+    SkAutoUnref aur(cf);
+    return new SkColorFilterImageFilter(cf);
+}
+static SkImageFilter* make3() {
+    SkImageFilter* outer = new SkOffsetImageFilter(SkIntToScalar(16), SkIntToScalar(16));
+    SkImageFilter* inner = new SkDownSampleImageFilter(SK_Scalar1 / 5);
+    SkAutoUnref aur0(outer);
+    SkAutoUnref aur1(inner);
+    return new SkComposeImageFilter(outer, inner);
+}
+static SkImageFilter* make4() {
+    SkImageFilter* first = new SkOffsetImageFilter(SkIntToScalar(16), SkIntToScalar(16));
+    SkImageFilter* second = new SkDownSampleImageFilter(SK_Scalar1 / 5);
+    SkAutoUnref aur0(first);
+    SkAutoUnref aur1(second);
+    return new SkMergeImageFilter(first, second);
+}
+
+static SkImageFilter* make5() {
+    SkImageFilter* outer = new SkOffsetImageFilter(SkIntToScalar(16), SkIntToScalar(16));
+    SkImageFilter* inner = new SkDownSampleImageFilter(SK_Scalar1 / 5);
+    SkAutoUnref aur0(outer);
+    SkAutoUnref aur1(inner);
+    SkImageFilter* compose = new SkComposeImageFilter(outer, inner);
+    SkAutoUnref aur2(compose);
+
+    SkColorFilter* cf = SkColorFilter::CreateModeFilter(0x880000FF,
+                                                        SkXfermode::kSrcIn_Mode);
+    SkAutoUnref aur3(cf);
+    SkImageFilter* blue = new SkColorFilterImageFilter(cf);
+    SkAutoUnref aur4(blue);
+
+    return new SkMergeImageFilter(compose, blue);
+}
+
+static void draw0(SkCanvas* canvas) {
+    SkPaint p;
+    p.setAntiAlias(true);
+    SkRect r = SkRect::MakeWH(FILTER_WIDTH, FILTER_HEIGHT);
+    r.inset(SK_Scalar1, SK_Scalar1);
+    p.setColor(SK_ColorRED);
+    canvas->drawOval(r, p);
+}
+
+class TestImageFiltersGM : public skiagm::GM {
+public:
+    TestImageFiltersGM () {}
+
+protected:
+
+    virtual SkString onShortName() {
+        return SkString("testimagefilters");
+    }
+
+    virtual SkISize onISize() { return SkISize::Make(640, 480); }
+
+    virtual void onDraw(SkCanvas* canvas) {
+        static SkImageFilter* (*gFilterProc[])() = {
+            make0, make1, make2, make3, make4, make5
+        };
+        
+        const SkRect bounds = SkRect::MakeWH(FILTER_WIDTH, FILTER_HEIGHT);
+        
+        const SkScalar dx = bounds.width() * 8 / 7;
+        const SkScalar dy = bounds.height() * 8 / 7;
+
+        for (size_t i = 0; i < SK_ARRAY_COUNT(gFilterProc); ++i) {
+            int ix = i % 3;
+            int iy = i / 3;
+
+            SkPaint paint;
+            paint.setImageFilter(gFilterProc[i]())->unref();
+
+            SkAutoCanvasRestore acr(canvas, true);
+            canvas->translate(ix * dx, iy * dy);
+
+            SkPaint p;
+            p.setColor(0xFFCCCCCC);
+            canvas->drawRect(bounds, p);
+            
+            canvas->saveLayer(&bounds, &paint);
+            draw0(canvas);
+        }
+    }
+
+private:
+    typedef GM INHERITED;
+};
+
+//////////////////////////////////////////////////////////////////////////////
+
+static skiagm::GM* MyFactory(void*) { return new TestImageFiltersGM; }
+static skiagm::GMRegistry reg(MyFactory);
+
+
index 4549439..39d9c8c 100644 (file)
@@ -69,6 +69,7 @@
         '../src/effects/SkPorterDuff.cpp',
         '../src/effects/SkRadialGradient_Table.h',
         '../src/effects/SkRectShape.cpp',
+        '../src/effects/SkTestImageFilters.cpp',
         '../src/effects/SkTransparentShader.cpp',
       ],
       'direct_dependent_settings': {
index d20cf73..aee917d 100644 (file)
@@ -36,6 +36,7 @@
     '../gm/shapes.cpp',
     '../gm/strokerects.cpp',
     '../gm/strokes.cpp',
+    '../gm/testimagefilters.cpp',
     '../gm/texdata.cpp',
     '../gm/tilemodes.cpp',
     '../gm/tinybitmap.cpp',
index bd4e8f5..1858791 100644 (file)
@@ -23,7 +23,17 @@ struct SkPoint;
  *  then be handed to the imagefilter, who in turn creates a new bitmap which
  *  is what will finally be drawn to the device (using the original xfermode).
  *
- *  If the imagefilter returns false, nothing is drawn.
+ *  THIS SIGNATURE IS TEMPORARY
+ *
+ *  There are several weaknesses in this function signature:
+ *  1. Does not expose the destination/target device, so filters that can draw
+ *     directly to it are unable to take advantage of that optimization.
+ *  2. Does not expose a way to create a "compabitible" image (i.e. gpu -> gpu)
+ *  3. As with #1, the filter is unable to "read" the dest (which would be slow)
+ *
+ *  Therefore, we should not create any real dependencies on this API yet -- it
+ *  is being checked in as a check-point so we can explore these and other
+ *  considerations.
  */
 class SK_API SkImageFilter : public SkFlattenable {
 public:
@@ -41,10 +51,16 @@ public:
      *  If the result image cannot be created, return false, in which case both
      *  the result and offset parameters will be ignored by the caller.
      */
-    bool filterImage(const SkBitmap& src, const SkMatrix&,
+    bool filterImage(const SkBitmap& src, const SkMatrix& ctm,
                      SkBitmap* result, SkIPoint* offset);
 
     /**
+     *  Given the src bounds of an image, this returns the bounds of the result
+     *  image after the filter has been applied.
+     */
+    bool filterBounds(const SkIRect& src, const SkMatrix& ctm, SkIRect* dst);
+
+    /**
      *  Experimental.
      *
      *  If the filter can be expressed as a gaussian-blur, return true and
@@ -55,8 +71,12 @@ public:
 protected:
     SkImageFilter() {}
     explicit SkImageFilter(SkFlattenableReadBuffer& rb) : INHERITED(rb) {}
+
+    // Default impl returns false
     virtual bool onFilterImage(const SkBitmap& src, const SkMatrix&,
                                SkBitmap* result, SkIPoint* offset);
+    // Default impl copies src into dst and returns true
+    virtual bool onFilterBounds(const SkIRect&, const SkMatrix&, SkIRect*);
 
 private:
     typedef SkFlattenable INHERITED;
diff --git a/include/effects/SkTestImageFilters.h b/include/effects/SkTestImageFilters.h
new file mode 100755 (executable)
index 0000000..4ec697e
--- /dev/null
@@ -0,0 +1,157 @@
+
+#ifndef _SkTestImageFilters_h
+#define _SkTestImageFilters_h
+
+#include "SkImageFilter.h"
+
+class SkOffsetImageFilter : public SkImageFilter {
+public:
+    SkOffsetImageFilter(SkScalar dx, SkScalar dy) {
+        fOffset.set(dx, dy);
+    }
+
+    static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
+        return SkNEW_ARGS(SkOffsetImageFilter, (buffer));
+    }
+
+protected:
+    SkOffsetImageFilter(SkFlattenableReadBuffer& buffer);
+
+    virtual bool onFilterImage(const SkBitmap& src, const SkMatrix&,
+                               SkBitmap* result, SkIPoint* loc) SK_OVERRIDE;
+    virtual bool onFilterBounds(const SkIRect&, const SkMatrix&, SkIRect*) SK_OVERRIDE;
+    // overrides from SkFlattenable
+    virtual void flatten(SkFlattenableWriteBuffer&) SK_OVERRIDE;
+    virtual Factory getFactory() SK_OVERRIDE;
+
+private:
+    SkVector fOffset;
+
+    typedef SkImageFilter INHERITED;
+};
+
+class SkComposeImageFilter : public SkImageFilter {
+public:
+    SkComposeImageFilter(SkImageFilter* outer, SkImageFilter* inner) {
+        fOuter = outer;
+        fInner = inner;
+        SkSafeRef(outer);
+        SkSafeRef(inner);
+    }
+    virtual ~SkComposeImageFilter();
+
+    static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
+        return SkNEW_ARGS(SkComposeImageFilter, (buffer));
+    }
+    
+protected:
+    SkComposeImageFilter(SkFlattenableReadBuffer& buffer);
+    
+    virtual bool onFilterImage(const SkBitmap& src, const SkMatrix&,
+                               SkBitmap* result, SkIPoint* loc) SK_OVERRIDE;
+    virtual bool onFilterBounds(const SkIRect&, const SkMatrix&, SkIRect*) SK_OVERRIDE;
+    // overrides from SkFlattenable
+    virtual void flatten(SkFlattenableWriteBuffer&) SK_OVERRIDE;
+    virtual Factory getFactory() SK_OVERRIDE;
+    
+private:
+    SkImageFilter*  fOuter;
+    SkImageFilter*  fInner;
+    
+    typedef SkImageFilter INHERITED;
+};
+
+#include "SkXfermode.h"
+
+class SkMergeImageFilter : public SkImageFilter {
+public:
+    SkMergeImageFilter(SkImageFilter* first, SkImageFilter* second,
+                       SkXfermode::Mode = SkXfermode::kSrcOver_Mode);
+    SkMergeImageFilter(SkImageFilter* const filters[], int count,
+                       const SkXfermode::Mode modes[] = NULL);
+    virtual ~SkMergeImageFilter();
+    
+    static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
+        return SkNEW_ARGS(SkMergeImageFilter, (buffer));
+    }
+    
+protected:
+    SkMergeImageFilter(SkFlattenableReadBuffer& buffer);
+    
+    virtual bool onFilterImage(const SkBitmap& src, const SkMatrix&,
+                               SkBitmap* result, SkIPoint* loc) SK_OVERRIDE;
+    virtual bool onFilterBounds(const SkIRect&, const SkMatrix&, SkIRect*) SK_OVERRIDE;
+    // overrides from SkFlattenable
+    virtual void flatten(SkFlattenableWriteBuffer&) SK_OVERRIDE;
+    virtual Factory getFactory() SK_OVERRIDE;
+    
+private:
+    SkImageFilter**     fFilters;
+    uint8_t*            fModes; // SkXfermode::Mode
+    int                 fCount;
+
+    // private storage, to avoid dynamically allocating storage for our copy
+    // of the filters and modes (unless fCount is so large we can't fit).
+    intptr_t    fStorage[16];
+
+    void initAlloc(int count, bool hasModes);
+    void init(SkImageFilter* const [], int count, const SkXfermode::Mode []);
+    
+    typedef SkImageFilter INHERITED;
+};
+
+class SkColorFilter;
+
+class SkColorFilterImageFilter : public SkImageFilter {
+public:
+    SkColorFilterImageFilter(SkColorFilter* cf) : fColorFilter(cf) {
+        SkSafeRef(cf);
+    }
+    virtual ~SkColorFilterImageFilter();
+
+    static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
+        return SkNEW_ARGS(SkColorFilterImageFilter, (buffer));
+    }
+    
+protected:
+    SkColorFilterImageFilter(SkFlattenableReadBuffer& buffer);
+    
+    virtual bool onFilterImage(const SkBitmap& src, const SkMatrix&,
+                               SkBitmap* result, SkIPoint* loc) SK_OVERRIDE;
+    // overrides from SkFlattenable
+    virtual void flatten(SkFlattenableWriteBuffer&) SK_OVERRIDE;
+    virtual Factory getFactory() SK_OVERRIDE;
+    
+private:
+    SkColorFilter*  fColorFilter;
+    
+    typedef SkImageFilter INHERITED;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+// Fun mode that scales down (only) and then scales back up to look pixelated
+class SkDownSampleImageFilter : public SkImageFilter {
+public:
+    SkDownSampleImageFilter(SkScalar scale) : fScale(scale) {}
+    
+    static SkFlattenable* CreateProc(SkFlattenableReadBuffer& buffer) {
+        return SkNEW_ARGS(SkDownSampleImageFilter, (buffer));
+    }
+    
+protected:
+    SkDownSampleImageFilter(SkFlattenableReadBuffer& buffer);
+    
+    virtual bool onFilterImage(const SkBitmap& src, const SkMatrix&,
+                               SkBitmap* result, SkIPoint* loc) SK_OVERRIDE;
+    // overrides from SkFlattenable
+    virtual void flatten(SkFlattenableWriteBuffer&) SK_OVERRIDE;
+    virtual Factory getFactory()  SK_OVERRIDE;
+    
+private:
+    SkScalar fScale;
+    
+    typedef SkImageFilter INHERITED;
+};
+
+#endif
index 1865212..4cad466 100644 (file)
@@ -7,6 +7,7 @@
  */
 #include "SkDevice.h"
 #include "SkDraw.h"
+#include "SkImageFilter.h"
 #include "SkMetaData.h"
 #include "SkRect.h"
 
@@ -353,7 +354,20 @@ void SkDevice::drawVertices(const SkDraw& draw, SkCanvas::VertexMode vmode,
 
 void SkDevice::drawDevice(const SkDraw& draw, SkDevice* device,
                               int x, int y, const SkPaint& paint) {
-    draw.drawSprite(device->accessBitmap(false), x, y, paint);
+    SkBitmap output;
+    const SkBitmap* src = &device->accessBitmap(false);
+    SkImageFilter* filter = paint.getImageFilter();
+
+    if (filter) {
+        SkIPoint loc;
+        loc.set(x, y);
+        if (filter->filterImage(*src, *draw.fMatrix, &output, &loc)) {
+            src = &output;
+            x = loc.fX;
+            y = loc.fY;
+        }
+    }
+    draw.drawSprite(*src, x, y, paint);
 }
 
 ///////////////////////////////////////////////////////////////////////////////
index ba99348..c0880b2 100644 (file)
@@ -1976,17 +1976,30 @@ bool SkPaint::nothingToDraw() const {
 //////////// Move these to their own file soon.
 
 bool SkImageFilter::filterImage(const SkBitmap& src, const SkMatrix& matrix,
-                                SkBitmap* result, SkIPoint* offset) {
+                                SkBitmap* result, SkIPoint* loc) {
     SkASSERT(result);
-    SkASSERT(offset);
-    return this->onFilterImage(src, matrix, result, offset);
+    SkASSERT(loc);
+    return this->onFilterImage(src, matrix, result, loc);
+}
+
+bool SkImageFilter::filterBounds(const SkIRect& src, const SkMatrix& ctm,
+                                 SkIRect* dst) {
+    SkASSERT(&src);
+    SkASSERT(dst);
+    return this->onFilterBounds(src, ctm, dst);
 }
 
 bool SkImageFilter::onFilterImage(const SkBitmap& src, const SkMatrix&,
-                                  SkBitmap* result, SkIPoint* offset) {
+                                  SkBitmap*, SkIPoint*) {
     return false;
 }
 
+bool SkImageFilter::onFilterBounds(const SkIRect& src, const SkMatrix& ctm,
+                                   SkIRect* dst) {
+    *dst = src;
+    return true;
+}
+
 bool SkImageFilter::asABlur(SkSize* sigma) const {
     return false;
 }
diff --git a/src/effects/SkTestImageFilters.cpp b/src/effects/SkTestImageFilters.cpp
new file mode 100755 (executable)
index 0000000..c974365
--- /dev/null
@@ -0,0 +1,396 @@
+#include "SkTestImageFilters.h"
+#include "SkCanvas.h"
+
+bool SkOffsetImageFilter::onFilterImage(const SkBitmap& src,
+                                        const SkMatrix& matrix,
+                                        SkBitmap* result,
+                                        SkIPoint* loc) {
+    SkVector vec;
+    matrix.mapVectors(&vec, &fOffset, 1);
+    
+    loc->fX += SkScalarRoundToInt(vec.fX);
+    loc->fY += SkScalarRoundToInt(vec.fY);
+    *result = src;
+    return true;
+}
+
+bool SkOffsetImageFilter::onFilterBounds(const SkIRect& src, const SkMatrix& ctm,
+                                         SkIRect* dst) {
+    SkVector vec;
+    ctm.mapVectors(&vec, &fOffset, 1);
+
+    *dst = src;
+    dst->offset(SkScalarRoundToInt(vec.fX), SkScalarRoundToInt(vec.fY));
+    return true;
+}
+
+void SkOffsetImageFilter::flatten(SkFlattenableWriteBuffer& buffer) {
+    this->INHERITED::flatten(buffer);
+    buffer.writeScalar(fOffset.x());
+    buffer.writeScalar(fOffset.y());
+}
+
+SkOffsetImageFilter::SkOffsetImageFilter(SkFlattenableReadBuffer& buffer) : INHERITED(buffer) {
+    fOffset.fX = buffer.readScalar();
+    fOffset.fY = buffer.readScalar();
+}
+
+SkFlattenable::Factory SkOffsetImageFilter::getFactory() {
+    return CreateProc;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkComposeImageFilter::~SkComposeImageFilter() {
+    SkSafeUnref(fInner);
+    SkSafeUnref(fOuter);
+}
+
+bool SkComposeImageFilter::onFilterImage(const SkBitmap& src,
+                                         const SkMatrix& ctm,
+                                         SkBitmap* result,
+                                         SkIPoint* loc) {
+    if (!fOuter && !fInner) {
+        return false;
+    }
+    
+    if (!fOuter || !fInner) {
+        return (fOuter ? fOuter : fInner)->filterImage(src, ctm, result, loc);
+    }
+    
+    SkBitmap tmp;
+    return fInner->filterImage(src, ctm, &tmp, loc) &&
+    fOuter->filterImage(tmp, ctm, result, loc);
+}
+
+bool SkComposeImageFilter::onFilterBounds(const SkIRect& src,
+                                          const SkMatrix& ctm,
+                                          SkIRect* dst) {
+    if (!fOuter && !fInner) {
+        return false;
+    }
+    
+    if (!fOuter || !fInner) {
+        return (fOuter ? fOuter : fInner)->filterBounds(src, ctm, dst);
+    }
+    
+    SkIRect tmp;
+    return fInner->filterBounds(src, ctm, &tmp) &&
+           fOuter->filterBounds(tmp, ctm, dst);
+}
+
+void SkComposeImageFilter::flatten(SkFlattenableWriteBuffer& buffer) {
+    this->INHERITED::flatten(buffer);
+
+    buffer.writeFlattenable(fOuter);
+    buffer.writeFlattenable(fInner);
+}
+
+SkComposeImageFilter::SkComposeImageFilter(SkFlattenableReadBuffer& buffer) : INHERITED(buffer) {
+    fOuter = (SkImageFilter*)buffer.readFlattenable();
+    fInner = (SkImageFilter*)buffer.readFlattenable();
+}
+
+SkFlattenable::Factory SkComposeImageFilter::getFactory() {
+    return CreateProc;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+template <typename T> T* SkSafeRefReturn(T* obj) {
+    SkSafeRef(obj);
+    return obj;
+}
+
+void SkMergeImageFilter::initAlloc(int count, bool hasModes) {
+    if (count < 1) {
+        fFilters = NULL;
+        fModes = NULL;
+        fCount = 0;
+    } else {
+        int modeCount = hasModes ? count : 0;
+        size_t size = sizeof(SkImageFilter*) * count + sizeof(uint8_t) * modeCount;
+        if (size <= sizeof(fStorage)) {
+            fFilters = SkTCast<SkImageFilter**>(fStorage);
+        } else {
+            fFilters = SkTCast<SkImageFilter**>(sk_malloc_throw(size));
+        }
+        fModes = hasModes ? SkTCast<uint8_t*>(fFilters + count) : NULL;
+        fCount = count;
+    }
+}
+
+void SkMergeImageFilter::init(SkImageFilter* const filters[], int count,
+                              const SkXfermode::Mode modes[]) {
+    this->initAlloc(count, !!modes);
+    for (int i = 0; i < count; ++i) {
+        fFilters[i] = SkSafeRefReturn(filters[i]);
+        if (modes) {
+            fModes[i] = SkToU8(modes[i]);
+        }
+    }
+}
+
+SkMergeImageFilter::SkMergeImageFilter(SkImageFilter* first, SkImageFilter* second,
+                                       SkXfermode::Mode mode) {
+    SkImageFilter* filters[] = { first, second };
+    SkXfermode::Mode modes[] = { mode, mode };
+    this->init(filters, 2, SkXfermode::kSrcOver_Mode == mode ? NULL : modes);
+}
+
+SkMergeImageFilter::SkMergeImageFilter(SkImageFilter* const filters[], int count,
+                                       const SkXfermode::Mode modes[]) {
+    this->init(filters, count, modes);
+}
+
+SkMergeImageFilter::~SkMergeImageFilter() {
+    for (int i = 0; i < fCount; ++i) {
+        SkSafeUnref(fFilters[i]);
+    }
+
+    if (fFilters != SkTCast<SkImageFilter**>(fStorage)) {
+        sk_free(fFilters);
+        // fModes is allocated in the same block as fFilters, so no need to
+        // separately free it.
+    }
+}
+
+bool SkMergeImageFilter::onFilterBounds(const SkIRect& src, const SkMatrix& ctm,
+                                        SkIRect* dst) {
+    if (fCount < 1) {
+        return false;
+    }
+
+    SkIRect totalBounds;
+    
+    for (int i = 0; i < fCount; ++i) {
+        SkImageFilter* filter = fFilters[i];
+        SkIRect r;
+        if (filter) {
+            if (!filter->filterBounds(src, ctm, &r)) {
+                return false;
+            }
+        } else {
+            r = src;
+        }
+        if (0 == i) {
+            totalBounds = r;
+        } else {
+            totalBounds.join(r);
+        }
+    }
+
+    // don't modify dst until now, so we don't accidentally change it in the
+    // loop, but then return false on the next filter.
+    *dst = totalBounds;
+    return true;
+}
+
+bool SkMergeImageFilter::onFilterImage(const SkBitmap& src, const SkMatrix& ctm,
+                                       SkBitmap* result, SkIPoint* loc) {
+    if (fCount < 1) {
+        return false;
+    }
+
+    const SkIRect srcBounds = SkIRect::MakeXYWH(loc->x(), loc->y(),
+                                                src.width(), src.height());
+    SkIRect bounds;
+    if (!this->filterBounds(srcBounds, ctm, &bounds)) {
+        return false;
+    }
+
+    const int x0 = bounds.left();
+    const int y0 = bounds.top();
+
+    SkBitmap dst;
+    dst.setConfig(SkBitmap::kARGB_8888_Config, bounds.width(), bounds.height());
+    dst.allocPixels();
+    dst.eraseColor(0);
+
+    SkCanvas canvas(dst);
+    SkPaint paint;
+
+    for (int i = 0; i < fCount; ++i) {
+        SkBitmap tmp;
+        const SkBitmap* srcPtr;
+        SkIPoint pos = *loc;
+        SkImageFilter* filter = fFilters[i];
+        if (filter) {
+            if (!filter->filterImage(src, ctm, &tmp, &pos)) {
+                return false;
+            }
+            srcPtr = &tmp;
+        } else {
+            srcPtr = &src;
+        }
+        
+        if (fModes) {
+            paint.setXfermodeMode((SkXfermode::Mode)fModes[i]);
+        } else {
+            paint.setXfermode(NULL);
+        }
+        canvas.drawSprite(*srcPtr, pos.x() - x0, pos.y() - y0, &paint);
+    }
+
+    loc->set(bounds.left(), bounds.top());
+    result->swap(dst);
+    return true;
+}
+
+void SkMergeImageFilter::flatten(SkFlattenableWriteBuffer& buffer) {
+    this->INHERITED::flatten(buffer);
+
+    int storedCount = fCount;
+    if (fModes) {
+        // negative count signals we have modes
+        storedCount = -storedCount;
+    }
+    buffer.write32(storedCount);
+
+    if (fCount) {
+        for (int i = 0; i < fCount; ++i) {
+            buffer.writeFlattenable(fFilters[i]);
+        }
+        if (fModes) {
+            buffer.write(fModes, fCount * sizeof(fModes[0]));
+        }
+    }
+}
+
+SkMergeImageFilter::SkMergeImageFilter(SkFlattenableReadBuffer& buffer) : INHERITED(buffer) {
+    int storedCount = buffer.readS32();
+    this->initAlloc(SkAbs32(storedCount), storedCount < 0);
+
+    for (int i = 0; i < fCount; ++i) {
+        fFilters[i] = (SkImageFilter*)buffer.readFlattenable();
+    }
+
+    if (fModes) {
+        SkASSERT(storedCount < 0);
+        buffer.read(fModes, fCount * sizeof(fModes[0]));
+    } else {
+        SkASSERT(storedCount >= 0);
+    }
+}
+
+SkFlattenable::Factory SkMergeImageFilter::getFactory() {
+    return CreateProc;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#include "SkColorFilter.h"
+
+SkColorFilterImageFilter::~SkColorFilterImageFilter() {
+    SkSafeUnref(fColorFilter);
+}
+
+bool SkColorFilterImageFilter::onFilterImage(const SkBitmap& src,
+                                             const SkMatrix& matrix,
+                                             SkBitmap* result,
+                                             SkIPoint* loc) {
+    if (SkBitmap::kARGB_8888_Config != src.config()) {
+        return false;
+    }
+
+    SkColorFilter* cf = fColorFilter;
+    if (NULL == cf) {
+        *result = src;
+        return true;
+    }
+
+    SkAutoLockPixels alpsrc(src);
+    if (!src.readyToDraw()) {
+        return false;
+    }
+
+    SkBitmap dst(src);
+    dst.allocPixels();
+    if (!dst.readyToDraw()) {
+        return false;
+    }
+    
+    for (int y = 0; y < src.height(); ++y) {
+        cf->filterSpan(src.getAddr32(0, y), src.width(), dst.getAddr32(0, y));
+    }
+    
+    result->swap(dst);
+    return true;
+}
+
+void SkColorFilterImageFilter::flatten(SkFlattenableWriteBuffer& buffer) {
+    this->INHERITED::flatten(buffer);
+    
+    buffer.writeFlattenable(fColorFilter);
+}
+
+SkColorFilterImageFilter::SkColorFilterImageFilter(SkFlattenableReadBuffer& buffer) : INHERITED(buffer) {
+    fColorFilter = (SkColorFilter*)buffer.readFlattenable();
+}
+
+SkFlattenable::Factory SkColorFilterImageFilter::getFactory() {
+    return CreateProc;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool SkDownSampleImageFilter::onFilterImage(const SkBitmap& src,
+                                            const SkMatrix& matrix,
+                                            SkBitmap* result, SkIPoint*) {
+    SkScalar scale = fScale;
+    if (scale > SK_Scalar1 || scale <= 0) {
+        return false;
+    }
+    
+    int dstW = SkScalarRoundToInt(src.width() * scale);
+    int dstH = SkScalarRoundToInt(src.height() * scale);
+    if (dstW < 1) {
+        dstW = 1;
+    }
+    if (dstH < 1) {
+        dstH = 1;
+    }
+    
+    SkBitmap dst;
+    dst.setConfig(SkBitmap::kARGB_8888_Config, dstW, dstH);
+    dst.allocPixels();
+    dst.eraseColor(0);
+    
+    // downsample
+    {
+        SkPaint paint;
+        paint.setFilterBitmap(true);
+        
+        SkCanvas canvas(dst);
+        canvas.scale(scale, scale);
+        canvas.drawBitmap(src, 0, 0, &paint);
+    }
+    
+    result->setConfig(SkBitmap::kARGB_8888_Config, src.width(), src.height());
+    result->allocPixels();
+    result->eraseColor(0);
+    
+    // upscale
+    {
+        SkRect r = SkRect::MakeWH(SkIntToScalar(result->width()),
+                                  SkIntToScalar(result->height()));
+        SkCanvas canvas(*result);
+        canvas.drawBitmapRect(dst, NULL, r, NULL);
+    }
+    return true;
+}
+
+void SkDownSampleImageFilter::flatten(SkFlattenableWriteBuffer& buffer) {
+    this->INHERITED::flatten(buffer);
+    
+    buffer.writeScalar(fScale);
+}
+
+SkDownSampleImageFilter::SkDownSampleImageFilter(SkFlattenableReadBuffer& buffer) : INHERITED(buffer) {
+    fScale = buffer.readScalar();
+}
+
+SkFlattenable::Factory SkDownSampleImageFilter::getFactory() {
+    return CreateProc;
+}
+