Update SampleAndroidShadows to use algorithm closer to Android OpenGL
authorjvanverth <jvanverth@google.com>
Mon, 12 Sep 2016 14:51:04 +0000 (07:51 -0700)
committerCommit bot <commit-bot@chromium.org>
Mon, 12 Sep 2016 14:51:05 +0000 (07:51 -0700)
Includes:
* Update light position to be at a similar distance to Android OS
* Scale spot shadows correctly
* Compute stroke shapes and radii correctly
* Allow for larger blur radius for shadows

GOLD_TRYBOT_URL= https://gold.skia.org/search?issue=2319003003
TBR=reed@google.com
NOTRY=true
NOTREECHECKS=true

Review-Url: https://codereview.chromium.org/2319003003

include/effects/SkGaussianEdgeShader.h
samplecode/SampleAndroidShadows.cpp
src/effects/SkGaussianEdgeShader.cpp

index ef54ece..9e07450 100644 (file)
@@ -16,7 +16,7 @@ public:
     * Currently this is only useable with Circle and RRect shapes on the GPU backend.
     * Raster will draw nothing.
     */
-    static sk_sp<SkShader> Make();
+    static sk_sp<SkShader> Make(bool largerBlur = false);
 
     SK_DECLARE_FLATTENABLE_REGISTRAR_GROUP()
 
index f411e5c..e14c571 100755 (executable)
@@ -41,7 +41,7 @@ protected:
         fCirclePath.addCircle(0, 0, 50);
         fRectPath.addRect(SkRect::MakeXYWH(-100, -50, 200, 100));
         fRRPath.addRRect(SkRRect::MakeRectXY(SkRect::MakeXYWH(-100, -50, 200, 100), 4, 4));
-        fLightPos = SkPoint3::Make(-2, -2, 6);
+        fLightPos = SkPoint3::Make(-700, -700, 2800);
     }
 
     // overrides from SkEventSink
@@ -148,10 +148,16 @@ protected:
 
         SkScalar umbraAlpha = 1 / (1 + SkMaxScalar(zValue*kHeightFactor, 0));
         SkScalar radius = zValue*kHeightFactor*kGeomFactor;
+        // distance to outer of edge of geometry from original shape edge
+        SkScalar offset = radius*umbraAlpha;
 
         SkRect pathRect;
         SkRRect pathRRect;
