diff options
-rw-r--r-- | gm/shadowutils.cpp | 73 | ||||
-rwxr-xr-x | samplecode/SampleShadowUtils.cpp | 69 | ||||
-rwxr-xr-x | src/utils/SkShadowTessellator.cpp | 10 | ||||
-rw-r--r-- | src/utils/SkShadowUtils.cpp | 112 |
4 files changed, 233 insertions, 31 deletions
diff --git a/gm/shadowutils.cpp b/gm/shadowutils.cpp index 0751174795..b88ecc2439 100644 --- a/gm/shadowutils.cpp +++ b/gm/shadowutils.cpp @@ -24,7 +24,7 @@ void draw_shadow(SkCanvas* canvas, const SkPath& path, SkScalar height, SkColor } static constexpr int kW = 800; -static constexpr int kH = 800; +static constexpr int kH = 960; enum ShadowMode { kDebugColorNoOccluders, @@ -43,6 +43,27 @@ void draw_paths(SkCanvas* canvas, ShadowMode mode) { paths.push_back().cubicTo(100, 50, 20, 100, 0, 0); paths.push_back().addOval(SkRect::MakeWH(20, 60)); + // star + SkTArray<SkPath> concavePaths; + concavePaths.push_back().moveTo(0.0f, -33.3333f); + concavePaths.back().lineTo(9.62f, -16.6667f); + concavePaths.back().lineTo(28.867f, -16.6667f); + concavePaths.back().lineTo(19.24f, 0.0f); + concavePaths.back().lineTo(28.867f, 16.6667f); + concavePaths.back().lineTo(9.62f, 16.6667f); + concavePaths.back().lineTo(0.0f, 33.3333f); + concavePaths.back().lineTo(-9.62f, 16.6667f); + concavePaths.back().lineTo(-28.867f, 16.6667f); + concavePaths.back().lineTo(-19.24f, 0.0f); + concavePaths.back().lineTo(-28.867f, -16.6667f); + concavePaths.back().lineTo(-9.62f, -16.6667f); + concavePaths.back().close(); + + // dumbbell + concavePaths.push_back().moveTo(50, 0); + concavePaths.back().cubicTo(100, 25, 60, 50, 50, 0); + concavePaths.back().cubicTo(0, -25, 40, -50, 50, 0); + static constexpr SkScalar kPad = 15.f; static constexpr SkScalar kLightR = 100.f; static constexpr SkScalar kHeight = 50.f; @@ -117,6 +138,56 @@ void draw_paths(SkCanvas* canvas, ShadowMode mode) { } } } + + // concave paths + canvas->restore(); + canvas->translate(kPad, dy); + canvas->save(); + x = kPad; + dy = 0; + for (auto& m : matrices) { + // for the concave paths we are not clipping, so transparent and opaque are the same + for (const auto& path : concavePaths) { + SkRect postMBounds = path.getBounds(); + m.mapRect(&postMBounds); + SkScalar w = postMBounds.width() + kHeight; + SkScalar dx = w + kPad; + + canvas->save(); + canvas->concat(m); + + if (kDebugColorNoOccluders == mode || kDebugColorOccluders == mode) { + draw_shadow(canvas, path, kHeight, SK_ColorRED, lightPos, kLightR, + true, kNone_ShadowFlag); + draw_shadow(canvas, path, kHeight, SK_ColorBLUE, lightPos, kLightR, + false, kNone_ShadowFlag); + } else if (kGrayscale == mode) { + SkColor ambientColor = SkColorSetARGB(0.1f * 255, 0, 0, 0); + SkColor spotColor = SkColorSetARGB(0.25f * 255, 0, 0, 0); + SkShadowUtils::DrawShadow(canvas, path, SkPoint3{ 0, 0, kHeight }, lightPos, + kLightR, ambientColor, spotColor, kNone_ShadowFlag); + } + + SkPaint paint; + paint.setAntiAlias(true); + if (kDebugColorNoOccluders == mode) { + // Draw the path outline in green on top of the ambient and spot shadows. + paint.setColor(SK_ColorGREEN); + paint.setStyle(SkPaint::kStroke_Style); + paint.setStrokeWidth(0); + } else { + paint.setColor(kDebugColorOccluders == mode ? SK_ColorLTGRAY : SK_ColorWHITE); + paint.setStyle(SkPaint::kFill_Style); + } + canvas->drawPath(path, paint); + canvas->restore(); + + canvas->translate(dx, 0); + x += dx; + dy = SkTMax(dy, postMBounds.height() + kPad + kHeight); + } + } + // Show where the light is in x,y as a circle (specified in device space). SkMatrix invCanvasM = canvas->getTotalMatrix(); if (invCanvasM.invert(&invCanvasM)) { diff --git a/samplecode/SampleShadowUtils.cpp b/samplecode/SampleShadowUtils.cpp index 19e5aae71c..4929877548 100755 --- a/samplecode/SampleShadowUtils.cpp +++ b/samplecode/SampleShadowUtils.cpp @@ -23,7 +23,8 @@ //////////////////////////////////////////////////////////////////////////// class ShadowUtilsView : public SampleView { - SkTArray<SkPath> fPaths; + SkTArray<SkPath> fConvexPaths; + SkTArray<SkPath> fConcavePaths; SkScalar fZDelta; bool fShowAmbient; @@ -43,14 +44,34 @@ public: protected: void onOnceBeforeDraw() override { - fPaths.push_back().addRoundRect(SkRect::MakeWH(50, 50), 10, 10); + fConvexPaths.push_back().addRoundRect(SkRect::MakeWH(50, 50), 10, 10); SkRRect oddRRect; oddRRect.setNinePatch(SkRect::MakeWH(50, 50), 9, 13, 6, 16); - fPaths.push_back().addRRect(oddRRect); - fPaths.push_back().addRect(SkRect::MakeWH(50, 50)); - fPaths.push_back().addCircle(25, 25, 25); - fPaths.push_back().cubicTo(100, 50, 20, 100, 0, 0); - fPaths.push_back().addOval(SkRect::MakeWH(20, 60)); + fConvexPaths.push_back().addRRect(oddRRect); + fConvexPaths.push_back().addRect(SkRect::MakeWH(50, 50)); + fConvexPaths.push_back().addCircle(25, 25, 25); + fConvexPaths.push_back().cubicTo(100, 50, 20, 100, 0, 0); + fConvexPaths.push_back().addOval(SkRect::MakeWH(20, 60)); + + // star + fConcavePaths.push_back().moveTo(0.0f, -33.3333f); + fConcavePaths.back().lineTo(9.62f, -16.6667f); + fConcavePaths.back().lineTo(28.867f, -16.6667f); + fConcavePaths.back().lineTo(19.24f, 0.0f); + fConcavePaths.back().lineTo(28.867f, 16.6667f); + fConcavePaths.back().lineTo(9.62f, 16.6667f); + fConcavePaths.back().lineTo(0.0f, 33.3333f); + fConcavePaths.back().lineTo(-9.62f, 16.6667f); + fConcavePaths.back().lineTo(-28.867f, 16.6667f); + fConcavePaths.back().lineTo(-19.24f, 0.0f); + fConcavePaths.back().lineTo(-28.867f, -16.6667f); + fConcavePaths.back().lineTo(-9.62f, -16.6667f); + fConcavePaths.back().close(); + + // dumbbell + fConcavePaths.push_back().moveTo(50, 0); + fConcavePaths.back().cubicTo(100, 25, 60, 50, 50, 0); + fConcavePaths.back().cubicTo(0, -25, 40, -50, 50, 0); } // overrides from SkEventSink @@ -167,9 +188,11 @@ protected: paint.setColor(SK_ColorGREEN); paint.setAntiAlias(true); SkPoint3 zPlaneParams = SkPoint3::Make(0, 0, SkTMax(1.0f, kHeight + fZDelta)); + + // convex paths for (auto& m : matrices) { for (auto flags : { kNone_ShadowFlag, kTransparentOccluder_ShadowFlag }) { - for (const auto& path : fPaths) { + for (const auto& path : fConvexPaths) { SkRect postMBounds = path.getBounds(); m.mapRect(&postMBounds); SkScalar w = postMBounds.width() + kHeight; @@ -184,8 +207,8 @@ protected: canvas->save(); canvas->concat(m); - drawShadowedPath(canvas, path, zPlaneParams, paint, kAmbientAlpha, lightPos, - kLightR, kSpotAlpha, flags); + this->drawShadowedPath(canvas, path, zPlaneParams, paint, kAmbientAlpha, + lightPos, kLightR, kSpotAlpha, flags); canvas->restore(); canvas->translate(dx, 0); @@ -194,6 +217,32 @@ protected: } } } + + // concave paths + canvas->restore(); + canvas->translate(kPad, dy); + canvas->save(); + x = kPad; + dy = 0; + for (auto& m : matrices) { + for (const auto& path : fConcavePaths) { + SkRect postMBounds = path.getBounds(); + m.mapRect(&postMBounds); + SkScalar w = postMBounds.width(); + SkScalar dx = w + kPad; + + canvas->save(); + canvas->concat(m); + this->drawShadowedPath(canvas, path, zPlaneParams, paint, kAmbientAlpha, lightPos, + kLightR, kSpotAlpha, kNone_ShadowFlag); + canvas->restore(); + + canvas->translate(dx, 0); + x += dx; + dy = SkTMax(dy, postMBounds.height() + kPad + kHeight); + } + } + // Show where the light is in x,y as a circle (specified in device space). SkMatrix invCanvasM = canvas->getTotalMatrix(); if (invCanvasM.invert(&invCanvasM)) { diff --git a/src/utils/SkShadowTessellator.cpp b/src/utils/SkShadowTessellator.cpp index 57bf5851fa..23754292b0 100755 --- a/src/utils/SkShadowTessellator.cpp +++ b/src/utils/SkShadowTessellator.cpp @@ -378,6 +378,11 @@ SkAmbientShadowTessellator::SkAmbientShadowTessellator(const SkPath& path, : INHERITED(zPlaneParams, transparent) , fSplitFirstEdge(false) , fSplitPreviousEdge(false) { + // TODO: support some concave paths + if (!path.isConvex()) { + return; + } + // Set base colors SkScalar umbraAlpha = SkScalarInvert(SkDrawShadowMetrics::AmbientRecipAlpha(heightFunc(0, 0))); // umbraColor is the interior value, penumbraColor the exterior value. @@ -801,6 +806,11 @@ SkSpotShadowTessellator::SkSpotShadowTessellator(const SkPath& path, const SkMat , fFirstUmbraOutside(false) , fValidUmbra(true) { + // TODO: support some concave paths + if (!path.isConvex()) { + return; + } + // make sure we're not below the canvas plane if (this->setZOffset(path.getBounds(), ctm.hasPerspective())) { // Adjust light height and radius diff --git a/src/utils/SkShadowUtils.cpp b/src/utils/SkShadowUtils.cpp index b2f50ad40d..26ef2adfc5 100644 --- a/src/utils/SkShadowUtils.cpp +++ b/src/utils/SkShadowUtils.cpp @@ -6,6 +6,7 @@ */ #include "SkShadowUtils.h" +#include "SkBlurMaskFilter.h" #include "SkCanvas.h" #include "SkColorFilter.h" #include "SkColorData.h" @@ -389,9 +390,9 @@ static void* kNamespace; * they are first found in SkResourceCache. */ template <typename FACTORY> - void draw_shadow(const FACTORY& factory, - std::function<void(const SkVertices*, SkBlendMode, const SkPaint&, - SkScalar tx, SkScalar ty)> drawProc, ShadowedPath& path, SkColor color) { +bool draw_shadow(const FACTORY& factory, + std::function<void(const SkVertices*, SkBlendMode, const SkPaint&, + SkScalar tx, SkScalar ty)> drawProc, ShadowedPath& path, SkColor color) { FindContext<FACTORY> context(&path.viewMatrix(), &factory); SkResourceCache::Key* key = nullptr; @@ -422,7 +423,7 @@ template <typename FACTORY> vertices = tessellations->add(path.path(), factory, path.viewMatrix(), &context.fTranslate); if (!vertices) { - return; + return false; } auto rec = new CachedTessellationsRec(*key, std::move(tessellations)); SkResourceCache::Add(rec); @@ -430,7 +431,7 @@ template <typename FACTORY> vertices = factory.makeVertices(path.path(), path.viewMatrix(), &context.fTranslate); if (!vertices) { - return; + return false; } } } @@ -444,6 +445,8 @@ template <typename FACTORY> drawProc(vertices.get(), SkBlendMode::kModulate, paint, context.fTranslate.fX, context.fTranslate.fY); + + return true; } } @@ -566,6 +569,7 @@ void SkBaseDevice::drawShadow(const SkPath& path, const SkDrawShadowRec& rec) { float lightRadius = rec.fLightRadius; if (SkColorGetA(rec.fAmbientColor) > 0) { + bool success = false; if (uncached) { sk_sp<SkVertices> vertices = SkShadowTessellator::MakeAmbient(path, viewMatrix, zPlaneParams, @@ -579,8 +583,11 @@ void SkBaseDevice::drawShadow(const SkPath& path, const SkDrawShadowRec& rec) { SkBlendMode::kModulate)->makeComposed( SkGaussianColorFilter::Make())); this->drawVertices(vertices.get(), SkBlendMode::kModulate, paint); + success = true; } - } else { + } + + if (!success) { AmbientVerticesFactory factory; factory.fOccluderHeight = zPlaneParams.fZ; factory.fTransparent = transparent; @@ -591,11 +598,58 @@ void SkBaseDevice::drawShadow(const SkPath& path, const SkDrawShadowRec& rec) { factory.fOffset.fY = viewMatrix.getTranslateY(); } - draw_shadow(factory, drawVertsProc, shadowedPath, rec.fAmbientColor); + if (!draw_shadow(factory, drawVertsProc, shadowedPath, rec.fAmbientColor)) { + // Pretransform the path to avoid transforming the stroke, below. + SkPath devSpacePath; + path.transform(viewMatrix, &devSpacePath); + + // The tesselator outsets by AmbientBlurRadius (or 'r') to get the outer ring of + // the tesselation, uses the original path as the inner ring, and sets the alpha + // of the inner ring to 1/AmbientRecipAlpha (or 'a'). + // + // We want to emulate this with a blur. The full blur width (2*blurRadius or 'f') + // can be calculated by interpolating: + // + // original edge outer edge + // | |<---------- r ------>| + // |<------|--- f -------------->| + // | | | + // alpha = 1 alpha = a alpha = 0 + // + // Taking ratios, f/1 = r/a, so f = r/a and blurRadius = f/2. + // + // We now need to outset the path to place the new edge in the center of the + // blur region: + // + // original new + // | |<------|--- r ------>| + // |<------|--- f -|------------>| + // | |<- o ->|<--- f/2 --->| + // + // r = o + f/2, so o = r - f/2 + // + // We outset by using the stroker, so the strokeWidth is o/2. + // + SkScalar devSpaceOutset = SkDrawShadowMetrics::AmbientBlurRadius(zPlaneParams.fZ); + SkScalar oneOverA = SkDrawShadowMetrics::AmbientRecipAlpha(zPlaneParams.fZ); + SkScalar blurRadius = 0.5f*devSpaceOutset*oneOverA; + SkScalar strokeWidth = 0.5f*(devSpaceOutset - blurRadius); + + // Now draw with blur + SkPaint paint; + paint.setColor(rec.fAmbientColor); + paint.setStrokeWidth(strokeWidth); + paint.setStyle(SkPaint::kStrokeAndFill_Style); + SkScalar sigma = SkBlurMaskFilter::ConvertRadiusToSigma(blurRadius); + uint32_t flags = SkBlurMaskFilter::kIgnoreTransform_BlurFlag; + paint.setMaskFilter(SkBlurMaskFilter::Make(kNormal_SkBlurStyle, sigma, flags)); + this->drawPath(devSpacePath, paint); + } } } if (SkColorGetA(rec.fSpotColor) > 0) { + bool success = false; if (uncached) { sk_sp<SkVertices> vertices = SkShadowTessellator::MakeSpot(path, viewMatrix, zPlaneParams, @@ -608,25 +662,25 @@ void SkBaseDevice::drawShadow(const SkPath& path, const SkDrawShadowRec& rec) { paint.setColorFilter( SkColorFilter::MakeModeFilter(rec.fSpotColor, SkBlendMode::kModulate)->makeComposed( - SkGaussianColorFilter::Make())); + SkGaussianColorFilter::Make())); this->drawVertices(vertices.get(), SkBlendMode::kModulate, paint); + success = true; } - } else { + } + + if (!success) { SpotVerticesFactory factory; - SkScalar occluderHeight = zPlaneParams.fZ; - float zRatio = SkTPin(occluderHeight / (devLightPos.fZ - occluderHeight), 0.0f, 0.95f); - SkScalar radius = lightRadius * zRatio; + factory.fOccluderHeight = zPlaneParams.fZ; + factory.fDevLightPos = devLightPos; + factory.fLightRadius = lightRadius; - // Compute the scale and translation for the spot shadow. - SkScalar scale = devLightPos.fZ / (devLightPos.fZ - occluderHeight); SkPoint center = SkPoint::Make(path.getBounds().centerX(), path.getBounds().centerY()); factory.fLocalCenter = center; viewMatrix.mapPoints(¢er, 1); - factory.fOffset = SkVector::Make(zRatio * (center.fX - devLightPos.fX), - zRatio * (center.fY - devLightPos.fY)); - factory.fOccluderHeight = occluderHeight; - factory.fDevLightPos = devLightPos; - factory.fLightRadius = lightRadius; + SkScalar radius, scale; + SkDrawShadowMetrics::GetSpotParams(zPlaneParams.fZ, devLightPos.fX - center.fX, + devLightPos.fY - center.fY, devLightPos.fZ, + lightRadius, &radius, &scale, &factory.fOffset); SkRect devBounds; viewMatrix.mapRect(&devBounds, path.getBounds()); if (transparent || @@ -644,6 +698,8 @@ void SkBaseDevice::drawShadow(const SkPath& path, const SkDrawShadowRec& rec) { // need to add this after we classify the shadow factory.fOffset.fX += viewMatrix.getTranslateX(); factory.fOffset.fY += viewMatrix.getTranslateY(); + + SkColor color = rec.fSpotColor; #ifdef DEBUG_SHADOW_CHECKS switch (factory.fOccluderType) { case SpotVerticesFactory::OccluderType::kTransparent: @@ -657,7 +713,23 @@ void SkBaseDevice::drawShadow(const SkPath& path, const SkDrawShadowRec& rec) { break; } #endif - draw_shadow(factory, drawVertsProc, shadowedPath, rec.fSpotColor); + if (!draw_shadow(factory, drawVertsProc, shadowedPath, color)) { + // draw with blur + SkVector translate; + SkDrawShadowMetrics::GetSpotParams(zPlaneParams.fZ, devLightPos.fX, + devLightPos.fY, devLightPos.fZ, + lightRadius, &radius, &scale, &translate); + SkMatrix shadowMatrix; + shadowMatrix.setScaleTranslate(scale, scale, translate.fX, translate.fY); + SkAutoDeviceCTMRestore adr(this, SkMatrix::Concat(shadowMatrix, viewMatrix)); + + SkPaint paint; + paint.setColor(rec.fSpotColor); + SkScalar sigma = SkBlurMaskFilter::ConvertRadiusToSigma(radius); + uint32_t flags = SkBlurMaskFilter::kIgnoreTransform_BlurFlag; + paint.setMaskFilter(SkBlurMaskFilter::Make(kNormal_SkBlurStyle, sigma, flags)); + this->drawPath(path, paint); + } } } } |