Circular shadow fixes for Flutter.
authorJim Van Verth <jvanverth@google.com>
Thu, 20 Apr 2017 19:48:37 +0000 (15:48 -0400)
committerSkia Commit-Bot <skia-commit-bot@chromium.org>
Fri, 21 Apr 2017 15:23:53 +0000 (15:23 +0000)
* Fix spot shadow placement for SkSpotShadowMaskFilter.
* Make sure we don't try to render an oval as a plain RRect
  due to floating point error.
* Use fast path for uncached circles.
* Make sure ShadowMaskFilters can handle near-circles.

Change-Id: Ia9967a00a6e1c980a1c0a7ba8248f09fde61a3b7
Reviewed-on: https://skia-review.googlesource.com/13969
Reviewed-by: Jim Van Verth <jvanverth@google.com>
Reviewed-by: Robert Phillips <robertphillips@google.com>
Commit-Queue: Jim Van Verth <jvanverth@google.com>

include/core/SkRRect.h
samplecode/SampleAndroidShadows.cpp
src/core/SkRRect.cpp
src/effects/shadows/SkAmbientShadowMaskFilter.cpp
src/effects/shadows/SkSpotShadowMaskFilter.cpp
src/utils/SkShadowUtils.cpp

index 3b691aa..4b7a33e 100644 (file)
@@ -110,7 +110,7 @@ public:
     inline bool isNinePatch() const { return kNinePatch_Type == this->getType(); }
     inline bool isComplex() const { return kComplex_Type == this->getType(); }
 
-    bool allCornersCircular() const;
+    bool allCornersCircular(SkScalar tolerance = SK_ScalarNearlyZero) const;
 
     SkScalar width() const { return fRect.width(); }
     SkScalar height() const { return fRect.height(); }
index 0c0baab..55acd57 100644 (file)
@@ -473,6 +473,7 @@ protected:
         paint.setAntiAlias(true);
 
         SkPoint3 lightPos = fLightPos;
+        lightPos.fX = canvas->getBaseLayerSize().fWidth * 0.5f;
 
         paint.setColor(SK_ColorWHITE);
         canvas->translate(200, 90);
@@ -496,7 +497,7 @@ protected:
         canvas->translate(-250, 110);
         lightPos.fX -= 250;
         lightPos.fY += 110;
-        zValue = SkTMax(1.0f, 8 + fZDelta);
+        zValue = SkTMax(1.0f, 12 + fZDelta);
         zFunc = [zValue](SkScalar, SkScalar) { return zValue; };
         this->drawShadowedPath(canvas, fCirclePath, zFunc, paint, kAmbientAlpha,
                                lightPos, kLightWidth, 0.5f);
index 824ee62..8d69f4a 100644 (file)
@@ -251,11 +251,11 @@ bool SkRRect::checkCornerContainment(SkScalar x, SkScalar y) const {
     return dist <= SkScalarSquare(fRadii[index].fX * fRadii[index].fY);
 }
 
