Fast path for blurred round rects -- blur a small 9patch rect on the CPU
And nonlinearly stretch the resulting texture across proxy geometry.

BUG=

Committed: http://code.google.com/p/skia/source/detail?r=14392

R=bsalomon@google.com, reed@google.com

Author: humper@google.com

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

git-svn-id: http://skia.googlecode.com/svn/trunk@14404 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/expectations/gm/ignored-tests.txt b/expectations/gm/ignored-tests.txt
index 51f2c8b..4e5eb97 100644
--- a/expectations/gm/ignored-tests.txt
+++ b/expectations/gm/ignored-tests.txt
@@ -57,3 +57,6 @@
 composeshader
 peekpixels
 
+# humper: https://codereview.chromium.org/248613004/
+# Changed the test in a few ways, will need rebaselining.
+simpleblurrrect
diff --git a/gm/blurroundrect.cpp b/gm/blurroundrect.cpp
index f688380..08086ac 100644
--- a/gm/blurroundrect.cpp
+++ b/gm/blurroundrect.cpp
@@ -106,30 +106,32 @@
     }
 
     virtual SkISize onISize() SK_OVERRIDE {
-        return SkISize::Make(750, 750);
+        return SkISize::Make(950, 950);
     }
 
     virtual void onDraw(SkCanvas* canvas) SK_OVERRIDE {
         canvas->scale(1.5f, 1.5f);
+        canvas->translate(50,50);
 
-        const int blurRadii[] = { 1, 3, 6, 10 };
-        const int cornerRadii[] = { 1, 3, 6, 10 };
+        const float blurRadii[] = { 1,5,10,20 };
+        const int cornerRadii[] = { 1,5,10,20 };
         const SkRect r = SkRect::MakeWH(SkIntToScalar(100), SkIntToScalar(100));
         for (size_t i = 0; i < SK_ARRAY_COUNT(blurRadii); ++i) {
             SkAutoCanvasRestore autoRestore(canvas, true);
-            canvas->translate(0, (r.height() + SkIntToScalar(20)) * i);
+            canvas->translate(0, (r.height() + SkIntToScalar(50)) * i);
             for (size_t j = 0; j < SK_ARRAY_COUNT(cornerRadii); ++j) {
                 SkMaskFilter* filter = SkBlurMaskFilter::Create(
                     SkBlurMaskFilter::kNormal_BlurStyle,
-                    SkBlurMask::ConvertRadiusToSigma(SkIntToScalar(blurRadii[i])));
+                    SkBlurMask::ConvertRadiusToSigma(SkIntToScalar(blurRadii[i])),
+                    SkBlurMaskFilter::kHighQuality_BlurFlag);
                 SkPaint paint;
-                paint.setColor(SK_ColorBLUE);
+                paint.setColor(SK_ColorBLACK);
                 paint.setMaskFilter(filter)->unref();
 
                 SkRRect rrect;
                 rrect.setRectXY(r, SkIntToScalar(cornerRadii[j]), SkIntToScalar(cornerRadii[j]));
                 canvas->drawRRect(rrect, paint);
-                canvas->translate(r.width() + SkIntToScalar(10), 0);
+                canvas->translate(r.width() + SkIntToScalar(50), 0);
             }
         }
     }
diff --git a/src/effects/SkBlurMask.cpp b/src/effects/SkBlurMask.cpp
index 9712ecc..f33817a 100644
--- a/src/effects/SkBlurMask.cpp
+++ b/src/effects/SkBlurMask.cpp
@@ -477,14 +477,14 @@
 
 bool SkBlurMask::BoxBlur(SkMask* dst, const SkMask& src,
                          SkScalar sigma, Style style, Quality quality,
-                         SkIPoint* margin) {
+                         SkIPoint* margin, bool force_quality) {
 
     if (src.fFormat != SkMask::kA8_Format) {
         return false;
     }
 
     // Force high quality off for small radii (performance)
-    if (sigma <= SkIntToScalar(2)) {
+    if (!force_quality && sigma <= SkIntToScalar(2)) {
         quality = kLow_Quality;
     }
 
diff --git a/src/effects/SkBlurMask.h b/src/effects/SkBlurMask.h
index eb67d4c..d4cd3d1 100644
--- a/src/effects/SkBlurMask.h
+++ b/src/effects/SkBlurMask.h
@@ -40,9 +40,16 @@
                          SkIPoint *margin = NULL,
                          SkMask::CreateMode createMode =
                                                 SkMask::kComputeBoundsAndRenderImage_CreateMode);
+
+    // forceQuality will prevent BoxBlur from falling back to the low quality approach when sigma
+    // is very small -- this can be used predict the margin bump ahead of time without completely
+    // replicating the internal logic.  This permits not only simpler caching of blurred results,
+    // but also being able to predict precisely at what pixels the blurred profile of e.g. a
+    // rectangle will lie.
+
     static bool BoxBlur(SkMask* dst, const SkMask& src,
                         SkScalar sigma, Style style, Quality quality,
-                        SkIPoint* margin = NULL);
+                        SkIPoint* margin = NULL, bool forceQuality = false);
 
     // the "ground truth" blur does a gaussian convolution; it's slow
     // but useful for comparison purposes.
