HQ filtering for tiled/bleed drawBitmap
sample at pix center in bicubic

R=senorblanco@chromium.org, jvanverth@google.com

Author: bsalomon@google.com

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

git-svn-id: http://skia.googlecode.com/svn/trunk/src@12440 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/gpu/SkGpuDevice.cpp b/gpu/SkGpuDevice.cpp
index 6fb1403..07a946e 100644
--- a/gpu/SkGpuDevice.cpp
+++ b/gpu/SkGpuDevice.cpp
@@ -7,6 +7,7 @@
 
 #include "SkGpuDevice.h"
 
+#include "effects/GrBicubicEffect.h"
 #include "effects/GrTextureDomainEffect.h"
 #include "effects/GrSimpleTextureEffect.h"
 
@@ -1057,22 +1058,29 @@
                            SkCanvas::kNone_DrawBitmapRectFlag);
 }
 
-// This method outsets 'iRect' by 1 all around and then clamps its extents to
+// This method outsets 'iRect' by 'outset' all around and then clamps its extents to
 // 'clamp'. 'offset' is adjusted to remain positioned over the top-left corner
 // of 'iRect' for all possible outsets/clamps.
-static inline void clamped_unit_outset_with_offset(SkIRect* iRect, SkPoint* offset,
-                                                   const SkIRect& clamp) {
-    iRect->outset(1, 1);
+static inline void clamped_outset_with_offset(SkIRect* iRect,
+                                              int outset,
+                                              SkPoint* offset,
+                                              const SkIRect& clamp) {
+    iRect->outset(outset, outset);
 
-    if (iRect->fLeft < clamp.fLeft) {
+    int leftClampDelta = clamp.fLeft - iRect->fLeft;
+    if (leftClampDelta > 0) {
+        offset->fX -= outset - leftClampDelta;
         iRect->fLeft = clamp.fLeft;
     } else {
-        offset->fX -= SK_Scalar1;
+        offset->fX -= outset;
     }
-    if (iRect->fTop < clamp.fTop) {
+
+    int topClampDelta = clamp.fTop - iRect->fTop;
+    if (topClampDelta > 0) {
+        offset->fY -= outset - topClampDelta;
         iRect->fTop = clamp.fTop;
     } else {
-        offset->fY -= SK_Scalar1;
+        offset->fY -= outset;
     }
 
     if (iRect->fRight > clamp.fRight) {
@@ -1092,10 +1100,17 @@
     CHECK_SHOULD_DRAW(draw, false);
 
     SkRect srcRect;
+    // If there is no src rect, or the src rect contains the entire bitmap then we're effectively
+    // in the (easier) bleed case, so update flags.
     if (NULL == srcRectPtr) {
         srcRect.set(0, 0, SkIntToScalar(bitmap.width()), SkIntToScalar(bitmap.height()));
+        flags = (SkCanvas::DrawBitmapRectFlags) (flags | SkCanvas::kBleed_DrawBitmapRectFlag);
     } else {
         srcRect = *srcRectPtr;
+        if (srcRect.fLeft <= 0 && srcRect.fTop <= 0 &&
+            srcRect.fRight >= bitmap.width() && srcRect.fBottom >= bitmap.height()) {
+            flags = (SkCanvas::DrawBitmapRectFlags) (flags | SkCanvas::kBleed_DrawBitmapRectFlag);
+        }
     }
 
     if (paint.getMaskFilter()){
@@ -1148,47 +1163,59 @@
     GrTextureParams params;
     SkPaint::FilterLevel paintFilterLevel = paint.getFilterLevel();
     GrTextureParams::FilterMode textureFilterMode;
+
+    int tileFilterPad;
+    bool doBicubic = false;
+
     switch(paintFilterLevel) {
         case SkPaint::kNone_FilterLevel:
+            tileFilterPad = 0;
             textureFilterMode = GrTextureParams::kNone_FilterMode;
             break;
         case SkPaint::kLow_FilterLevel:
+            tileFilterPad = 1;
             textureFilterMode = GrTextureParams::kBilerp_FilterMode;
             break;
         case SkPaint::kMedium_FilterLevel:
+            tileFilterPad = 1;
             textureFilterMode = GrTextureParams::kMipMap_FilterMode;
             break;
         case SkPaint::kHigh_FilterLevel:
-            // Fall back to mips for now
-            textureFilterMode = GrTextureParams::kMipMap_FilterMode;
+            if (flags & SkCanvas::kBleed_DrawBitmapRectFlag) {
+                // We will install an effect that does the filtering in the shader.
+                textureFilterMode = GrTextureParams::kNone_FilterMode;
+                tileFilterPad = GrBicubicEffect::kFilterTexelPad;
+                doBicubic = true;
+            } else {
+                // We don't yet support doing bicubic filtering with an interior clamp. Fall back
+                // to MIPs
+                textureFilterMode = GrTextureParams::kMipMap_FilterMode;
+                tileFilterPad = 1;
+            }
             break;
         default:
             SkErrorInternals::SetError( kInvalidPaint_SkError,
                                         "Sorry, I don't understand the filtering "
                                         "mode you asked for.  Falling back to "
                                         "MIPMaps.");
+            tileFilterPad = 1;
             textureFilterMode = GrTextureParams::kMipMap_FilterMode;
             break;
-
     }
 
     params.setFilterMode(textureFilterMode);
 
-    int maxTileSize = fContext->getMaxTextureSize();
-    if (SkPaint::kNone_FilterLevel != paint.getFilterLevel()) {
-        // We may need a skosh more room if we have to bump out the tile
-        // by 1 pixel all around
-        maxTileSize -= 2;
-    }
+    int maxTileSize = fContext->getMaxTextureSize() - 2 * tileFilterPad;
     int tileSize;
 
     SkIRect clippedSrcRect;
     if (this->shouldTileBitmap(bitmap, params, srcRectPtr, maxTileSize, &tileSize,
                                &clippedSrcRect)) {
-        this->drawTiledBitmap(bitmap, srcRect, clippedSrcRect, params, paint, flags, tileSize);
+        this->drawTiledBitmap(bitmap, srcRect, clippedSrcRect, params, paint, flags, tileSize,
+                              doBicubic);
     } else {
         // take the simple case
-        this->internalDrawBitmap(bitmap, srcRect, params, paint, flags);
+        this->internalDrawBitmap(bitmap, srcRect, params, paint, flags, doBicubic);
     }
 }
 
@@ -1200,7 +1227,8 @@
                                   const GrTextureParams& params,
                                   const SkPaint& paint,
                                   SkCanvas::DrawBitmapRectFlags flags,
-                                  int tileSize) {
+                                  int tileSize,
+                                  bool bicubic) {
     SkRect clippedSrcRect = SkRect::Make(clippedSrcIRect);
 
     int nx = bitmap.width() / tileSize;
@@ -1227,7 +1255,7 @@
             SkPoint offset = SkPoint::Make(SkIntToScalar(iTileR.fLeft),
                                            SkIntToScalar(iTileR.fTop));
 
-            if (SkPaint::kNone_FilterLevel != paint.getFilterLevel()) {
+            if (SkPaint::kNone_FilterLevel != paint.getFilterLevel() || bicubic) {
                 SkIRect iClampRect;
 
                 if (SkCanvas::kBleed_DrawBitmapRectFlag & flags) {
@@ -1235,13 +1263,15 @@
                     // but stay within the bitmap bounds
                     iClampRect = SkIRect::MakeWH(bitmap.width(), bitmap.height());
                 } else {
+                    SkASSERT(!bicubic); // Bicubic is not supported with non-bleed yet.
+
                     // In texture-domain/clamp mode we only want to expand the
                     // tile on edges interior to "srcRect" (i.e., we want to
                     // not bleed across the original clamped edges)
                     srcRect.roundOut(&iClampRect);
                 }
-
-                clamped_unit_outset_with_offset(&iTileR, &offset, iClampRect);
+                int outset = bicubic ? GrBicubicEffect::kFilterTexelPad : 1;
+                clamped_outset_with_offset(&iTileR, outset, &offset, iClampRect);
             }
 
             if (bitmap.extractSubset(&tmpB, iTileR)) {
@@ -1251,7 +1281,7 @@
                 tmpM.setTranslate(offset.fX, offset.fY);
                 GrContext::AutoMatrix am;
                 am.setPreConcat(fContext, tmpM);
-                this->internalDrawBitmap(tmpB, tileR, params, paint, flags);
+                this->internalDrawBitmap(tmpB, tileR, params, paint, flags, bicubic);
             }
         }
     }
@@ -1310,7 +1340,8 @@
                                      const SkRect& srcRect,
                                      const GrTextureParams& params,
                                      const SkPaint& paint,
-                                     SkCanvas::DrawBitmapRectFlags flags) {
+                                     SkCanvas::DrawBitmapRectFlags flags,
+                                     bool bicubic) {
     SkASSERT(bitmap.width() <= fContext->getMaxTextureSize() &&
              bitmap.height() <= fContext->getMaxTextureSize());
 
@@ -1332,6 +1363,7 @@
     bool needsTextureDomain = false;
     if (!(flags & SkCanvas::kBleed_DrawBitmapRectFlag) &&
         params.filterMode() != GrTextureParams::kNone_FilterMode) {
+        SkASSERT(!bicubic);
         // Need texture domain if drawing a sub rect.
         needsTextureDomain = srcRect.width() < bitmap.width() ||
                              srcRect.height() < bitmap.height();
@@ -1376,6 +1408,8 @@
                                                    textureDomain,
                                                    GrTextureDomainEffect::kClamp_WrapMode,
                                                    params.filterMode()));
+    } else if (bicubic) {
+        effect.reset(GrBicubicEffect::Create(texture, SkMatrix::I(), params));
     } else {
         effect.reset(GrSimpleTextureEffect::Create(texture, SkMatrix::I(), params));
     }
diff --git a/gpu/effects/GrBicubicEffect.cpp b/gpu/effects/GrBicubicEffect.cpp
index f6cc37d..cd013cb 100644
--- a/gpu/effects/GrBicubicEffect.cpp
+++ b/gpu/effects/GrBicubicEffect.cpp
@@ -73,8 +73,14 @@
                             "\tvec4 c = coefficients * ts;\n"
                             "\treturn c.x * c0 + c.y * c1 + c.z * c2 + c.w * c3;\n",
                             &cubicBlendName);
-    builder->fsCodeAppendf("\tvec2 coord = %s - %s * vec2(0.5, 0.5);\n", coords2D.c_str(), imgInc);
-    builder->fsCodeAppendf("\tvec2 f = fract(coord / %s);\n", imgInc);
+    builder->fsCodeAppendf("\tvec2 coord = %s - %s * vec2(0.5);\n", coords2D.c_str(), imgInc);
+    // We unnormalize the coord in order to determine our fractional offset (f) within the texel
+    // We then snap coord to a texel center and renormalize. The snap prevents cases where the
+    // starting coords are near a texel boundary and accumulations of imgInc would cause us to skip/
+    // double hit a texel.
+    builder->fsCodeAppendf("\tcoord /= %s;\n", imgInc);
+    builder->fsCodeAppend("\tvec2 f = fract(coord);\n");
+    builder->fsCodeAppendf("\tcoord = (coord - f + vec2(0.5)) * %s;\n", imgInc);
     for (int y = 0; y < 4; ++y) {
         for (int x = 0; x < 4; ++x) {
             SkString coord;
diff --git a/gpu/effects/GrBicubicEffect.h b/gpu/effects/GrBicubicEffect.h
index eabc79f..d8c431a 100644
--- a/gpu/effects/GrBicubicEffect.h
+++ b/gpu/effects/GrBicubicEffect.h
@@ -17,6 +17,10 @@
 
 class GrBicubicEffect : public GrSingleTextureEffect {
 public:
+    enum {
+        kFilterTexelPad = 2, // Given a src rect in texels to be filtered, this number of
+                             // surrounding texels are needed by the kernel in x and y.
+    };
     virtual ~GrBicubicEffect();
 
     static const char* Name() { return "Bicubic"; }