-bool SkRRect::allCornersCircular() const {
-    return fRadii[0].fX == fRadii[0].fY &&
-        fRadii[1].fX == fRadii[1].fY &&
-        fRadii[2].fX == fRadii[2].fY &&
-        fRadii[3].fX == fRadii[3].fY;
+bool SkRRect::allCornersCircular(SkScalar tolerance) const {
+    return SkScalarNearlyEqual(fRadii[0].fX, fRadii[0].fY, tolerance) &&
+           SkScalarNearlyEqual(fRadii[1].fX, fRadii[1].fY, tolerance) &&
+           SkScalarNearlyEqual(fRadii[2].fX, fRadii[2].fY, tolerance) &&
+           SkScalarNearlyEqual(fRadii[3].fX, fRadii[3].fY, tolerance);
 }
 
 bool SkRRect::contains(const SkRect& rect) const {
index e6a8de4..84f9836 100644 (file)
@@ -163,9 +163,8 @@ bool SkAmbientShadowMaskFilterImpl::directFilterMaskGPU(GrContext* context,
     }
 
     // if circle
-    // TODO: switch to SkScalarNearlyEqual when either oval renderer is updated or we
-    // have our own GeometryProc.
-    if (path.isOval(nullptr) && path.getBounds().width() == path.getBounds().height()) {
+    if (path.isOval(nullptr) && SkScalarNearlyEqual(path.getBounds().width(),
+                                                    path.getBounds().height())) {
         SkRRect rrect = SkRRect::MakeOval(path.getBounds());
         return this->directFilterRRectMaskGPU(context, rtContext, std::move(paint), clip,
                                               SkMatrix::I(), strokeRec, rrect, rrect);
index ee82342..93c7e9c 100644 (file)
@@ -180,9 +180,8 @@ bool SkSpotShadowMaskFilterImpl::directFilterMaskGPU(GrContext* context,
     }
 
     // if circle
-    // TODO: switch to SkScalarNearlyEqual when either oval renderer is updated or we
-    // have our own GeometryProc.
-    if (path.isOval(nullptr) && path.getBounds().width() == path.getBounds().height()) {
+    if (path.isOval(nullptr) && SkScalarNearlyEqual(path.getBounds().width(),
+                                                    path.getBounds().height())) {
         SkRRect rrect = SkRRect::MakeOval(path.getBounds());
         return this->directFilterRRectMaskGPU(context, rtContext, std::move(paint), clip,
                                               SkMatrix::I(), strokeRec, rrect, rrect);
@@ -211,7 +210,7 @@ bool SkSpotShadowMaskFilterImpl::directFilterRRectMaskGPU(GrContext*,
     if (SkStrokeRec::kFill_Style != strokeRec.getStyle()) {
         return false;
     }
-    // Fast path only supports simple rrects with circular corners.
+    // Fast path only supports simple rrects with near-circular corners.
     SkASSERT(devRRect.allCornersCircular());
     if (!rrect.isRect() && !rrect.isOval() && !rrect.isSimple()) {
         return false;
@@ -236,7 +235,9 @@ bool SkSpotShadowMaskFilterImpl::directFilterRRectMaskGPU(GrContext*,
     if (fSpotAlpha > 0.0f) {
         float zRatio = SkTPin(fOccluderHeight / (fLightPos.fZ - fOccluderHeight), 0.0f, 0.95f);
 
-        SkScalar srcSpaceSpotRadius = 2.0f * fLightRadius * zRatio;
+        SkScalar devSpaceSpotRadius = 2.0f * fLightRadius * zRatio;
+        // handle scale of radius and pad due to CTM
+        const SkScalar srcSpaceSpotRadius = devSpaceSpotRadius / scaleFactor;
 
         SkRRect spotRRect;
         if (isRect) {
@@ -250,18 +251,18 @@ bool SkSpotShadowMaskFilterImpl::directFilterRRectMaskGPU(GrContext*,
         const SkScalar scale = fLightPos.fZ / (fLightPos.fZ - fOccluderHeight);
         spotRRect.transform(SkMatrix::MakeScale(scale, scale), &spotShadowRRect);
 
-        SkPoint center = SkPoint::Make(spotShadowRRect.rect().centerX(),
-                                       spotShadowRRect.rect().centerY());
+        SkPoint spotOffset = SkPoint::Make(zRatio*(-fLightPos.fX), zRatio*(-fLightPos.fY));
+        // Adjust for the effect of the scale.
+        spotOffset.fX += scale*viewMatrix[SkMatrix::kMTransX];
+        spotOffset.fY += scale*viewMatrix[SkMatrix::kMTransY];
+        // This offset is in dev space, need to transform it into source space.
         SkMatrix ctmInverse;
         if (!viewMatrix.invert(&ctmInverse)) {
             SkDebugf("Matrix is degenerate. Will not render spot shadow!\n");
             //**** TODO: this is not good
             return true;
         }
-        SkPoint lightPos2D = SkPoint::Make(fLightPos.fX, fLightPos.fY);
-        ctmInverse.mapPoints(&lightPos2D, 1);
-        const SkPoint spotOffset = SkPoint::Make(zRatio*(center.fX - lightPos2D.fX),
-                                                 zRatio*(center.fY - lightPos2D.fY));
+        ctmInverse.mapPoints(&spotOffset, 1);
 
         // We want to extend the stroked area in so that it meets up with the caster
         // geometry. The stroked geometry will, by definition already be inset half the
@@ -292,17 +293,20 @@ bool SkSpotShadowMaskFilterImpl::directFilterRRectMaskGPU(GrContext*,
         } else {
             // Since we can't have unequal strokes, inset the shadow rect so the inner
             // and outer edges of the stroke will land where we want.
-            SkRect insetRect = spotShadowRRect.rect().makeInset(insetAmount / 2.0f,
-                                                                insetAmount / 2.0f);
-            SkScalar insetRad = SkTMax(spotShadowRRect.getSimpleRadii().fX - insetAmount / 2.0f,
-                                       minRadius);
-            spotShadowRRect = SkRRect::MakeRectXY(insetRect, insetRad, insetRad);
+            insetAmount *= 0.5f;
+            SkRect insetRect = spotShadowRRect.rect().makeInset(insetAmount, insetAmount);
+            // If the shadowRRect was an oval then its inset will also be one.
+            // We set it explicitly to avoid errors.
+            if (spotShadowRRect.isOval()) {
+                spotShadowRRect = SkRRect::MakeOval(insetRect);
+            } else {
+                SkScalar insetRad = SkTMax(spotShadowRRect.getSimpleRadii().fX - insetAmount,
+                                           minRadius);
+                spotShadowRRect = SkRRect::MakeRectXY(insetRect, insetRad, insetRad);
+            }
             spotStrokeRec.setStrokeStyle(strokeWidth, false);
         }
 
-        // handle scale of radius and pad due to CTM
-        const SkScalar devSpaceSpotRadius = srcSpaceSpotRadius * scaleFactor;
-
         spotShadowRRect.offset(spotOffset.fX, spotOffset.fY);
 
         rtContext->drawShadowRRect(clip, std::move(paint), viewMatrix, spotShadowRRect,
index 2e560b4..8206bd3 100644 (file)
@@ -471,12 +471,12 @@ void SkShadowUtils::DrawShadow(SkCanvas* canvas, const SkPath& path, SkScalar oc
         if (ambientAlpha > 0) {
             newPaint.setMaskFilter(SkAmbientShadowMaskFilter::Make(occluderHeight, ambientAlpha,
                                                                    flags));
-            canvas->drawPath(path, newPaint);
+            canvas->drawOval(rect, newPaint);
         }
         if (spotAlpha > 0) {
             newPaint.setMaskFilter(SkSpotShadowMaskFilter::Make(occluderHeight, devLightPos,
                                                                 lightRadius, spotAlpha, flags));
-            canvas->drawPath(path, newPaint);
+            canvas->drawOval(rect, newPaint);
         }
         return;
     }
@@ -565,6 +565,26 @@ void SkShadowUtils::DrawUncachedShadow(SkCanvas* canvas, const SkPath& path,
                                        uint32_t flags) {
     SkAutoCanvasRestore acr(canvas, true);
     SkMatrix viewMatrix = canvas->getTotalMatrix();
+
+    // try circular fast path
+    SkRect rect;
+    if (viewMatrix.isSimilarity() &&
+        path.isOval(&rect) && rect.width() == rect.height()) {
+        SkPaint newPaint;
+        newPaint.setColor(color);
+        if (ambientAlpha > 0) {
+            newPaint.setMaskFilter(SkAmbientShadowMaskFilter::Make(heightFunc(0,0), ambientAlpha,
+                                                                   flags));
+            canvas->drawOval(rect, newPaint);
+        }
+        if (spotAlpha > 0) {
+            newPaint.setMaskFilter(SkSpotShadowMaskFilter::Make(heightFunc(0,0), lightPos,
+                                                                lightRadius, spotAlpha, flags));
+            canvas->drawOval(rect, newPaint);
+        }
+        return;
+    }
+
     canvas->resetMatrix();
 
     bool transparent = SkToBool(flags & SkShadowFlags::kTransparentOccluder_ShadowFlag);