diff --git a/src/effects/SkBlurMaskFilter.cpp b/src/effects/SkBlurMaskFilter.cpp
index ff98daf..9f8935a 100644
--- a/src/effects/SkBlurMaskFilter.cpp
+++ b/src/effects/SkBlurMaskFilter.cpp
@@ -25,6 +25,7 @@
 #include "effects/GrSimpleTextureEffect.h"
 #include "GrTBackendEffectFactory.h"
 #include "SkGrPixelRef.h"
+#include "SkDraw.h"
 #endif
 
 class SkBlurMaskFilterImpl : public SkMaskFilter {
@@ -260,9 +261,9 @@
 }
 
 #ifdef SK_IGNORE_FAST_RRECT_BLUR
-SK_CONF_DECLARE( bool, c_analyticBlurRRect, "mask.filter.blur.analyticRRect", false, "Use the faster analytic blur approach for ninepatch rects" );
+SK_CONF_DECLARE( bool, c_analyticBlurRRect, "mask.filter.blur.analyticblurrrect", false, "Use the faster analytic blur approach for ninepatch rects" );
 #else
-SK_CONF_DECLARE( bool, c_analyticBlurRRect, "mask.filter.blur.analyticRRect", true, "Use the faster analytic blur approach for ninepatch round rects" );
+SK_CONF_DECLARE( bool, c_analyticBlurRRect, "mask.filter.blur.analyticblurrrect", true, "Use the faster analytic blur approach for ninepatch round rects" );
 #endif
 
 SkMaskFilter::FilterReturn
@@ -819,11 +820,285 @@
     return true;
 }
 
