Circular shadow fixes for Flutter.
* 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>
This commit is contained in:
parent
5e958e9291
commit
8f7dc9f6ca
@ -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(); }
|
||||
|
@ -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);
|
||||
|
@ -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 {
|
||||
|
@ -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);
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user