Make SkImageFilter crop rects relative to the primitive origin, instead of relative to their parent's crop rect.  This is required by SVG semantics, and is more sane anyway.

To do this, this patch changes the "offset/loc" parameter in filterImage() / onFilterImage() from an inout-param to an out-param only, so that the calling filter can know how much the input filter wants its result offset (and doesn't include the original primitive position). This offset can then be applied to the current filter's crop rect. (I've renamed the parameter "offset" in all cases to make this clear.) This makes the call sites in SkCanvas/SkGpuDevice responsible for applying the resulting offset to the primitive's position, which is actually a fairly small change.

This change also fixes SkTileImageFilter and SkOffsetImageFilter to correctly handle an input offset, which they weren't before. This required modifying the GM's, since they assumed the broken behaviour.

NOTE: this will require rebaselining the imagefiltersgraph test, since it has a new test case.

NOTE: this will "break" the Blink layout tests css3/filters/effect-reference-subregion-chained-hw.html and css3/filters/effect-reference-subregion-hw.html, but it actually makes them give correct results. It should be suppressed on the skia roll, and I'll rebaseline it.

R=reed@google.com

Review URL: https://codereview.chromium.org/112803004

git-svn-id: http://skia.googlecode.com/svn/trunk@12895 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/src/core/SkCanvas.cpp b/src/core/SkCanvas.cpp
index 62021cb..29c09d1 100644
--- a/src/core/SkCanvas.cpp
+++ b/src/core/SkCanvas.cpp
@@ -998,13 +998,15 @@
         if (filter && !dstDev->canHandleImageFilter(filter)) {
             SkDeviceImageFilterProxy proxy(dstDev);
             SkBitmap dst;
+            SkIPoint offset = SkIPoint::Make(0, 0);
             const SkBitmap& src = srcDev->accessBitmap(false);
             SkMatrix matrix = *iter.fMatrix;
             matrix.postTranslate(SkIntToScalar(-x), SkIntToScalar(-y));
-            if (filter->filterImage(&proxy, src, matrix, &dst, &pos)) {
+            if (filter->filterImage(&proxy, src, matrix, &dst, &offset)) {
                 SkPaint tmpUnfiltered(*paint);
                 tmpUnfiltered.setImageFilter(NULL);
-                dstDev->drawSprite(iter, dst, pos.x(), pos.y(), tmpUnfiltered);
+                dstDev->drawSprite(iter, dst, pos.x() + offset.x(), pos.y() + offset.y(),
+                                   tmpUnfiltered);
             }
         } else {
             dstDev->drawDevice(iter, srcDev, pos.x(), pos.y(), *paint);
@@ -1036,12 +1038,13 @@
         if (filter && !iter.fDevice->canHandleImageFilter(filter)) {
             SkDeviceImageFilterProxy proxy(iter.fDevice);
             SkBitmap dst;
+            SkIPoint offset = SkIPoint::Make(0, 0);
             SkMatrix matrix = *iter.fMatrix;
             matrix.postTranslate(SkIntToScalar(-x), SkIntToScalar(-y));
-            if (filter->filterImage(&proxy, bitmap, matrix, &dst, &pos)) {
+            if (filter->filterImage(&proxy, bitmap, matrix, &dst, &offset)) {
                 SkPaint tmpUnfiltered(*paint);
                 tmpUnfiltered.setImageFilter(NULL);
-                iter.fDevice->drawSprite(iter, dst, pos.x(), pos.y(),
+                iter.fDevice->drawSprite(iter, dst, pos.x() + offset.x(), pos.y() + offset.y(),
                                          tmpUnfiltered);
             }
         } else {
diff --git a/src/core/SkImageFilter.cpp b/src/core/SkImageFilter.cpp
index cda635b..d62685a 100644
--- a/src/core/SkImageFilter.cpp
+++ b/src/core/SkImageFilter.cpp
@@ -93,15 +93,15 @@
 
 bool SkImageFilter::filterImage(Proxy* proxy, const SkBitmap& src,
                                 const SkMatrix& ctm,
-                                SkBitmap* result, SkIPoint* loc) {
+                                SkBitmap* result, SkIPoint* offset) {
     SkASSERT(result);
-    SkASSERT(loc);
+    SkASSERT(offset);
     /*
      *  Give the proxy first shot at the filter. If it returns false, ask
      *  the filter to do it.
      */
-    return (proxy && proxy->filterImage(this, src, ctm, result, loc)) ||
-           this->onFilterImage(proxy, src, ctm, result, loc);
+    return (proxy && proxy->filterImage(this, src, ctm, result, offset)) ||
+           this->onFilterImage(proxy, src, ctm, result, offset);
 }
 
 bool SkImageFilter::filterBounds(const SkIRect& src, const SkMatrix& ctm,
diff --git a/src/core/SkImageFilterUtils.cpp b/src/core/SkImageFilterUtils.cpp
index a59cf7b..92fe67e 100644
--- a/src/core/SkImageFilterUtils.cpp
+++ b/src/core/SkImageFilterUtils.cpp
@@ -35,6 +35,7 @@
     GrContext* context = src.getTexture()->getContext();
     GrContext::AutoWideOpenIdentityDraw awoid(context, NULL);
     if (!filter) {
+        offset->fX = offset->fY = 0;
         *result = src;
         return true;
     } else if (filter->canFilterImageGPU()) {
diff --git a/src/effects/SkBicubicImageFilter.cpp b/src/effects/SkBicubicImageFilter.cpp
index 87ff046..96d46aa 100644
--- a/src/effects/SkBicubicImageFilter.cpp
+++ b/src/effects/SkBicubicImageFilter.cpp
@@ -84,9 +84,10 @@
                                          const SkBitmap& source,
                                          const SkMatrix& matrix,
                                          SkBitmap* result,
-                                         SkIPoint* loc) {
+                                         SkIPoint* offset) {
     SkBitmap src = source;
-    if (getInput(0) && !getInput(0)->filterImage(proxy, source, matrix, &src, loc)) {
+    SkIPoint srcOffset = SkIPoint::Make(0, 0);
+    if (getInput(0) && !getInput(0)->filterImage(proxy, source, matrix, &src, &srcOffset)) {
         return false;
     }
 
@@ -114,6 +115,7 @@
 
     SkRect srcRect;
     src.getBounds(&srcRect);
+    srcRect.offset(SkPoint::Make(SkIntToScalar(srcOffset.fX), SkIntToScalar(srcOffset.fY)));
     SkMatrix inverse;
     inverse.setRectToRect(dstRect, srcRect, SkMatrix::kFill_ScaleToFit);
     inverse.postTranslate(-0.5f, -0.5f);
@@ -158,6 +160,8 @@
             *dptr++ = cubicBlend(fCoefficients, fracty, s0, s1, s2, s3);
         }
     }
+    offset->fX = dstIRect.fLeft;
+    offset->fY = dstIRect.fTop;
     return true;
 }
 
diff --git a/src/effects/SkBitmapSource.cpp b/src/effects/SkBitmapSource.cpp
index b5ee1dd..daf4fb0 100644
--- a/src/effects/SkBitmapSource.cpp
+++ b/src/effects/SkBitmapSource.cpp
@@ -49,6 +49,7 @@
     if (fSrcRect == bounds && dstRect == bounds) {
         // No regions cropped out or resized; return entire bitmap.
         *result = fBitmap;
+        offset->fX = offset->fY = 0;
         return true;
     }
     SkIRect dstIRect;
@@ -73,7 +74,7 @@
     canvas.drawBitmapRectToRect(fBitmap, &fSrcRect, dstRect, &paint);
 
     *result = device.get()->accessBitmap(false);
-    offset->fX += dstIRect.fLeft;
-    offset->fY += dstIRect.fTop;
+    offset->fX = dstIRect.fLeft;
+    offset->fY = dstIRect.fTop;
     return true;
 }
diff --git a/src/effects/SkBlurImageFilter.cpp b/src/effects/SkBlurImageFilter.cpp
index 2795f3a..5efef0b 100644
--- a/src/effects/SkBlurImageFilter.cpp
+++ b/src/effects/SkBlurImageFilter.cpp
@@ -137,7 +137,8 @@
                                       const SkBitmap& source, const SkMatrix& ctm,
                                       SkBitmap* dst, SkIPoint* offset) {
     SkBitmap src = source;
-    if (getInput(0) && !getInput(0)->filterImage(proxy, source, ctm, &src, offset)) {
+    SkIPoint srcOffset = SkIPoint::Make(0, 0);
+    if (getInput(0) && !getInput(0)->filterImage(proxy, source, ctm, &src, &srcOffset)) {
         return false;
     }
 
@@ -152,6 +153,7 @@
 
     SkIRect srcBounds, dstBounds;
     src.getBounds(&srcBounds);
+    srcBounds.offset(srcOffset);
     if (!this->applyCropRect(&srcBounds, ctm)) {
         return false;
     }
@@ -174,6 +176,8 @@
 
     if (kernelSizeX == 0 && kernelSizeY == 0) {
         src.copyTo(dst, dst->config());
+        offset->fX = srcBounds.fLeft;
+        offset->fY = srcBounds.fTop;
         return true;
     }
 
@@ -183,6 +187,9 @@
         return false;
     }
 
+    offset->fX = srcBounds.fLeft;
+    offset->fY = srcBounds.fTop;
+    srcBounds.offset(-srcOffset);
     const SkPMColor* s = src.getAddr32(srcBounds.left(), srcBounds.top());
     SkPMColor* t = temp.getAddr32(0, 0);
     SkPMColor* d = dst->getAddr32(0, 0);
@@ -212,8 +219,6 @@
         boxBlurX(d,  h,  t, kernelSizeY,  highOffsetY, lowOffsetY,  h, w);
         boxBlurXY(t, h,  d, kernelSizeY3, highOffsetY, highOffsetY, h, w);
     }
-    offset->fX += srcBounds.fLeft;
-    offset->fY += srcBounds.fTop;
     return true;
 }
 
@@ -237,8 +242,8 @@
                                                              true,
                                                              fSigma.width(),
                                                              fSigma.height()));
-    offset->fX += rect.fLeft;
-    offset->fY += rect.fTop;
+    offset->fX = rect.fLeft;
+    offset->fY = rect.fTop;
     return SkImageFilterUtils::WrapTexture(tex, rect.width(), rect.height(), result);
 #else
     SkDEBUGFAIL("Should not call in GPU-less build");
diff --git a/src/effects/SkColorFilterImageFilter.cpp b/src/effects/SkColorFilterImageFilter.cpp
index 8b7b390..d648b6c 100755
--- a/src/effects/SkColorFilterImageFilter.cpp
+++ b/src/effects/SkColorFilterImageFilter.cpp
@@ -100,14 +100,16 @@
 bool SkColorFilterImageFilter::onFilterImage(Proxy* proxy, const SkBitmap& source,
                                              const SkMatrix& matrix,
                                              SkBitmap* result,
-                                             SkIPoint* loc) {
+                                             SkIPoint* offset) {
     SkBitmap src = source;
-    if (getInput(0) && !getInput(0)->filterImage(proxy, source, matrix, &src, loc)) {
+    SkIPoint srcOffset = SkIPoint::Make(0, 0);
+    if (getInput(0) && !getInput(0)->filterImage(proxy, source, matrix, &src, &srcOffset)) {
         return false;
     }
 
     SkIRect bounds;
     src.getBounds(&bounds);
+    bounds.offset(srcOffset);
     if (!this->applyCropRect(&bounds, matrix)) {
         return false;
     }
@@ -121,11 +123,11 @@
 
     paint.setXfermodeMode(SkXfermode::kSrc_Mode);
     paint.setColorFilter(fColorFilter);
-    canvas.drawSprite(src, -bounds.fLeft, -bounds.fTop, &paint);
+    canvas.drawSprite(src, srcOffset.fX - bounds.fLeft, srcOffset.fY - bounds.fTop, &paint);
 
     *result = device.get()->accessBitmap(false);
-    loc->fX += bounds.fLeft;
-    loc->fY += bounds.fTop;
+    offset->fX = bounds.fLeft;
+    offset->fY = bounds.fTop;
     return true;
 }
 
diff --git a/src/effects/SkComposeImageFilter.cpp b/src/effects/SkComposeImageFilter.cpp
index 2412d9f..7445bdf 100644
--- a/src/effects/SkComposeImageFilter.cpp
+++ b/src/effects/SkComposeImageFilter.cpp
@@ -16,7 +16,7 @@
                                          const SkBitmap& src,
                                          const SkMatrix& ctm,
                                          SkBitmap* result,
-                                         SkIPoint* loc) {
+                                         SkIPoint* offset) {
     SkImageFilter* outer = getInput(0);
     SkImageFilter* inner = getInput(1);
 
@@ -25,12 +25,12 @@
     }
 
     if (!outer || !inner) {
-        return (outer ? outer : inner)->filterImage(proxy, src, ctm, result, loc);
+        return (outer ? outer : inner)->filterImage(proxy, src, ctm, result, offset);
     }
 
     SkBitmap tmp;
-    return inner->filterImage(proxy, src, ctm, &tmp, loc) &&
-           outer->filterImage(proxy, tmp, ctm, result, loc);
+    return inner->filterImage(proxy, src, ctm, &tmp, offset) &&
+           outer->filterImage(proxy, tmp, ctm, result, offset);
 }
 
 bool SkComposeImageFilter::onFilterBounds(const SkIRect& src,
diff --git a/src/effects/SkDisplacementMapEffect.cpp b/src/effects/SkDisplacementMapEffect.cpp
index 3087b63..e9a9acb 100644
--- a/src/effects/SkDisplacementMapEffect.cpp
+++ b/src/effects/SkDisplacementMapEffect.cpp
@@ -221,8 +221,8 @@
 
     computeDisplacement(fXChannelSelector, fYChannelSelector, fScale, dst, &displ, &color, bounds);
 
-    offset->fX += bounds.left();
-    offset->fY += bounds.top();
+    offset->fX = bounds.left();
+    offset->fY = bounds.top();
     return true;
 }
 
@@ -356,8 +356,8 @@
     SkRect srcRect = SkRect::Make(bounds);
     SkRect dstRect = SkRect::MakeWH(srcRect.width(), srcRect.height());
     context->drawRectToRect(paint, dstRect, srcRect);
-    offset->fX += bounds.left();
-    offset->fY += bounds.top();
+    offset->fX = bounds.left();
+    offset->fY = bounds.top();
     return SkImageFilterUtils::WrapTexture(dst, bounds.width(), bounds.height(), result);
 }
 