+class GrGLRRectBlurEffect;
+
+class GrRRectBlurEffect : public GrEffect {
+public:
+
+    static GrEffectRef* Create(GrContext* context, float sigma, const SkRRect&);
+
+    virtual ~GrRRectBlurEffect() {};
+    static const char* Name() { return "GrRRectBlur"; }
+
+    const SkRRect& getRRect() const { return fRRect; }
+    float getSigma() const { return fSigma; }
+
+    typedef GrGLRRectBlurEffect GLEffect;
+
+    virtual void getConstantColorComponents(GrColor* color, uint32_t* validFlags) const SK_OVERRIDE;
+
+    virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE;
+
+private:
+    GrRRectBlurEffect(float sigma, const SkRRect&, GrTexture* profileTexture);
+
+    virtual bool onIsEqual(const GrEffect& other) const SK_OVERRIDE;
+
+    SkRRect             fRRect;
+    float               fSigma;
+    GrTextureAccess     fNinePatchAccess;
+
+    GR_DECLARE_EFFECT_TEST;
+
+    typedef GrEffect INHERITED;
+};
+
+
+GrEffectRef* GrRRectBlurEffect::Create(GrContext* context, float sigma, const SkRRect& rrect) {
+    if (!rrect.isSimpleCircular()) {
+        SkDebugf( "not simple circular\n" );
+        return NULL;
+    }
+
+    // Make sure we can successfully ninepatch this rrect -- the blur sigma has to be
+    // sufficiently small relative to both the size of the corner radius and the
+    // width (and height) of the rrect.
+
+    unsigned int blurRadius = 3*SkScalarCeilToInt(sigma-1/6.0f);
+    unsigned int cornerRadius = SkScalarCeilToInt(rrect.getSimpleRadii().x());
+    if (cornerRadius + blurRadius > rrect.width()/2 ||
+        cornerRadius + blurRadius > rrect.height()/2) {
+        return NULL;
+    }
+
+    static const GrCacheID::Domain gRRectBlurDomain = GrCacheID::GenerateDomain();
+    GrCacheID::Key key;
+    memset(&key, 0, sizeof(key));
+    key.fData32[0] = blurRadius;
+    key.fData32[1] = cornerRadius;
+    GrCacheID blurRRectNinePatchID(gRRectBlurDomain, key);
+
+    GrTextureParams params;
+    params.setFilterMode(GrTextureParams::kBilerp_FilterMode);
+
+    unsigned int smallRectSide = 2*(blurRadius + cornerRadius) + 1;
+    unsigned int texSide = smallRectSide + 2*blurRadius;
+    GrTextureDesc texDesc;
+    texDesc.fWidth = texSide;
+    texDesc.fHeight = texSide;
+    texDesc.fConfig = kAlpha_8_GrPixelConfig;
+
+    GrTexture *blurNinePatchTexture = context->findAndRefTexture(texDesc, blurRRectNinePatchID, &params);
+
+    if (NULL == blurNinePatchTexture) {
+        SkMask mask;
+
+        mask.fBounds = SkIRect::MakeWH(smallRectSide, smallRectSide);
+        mask.fFormat = SkMask::kA8_Format;
+        mask.fRowBytes = mask.fBounds.width();
+        mask.fImage = SkMask::AllocImage(mask.computeTotalImageSize());
+        SkAutoMaskFreeImage amfi(mask.fImage);
+
+        memset(mask.fImage, 0, mask.computeTotalImageSize());
+
+        SkRect smallRect;
+        smallRect.setWH(SkIntToScalar(smallRectSide), SkIntToScalar(smallRectSide));
+
+        SkRRect smallRRect;
+        smallRRect.setRectXY(smallRect, SkIntToScalar(cornerRadius), SkIntToScalar(cornerRadius));
+
+        SkPath path;
+        path.addRRect( smallRRect );
+
+        SkDraw::DrawToMask(path, &mask.fBounds, NULL, NULL, &mask, SkMask::kJustRenderImage_CreateMode, SkPaint::kFill_Style);
+
+        SkMask blurred_mask;
+        SkBlurMask::BoxBlur(&blurred_mask, mask, sigma, SkBlurMask::kNormal_Style, SkBlurMask::kHigh_Quality, NULL, true );
+
+        blurNinePatchTexture = context->createTexture(&params, texDesc, blurRRectNinePatchID, blurred_mask.fImage, 0);
+    }
+
+    if (NULL == blurNinePatchTexture) {
+        return NULL;
+    }
+
+    return CreateEffectRef(AutoEffectUnref(SkNEW_ARGS(GrRRectBlurEffect,
+                                                      (sigma, rrect, blurNinePatchTexture))));
+}
+
+void GrRRectBlurEffect::getConstantColorComponents(GrColor* color, uint32_t* validFlags) const {
+    *validFlags = 0;
+}
+
+const GrBackendEffectFactory& GrRRectBlurEffect::getFactory() const {
+    return GrTBackendEffectFactory<GrRRectBlurEffect>::getInstance();
+}
+
+GrRRectBlurEffect::GrRRectBlurEffect(float sigma, const SkRRect& rrect, GrTexture *ninePatchTexture)
+    : fRRect(rrect),
+      fSigma(sigma),
+      fNinePatchAccess(ninePatchTexture) {
+    this->addTextureAccess(&fNinePatchAccess);
+    this->setWillReadFragmentPosition();
+}
+
+bool GrRRectBlurEffect::onIsEqual(const GrEffect& other) const {
+    const GrRRectBlurEffect& rrbe = CastEffect<GrRRectBlurEffect>(other);
+    return fRRect.getSimpleRadii().fX == rrbe.fRRect.getSimpleRadii().fX && fSigma == rrbe.fSigma;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+GR_DEFINE_EFFECT_TEST(GrRRectBlurEffect);
+
+GrEffectRef* GrRRectBlurEffect::TestCreate(SkRandom* random,
+                                     GrContext* context,
+                                     const GrDrawTargetCaps& caps,
+                                     GrTexture*[]) {
+    SkScalar w = random->nextRangeScalar(100.f, 1000.f);
+    SkScalar h = random->nextRangeScalar(100.f, 1000.f);
+    SkScalar r = random->nextRangeF(1.f, 9.f);
+    SkScalar sigma = random->nextRangeF(1.f,20.f);
+    SkRRect rrect;
+    rrect.setRectXY(SkRect::MakeWH(w, h), r, r);
+    return GrRRectBlurEffect::Create(context, sigma, rrect);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+class GrGLRRectBlurEffect : public GrGLEffect {
+public:
+    GrGLRRectBlurEffect(const GrBackendEffectFactory&, const GrDrawEffect&);
+
+    virtual void emitCode(GrGLShaderBuilder* builder,
+                          const GrDrawEffect& drawEffect,
+                          EffectKey key,
+                          const char* outputColor,
+                          const char* inputColor,
+                          const TransformedCoordsArray&,
+                          const TextureSamplerArray&) SK_OVERRIDE;
+
+    virtual void setData(const GrGLUniformManager&, const GrDrawEffect&) SK_OVERRIDE;
+
+private:
+    GrGLUniformManager::UniformHandle   fProxyRectUniform;
+    GrGLUniformManager::UniformHandle   fCornerRadiusUniform;
+    GrGLUniformManager::UniformHandle   fBlurRadiusUniform;
+    typedef GrGLEffect INHERITED;
+};
+
+GrGLRRectBlurEffect::GrGLRRectBlurEffect(const GrBackendEffectFactory& factory,
+                             const GrDrawEffect& drawEffect)
+    : INHERITED (factory) {
+}
+
+void GrGLRRectBlurEffect::emitCode(GrGLShaderBuilder* builder,
+                             const GrDrawEffect& drawEffect,
+                             EffectKey key,
+                             const char* outputColor,
+                             const char* inputColor,
+                             const TransformedCoordsArray&,
+                             const TextureSamplerArray& samplers) {
+    const char *rectName;
+    const char *cornerRadiusName;
+    const char *blurRadiusName;
+
+    // The proxy rect has left, top, right, and bottom edges correspond to
+    // components x, y, z, and w, respectively.
+
+    fProxyRectUniform = builder->addUniform(GrGLShaderBuilder::kFragment_Visibility,
+                                            kVec4f_GrSLType,
+                                            "proxyRect",
+                                            &rectName);
+    fCornerRadiusUniform = builder->addUniform(GrGLShaderBuilder::kFragment_Visibility,
+                                                 kFloat_GrSLType,
+                                                 "cornerRadius",
+                                                 &cornerRadiusName);
+    fBlurRadiusUniform = builder->addUniform(GrGLShaderBuilder::kFragment_Visibility,
+                                                 kFloat_GrSLType,
+                                                 "blurRadius",
+                                                 &blurRadiusName);
+    const char* fragmentPos = builder->fragmentPosition();
+
+    // warp the fragment position to the appropriate part of the 9patch blur texture
+
+    builder->fsCodeAppendf("\t\tvec2 rectCenter = (%s.xy + %s.zw)/2.0;\n", rectName, rectName);
+    builder->fsCodeAppendf("\t\tvec2 translatedFragPos = %s.xy - %s.xy;\n", fragmentPos, rectName);
+    builder->fsCodeAppendf("\t\tfloat threshold = %s + 2.0*%s;\n", cornerRadiusName, blurRadiusName );
+    builder->fsCodeAppendf("\t\tvec2 middle = %s.zw - %s.xy - 2.0*threshold;\n", rectName, rectName );
+
+    builder->fsCodeAppendf("\t\tif (translatedFragPos.x >= threshold && translatedFragPos.x < (middle.x+threshold)) {\n" );
+    builder->fsCodeAppendf("\t\t\ttranslatedFragPos.x = threshold;\n");
+    builder->fsCodeAppendf("\t\t} else if (translatedFragPos.x >= (middle.x + threshold)) {\n");
+    builder->fsCodeAppendf("\t\t\ttranslatedFragPos.x -= middle.x;\n");
+    builder->fsCodeAppendf("\t\t}\n");
+
+    builder->fsCodeAppendf("\t\tif (translatedFragPos.y > threshold && translatedFragPos.y < (middle.y+threshold)) {\n" );
+    builder->fsCodeAppendf("\t\t\ttranslatedFragPos.y = threshold;\n");
+    builder->fsCodeAppendf("\t\t} else if (translatedFragPos.y >= (middle.y + threshold)) {\n");
+    builder->fsCodeAppendf("\t\t\ttranslatedFragPos.y -= middle.y;\n");
+    builder->fsCodeAppendf("\t\t}\n");
+
+    builder->fsCodeAppendf("\t\tvec2 proxyDims = vec2(2.0*threshold+1.0);\n");
+    builder->fsCodeAppendf("\t\tvec2 texCoord = translatedFragPos / proxyDims;\n");
+
+    builder->fsCodeAppendf("\t%s = ", outputColor);
+    builder->fsAppendTextureLookupAndModulate(inputColor, samplers[0], "texCoord");
+    builder->fsCodeAppend(";\n");
+}
+
+void GrGLRRectBlurEffect::setData(const GrGLUniformManager& uman,
+                                    const GrDrawEffect& drawEffect) {
+    const GrRRectBlurEffect& brre = drawEffect.castEffect<GrRRectBlurEffect>();
+    SkRRect rrect = brre.getRRect();
+
+    float blurRadius = 3.f*SkScalarCeilToScalar(brre.getSigma()-1/6.0f);
+    uman.set1f(fBlurRadiusUniform, blurRadius);
+
+    SkRect rect = rrect.getBounds();
+    rect.outset(blurRadius, blurRadius);
+    uman.set4f(fProxyRectUniform, rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
+
+    SkScalar radius = 0;
+    SkASSERT(rrect.isSimpleCircular() || rrect.isRect());
+    radius = rrect.getSimpleRadii().fX;
+    uman.set1f(fCornerRadiusUniform, radius);
+}
+
+
 bool SkBlurMaskFilterImpl::directFilterRRectMaskGPU(GrContext* context,
                                                     GrPaint* grp,
                                                     const SkStrokeRec& strokeRec,
                                                     const SkRRect& rrect) const {
-    return false;
+    if (fBlurStyle != SkBlurMaskFilter::kNormal_BlurStyle) {
+        return false;
+    }
+
+    if (!strokeRec.isFillStyle()) {
+        return false;
+    }
+
+    SkRect proxy_rect = rrect.rect();
+    SkMatrix ctm = context->getMatrix();
+    SkScalar xformedSigma = this->computeXformedSigma(ctm);
+    float extra=3.f*SkScalarCeilToScalar(xformedSigma-1/6.0f);
+    proxy_rect.outset(extra, extra);
+
+    SkAutoTUnref<GrEffectRef> effect(GrRRectBlurEffect::Create(
+            context, xformedSigma, rrect));
+    if (!effect) {
+        return false;
+    }
+
+    GrContext::AutoMatrix am;
+    if (!am.setIdentity(context, grp)) {
+       return false;
+    }
+
+    grp->addCoverageEffect(effect);
+
+    context->drawRect(*grp, proxy_rect);
+    return true;
 }
 
 bool SkBlurMaskFilterImpl::canFilterMaskGPU(const SkRect& srcBounds,
diff --git a/src/images/SkImageDecoder_libwebp.cpp b/src/images/SkImageDecoder_libwebp.cpp
index 4e23e50..0299025 100644
--- a/src/images/SkImageDecoder_libwebp.cpp
+++ b/src/images/SkImageDecoder_libwebp.cpp
@@ -559,15 +559,6 @@
   }
 }
 
-static void Alpha8_To_RGB(const uint8_t* in, uint8_t* rgb, int width,
-                          const SkPMColor* SK_RESTRICT ctable) {
-  const uint8_t* SK_RESTRICT src = (const uint8_t*)in;
-  for (int i = 0; i < width; ++i) {
-      rgb[0] = rgb[1] = rgb[2] = *src++;
-      rgb += 3;
-  }
-}
-
 static ScanlineImporter ChooseImporter(const SkBitmap::Config& config,
                                        bool  hasAlpha,
                                        int*  bpp) {
@@ -594,9 +585,6 @@
         case SkBitmap::kIndex8_Config:
             *bpp = 3;
             return Index8_To_RGB;
-        case SkBitmap::kA8_Config:
-            *bpp = 3;
-            return Alpha8_To_RGB;
         default:
             return NULL;
     }