-        if (radius >= 64 ||
+        SkScalar scaleFactors[2];
+        if (!canvas->getTotalMatrix().getMinMaxScales(scaleFactors)) {
+            return;
+        }
+        if (scaleFactors[0] != scaleFactors[1] || radius*scaleFactors[0] >= 64 ||
             !((path.isOval(&pathRect) && pathRect.width() == pathRect.height()) ||
               (path.isRRect(&pathRRect) && pathRRect.allCornersCircular()) ||
               path.isRect(&pathRect))) {
@@ -159,33 +165,41 @@ protected:
             return;
         }
 
-        // For all of these, we outset the rect by half the radius to get our stroke shape.
-        SkScalar halfRadius = SK_ScalarHalf*radius;
+        // For all of these, we inset the offset rect by half the radius to get our stroke shape.
+        SkScalar strokeOutset = offset - SK_ScalarHalf*radius;
+        // Make sure we'll have a radius of at least 0.5 after xform
+        if (strokeOutset*scaleFactors[0] < 0.5f) {
+            strokeOutset = 0.5f / scaleFactors[0];
+        }
         if (path.isOval(nullptr)) {
-            pathRect.outset(halfRadius, halfRadius);
+            pathRect.outset(strokeOutset, strokeOutset);
             pathRRect = SkRRect::MakeOval(pathRect);
         } else if (path.isRect(nullptr)) {
-            pathRect.outset(halfRadius, halfRadius);
-            pathRRect = SkRRect::MakeRectXY(pathRect, halfRadius, halfRadius);
+            pathRect.outset(strokeOutset, strokeOutset);
+            pathRRect = SkRRect::MakeRectXY(pathRect, strokeOutset, strokeOutset);
         } else {
-            pathRRect.outset(halfRadius, halfRadius);
+            pathRRect.outset(strokeOutset, strokeOutset);
         }
 
         SkPaint paint;
         paint.setAntiAlias(true);
         paint.setStyle(SkPaint::kStroke_Style);
         // we outset the stroke a little to cover up AA on the interior edge
-        paint.setStrokeWidth(radius + 1);
-        // handle scale of radius due to CTM
-        SkScalar maxScale = canvas->getTotalMatrix().getMaxScale();
-        radius *= maxScale;
-        unsigned char gray = (unsigned char)(ambientAlpha*umbraAlpha*255.999f);
-        SkASSERT(radius < 64);
-        // Convert radius to 6.2 fixed point and place in the G component.
-        paint.setColor(SkColorSetARGB(1, gray, (unsigned char)(4.0f*radius), 0));
-
-        sk_sp<SkShader> gaussShader = SkGaussianEdgeShader::Make();
-        paint.setShader(gaussShader);
+        SkScalar pad = 0.5f;
+        paint.setStrokeWidth(radius + 2*pad);
+        // handle scale of radius and pad due to CTM
+        radius *= scaleFactors[0];
+        pad *= scaleFactors[0];
+        SkASSERT(radius < 16384);
+        SkASSERT(pad < 64);
+        // Convert radius to 14.2 fixed point and place in the R & G components.
+        // Convert pad to 6.2 fixed point and place in the B component.
+        uint16_t iRadius = (uint16_t)(radius*4.0f);
+        unsigned char alpha = (unsigned char)(ambientAlpha*255.999f);
+        paint.setColor(SkColorSetARGB(alpha, iRadius >> 8, iRadius & 0xff,
+                                      (unsigned char)(4.0f*pad)));
+
+        paint.setShader(SkGaussianEdgeShader::Make(true));
         canvas->drawRRect(pathRRect, paint);
     }
 
@@ -201,42 +215,24 @@ protected:
         } else if (zRatio > 0.95f) {
             zRatio = 0.95f;
         }
-        SkScalar radius = lightWidth*zRatio;
+        SkScalar blurRadius = lightWidth*zRatio;
 
         // compute the transformation params
         SkPoint center = SkPoint::Make(path.getBounds().centerX(), path.getBounds().centerY());
-        canvas->getTotalMatrix().mapPoints(&center, 1);
-        SkPoint offset = SkPoint::Make(-zRatio*(lightPos.fX - center.fX), 
-                                       -zRatio*(lightPos.fY - center.fY));
-        SkScalar scale = lightPos.fZ / (lightPos.fZ - zValue);
-        if (scale < 1.0f) {
-            scale = 1.0f;
-        } else if (scale > 1024.f) {
-            scale = 1024.f;
+        SkMatrix ctmInverse;
+        if (!canvas->getTotalMatrix().invert(&ctmInverse)) {
+            return;
         }
+        SkPoint lightPos2D = SkPoint::Make(lightPos.fX, lightPos.fY);
+        ctmInverse.mapPoints(&lightPos2D, 1);
+        SkPoint offset = SkPoint::Make(zRatio*(center.fX - lightPos2D.fX),
+                                       zRatio*(center.fY - lightPos2D.fY));
+        SkScalar scale = lightPos.fZ / (lightPos.fZ - zValue);
 
         SkAutoCanvasRestore acr(canvas, true);
 
-        SkRect occlRect;
-        GetOcclRect(path, &occlRect);
-        // apply inverse transform
-        occlRect.offset(-offset);
-#if 0
-        // It looks like the scale may be invalid
-        SkScalar scale = lightPos.fZ / (lightPos.fZ - zValue);
-        if (scale < 1.0f) {
-            scale = 1.0f;
-        } else if (scale > 1024.f) {
-            scale = 1024.f;
-        }
-        occlRect.fLeft /= scale;
-        occlRect.fRight /= scale;
-        occlRect.fTop /= scale;
-        occlRect.fBottom /= scale;
-#endif
         sk_sp<SkMaskFilter> mf = SkBlurMaskFilter::Make(kNormal_SkBlurStyle,
-                                                        SkBlurMask::ConvertRadiusToSigma(radius),
-                                                        occlRect,
+                                                        SkBlurMask::ConvertRadiusToSigma(blurRadius),
                                                         SkBlurMaskFilter::kNone_BlurFlag);
 
         SkPaint paint;
@@ -245,20 +241,9 @@ protected:
         paint.setColor(SkColorSetARGB((unsigned char)(spotAlpha*255.999f), 0, 0, 0));
 
         // apply transformation to shadow
-        canvas->translate(offset.fX, offset.fY);
-#if 0
-        // It looks like the scale may be invalid
         canvas->scale(scale, scale);
-#endif
+        canvas->translate(offset.fX, offset.fY);
         canvas->drawPath(path, paint);
-
-        // draw occlusion rect
-#if DRAW_OCCL_RECT
-        SkPaint stroke;
-        stroke.setStyle(SkPaint::kStroke_Style);
-        stroke.setColor(SK_ColorRED);
-        canvas->drawRect(occlRect, stroke)
-#endif
     }
 
     void drawSpotShadowAlt(SkCanvas* canvas, const SkPath& path, SkScalar zValue,
@@ -273,11 +258,15 @@ protected:
         } else if (zRatio > 0.95f) {
             zRatio = 0.95f;
         }