diff --git a/src/effects/SkDropShadowImageFilter.cpp b/src/effects/SkDropShadowImageFilter.cpp
index 24a910d..60294e4 100644
--- a/src/effects/SkDropShadowImageFilter.cpp
+++ b/src/effects/SkDropShadowImageFilter.cpp
@@ -57,14 +57,16 @@
     buffer.writeColor(fColor);
 }
 
-bool SkDropShadowImageFilter::onFilterImage(Proxy* proxy, const SkBitmap& source, const SkMatrix& matrix, SkBitmap* result, SkIPoint* loc)
+bool SkDropShadowImageFilter::onFilterImage(Proxy* proxy, const SkBitmap& source, const SkMatrix& matrix, SkBitmap* result, SkIPoint* offset)
 {
     SkBitmap src = source;
-    if (getInput(0) && !getInput(0)->filterImage(proxy, source, matrix, &src, loc))
+    SkIPoint srcOffset = SkIPoint::Make(0, 0);
+    if (getInput(0) && !getInput(0)->filterImage(proxy, source, matrix, &src, &srcOffset))
         return false;
 
     SkIRect bounds;
     src.getBounds(&bounds);
+    bounds.offset(srcOffset);
     if (!this->applyCropRect(&bounds, matrix)) {
         return false;
     }
@@ -85,7 +87,7 @@
     canvas.drawBitmap(src, fDx, fDy, &paint);
     canvas.drawBitmap(src, 0, 0);
     *result = device->accessBitmap(false);
-    loc->fX += bounds.fLeft;
-    loc->fY += bounds.fTop;
+    offset->fX = bounds.fLeft;
+    offset->fY = bounds.fTop;
     return true;
 }
