Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve blur effect accuracy #2571

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ import kotlin.math.roundToInt
* expense of performance.
* Note: This process is very expensive. The performance impact will be reduced
* when hardware acceleration is enabled.
* @param applyEffectsToLayers Sets whether to apply effects such as drop shadow and blur to each layer instead of
* individually per shape. In case where effects are applied to shape groups and
* composition layers, this will improve correctness but at a performance cost.
* Note: This process is expensive when hardware acceleration is unavailable.
* @param applyShadowToLayers Equivalent to applyEffectsToLayers, but exposed for backwards compatibility.
* @param enableMergePaths Enables experimental merge paths support. Most animations with merge paths will
* want this on but merge path support is more limited than some other rendering
* features so it defaults to off. The only way to know if your animation will work
Expand Down Expand Up @@ -83,7 +88,8 @@ fun LottieAnimation(
modifier: Modifier = Modifier,
outlineMasksAndMattes: Boolean = false,
applyOpacityToLayers: Boolean = false,
applyShadowToLayers: Boolean = true,
applyShadowToLayers: Boolean? = null,
applyEffectsToLayers: Boolean = true,
enableMergePaths: Boolean = false,
renderMode: RenderMode = RenderMode.AUTOMATIC,
maintainOriginalImageBounds: Boolean = false,
Expand Down Expand Up @@ -131,7 +137,10 @@ fun LottieAnimation(
}
drawable.setOutlineMasksAndMattes(outlineMasksAndMattes)
drawable.isApplyingOpacityToLayersEnabled = applyOpacityToLayers
drawable.isApplyingShadowToLayersEnabled = applyShadowToLayers
drawable.isApplyingEffectsToLayersEnabled = applyEffectsToLayers
if (applyShadowToLayers != null) {
drawable.isApplyingEffectsToLayersEnabled = applyShadowToLayers;
}
drawable.maintainOriginalImageBounds = maintainOriginalImageBounds
drawable.clipToCompositionBounds = clipToCompositionBounds
drawable.clipTextToBoundingBox = clipTextToBoundingBox
Expand Down Expand Up @@ -160,7 +169,7 @@ fun LottieAnimation(
modifier: Modifier = Modifier,
outlineMasksAndMattes: Boolean = false,
applyOpacityToLayers: Boolean = false,
applyShadowToLayers: Boolean = true,
applyEffectsToLayers: Boolean = true,
enableMergePaths: Boolean = false,
renderMode: RenderMode = RenderMode.AUTOMATIC,
maintainOriginalImageBounds: Boolean = false,
Expand All @@ -177,7 +186,7 @@ fun LottieAnimation(
modifier = modifier,
outlineMasksAndMattes = outlineMasksAndMattes,
applyOpacityToLayers = applyOpacityToLayers,
applyShadowToLayers = applyShadowToLayers,
applyEffectsToLayers = applyEffectsToLayers,
enableMergePaths = enableMergePaths,
renderMode = renderMode,
maintainOriginalImageBounds = maintainOriginalImageBounds,
Expand Down Expand Up @@ -209,7 +218,7 @@ fun LottieAnimation(
iterations: Int = 1,
outlineMasksAndMattes: Boolean = false,
applyOpacityToLayers: Boolean = false,
applyShadowToLayers: Boolean = true,
applyEffectsToLayers: Boolean = true,
enableMergePaths: Boolean = false,
renderMode: RenderMode = RenderMode.AUTOMATIC,
reverseOnRepeat: Boolean = false,
Expand Down Expand Up @@ -238,7 +247,7 @@ fun LottieAnimation(
modifier = modifier,
outlineMasksAndMattes = outlineMasksAndMattes,
applyOpacityToLayers = applyOpacityToLayers,
applyShadowToLayers = applyShadowToLayers,
applyEffectsToLayers = applyEffectsToLayers,
enableMergePaths = enableMergePaths,
renderMode = renderMode,
maintainOriginalImageBounds = maintainOriginalImageBounds,
Expand Down
23 changes: 19 additions & 4 deletions lottie/src/main/java/com/airbnb/lottie/LottieAnimationView.java
Original file line number Diff line number Diff line change
Expand Up @@ -225,8 +225,14 @@ private void init(@Nullable AttributeSet attrs, @AttrRes int defStyleAttr) {
R.styleable.LottieAnimationView_lottie_enableMergePathsForKitKatAndAbove, false));
setApplyingOpacityToLayersEnabled(ta.getBoolean(
R.styleable.LottieAnimationView_lottie_applyOpacityToLayers, false));
setApplyingShadowToLayersEnabled(ta.getBoolean(
R.styleable.LottieAnimationView_lottie_applyShadowToLayers, true));
setApplyingEffectsToLayersEnabled(ta.getBoolean(
R.styleable.LottieAnimationView_lottie_applyEffectsToLayers, true));

// Respect the old applyShadowsToLayers if provided
if (ta.hasValue(R.styleable.LottieAnimationView_lottie_applyShadowToLayers)) {
setApplyingEffectsToLayersEnabled(ta.getBoolean(
R.styleable.LottieAnimationView_lottie_applyShadowToLayers, true));
}

if (ta.hasValue(R.styleable.LottieAnimationView_lottie_colorFilter)) {
int colorRes = ta.getResourceId(R.styleable.LottieAnimationView_lottie_colorFilter, -1);
Expand Down Expand Up @@ -1266,10 +1272,19 @@ public void setApplyingOpacityToLayersEnabled(boolean isApplyingOpacityToLayersE
*
* @see LottieAnimationView::setApplyingOpacityToLayersEnabled
*/
public void setApplyingShadowToLayersEnabled(boolean isApplyingShadowToLayersEnabled) {
lottieDrawable.setApplyingShadowToLayersEnabled(isApplyingShadowToLayersEnabled);
public void setApplyingEffectsToLayersEnabled(boolean isApplyingEffectsToLayersEnabled) {
lottieDrawable.setApplyingEffectsToLayersEnabled(isApplyingEffectsToLayersEnabled);
}

/**
* This has been renamed to setApplyingEffectsToLayersEnabled. Prefer using that instead.
*
* @see LottieAnimationView::setApplyingEffectsToLayersEnabled
*/
@Deprecated
public void setApplyingShadowToLayersEnabled(boolean isApplyingShadowsToLayersEnabled) {
lottieDrawable.setApplyingEffectsToLayersEnabled(isApplyingShadowsToLayersEnabled);
}
/**
* @see #setClipTextToBoundingBox(boolean)
*/
Expand Down
25 changes: 13 additions & 12 deletions lottie/src/main/java/com/airbnb/lottie/LottieDrawable.java
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ private enum OnVisibleAction {
private boolean performanceTrackingEnabled;
private boolean outlineMasksAndMattes;
private boolean isApplyingOpacityToLayersEnabled;
private boolean isApplyingShadowToLayersEnabled;
private boolean isApplyingEffectsToLayersEnabled;
private boolean clipTextToBoundingBox = false;

private RenderMode renderMode = RenderMode.AUTOMATIC;
Expand All @@ -174,7 +174,7 @@ private enum OnVisibleAction {
private RectF softwareRenderingDstBoundsRectF;
private RectF softwareRenderingTransformedBounds;
private Matrix softwareRenderingOriginalCanvasMatrix;
private float[] softwareRenderingOriginalCanvasMatrixElements = new float[9];
private final float[] softwareRenderingOriginalCanvasMatrixElements = new float[9];
private Matrix softwareRenderingOriginalCanvasMatrixInverse;

/**
Expand Down Expand Up @@ -572,21 +572,22 @@ public void setApplyingOpacityToLayersEnabled(boolean isApplyingOpacityToLayersE
}

/**
* Sets whether to apply drop shadows to each layer instead of shape.
* Sets whether to apply drop shadows and blurs to each layer instead of shape.
* <p>
* When true, the behavior will be more correct: it will mimic lottie-web and other renderers, in that drop shadows will be applied to a layer
* When true, the behavior will be more correct: it will mimic lottie-web and other renderers, in that effects will be applied to a layer
* as a whole, no matter its contents.
* <p>
* When false, the performance will be better at the expense of correctness: for each shape element individually, the first drop shadow upwards
* in the hierarchy is applied to it directly. Visually, this may manifest as phantom shadows or artifacts where the artist has intended to treat a
* layer as a whole, and this option exposes its internal structure.
* in the hierarchy is applied to it directly, and the combined (summed) blur radius of all blur effects upwards in the hierarchy is applied
* to it directly. Visually, this may manifest as phantom shadows, unwanted blur smudges in transparent areas, or other artifacts where the artist
* has intended to treat a layer as a whole, and this option exposes its internal structure.
* <p>
* The default value is true.
*
* @see LottieDrawable::setApplyingOpacityToLayersEnabled
*/
public void setApplyingShadowToLayersEnabled(boolean isApplyingShadowsToLayersEnabled) {
this.isApplyingShadowToLayersEnabled = isApplyingShadowsToLayersEnabled;
public void setApplyingEffectsToLayersEnabled(boolean isApplyingEffectsToLayersEnabled) {
this.isApplyingEffectsToLayersEnabled = isApplyingEffectsToLayersEnabled;
}

/**
Expand All @@ -600,7 +601,7 @@ public boolean isApplyingOpacityToLayersEnabled() {
return isApplyingOpacityToLayersEnabled;
}

public boolean isApplyingShadowToLayersEnabled() { return isApplyingShadowToLayersEnabled; }
public boolean isApplyingEffectsToLayersEnabled() { return isApplyingEffectsToLayersEnabled; }

/**
* @see #setClipTextToBoundingBox(boolean)
Expand Down Expand Up @@ -823,7 +824,7 @@ private void draw(Canvas canvas, Matrix matrix, CompositionLayer compositionLaye
renderAndDrawAsBitmap(canvas, compositionLayer);
canvas.restore();
} else {
compositionLayer.draw(canvas, matrix, alpha, null);
compositionLayer.draw(canvas, matrix, alpha, null, 0);
}
}

Expand Down Expand Up @@ -1748,7 +1749,7 @@ private void drawDirectlyToCanvas(Canvas canvas) {
renderingMatrix.preTranslate(bounds.left, bounds.top);
renderingMatrix.preScale(scaleX, scaleY);
}
compositionLayer.draw(canvas, renderingMatrix, alpha, null);
compositionLayer.draw(canvas, renderingMatrix, alpha, null, 0);
}

/**
Expand Down Expand Up @@ -1819,7 +1820,7 @@ private void renderAndDrawAsBitmap(Canvas originalCanvas, CompositionLayer compo
softwareRenderingBitmap.eraseColor(0);
softwareRenderingCanvas.setMatrix(Utils.IDENTITY_MATRIX);
softwareRenderingCanvas.scale(preExistingScaleX, preExistingScaleY);
compositionLayer.draw(softwareRenderingCanvas, renderingMatrix, alpha, null);
compositionLayer.draw(softwareRenderingCanvas, renderingMatrix, alpha, null, 0);

// Calculate the dst bounds.
// We need to map the rendered coordinates back to the canvas's coordinates. To do so, we need to invert the transform
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@ public abstract class BaseStrokeContent
private final List<BaseKeyframeAnimation<?, Float>> dashPatternAnimations;
@Nullable private final BaseKeyframeAnimation<?, Float> dashPatternOffsetAnimation;
@Nullable private BaseKeyframeAnimation<ColorFilter, ColorFilter> colorFilterAnimation;
@Nullable private BaseKeyframeAnimation<Float, Float> blurAnimation;
float blurMaskFilterRadius = 0f;

BaseStrokeContent(final LottieDrawable lottieDrawable, BaseLayer layer, Paint.Cap cap,
Expand Down Expand Up @@ -101,12 +100,6 @@ public abstract class BaseStrokeContent
if (dashPatternOffsetAnimation != null) {
dashPatternOffsetAnimation.addUpdateListener(this);
}

if (layer.getBlurEffect() != null) {
blurAnimation = layer.getBlurEffect().getBlurriness().createAnimation();
blurAnimation.addUpdateListener(this);
layer.addAnimation(blurAnimation);
}
}

@Override public void onValueChanged() {
Expand Down Expand Up @@ -148,7 +141,7 @@ public abstract class BaseStrokeContent
}
}

@Override public void draw(Canvas canvas, Matrix parentMatrix, int parentAlpha, @Nullable DropShadow shadowToApply) {
@Override public void draw(Canvas canvas, Matrix parentMatrix, int parentAlpha, @Nullable DropShadow shadowToApply, float blurToApply) {
if (L.isTraceEnabled()) {
L.beginSection("StrokeContent#draw");
}
Expand Down Expand Up @@ -176,16 +169,14 @@ public abstract class BaseStrokeContent
paint.setColorFilter(colorFilterAnimation.getValue());
}

if (blurAnimation != null) {
float blurRadius = blurAnimation.getValue();
if (blurRadius == 0f) {
paint.setMaskFilter(null);
} else if (blurRadius != blurMaskFilterRadius){
BlurMaskFilter blur = layer.getBlurMaskFilter(blurRadius);
paint.setMaskFilter(blur);
}
blurMaskFilterRadius = blurRadius;
if (blurToApply == 0f) {
paint.setMaskFilter(null);
} else if (blurToApply != blurMaskFilterRadius){
BlurMaskFilter blur = layer.getBlurMaskFilter(blurToApply);
paint.setMaskFilter(blur);
}
blurMaskFilterRadius = blurToApply;

if (shadowToApply != null) {
shadowToApply.applyWithAlpha((int)(strokeAlpha * 255), paint);
}
Expand Down Expand Up @@ -394,15 +385,6 @@ public <T> void addValueCallback(T property, @Nullable LottieValueCallback<T> ca
colorFilterAnimation.addUpdateListener(this);
layer.addAnimation(colorFilterAnimation);
}
} else if (property == LottieProperty.BLUR_RADIUS) {
if (blurAnimation != null) {
blurAnimation.setValueCallback((LottieValueCallback<Float>) callback);
} else {
blurAnimation =
new ValueCallbackKeyframeAnimation<>((LottieValueCallback<Float>) callback);
blurAnimation.addUpdateListener(this);
layer.addAnimation(blurAnimation);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ Matrix getTransformationMatrix() {
return path;
}

@Override public void draw(Canvas canvas, Matrix parentMatrix, int parentAlpha, @Nullable DropShadow shadowToApply) {
@Override public void draw(Canvas canvas, Matrix parentMatrix, int parentAlpha, @Nullable DropShadow shadowToApply, float blurToApply) {
if (hidden) {
return;
}
Expand All @@ -177,7 +177,7 @@ Matrix getTransformationMatrix() {
// Apply off-screen rendering only when needed in order to improve rendering performance.
boolean isRenderingWithOffScreen =
(lottieDrawable.isApplyingOpacityToLayersEnabled() && hasTwoOrMoreDrawableContent() && layerAlpha != 255) ||
(shadowToApply != null && lottieDrawable.isApplyingShadowToLayersEnabled() && hasTwoOrMoreDrawableContent());
((shadowToApply != null || blurToApply > 0.0f) && lottieDrawable.isApplyingEffectsToLayersEnabled() && hasTwoOrMoreDrawableContent());
int childAlpha = isRenderingWithOffScreen ? 255 : layerAlpha;

Canvas contentCanvas = canvas;
Expand All @@ -187,7 +187,10 @@ Matrix getTransformationMatrix() {
offscreenOp.alpha = layerAlpha;
if (shadowToApply != null) {
shadowToApply.applyTo(offscreenOp);
shadowToApply = null; // Don't pass it to children - OffscreenLayer now takes care of this

// Don't pass effects to children - OffscreenLayer now takes care of this
shadowToApply = null;
blurToApply = 0.0f;
} else {
offscreenOp.shadow = null;
}
Expand All @@ -203,7 +206,7 @@ Matrix getTransformationMatrix() {
for (int i = contents.size() - 1; i >= 0; i--) {
Object content = contents.get(i);
if (content instanceof DrawingContent) {
((DrawingContent) content).draw(contentCanvas, matrix, childAlpha, shadowToApply);
((DrawingContent) content).draw(contentCanvas, matrix, childAlpha, shadowToApply, blurToApply);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import com.airbnb.lottie.utils.DropShadow;

public interface DrawingContent extends Content {
void draw(Canvas canvas, Matrix parentMatrix, int alpha, @Nullable DropShadow shadowToApply);
void draw(Canvas canvas, Matrix parentMatrix, int alpha, @Nullable DropShadow shadowToApply, float blurToApply);

void getBounds(RectF outBounds, Matrix parentMatrix, boolean applyParents);
}
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ public FillContent(final LottieDrawable lottieDrawable, BaseLayer layer, ShapeFi
return name;
}

@Override public void draw(Canvas canvas, Matrix parentMatrix, int parentAlpha, @Nullable DropShadow shadowToApply) {
@Override public void draw(Canvas canvas, Matrix parentMatrix, int parentAlpha, @Nullable DropShadow shadowToApply, float blurToApply) {
if (hidden) {
return;
}
Expand All @@ -106,16 +106,15 @@ public FillContent(final LottieDrawable lottieDrawable, BaseLayer layer, ShapeFi
paint.setColorFilter(colorFilterAnimation.getValue());
}

if (blurAnimation != null) {
float blurRadius = blurAnimation.getValue();
if (blurRadius == 0f) {
paint.setMaskFilter(null);
} else if (blurRadius != blurMaskFilterRadius) {
BlurMaskFilter blur = layer.getBlurMaskFilter(blurRadius);
paint.setMaskFilter(blur);
}
blurMaskFilterRadius = blurRadius;

if (blurToApply == 0f) {
paint.setMaskFilter(null);
} else if (blurToApply != blurMaskFilterRadius) {
BlurMaskFilter blur = layer.getBlurMaskFilter(blurToApply);
paint.setMaskFilter(blur);
}
blurMaskFilterRadius = blurToApply;

if (shadowToApply != null) {
shadowToApply.applyWithAlpha((int)(fillAlpha * 255), paint);
} else {
Expand Down
Loading
Loading