-        SkScalar radius = lightWidth*zRatio;
+        SkScalar radius = 2.0f*lightWidth*zRatio;
 
         SkRect pathRect;
         SkRRect pathRRect;
-        if (radius >= 64 ||
+        SkScalar scaleFactors[2];
+        if (!canvas->getTotalMatrix().getMinMaxScales(scaleFactors)) {
+            return;
+        }
+        if (scaleFactors[0] != scaleFactors[1] || radius*scaleFactors[0] >= 16384 ||
             !((path.isOval(&pathRect) && pathRect.width() == pathRect.height()) ||
               (path.isRRect(&pathRRect) && pathRRect.allCornersCircular()) ||
               path.isRect(&pathRect))) {
@@ -285,23 +274,31 @@ protected:
             return;
         }
 
-        // For all of these, we outset the rect by half the radius to get our stroke shape.
-        SkScalar halfRadius = SK_ScalarHalf*radius;
+        // For all of these, we need to ensure we have a rrect with radius >= 0.5f in device space
+        SkScalar minRadius = SK_ScalarHalf/scaleFactors[0];
         if (path.isOval(nullptr)) {
-            pathRect.outset(halfRadius, halfRadius);
             pathRRect = SkRRect::MakeOval(pathRect);
         } else if (path.isRect(nullptr)) {
-            pathRect.outset(halfRadius, halfRadius);
-            pathRRect = SkRRect::MakeRectXY(pathRect, halfRadius, halfRadius);
+            pathRRect = SkRRect::MakeRectXY(pathRect, minRadius, minRadius);
         } else {
-            pathRRect.outset(halfRadius, halfRadius);
+            if (pathRRect.getSimpleRadii().fX < minRadius) {
+                pathRRect.setRectXY(pathRRect.rect(), minRadius, minRadius);
+            }
         }
 
-        // compute the transformation params
-        SkPoint center = SkPoint::Make(path.getBounds().centerX(), path.getBounds().centerY());
-        canvas->getTotalMatrix().mapPoints(&center, 1);
-        SkPoint offset = SkPoint::Make(-zRatio*(lightPos.fX - center.fX),
-                                       -zRatio*(lightPos.fY - center.fY));
+        // compute the scale and translation for the shadow
+        SkScalar scale = lightPos.fZ / (lightPos.fZ - zValue);
+        SkRRect shadowRRect;
+        pathRRect.transform(SkMatrix::MakeScale(scale, scale), &shadowRRect);
+        SkPoint center = SkPoint::Make(shadowRRect.rect().centerX(), shadowRRect.rect().centerY());
+        SkMatrix ctmInverse;
+        if (!canvas->getTotalMatrix().invert(&ctmInverse)) {
+            return;
+        }
+        SkPoint lightPos2D = SkPoint::Make(lightPos.fX, lightPos.fY);
+        ctmInverse.mapPoints(&lightPos2D, 1);
+        SkPoint offset = SkPoint::Make(zRatio*(center.fX - lightPos2D.fX),
+                                       zRatio*(center.fY - lightPos2D.fY));
         SkAutoCanvasRestore acr(canvas, true);
 
         SkPaint paint;
@@ -310,9 +307,9 @@ protected:
         // the edge of the shape. We also add 1/2 to cover up AA on the interior edge.
         SkScalar pad = offset.length() + 0.5f;
         // compute area
-        SkScalar strokeWidth = radius + 2.0f*pad;
-        SkScalar strokedArea = 2.0f*strokeWidth*(pathRRect.width() + pathRRect.height());
-        SkScalar filledArea = (pathRRect.height() + radius)*(pathRRect.width() + radius);
+        SkScalar strokeWidth = radius + 2.0f*pad/scaleFactors[0];
+        SkScalar strokedArea = 2.0f*strokeWidth*(shadowRRect.width() + shadowRRect.height());
+        SkScalar filledArea = (shadowRRect.height() + radius)*(shadowRRect.width() + radius);
         // If the area of the stroked geometry is larger than the fill geometry, or
         // if our pad is too big to convert to 6.2 fixed point, just fill it.
         if (strokedArea > filledArea || pad >= 64) {
@@ -323,31 +320,22 @@ protected:
             paint.setStyle(SkPaint::kStroke_Style);
             paint.setStrokeWidth(strokeWidth);
         }
-        sk_sp<SkShader> gaussShader = SkGaussianEdgeShader::Make();
-        paint.setShader(gaussShader);
+        paint.setShader(SkGaussianEdgeShader::Make(true));
         // handle scale of radius due to CTM
-        SkScalar maxScale = canvas->getTotalMatrix().getMaxScale();
-        radius *= maxScale;
-        unsigned char gray = (unsigned char)(spotAlpha*255.999f);
-        SkASSERT(radius < 64);
+        radius *= scaleFactors[0];
+        // don't need to scale pad as it was computed from the transformed offset
+        SkASSERT(radius < 16384);
         SkASSERT(pad < 64);
-        // Convert radius and pad to 6.2 fixed point and place in the G & B components.
-        paint.setColor(SkColorSetARGB(1, gray, (unsigned char)(radius*4.0f),
-                                     (unsigned char)(pad*4.0f)));
+        // Convert radius to 14.2 fixed point and place in the R & G components.
+        // Convert pad to 6.2 fixed point and place in the B component.
+        uint16_t iRadius = (uint16_t)(radius*4.0f);
+        unsigned char alpha = (unsigned char)(spotAlpha*255.999f);
+        paint.setColor(SkColorSetARGB(alpha, iRadius >> 8, iRadius & 0xff,
+                                      (unsigned char)(4.0f*pad)));
 
         // apply transformation to shadow
         canvas->translate(offset.fX, offset.fY);
-#if 0
-        // It looks like the scale may be invalid
-        SkScalar scale = lightPos.fZ / (lightPos.fZ - zValue);
-        if (scale < 1.0f) {
-            scale = 1.0f;
-        } else if (scale > 1024.f) {
-            scale = 1024.f;
-        }
-        canvas->scale(scale, scale);
-#endif
-        canvas->drawRRect(pathRRect, paint);
+        canvas->drawRRect(shadowRRect, paint);
     }
 
     void drawShadowedPath(SkCanvas* canvas, const SkPath& path, SkScalar zValue,
@@ -374,7 +362,7 @@ protected:
 
     void onDrawContent(SkCanvas* canvas) override {
         this->drawBG(canvas);
-        const SkScalar kLightWidth = 3;
+        const SkScalar kLightWidth = 2800;
         const SkScalar kAmbientAlpha = 0.25f;
         const SkScalar kSpotAlpha = 0.25f;
 
@@ -387,26 +375,26 @@ protected:
         canvas->translate(200, 90);
         lightPos.fX += 200;
         lightPos.fY += 90;
-        this->drawShadowedPath(canvas, fRectPath, 5, paint, kAmbientAlpha, 
+        this->drawShadowedPath(canvas, fRectPath, 2, paint, kAmbientAlpha, 
                                lightPos, kLightWidth, kSpotAlpha);
 
         paint.setColor(SK_ColorRED);
         canvas->translate(250, 0);
         lightPos.fX += 250;
-        this->drawShadowedPath(canvas, fRRPath, 5, paint, kAmbientAlpha,
+        this->drawShadowedPath(canvas, fRRPath, 4, paint, kAmbientAlpha,
                                lightPos, kLightWidth, kSpotAlpha);
 
         paint.setColor(SK_ColorBLUE);
         canvas->translate(-250, 110);
         lightPos.fX -= 250;
         lightPos.fY += 110;
-        this->drawShadowedPath(canvas, fCirclePath, 5, paint, 0.0f,
+        this->drawShadowedPath(canvas, fCirclePath, 8, paint, 0.0f,
                                lightPos, kLightWidth, 0.5f);
 
         paint.setColor(SK_ColorGREEN);
         canvas->translate(250, 0);
         lightPos.fX += 250;
-        this->drawShadowedPath(canvas, fRRPath, 5, paint, kAmbientAlpha,
+        this->drawShadowedPath(canvas, fRRPath, 64, paint, kAmbientAlpha,
                                lightPos, kLightWidth, kSpotAlpha);
     }
 
index 01b0606..a7c2700 100644 (file)
  /** \class SkGaussianEdgeShaderImpl
  This subclass of shader applies a Gaussian to shadow edge
 
+ If largerBlur is false:
  The radius of the Gaussian blur is specified by the g value of the color, in 6.2 fixed point.
  For spot shadows, we increase the stroke width to set the shadow against the shape. This pad
  is specified by b, also in 6.2 fixed point. The r value represents the max final alpha.
  The incoming alpha should be 1.
+
+ If largerBlur is true:
+ The radius of the Gaussian blur is specified by the r & g values of the color in 14.2 fixed point.
+ For spot shadows, we increase the stroke width to set the shadow against the shape. This pad
+ is specified by b, also in 6.2 fixed point. The a value represents the max final alpha.
+
+ LargerBlur will be removed once Android is migrated to the updated shader.
  */
 class SkGaussianEdgeShaderImpl : public SkShader {
 public:
-    SkGaussianEdgeShaderImpl() {}
+    SkGaussianEdgeShaderImpl()
+        : fLargerBlur(false) {}
+
+    SkGaussianEdgeShaderImpl(bool largerBlur)
+        : fLargerBlur(largerBlur) {}
 
     bool isOpaque() const override;
 
@@ -35,6 +47,7 @@ protected:
 
 private:
     friend class SkGaussianEdgeShader;
+    bool fLargerBlur;
 
     typedef SkShader INHERITED;
 };
@@ -55,7 +68,7 @@ private:
 
 class GaussianEdgeFP : public GrFragmentProcessor {
 public:
-    GaussianEdgeFP() {
+    GaussianEdgeFP(bool largerBlur) : fLargerBlur(largerBlur) {
         this->initClassID<GaussianEdgeFP>();
 
         // enable output of distance information for shape
@@ -64,7 +77,7 @@ public:
 
     class GLSLGaussianEdgeFP : public GrGLSLFragmentProcessor {
     public:
-        GLSLGaussianEdgeFP() {}
+        GLSLGaussianEdgeFP(bool largerBlur) : fLargerBlur(largerBlur) {}
 
         void emitCode(EmitArgs& args) override {
             GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
@@ -74,28 +87,39 @@ public:
                                          " returning grey in GLSLGaussianEdgeFP\n");
                 fragBuilder->codeAppendf("vec4 color = %s;", args.fInputColor);
                 fragBuilder->codeAppendf("%s = vec4(0, 0, 0, color.r);", args.fOutputColor);
+            } else if (fLargerBlur) {
+                fragBuilder->codeAppendf("vec4 color = %s;", args.fInputColor);
+                fragBuilder->codeAppend("float radius = color.r*256*64 + color.g*64;");
+                fragBuilder->codeAppend("float pad = color.b*64;");
+
+                fragBuilder->codeAppendf("float factor = 1 - clamp((%s.z - pad)/radius, 0, 1);",
+                                         fragBuilder->distanceVectorName());
+                fragBuilder->codeAppend("factor = exp(-factor * factor * 4) - 0.018;");
+                fragBuilder->codeAppendf("%s = factor*vec4(0, 0, 0, color.a);",
+                                         args.fOutputColor);
             } else {
                 fragBuilder->codeAppendf("vec4 color = %s;", args.fInputColor);
-                fragBuilder->codeAppend("float radius = color.g*64.0;");
-                fragBuilder->codeAppend("float pad = color.b*64.0;");
+                fragBuilder->codeAppend("float radius = color.g*64;");
+                fragBuilder->codeAppend("float pad = color.b*64;");
 
-                fragBuilder->codeAppendf("float factor = 1.0 - clamp((%s.z - pad)/radius,"
-                                                                    "0.0, 1.0);",
+                fragBuilder->codeAppendf("float factor = 1 - clamp((%s.z - pad)/radius, 0, 1);",
                                          fragBuilder->distanceVectorName());
-                fragBuilder->codeAppend("factor = exp(-factor * factor * 4.0) - 0.018;");
-                fragBuilder->codeAppendf("%s = factor*vec4(0.0, 0.0, 0.0, color.r);",
+                fragBuilder->codeAppend("factor = exp(-factor * factor * 4) - 0.018;");
+                fragBuilder->codeAppendf("%s = factor*vec4(0, 0, 0, color.r);",
                                          args.fOutputColor);
             }
         }
 
         static void GenKey(const GrProcessor& proc, const GrGLSLCaps&,
                            GrProcessorKeyBuilder* b) {
-            // only one shader generated currently
-            b->add32(0x0);
+            const GaussianEdgeFP& gefp = proc.cast<GaussianEdgeFP>();
+            b->add32(gefp.fLargerBlur ? 0x1 : 0x0);
         }
 
     protected:
         void onSetData(const GrGLSLProgramDataManager& pdman, const GrProcessor& proc) override {}
+
+        bool fLargerBlur;
     };
 
     void onGetGLSLProcessorKey(const GrGLSLCaps& caps, GrProcessorKeyBuilder* b) const override {
@@ -109,15 +133,19 @@ public:
     }
 
 private:
-    GrGLSLFragmentProcessor* onCreateGLSLInstance() const override { return new GLSLGaussianEdgeFP; }
+    GrGLSLFragmentProcessor* onCreateGLSLInstance() const override {
+        return new GLSLGaussianEdgeFP(fLargerBlur);
+    }
 
     bool onIsEqual(const GrFragmentProcessor& proc) const override { return true; }
+
+    bool fLargerBlur;
 };
 
 ////////////////////////////////////////////////////////////////////////////
 
 sk_sp<GrFragmentProcessor> SkGaussianEdgeShaderImpl::asFragmentProcessor(const AsFPArgs& args) const {
-    return sk_make_sp<GaussianEdgeFP>();
+    return sk_make_sp<GaussianEdgeFP>(fLargerBlur);
 }
 
 #endif
@@ -145,8 +173,8 @@ void SkGaussianEdgeShaderImpl::flatten(SkWriteBuffer& buf) const {
 
 ///////////////////////////////////////////////////////////////////////////////
 
-sk_sp<SkShader> SkGaussianEdgeShader::Make() {
-    return sk_make_sp<SkGaussianEdgeShaderImpl>();
+sk_sp<SkShader> SkGaussianEdgeShader::Make(bool largerBlur) {
+    return sk_make_sp<SkGaussianEdgeShaderImpl>(largerBlur);
 }
 
 ///////////////////////////////////////////////////////////////////////////////