diff --git a/src/effects/SkLightingImageFilter.cpp b/src/effects/SkLightingImageFilter.cpp
index 50cca07..b24a91e 100644
--- a/src/effects/SkLightingImageFilter.cpp
+++ b/src/effects/SkLightingImageFilter.cpp
@@ -927,7 +927,8 @@
                                                  SkIPoint* offset) {
     SkImageFilter* input = getInput(0);
     SkBitmap src = source;
-    if (input && !input->filterImage(proxy, source, ctm, &src, offset)) {
+    SkIPoint srcOffset = SkIPoint::Make(0, 0);
+    if (input && !input->filterImage(proxy, source, ctm, &src, &srcOffset)) {
         return false;
     }
 
@@ -941,6 +942,7 @@
 
     SkIRect bounds;
     src.getBounds(&bounds);
+    bounds.offset(srcOffset);
     if (!this->applyCropRect(&bounds, ctm)) {
         return false;
     }
@@ -958,6 +960,9 @@
     SkAutoTUnref<SkLight> transformedLight(light()->transform(ctm));
 
     DiffuseLightingType lightingType(fKD);
+    offset->fX = bounds.left();
+    offset->fY = bounds.top();
+    bounds.offset(-srcOffset);
     switch (transformedLight->type()) {
         case SkLight::kDistant_LightType:
             lightBitmap<DiffuseLightingType, SkDistantLight>(lightingType, transformedLight, src, dst, surfaceScale(), bounds);
@@ -970,8 +975,6 @@
             break;
     }
 
-    offset->fX += bounds.left();
-    offset->fY += bounds.top();
     return true;
 }
 
@@ -1018,7 +1021,8 @@
                                                   SkIPoint* offset) {
     SkImageFilter* input = getInput(0);
     SkBitmap src = source;
-    if (input && !input->filterImage(proxy, source, ctm, &src, offset)) {
+    SkIPoint srcOffset = SkIPoint::Make(0, 0);
+    if (input && !input->filterImage(proxy, source, ctm, &src, &srcOffset)) {
         return false;
     }
 
@@ -1032,6 +1036,7 @@
 
     SkIRect bounds;
     src.getBounds(&bounds);
+    bounds.offset(srcOffset);
     if (!this->applyCropRect(&bounds, ctm)) {
         return false;
     }
@@ -1047,6 +1052,9 @@
     }
 
     SpecularLightingType lightingType(fKS, fShininess);
+    offset->fX = bounds.left();
+    offset->fY = bounds.top();
+    bounds.offset(-srcOffset);
     SkAutoTUnref<SkLight> transformedLight(light()->transform(ctm));
     switch (transformedLight->type()) {
         case SkLight::kDistant_LightType:
@@ -1059,8 +1067,6 @@
             lightBitmap<SpecularLightingType, SkSpotLight>(lightingType, transformedLight, src, dst, surfaceScale(), bounds);
             break;
     }
-    offset->fX += bounds.left();
-    offset->fY += bounds.top();
     return true;
 }
 
