Avoid caching shadow properties in Java & HWUI.

bug: 10650594
Change-Id: I6f57df002710bb0567ed7e53fc0bfe96cfd504b8
diff --git a/core/java/android/view/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java
index 34b85d9..5b2ce60 100644
--- a/core/java/android/view/GLES20Canvas.java
+++ b/core/java/android/view/GLES20Canvas.java
@@ -42,7 +42,6 @@
 class GLES20Canvas extends HardwareCanvas {
     // Must match modifiers used in the JNI layer
     private static final int MODIFIER_NONE = 0;
-    private static final int MODIFIER_SHADOW = 1;
     private static final int MODIFIER_SHADER = 2;
 
     private final boolean mOpaque;
@@ -1307,12 +1306,6 @@
     private int setupModifiers(Paint paint) {
         int modifiers = MODIFIER_NONE;
 
-        if (paint.hasShadow) {
-            nSetupShadow(mRenderer, paint.shadowRadius, paint.shadowDx, paint.shadowDy,
-                    paint.shadowColor);
-            modifiers |= MODIFIER_SHADOW;
-        }
-
         final Shader shader = paint.getShader();
         if (shader != null) {
             nSetupShader(mRenderer, shader.native_shader);
@@ -1325,12 +1318,6 @@
     private int setupModifiers(Paint paint, int flags) {
         int modifiers = MODIFIER_NONE;
 
-        if (paint.hasShadow && (flags & MODIFIER_SHADOW) != 0) {
-            nSetupShadow(mRenderer, paint.shadowRadius, paint.shadowDx, paint.shadowDy,
-                    paint.shadowColor);
-            modifiers |= MODIFIER_SHADOW;
-        }
-
         final Shader shader = paint.getShader();
         if (shader != null && (flags & MODIFIER_SHADER) != 0) {
             nSetupShader(mRenderer, shader.native_shader);
@@ -1341,8 +1328,6 @@
     }
 
     private static native void nSetupShader(long renderer, long shader);
-    private static native void nSetupShadow(long renderer, float radius,
-            float dx, float dy, int color);
 
     private static native void nResetModifiers(long renderer, int modifiers);
 }
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index b91111d..8f073de 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -289,6 +289,8 @@
     private Spannable.Factory mSpannableFactory = Spannable.Factory.getInstance();
 
     private float mShadowRadius, mShadowDx, mShadowDy;
+    private int mShadowColor;
+
 
     private boolean mPreDrawRegistered;
 
@@ -2755,6 +2757,7 @@
         mShadowRadius = radius;
         mShadowDx = dx;
         mShadowDy = dy;
+        mShadowColor = color;
 
         // Will change text clip region
         if (mEditor != null) mEditor.invalidateTextDisplayList();
@@ -2804,7 +2807,7 @@
      * @attr ref android.R.styleable#TextView_shadowColor
      */
     public int getShadowColor() {
-        return mTextPaint.shadowColor;
+        return mShadowColor;
     }
 
     /**
diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp
index 08a88d1..22c17dd 100644
--- a/core/jni/android/graphics/Paint.cpp
+++ b/core/jni/android/graphics/Paint.cpp
@@ -33,6 +33,7 @@
 #include "SkXfermode.h"
 #include "unicode/uloc.h"
 #include "unicode/ushape.h"
+#include "utils/Blur.h"
 #include "TextLayout.h"
 
 // temporary for debugging
@@ -776,19 +777,23 @@
         env->ReleaseStringChars(text, textArray);
     }
 
-    static void setShadowLayer(JNIEnv* env, jobject jpaint, jfloat radius,
+    static void setShadowLayer(JNIEnv* env, jobject clazz, jlong paintHandle, jfloat radius,
                                jfloat dx, jfloat dy, jint color) {
-        NPE_CHECK_RETURN_VOID(env, jpaint);
-
-        SkPaint* paint = GraphicsJNI::getNativePaint(env, jpaint);
+        SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
         if (radius <= 0) {
             paint->setLooper(NULL);
         }
         else {
-            paint->setLooper(new SkBlurDrawLooper(radius, dx, dy, (SkColor)color))->unref();
+            SkScalar sigma = android::uirenderer::Blur::convertRadiusToSigma(radius);
+            paint->setLooper(new SkBlurDrawLooper((SkColor)color, sigma, dx, dy))->unref();
         }
     }
 
+    static jboolean hasShadowLayer(JNIEnv* env, jobject clazz, jlong paintHandle) {
+        SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
+        return paint->getLooper() && paint->getLooper()->asABlurShadow(NULL);
+    }
+
     static int breakText(JNIEnv* env, SkPaint& paint, const jchar text[],
                          int count, float maxWidth, jint bidiFlags, jfloatArray jmeasured,
                          SkPaint::TextBufferDirection tbd) {
@@ -968,7 +973,8 @@
                                         (void*) SkPaintGlue::getStringBounds },
     {"nativeGetCharArrayBounds", "(J[CIIILandroid/graphics/Rect;)V",
                                     (void*) SkPaintGlue::getCharArrayBounds },
-    {"nSetShadowLayer", "(FFFI)V", (void*)SkPaintGlue::setShadowLayer}
+    {"native_setShadowLayer", "(JFFFI)V", (void*)SkPaintGlue::setShadowLayer},
+    {"native_hasShadowLayer", "(J)Z", (void*)SkPaintGlue::hasShadowLayer}
 };
 
 static jfieldID req_fieldID(jfieldID id) {
diff --git a/core/jni/android_view_GLES20Canvas.cpp b/core/jni/android_view_GLES20Canvas.cpp
index ef5ebd0..ca840d91 100644
--- a/core/jni/android_view_GLES20Canvas.cpp
+++ b/core/jni/android_view_GLES20Canvas.cpp
@@ -78,7 +78,6 @@
     #define RENDERER_LOGD(...)
 #endif
 
-#define MODIFIER_SHADOW 1
 #define MODIFIER_SHADER 2
 
 // ----------------------------------------------------------------------------
@@ -632,7 +631,6 @@
 static void android_view_GLES20Canvas_resetModifiers(JNIEnv* env, jobject clazz,
         jlong rendererPtr, jint modifiers) {
     OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr);
-    if (modifiers & MODIFIER_SHADOW) renderer->resetShadow();
     if (modifiers & MODIFIER_SHADER) renderer->resetShader();
 }
 
@@ -644,12 +642,6 @@
 }
 
 
-static void android_view_GLES20Canvas_setupShadow(JNIEnv* env, jobject clazz,
-        jlong rendererPtr, jfloat radius, jfloat dx, jfloat dy, jint color) {
-    OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr);
-    renderer->setupShadow(radius, dx, dy, color);
-}
-
 // ----------------------------------------------------------------------------
 // Draw filters
 // ----------------------------------------------------------------------------
@@ -1050,7 +1042,6 @@
 
     { "nResetModifiers",    "(JI)V",           (void*) android_view_GLES20Canvas_resetModifiers },
     { "nSetupShader",       "(JJ)V",           (void*) android_view_GLES20Canvas_setupShader },
-    { "nSetupShadow",       "(JFFFI)V",        (void*) android_view_GLES20Canvas_setupShadow },
 
     { "nSetupPaintFilter",  "(JII)V",          (void*) android_view_GLES20Canvas_setupPaintFilter },
     { "nResetPaintFilter",  "(J)V",            (void*) android_view_GLES20Canvas_resetPaintFilter },
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index 1e1128e..457b3ea 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -55,27 +55,6 @@
     /**
      * @hide
      */
-    public boolean hasShadow;
-    /**
-     * @hide
-     */
-    public float shadowDx;
-    /**
-     * @hide
-     */
-    public float shadowDy;
-    /**
-     * @hide
-     */
-    public float shadowRadius;
-    /**
-     * @hide
-     */
-    public int shadowColor;
-
-    /**
-     * @hide
-     */
     public  int         mBidiFlags = BIDI_DEFAULT_LTR;
     
     static final Style[] sStyleArray = {
@@ -492,12 +471,6 @@
         mCompatScaling = 1;
         mInvCompatScaling = 1;
 
-        hasShadow = false;
-        shadowDx = 0;
-        shadowDy = 0;
-        shadowRadius = 0;
-        shadowColor = 0;
-
         mBidiFlags = BIDI_DEFAULT_LTR;
         setTextLocale(Locale.getDefault());
         setElegantTextHeight(false);
@@ -538,12 +511,6 @@
         mCompatScaling = paint.mCompatScaling;
         mInvCompatScaling = paint.mInvCompatScaling;
 
-        hasShadow = paint.hasShadow;
-        shadowDx = paint.shadowDx;
-        shadowDy = paint.shadowDy;
-        shadowRadius = paint.shadowRadius;
-        shadowColor = paint.shadowColor;
-
         mBidiFlags = paint.mBidiFlags;
         mLocale = paint.mLocale;
     }
@@ -1135,22 +1102,24 @@
      * layer is removed.
      */
     public void setShadowLayer(float radius, float dx, float dy, int color) {
-        hasShadow = radius > 0.0f;
-        shadowRadius = radius;
-        shadowDx = dx;
-        shadowDy = dy;
-        shadowColor = color;
-        nSetShadowLayer(radius, dx, dy, color);
+      native_setShadowLayer(mNativePaint, radius, dx, dy, color);
     }
-    
-    private native void nSetShadowLayer(float radius, float dx, float dy, int color);
 
     /**
      * Clear the shadow layer.
      */
     public void clearShadowLayer() {
-        hasShadow = false;
-        nSetShadowLayer(0, 0, 0, 0);
+        setShadowLayer(0, 0, 0, 0);
+    }
+
+    /**
+     * Checks if the paint has a shadow layer attached
+     *
+     * @return true if the paint has a shadow layer attached and false otherwise
+     * @hide
+     */
+    public boolean hasShadowLayer() {
+      return native_hasShadowLayer(mNativePaint);
     }
 
     /**
@@ -2295,4 +2264,8 @@
     private static native void nativeGetCharArrayBounds(long nativePaint,
                                 char[] text, int index, int count, int bidiFlags, Rect bounds);
     private static native void finalizer(long nativePaint);
+
+    private static native void native_setShadowLayer(long native_object,
+            float radius, float dx, float dy, int color);
+    private static native boolean native_hasShadowLayer(long native_object);
 }
diff --git a/graphics/java/android/graphics/drawable/ShapeDrawable.java b/graphics/java/android/graphics/drawable/ShapeDrawable.java
index 61b1b85..99ab4dd 100644
--- a/graphics/java/android/graphics/drawable/ShapeDrawable.java
+++ b/graphics/java/android/graphics/drawable/ShapeDrawable.java
@@ -237,7 +237,7 @@
         paint.setAlpha(modulateAlpha(prevAlpha, state.mAlpha));
 
         // only draw shape if it may affect output
-        if (paint.getAlpha() != 0 || paint.getXfermode() != null || paint.hasShadow) {
+        if (paint.getAlpha() != 0 || paint.getXfermode() != null || paint.hasShadowLayer()) {
             final boolean clearColorFilter;
             if (mTintFilter != null && paint.getColorFilter() == null) {
                 paint.setColorFilter(mTintFilter);
diff --git a/libs/hwui/DeferredDisplayList.cpp b/libs/hwui/DeferredDisplayList.cpp
index 3d58964..45b6624 100644
--- a/libs/hwui/DeferredDisplayList.cpp
+++ b/libs/hwui/DeferredDisplayList.cpp
@@ -190,7 +190,7 @@
 
         // Overlapping other operations is only allowed for text without shadow. For other ops,
         // multiDraw isn't guaranteed to overdraw correctly
-        if (!isTextBatch || state->mDrawModifiers.mHasShadow) {
+        if (!isTextBatch || op->hasTextShadow()) {
             if (intersects(state->mBounds)) return false;
         }
         const DeferredDisplayState* lhs = state;
diff --git a/libs/hwui/DisplayListOp.h b/libs/hwui/DisplayListOp.h
index 6dfb918..77afc97 100644
--- a/libs/hwui/DisplayListOp.h
+++ b/libs/hwui/DisplayListOp.h
@@ -183,6 +183,10 @@
         return OpenGLRenderer::getAlphaDirect(mPaint);
     }
 
+    virtual bool hasTextShadow() const {
+        return false;
+    }
+
     inline float strokeWidthOutset() {
         // since anything AA stroke with less than 1.0 pixel width is drawn with an alpha-reduced
         // 1.0 stroke, treat 1.0 as minimum.
@@ -244,11 +248,11 @@
 
     bool getLocalBounds(const DrawModifiers& drawModifiers, Rect& localBounds) {
         localBounds.set(mLocalBounds);
-        if (drawModifiers.mHasShadow) {
-            // TODO: inspect paint's looper directly
+        OpenGLRenderer::TextShadow textShadow;
+        if (OpenGLRenderer::getTextShadow(mPaint, &textShadow)) {
             Rect shadow(mLocalBounds);
-            shadow.translate(drawModifiers.mShadowDx, drawModifiers.mShadowDy);
-            shadow.outset(drawModifiers.mShadowRadius);
+            shadow.translate(textShadow.dx, textShadow.dx);
+            shadow.outset(textShadow.radius);
             localBounds.unionWith(shadow);
         }
         return true;
@@ -619,41 +623,6 @@
     SkiaShader* mShader;
 };
 
-class ResetShadowOp : public StateOp {
-public:
-    virtual void applyState(OpenGLRenderer& renderer, int saveCount) const {
-        renderer.resetShadow();
-    }
-
-    virtual void output(int level, uint32_t logFlags) const {
-        OP_LOGS("ResetShadow");
-    }
-
-    virtual const char* name() { return "ResetShadow"; }
-};
-
-class SetupShadowOp : public StateOp {
-public:
-    SetupShadowOp(float radius, float dx, float dy, int color)
-            : mRadius(radius), mDx(dx), mDy(dy), mColor(color) {}
-
-    virtual void applyState(OpenGLRenderer& renderer, int saveCount) const {
-        renderer.setupShadow(mRadius, mDx, mDy, mColor);
-    }
-
-    virtual void output(int level, uint32_t logFlags) const {
-        OP_LOG("SetupShadow, radius %f, %f, %f, color %#x", mRadius, mDx, mDy, mColor);
-    }
-
-    virtual const char* name() { return "SetupShadow"; }
-
-private:
-    float mRadius;
-    float mDx;
-    float mDy;
-    int mColor;
-};
-
 class ResetPaintFilterOp : public StateOp {
 public:
     virtual void applyState(OpenGLRenderer& renderer, int saveCount) const {
@@ -1330,6 +1299,10 @@
         OP_LOG("Draw some text, %d bytes", mBytesCount);
     }
 
+    virtual bool hasTextShadow() const {
+        return OpenGLRenderer::hasTextShadow(mPaint);
+    }
+
     virtual void onDefer(OpenGLRenderer& renderer, DeferInfo& deferInfo,
             const DeferredDisplayState& state) {
         const SkPaint* paint = getPaint(renderer);
diff --git a/libs/hwui/DisplayListRenderer.cpp b/libs/hwui/DisplayListRenderer.cpp
index e36d975..1b8eb82 100644
--- a/libs/hwui/DisplayListRenderer.cpp
+++ b/libs/hwui/DisplayListRenderer.cpp
@@ -399,16 +399,6 @@
     addStateOp(new (alloc()) SetupShaderOp(shader));
 }
 
-void DisplayListRenderer::resetShadow() {
-    addStateOp(new (alloc()) ResetShadowOp());
-    OpenGLRenderer::resetShadow();
-}
-
-void DisplayListRenderer::setupShadow(float radius, float dx, float dy, int color) {
-    addStateOp(new (alloc()) SetupShadowOp(radius, dx, dy, color));
-    OpenGLRenderer::setupShadow(radius, dx, dy, color);
-}
-
 void DisplayListRenderer::resetPaintFilter() {
     addStateOp(new (alloc()) ResetPaintFilterOp());
 }
diff --git a/libs/hwui/DisplayListRenderer.h b/libs/hwui/DisplayListRenderer.h
index 04c5a73..bd75952 100644
--- a/libs/hwui/DisplayListRenderer.h
+++ b/libs/hwui/DisplayListRenderer.h
@@ -102,9 +102,6 @@
     virtual void resetShader();
     virtual void setupShader(SkiaShader* shader);
 
-    virtual void resetShadow();
-    virtual void setupShadow(float radius, float dx, float dy, int color);
-
     virtual void resetPaintFilter();
     virtual void setupPaintFilter(int clearBits, int setBits);
 
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index 95fdb04..3f2f43e 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -2694,28 +2694,32 @@
         FontRenderer& fontRenderer, int alpha, float x, float y) {
     mCaches.activeTexture(0);
 
+    TextShadow textShadow;
+    if (!getTextShadow(paint, &textShadow)) {
+        LOG_ALWAYS_FATAL("failed to query shadow attributes");
+    }
+
     // NOTE: The drop shadow will not perform gamma correction
     //       if shader-based correction is enabled
     mCaches.dropShadowCache.setFontRenderer(fontRenderer);
     const ShadowTexture* shadow = mCaches.dropShadowCache.get(
-            paint, text, bytesCount, count, mDrawModifiers.mShadowRadius, positions);
+            paint, text, bytesCount, count, textShadow.radius, positions);
     // If the drop shadow exceeds the max texture size or couldn't be
     // allocated, skip drawing
     if (!shadow) return;
     const AutoTexture autoCleanup(shadow);
 
-    const float sx = x - shadow->left + mDrawModifiers.mShadowDx;
-    const float sy = y - shadow->top + mDrawModifiers.mShadowDy;
+    const float sx = x - shadow->left + textShadow.dx;
+    const float sy = y - shadow->top + textShadow.dy;
 
-    const int shadowAlpha = ((mDrawModifiers.mShadowColor >> 24) & 0xFF) * mSnapshot->alpha;
-    int shadowColor = mDrawModifiers.mShadowColor;
+    const int shadowAlpha = ((textShadow.color >> 24) & 0xFF) * mSnapshot->alpha;
     if (mDrawModifiers.mShader) {
-        shadowColor = 0xffffffff;
+        textShadow.color = SK_ColorWHITE;
     }
 
     setupDraw();
     setupDrawWithTexture(true);
-    setupDrawAlpha8Color(shadowColor, shadowAlpha < 255 ? shadowAlpha : alpha);
+    setupDrawAlpha8Color(textShadow.color, shadowAlpha < 255 ? shadowAlpha : alpha);
     setupDrawColorFilter(getColorFilter(paint));
     setupDrawShader();
     setupDrawBlending(paint, true);
@@ -2732,7 +2736,7 @@
 }
 
 bool OpenGLRenderer::canSkipText(const SkPaint* paint) const {
-    float alpha = (mDrawModifiers.mHasShadow ? 1.0f : paint->getAlpha()) * mSnapshot->alpha;
+    float alpha = (hasTextShadow(paint) ? 1.0f : paint->getAlpha()) * mSnapshot->alpha;
     return alpha == 0.0f && getXfermode(paint->getXfermode()) == SkXfermode::kSrcOver_Mode;
 }
 
@@ -2764,7 +2768,7 @@
     SkXfermode::Mode mode;
     getAlphaAndMode(paint, &alpha, &mode);
 
-    if (CC_UNLIKELY(mDrawModifiers.mHasShadow)) {
+    if (CC_UNLIKELY(hasTextShadow(paint))) {
         drawTextShadow(paint, text, bytesCount, count, positions, fontRenderer,
                 alpha, 0.0f, 0.0f);
     }
@@ -2841,7 +2845,7 @@
 
     FontRenderer& fontRenderer = mCaches.fontRenderer->getFontRenderer(paint);
 
-    if (CC_UNLIKELY(mDrawModifiers.mHasShadow)) {
+    if (CC_UNLIKELY(hasTextShadow(paint))) {
         fontRenderer.setFont(paint, mat4::identity());
         drawTextShadow(paint, text, bytesCount, count, positions, fontRenderer,
                 alpha, oldX, oldY);
@@ -3062,22 +3066,6 @@
 }
 
 ///////////////////////////////////////////////////////////////////////////////
-// Drop shadow
-///////////////////////////////////////////////////////////////////////////////
-
-void OpenGLRenderer::resetShadow() {
-    mDrawModifiers.mHasShadow = false;
-}
-
-void OpenGLRenderer::setupShadow(float radius, float dx, float dy, int color) {
-    mDrawModifiers.mHasShadow = true;
-    mDrawModifiers.mShadowRadius = radius;
-    mDrawModifiers.mShadowDx = dx;
-    mDrawModifiers.mShadowDy = dy;
-    mDrawModifiers.mShadowColor = color;
-}
-
-///////////////////////////////////////////////////////////////////////////////
 // Draw filters
 ///////////////////////////////////////////////////////////////////////////////
 
diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h
index b49d1e1..e8e9885 100644
--- a/libs/hwui/OpenGLRenderer.h
+++ b/libs/hwui/OpenGLRenderer.h
@@ -29,6 +29,7 @@
 #include <SkShader.h>
 #include <SkXfermode.h>
 
+#include <utils/Blur.h>
 #include <utils/Functor.h>
 #include <utils/RefBase.h>
 #include <utils/SortedVector.h>
@@ -71,13 +72,6 @@
     SkiaShader* mShader;
     float mOverrideLayerAlpha;
 
-    // Drop shadow
-    bool mHasShadow;
-    float mShadowRadius;
-    float mShadowDx;
-    float mShadowDy;
-    int mShadowColor;
-
     // Draw filters
     bool mHasDrawFilter;
     int mPaintFilterClearBits;
@@ -222,9 +216,6 @@
     virtual void resetShader();
     virtual void setupShader(SkiaShader* shader);
 
-    virtual void resetShadow();
-    virtual void setupShadow(float radius, float dx, float dy, int color);
-
     virtual void resetPaintFilter();
     virtual void setupPaintFilter(int clearBits, int setBits);
 
@@ -312,6 +303,31 @@
         return paint->getAlpha();
     }
 
+    struct TextShadow {
+        SkScalar radius;
+        float dx;
+        float dy;
+        SkColor color;
+    };
+
+    static inline bool getTextShadow(const SkPaint* paint, TextShadow* textShadow) {
+        SkDrawLooper::BlurShadowRec blur;
+        if (paint && paint->getLooper() && paint->getLooper()->asABlurShadow(&blur)) {
+            if (textShadow) {
+                textShadow->radius = Blur::convertSigmaToRadius(blur.fSigma);
+                textShadow->dx = blur.fOffset.fX;
+                textShadow->dy = blur.fOffset.fY;
+                textShadow->color = blur.fColor;
+            }
+            return true;
+        }
+        return false;
+    }
+
+    static inline bool hasTextShadow(const SkPaint* paint) {
+        return getTextShadow(paint, NULL);
+    }
+
     /**
      * Return the best transform to use to rasterize text given a full
      * transform matrix.
diff --git a/libs/hwui/Renderer.h b/libs/hwui/Renderer.h
index 3209a53..57db816 100644
--- a/libs/hwui/Renderer.h
+++ b/libs/hwui/Renderer.h
@@ -178,9 +178,6 @@
     virtual void resetShader() = 0;
     virtual void setupShader(SkiaShader* shader) = 0;
 
-    virtual void resetShadow() = 0;
-    virtual void setupShadow(float radius, float dx, float dy, int color) = 0;
-
     virtual void resetPaintFilter() = 0;
     virtual void setupPaintFilter(int clearBits, int setBits) = 0;
 
diff --git a/libs/hwui/utils/Blur.cpp b/libs/hwui/utils/Blur.cpp
index 85d90d0..c020b40 100644
--- a/libs/hwui/utils/Blur.cpp
+++ b/libs/hwui/utils/Blur.cpp
@@ -23,6 +23,31 @@
 namespace android {
 namespace uirenderer {
 
+// This constant approximates the scaling done in the software path's
+// "high quality" mode, in SkBlurMask::Blur() (1 / sqrt(3)).
+static const float BLUR_SIGMA_SCALE = 0.57735f;
+
+float Blur::convertRadiusToSigma(float radius) {
+    return radius > 0 ? BLUR_SIGMA_SCALE * radius + 0.5f : 0.0f;
+}
+
+float Blur::convertSigmaToRadius(float sigma) {
+    return sigma > 0.5f ? (sigma - 0.5f) / BLUR_SIGMA_SCALE : 0.0f;
+}
+
+/**
+ * HWUI has used a slightly different equation than Skia to generate the value
+ * for sigma and to preserve compatibility we have kept that logic.
+ *
+ * Based on some experimental radius and sigma values we approximate the
+ * equation sigma = f(radius) as sigma = radius * 0.3  + 0.6.  The larger the
+ * radius gets, the more our gaussian blur will resemble a box blur since with
+ * large sigma the gaussian curve begins to lose its shape.
+ */
+static float legacyConvertRadiusToSigma(float radius) {
+    return radius > 0 ? 0.3f * radius + 0.6f : 0.0f;
+}
+
 void Blur::generateGaussianWeights(float* weights, int32_t radius) {
     // Compute gaussian weights for the blur
     // e is the euler's number
@@ -31,13 +56,7 @@
     // g(x) = ( 1 / sqrt( 2 * pi ) * sigma) * e ^ ( -x^2 / 2 * sigma^2 )
     // x is of the form [-radius .. 0 .. radius]
     // and sigma varies with radius.
-    // Based on some experimental radius values and sigma's
-    // we approximately fit sigma = f(radius) as
-    // sigma = radius * 0.3  + 0.6
-    // The larger the radius gets, the more our gaussian blur
-    // will resemble a box blur since with large sigma
-    // the gaussian curve begins to lose its shape
-    float sigma = 0.3f * (float) radius + 0.6f;
+    float sigma = legacyConvertRadiusToSigma((float) radius);
 
     // Now compute the coefficints
     // We will store some redundant values to save some math during
diff --git a/libs/hwui/utils/Blur.h b/libs/hwui/utils/Blur.h
index 6c176e9..79aff65 100644
--- a/libs/hwui/utils/Blur.h
+++ b/libs/hwui/utils/Blur.h
@@ -18,12 +18,18 @@
 #define ANDROID_HWUI_BLUR_H
 
 #include <stdint.h>
+#include <cutils/compiler.h>
 
 namespace android {
 namespace uirenderer {
 
 class Blur {
 public:
+    // If radius > 0, return the corresponding sigma, else return 0
+    ANDROID_API static float convertRadiusToSigma(float radius);
+    // If sigma > 0.6, return the corresponding radius, else return 0
+    ANDROID_API static float convertSigmaToRadius(float sigma);
+
     static void generateGaussianWeights(float* weights, int32_t radius);
     static void horizontal(float* weights, int32_t radius, const uint8_t* source,
         uint8_t* dest, int32_t width, int32_t height);