diff --git a/src/effects/SkMatrixConvolutionImageFilter.cpp b/src/effects/SkMatrixConvolutionImageFilter.cpp
index 3da27ce..a450040 100644
--- a/src/effects/SkMatrixConvolutionImageFilter.cpp
+++ b/src/effects/SkMatrixConvolutionImageFilter.cpp
@@ -252,9 +252,10 @@
                                                    const SkBitmap& source,
                                                    const SkMatrix& matrix,
                                                    SkBitmap* result,
-                                                   SkIPoint* loc) {
+                                                   SkIPoint* offset) {
     SkBitmap src = source;
-    if (getInput(0) && !getInput(0)->filterImage(proxy, source, matrix, &src, loc)) {
+    SkIPoint srcOffset = SkIPoint::Make(0, 0);
+    if (getInput(0) && !getInput(0)->filterImage(proxy, source, matrix, &src, &srcOffset)) {
         return false;
     }
 
@@ -264,6 +265,7 @@
 
     SkIRect bounds;
     src.getBounds(&bounds);
+    bounds.offset(srcOffset);
     if (!this->applyCropRect(&bounds, matrix)) {
         return false;
     }
@@ -283,6 +285,9 @@
         return false;
     }
 
+    offset->fX = bounds.fLeft;
+    offset->fY = bounds.fTop;
+    bounds.offset(-srcOffset);
     SkIRect interior = SkIRect::MakeXYWH(bounds.left() + fTarget.fX,
                                          bounds.top() + fTarget.fY,
                                          bounds.width() - fKernelSize.fWidth + 1,
@@ -299,8 +304,6 @@
     filterInteriorPixels(src, result, interior, bounds);
     filterBorderPixels(src, result, right, bounds);
     filterBorderPixels(src, result, bottom, bounds);
-    loc->fX += bounds.fLeft;
-    loc->fY += bounds.fTop;
     return true;
 }
 
diff --git a/src/effects/SkMergeImageFilter.cpp b/src/effects/SkMergeImageFilter.cpp
index 528fe82..9c0fa92 100755
--- a/src/effects/SkMergeImageFilter.cpp
+++ b/src/effects/SkMergeImageFilter.cpp
@@ -98,7 +98,7 @@
 
 bool SkMergeImageFilter::onFilterImage(Proxy* proxy, const SkBitmap& src,
                                        const SkMatrix& ctm,
-                                       SkBitmap* result, SkIPoint* loc) {
+                                       SkBitmap* result, SkIPoint* offset) {
     if (countInputs() < 1) {
         return false;
     }
@@ -142,8 +142,8 @@
         canvas.drawSprite(*srcPtr, pos.x() - x0, pos.y() - y0, &paint);
     }
 
-    loc->fX += bounds.left();
-    loc->fY += bounds.top();
+    offset->fX = bounds.left();
+    offset->fY = bounds.top();
     *result = dst->accessBitmap(false);
     return true;
 }
diff --git a/src/effects/SkMorphologyImageFilter.cpp b/src/effects/SkMorphologyImageFilter.cpp
index 0d00c35..1f8bca1 100644
--- a/src/effects/SkMorphologyImageFilter.cpp
+++ b/src/effects/SkMorphologyImageFilter.cpp
@@ -167,7 +167,8 @@
                                        const SkBitmap& source, const SkMatrix& ctm,
                                        SkBitmap* dst, SkIPoint* offset) {
     SkBitmap src = source;
-    if (getInput(0) && !getInput(0)->filterImage(proxy, source, ctm, &src, offset)) {
+    SkIPoint srcOffset = SkIPoint::Make(0, 0);
+    if (getInput(0) && !getInput(0)->filterImage(proxy, source, ctm, &src, &srcOffset)) {
         return false;
     }
 
@@ -177,6 +178,7 @@
 
     SkIRect bounds;
     src.getBounds(&bounds);
+    bounds.offset(srcOffset);
     if (!this->applyCropRect(&bounds, ctm)) {
         return false;
     }
@@ -201,8 +203,8 @@
 
     if (width == 0 && height == 0) {
         src.extractSubset(dst, bounds);
-        offset->fX += bounds.left();
-        offset->fY += bounds.top();
+        offset->fX = bounds.left();
+        offset->fY = bounds.top();
         return true;
     }
 
@@ -212,6 +214,9 @@
         return false;
     }
 
+    offset->fX = bounds.left();
+    offset->fY = bounds.top();
+    bounds.offset(-srcOffset);
     if (width > 0 && height > 0) {
         erodeX(src, &temp, width, bounds);
         SkIRect tmpBounds = SkIRect::MakeWH(bounds.width(), bounds.height());
@@ -221,8 +226,6 @@
     } else if (height > 0) {
         erodeY(src, dst, height, bounds);
     }
-    offset->fX += bounds.left();
-    offset->fY += bounds.top();
     return true;
 }
 
@@ -230,7 +233,8 @@
                                         const SkBitmap& source, const SkMatrix& ctm,
                                         SkBitmap* dst, SkIPoint* offset) {
     SkBitmap src = source;
-    if (getInput(0) && !getInput(0)->filterImage(proxy, source, ctm, &src, offset)) {
+    SkIPoint srcOffset = SkIPoint::Make(0, 0);
+    if (getInput(0) && !getInput(0)->filterImage(proxy, source, ctm, &src, &srcOffset)) {
         return false;
     }
     if (src.config() != SkBitmap::kARGB_8888_Config) {
@@ -239,6 +243,7 @@
 
     SkIRect bounds;
     src.getBounds(&bounds);
+    bounds.offset(srcOffset);
     if (!this->applyCropRect(&bounds, ctm)) {
         return false;
     }
@@ -263,8 +268,8 @@
 
     if (width == 0 && height == 0) {
         src.extractSubset(dst, bounds);
-        offset->fX += bounds.left();
-        offset->fY += bounds.top();
+        offset->fX = bounds.left();
+        offset->fY = bounds.top();
         return true;
     }
 
@@ -274,6 +279,9 @@
         return false;
     }
 
+    offset->fX = bounds.left();
+    offset->fY = bounds.top();
+    bounds.offset(-srcOffset);
     if (width > 0 && height > 0) {
         dilateX(src, &temp, width, bounds);
         SkIRect tmpBounds = SkIRect::MakeWH(bounds.width(), bounds.height());
@@ -283,8 +291,6 @@
     } else if (height > 0) {
         dilateY(src, dst, height, bounds);
     }
-    offset->fX += bounds.left();
-    offset->fY += bounds.top();
     return true;
 }
 
@@ -580,16 +586,16 @@
 
     if (width == 0 && height == 0) {
         src.extractSubset(result, bounds);
-        offset->fX += bounds.left();
-        offset->fY += bounds.top();
+        offset->fX = bounds.left();
+        offset->fY = bounds.top();
         return true;
     }
 
     if (!apply_morphology(input, bounds, GrMorphologyEffect::kDilate_MorphologyType, radius(), result)) {
         return false;
     }
-    offset->fX += bounds.left();
-    offset->fY += bounds.top();
+    offset->fX = bounds.left();
+    offset->fY = bounds.top();
     return true;
 }
 
@@ -613,16 +619,16 @@
 
     if (width == 0 && height == 0) {
         src.extractSubset(result, bounds);
-        offset->fX += bounds.left();
-        offset->fY += bounds.top();
+        offset->fX = bounds.left();
+        offset->fY = bounds.top();
         return true;
     }
 
     if (!apply_morphology(input, bounds, GrMorphologyEffect::kErode_MorphologyType, radius(), result)) {
         return false;
     }
-    offset->fX += bounds.left();
-    offset->fY += bounds.top();
+    offset->fX = bounds.left();
+    offset->fY = bounds.top();
     return true;
 }
 
diff --git a/src/effects/SkOffsetImageFilter.cpp b/src/effects/SkOffsetImageFilter.cpp
index 59318e3..61f68f7 100644
--- a/src/effects/SkOffsetImageFilter.cpp
+++ b/src/effects/SkOffsetImageFilter.cpp
@@ -16,32 +16,33 @@
 bool SkOffsetImageFilter::onFilterImage(Proxy* proxy, const SkBitmap& source,
                                         const SkMatrix& matrix,
                                         SkBitmap* result,
-                                        SkIPoint* loc) {
+                                        SkIPoint* offset) {
     SkImageFilter* input = getInput(0);
     SkBitmap src = source;
+    SkIPoint srcOffset = SkIPoint::Make(0, 0);
 #ifdef SK_DISABLE_OFFSETIMAGEFILTER_OPTIMIZATION
     if (false) {
 #else
     if (!cropRectIsSet()) {
 #endif
-        if (input && !input->filterImage(proxy, source, matrix, &src, loc)) {
+        if (input && !input->filterImage(proxy, source, matrix, &src, &srcOffset)) {
             return false;
         }
 
         SkVector vec;
         matrix.mapVectors(&vec, &fOffset, 1);
 
-        loc->fX += SkScalarRoundToInt(vec.fX);
-        loc->fY += SkScalarRoundToInt(vec.fY);
+        offset->fX = srcOffset.fX + SkScalarRoundToInt(vec.fX);
+        offset->fY = srcOffset.fY + SkScalarRoundToInt(vec.fY);
         *result = src;
     } else {
-        SkIPoint srcOffset = SkIPoint::Make(0, 0);
         if (input && !input->filterImage(proxy, source, matrix, &src, &srcOffset)) {
             return false;
         }
 
         SkIRect bounds;
         src.getBounds(&bounds);
+        bounds.offset(srcOffset);
 
         if (!applyCropRect(&bounds, matrix)) {
             return false;
@@ -54,10 +55,12 @@
         SkCanvas canvas(device);
         SkPaint paint;
         paint.setXfermodeMode(SkXfermode::kSrc_Mode);
-        canvas.drawBitmap(src, fOffset.fX - bounds.left(), fOffset.fY - bounds.top(), &paint);
+        canvas.translate(SkIntToScalar(srcOffset.fX - bounds.fLeft),
+                         SkIntToScalar(srcOffset.fY - bounds.fTop));
+        canvas.drawBitmap(src, fOffset.x(), fOffset.y(), &paint);
         *result = device->accessBitmap(false);
-        loc->fX += bounds.left();
-        loc->fY += bounds.top();
+        offset->fX = bounds.fLeft;
+        offset->fY = bounds.fTop;
     }
     return true;
 }
diff --git a/src/effects/SkPictureImageFilter.cpp b/src/effects/SkPictureImageFilter.cpp
index 5b4af65..b054ee2 100644
--- a/src/effects/SkPictureImageFilter.cpp
+++ b/src/effects/SkPictureImageFilter.cpp
@@ -46,6 +46,7 @@
 bool SkPictureImageFilter::onFilterImage(Proxy* proxy, const SkBitmap&, const SkMatrix& matrix,
                                    SkBitmap* result, SkIPoint* offset) {
     if (!fPicture) {
+        offset->fX = offset->fY = 0;
         return true;
     }
 
@@ -55,6 +56,7 @@
     floatBounds.roundOut(&bounds);
 
     if (bounds.isEmpty()) {
+        offset->fX = offset->fY = 0;
         return true;
     }
 
@@ -71,7 +73,7 @@
     canvas.drawPicture(*fPicture);
 
     *result = device.get()->accessBitmap(false);
-    offset->fX += bounds.fLeft;
-    offset->fY += bounds.fTop;
+    offset->fX = bounds.fLeft;
+    offset->fY = bounds.fTop;
     return true;
 }
diff --git a/src/effects/SkRectShaderImageFilter.cpp b/src/effects/SkRectShaderImageFilter.cpp
index 5c34547..f55df2c 100644
--- a/src/effects/SkRectShaderImageFilter.cpp
+++ b/src/effects/SkRectShaderImageFilter.cpp
@@ -74,7 +74,7 @@
     SkRect rect = SkRect::MakeWH(SkIntToScalar(bounds.width()), SkIntToScalar(bounds.height()));
     canvas.drawRect(rect, paint);
     *result = device.get()->accessBitmap(false);
-    offset->fX += bounds.fLeft;
-    offset->fY += bounds.fTop;
+    offset->fX = bounds.fLeft;
+    offset->fY = bounds.fTop;
     return true;
 }
diff --git a/src/effects/SkTileImageFilter.cpp b/src/effects/SkTileImageFilter.cpp
index 73e5304..08dfec4 100644
--- a/src/effects/SkTileImageFilter.cpp
+++ b/src/effects/SkTileImageFilter.cpp
@@ -19,15 +19,17 @@
                                       SkBitmap* dst, SkIPoint* offset) {
     SkBitmap source = src;
     SkImageFilter* input = getInput(0);
-    SkIPoint localOffset = SkIPoint::Make(0, 0);
-    if (input && !input->filterImage(proxy, src, ctm, &source, &localOffset)) {
+    SkIPoint srcOffset = SkIPoint::Make(0, 0);
+    if (input && !input->filterImage(proxy, src, ctm, &source, &srcOffset)) {
         return false;
     }
 
     SkRect dstRect;
     ctm.mapRect(&dstRect, fDstRect);
-    int w = SkScalarCeilToInt(dstRect.width());
-    int h = SkScalarCeilToInt(dstRect.height());
+    SkIRect dstIRect;
+    dstRect.roundOut(&dstIRect);
+    int w = dstIRect.width();
+    int h = dstIRect.height();
     if (!fSrcRect.width() || !fSrcRect.height() || !w || !h) {
         return false;
     }
@@ -36,10 +38,13 @@
     ctm.mapRect(&srcRect, fSrcRect);
     SkIRect srcIRect;
     srcRect.roundOut(&srcIRect);
+    srcIRect.offset(-srcOffset);
     SkBitmap subset;
     SkIRect bounds;
     source.getBounds(&bounds);
+
     if (!srcIRect.intersect(bounds)) {
+        offset->fX = offset->fY = 0;
         return true;
     } else if (!source.extractSubset(&subset, srcIRect)) {
         return false;
@@ -55,10 +60,16 @@
 
     SkAutoTUnref<SkShader> shader(SkShader::CreateBitmapShader(subset,
                                   SkShader::kRepeat_TileMode, SkShader::kRepeat_TileMode));
+    SkMatrix shaderMatrix;
+    shaderMatrix.setTranslate(SkIntToScalar(srcOffset.fX),
+                              SkIntToScalar(srcOffset.fY));
+    shader->setLocalMatrix(shaderMatrix);
     paint.setShader(shader);
-    dstRect.offset(SkIntToScalar(localOffset.fX), SkIntToScalar(localOffset.fY));
+    canvas.translate(-dstRect.fLeft, -dstRect.fTop);
     canvas.drawRect(dstRect, paint);
     *dst = device->accessBitmap(false);
+    offset->fX = dstIRect.fLeft;
+    offset->fY = dstIRect.fTop;
     return true;
 }
 
diff --git a/src/effects/SkXfermodeImageFilter.cpp b/src/effects/SkXfermodeImageFilter.cpp
index 4674ff4..beb1de2 100644
--- a/src/effects/SkXfermodeImageFilter.cpp
+++ b/src/effects/SkXfermodeImageFilter.cpp
@@ -88,8 +88,8 @@
     paint.setColor(SK_ColorTRANSPARENT);
     canvas.drawPaint(paint);
     *dst = device->accessBitmap(false);
-    offset->fX += bounds.left();
-    offset->fY += bounds.top();
+    offset->fX = bounds.left();
+    offset->fY = bounds.top();
     return true;
 }
 
@@ -157,8 +157,8 @@
         foregroundPaint.addColorTextureEffect(foregroundTex, foregroundMatrix);
         context->drawRect(foregroundPaint, srcRect);
     }
-    offset->fX += backgroundOffset.fX;
-    offset->fY += backgroundOffset.fY;
+    offset->fX = backgroundOffset.fX;
+    offset->fY = backgroundOffset.fY;
     return SkImageFilterUtils::WrapTexture(dst, src.width(), src.height(), result);
 }
 
diff --git a/src/gpu/SkGpuDevice.cpp b/src/gpu/SkGpuDevice.cpp
index b18f54b..680dd89 100644
--- a/src/gpu/SkGpuDevice.cpp
+++ b/src/gpu/SkGpuDevice.cpp
@@ -1494,11 +1494,11 @@
     SkAutoCachedTexture act(this, bitmap, NULL, &texture);
 
     SkImageFilter* filter = paint.getImageFilter();
-    SkIPoint offset = SkIPoint::Make(left, top);
     // This bitmap will own the filtered result as a texture.
     SkBitmap filteredBitmap;
 
     if (NULL != filter) {
+        SkIPoint offset = SkIPoint::Make(0, 0);
         SkMatrix matrix(*draw.fMatrix);
         matrix.postTranslate(SkIntToScalar(-left), SkIntToScalar(-top));
         if (filter_texture(this, fContext, texture, filter, w, h, matrix, &filteredBitmap,
@@ -1506,6 +1506,8 @@
             texture = (GrTexture*) filteredBitmap.getTexture();
             w = filteredBitmap.width();
             h = filteredBitmap.height();
+            left += offset.x();
+            top += offset.y();
         } else {
             return;
         }
@@ -1519,8 +1521,8 @@
     }
 
     fContext->drawRectToRect(grPaint,
-                             SkRect::MakeXYWH(SkIntToScalar(offset.fX),
-                                              SkIntToScalar(offset.fY),
+                             SkRect::MakeXYWH(SkIntToScalar(left),
+                                              SkIntToScalar(top),
                                               SkIntToScalar(w),
                                               SkIntToScalar(h)),
                              SkRect::MakeXYWH(0,