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 drop shadow effect accuracy #2523

Merged
merged 22 commits into from
Aug 4, 2024
Merged
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 @@ -191,7 +191,7 @@ public abstract class BaseStrokeContent
blurMaskFilterRadius = blurRadius;
}
if (dropShadowAnimation != null) {
dropShadowAnimation.applyTo(paint);
dropShadowAnimation.applyTo(paint, parentMatrix, Utils.mixOpacities(parentAlpha, alpha));
}

for (int i = 0; i < pathGroups.size(); i++) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import com.airbnb.lottie.model.content.ShapeFill;
import com.airbnb.lottie.model.layer.BaseLayer;
import com.airbnb.lottie.utils.MiscUtils;
import com.airbnb.lottie.utils.Utils;
import com.airbnb.lottie.value.LottieValueCallback;

import java.util.ArrayList;
Expand Down Expand Up @@ -123,7 +124,7 @@ public FillContent(final LottieDrawable lottieDrawable, BaseLayer layer, ShapeFi
blurMaskFilterRadius = blurRadius;
}
if (dropShadowAnimation != null) {
dropShadowAnimation.applyTo(paint);
dropShadowAnimation.applyTo(paint, parentMatrix, Utils.mixOpacities(parentAlpha, alpha));
}

path.reset();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import com.airbnb.lottie.model.content.GradientType;
import com.airbnb.lottie.model.layer.BaseLayer;
import com.airbnb.lottie.utils.MiscUtils;
import com.airbnb.lottie.utils.Utils;
import com.airbnb.lottie.value.LottieValueCallback;

import java.util.ArrayList;
Expand Down Expand Up @@ -150,13 +151,14 @@ public GradientFillContent(final LottieDrawable lottieDrawable, LottieCompositio
}
blurMaskFilterRadius = blurRadius;
}
if (dropShadowAnimation != null) {
dropShadowAnimation.applyTo(paint);
}

int alpha = (int) ((parentAlpha / 255f * opacityAnimation.getValue() / 100f) * 255);
paint.setAlpha(clamp(alpha, 0, 255));

if (dropShadowAnimation != null) {
dropShadowAnimation.applyTo(paint, parentMatrix, Utils.mixOpacities(parentAlpha, alpha));
}

canvas.drawPath(path, paint);
if (L.isTraceEnabled()) {
L.endSection("GradientFillContent#draw");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,29 +1,38 @@
package com.airbnb.lottie.animation.keyframe;

import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;

import androidx.annotation.Nullable;

import com.airbnb.lottie.model.layer.BaseLayer;
import com.airbnb.lottie.parser.DropShadowEffect;
import com.airbnb.lottie.value.LottieFrameInfo;
import com.airbnb.lottie.value.LottieValueCallback;


public class DropShadowKeyframeAnimation implements BaseKeyframeAnimation.AnimationListener {
private static final double DEG_TO_RAD = Math.PI / 180.0;
private static final float DEG_TO_RAD = (float) (Math.PI / 180.0);

private final BaseLayer layer;
private final BaseKeyframeAnimation.AnimationListener listener;
private final BaseKeyframeAnimation<Integer, Integer> color;
private final BaseKeyframeAnimation<Float, Float> opacity;
private final BaseKeyframeAnimation<Float, Float> direction;
private final BaseKeyframeAnimation<Float, Float> distance;
private final BaseKeyframeAnimation<Float, Float> radius;
private final FloatKeyframeAnimation opacity;
private final FloatKeyframeAnimation direction;
private final FloatKeyframeAnimation distance;
private final FloatKeyframeAnimation radius;

// Cached paint values.
private float paintRadius = Float.NaN;
private float paintX = Float.NaN;
private float paintY = Float.NaN;
// 0 is a valid color but it is transparent so it will not draw anything anyway.
private int paintColor = 0;

private boolean isDirty = true;
private final float[] matrixValues = new float[9];

public DropShadowKeyframeAnimation(BaseKeyframeAnimation.AnimationListener listener, BaseLayer layer, DropShadowEffect dropShadowEffect) {
this.listener = listener;
this.layer = layer;
color = dropShadowEffect.getColor().createAnimation();
color.addUpdateListener(this);
layer.addAnimation(color);
Expand All @@ -42,24 +51,49 @@ public DropShadowKeyframeAnimation(BaseKeyframeAnimation.AnimationListener liste
}

@Override public void onValueChanged() {
isDirty = true;
listener.onValueChanged();
}

public void applyTo(Paint paint) {
if (!isDirty) {
allenchen1154 marked this conversation as resolved.
Show resolved Hide resolved
return;
}
isDirty = false;

double directionRad = ((double) direction.getValue()) * DEG_TO_RAD;
/**
* Applies a shadow to the provided Paint object, which will be applied to the Canvas behind whatever is drawn
* (a shape, bitmap, path, etc.)
* @param parentAlpha A value between 0 and 255 representing the combined alpha of all parents of this drop shadow effect.
* E.g. The layer via transform, the fill/stroke via its opacity, etc.
*/
public void applyTo(Paint paint, Matrix parentMatrix, int parentAlpha) {
float directionRad = this.direction.getFloatValue() * DEG_TO_RAD;
float distance = this.distance.getValue();
float x = ((float) Math.sin(directionRad)) * distance;
float y = ((float) Math.cos(directionRad + Math.PI)) * distance;
float rawX = ((float) Math.sin(directionRad)) * distance;
float rawY = ((float) Math.cos(directionRad + Math.PI)) * distance;

// The x and y coordinates are relative to the shape that is being drawn.
// The distance in the animation is relative to the original size of the shape.
// If the shape will be drawn scaled, we need to scale the distance we draw the shadow.
layer.transform.getMatrix().getValues(matrixValues);
float layerScaleX = matrixValues[Matrix.MSCALE_X];
float layerScaleY = matrixValues[Matrix.MSCALE_Y];
parentMatrix.getValues(matrixValues);
float parentScaleX = matrixValues[Matrix.MSCALE_X];
float parentScaleY = matrixValues[Matrix.MSCALE_Y];
float scaleX = parentScaleX / layerScaleX;
float scaleY = parentScaleY / layerScaleY;
float x = rawX * scaleX;
float y = rawY * scaleY;

int baseColor = color.getValue();
int opacity = Math.round(this.opacity.getValue());
int opacity = Math.round(this.opacity.getValue() * parentAlpha / 255f);
int color = Color.argb(opacity, Color.red(baseColor), Color.green(baseColor), Color.blue(baseColor));
float radius = this.radius.getValue();

// Paint.setShadowLayer() removes the shadow if radius is 0, so we use a small nonzero value in that case
float radius = Math.max(this.radius.getValue() * scaleX, Float.MIN_VALUE);

if (paintRadius == radius && paintX == x && paintY == y && paintColor == color) {
return;
}
paintRadius = radius;
paintX = x;
paintY = y;
paintColor = color;
paint.setShadowLayer(radius, x, y, color);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.airbnb.lottie.model.animatable;

import com.airbnb.lottie.animation.keyframe.BaseKeyframeAnimation;
import com.airbnb.lottie.animation.keyframe.FloatKeyframeAnimation;
import com.airbnb.lottie.value.Keyframe;

Expand All @@ -12,7 +11,7 @@ public AnimatableFloatValue(List<Keyframe<Float>> keyframes) {
super(keyframes);
}

@Override public BaseKeyframeAnimation<Float, Float> createAnimation() {
@Override public FloatKeyframeAnimation createAnimation() {
return new FloatKeyframeAnimation(keyframes);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ static BaseLayer forModel(
private List<BaseLayer> parentLayers;

private final List<BaseKeyframeAnimation<?, ?>> animations = new ArrayList<>();
final TransformKeyframeAnimation transform;
public final TransformKeyframeAnimation transform;
private boolean visible = true;

private boolean outlineMasksAndMattes;
Expand Down
10 changes: 10 additions & 0 deletions lottie/src/main/java/com/airbnb/lottie/model/layer/ImageLayer.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import com.airbnb.lottie.LottieProperty;
import com.airbnb.lottie.animation.LPaint;
import com.airbnb.lottie.animation.keyframe.BaseKeyframeAnimation;
import com.airbnb.lottie.animation.keyframe.DropShadowKeyframeAnimation;
import com.airbnb.lottie.animation.keyframe.ValueCallbackKeyframeAnimation;
import com.airbnb.lottie.utils.Utils;
import com.airbnb.lottie.value.LottieValueCallback;
Expand All @@ -28,10 +29,15 @@ public class ImageLayer extends BaseLayer {
@Nullable private final LottieImageAsset lottieImageAsset;
@Nullable private BaseKeyframeAnimation<ColorFilter, ColorFilter> colorFilterAnimation;
@Nullable private BaseKeyframeAnimation<Bitmap, Bitmap> imageAnimation;
@Nullable private DropShadowKeyframeAnimation dropShadowAnimation;

ImageLayer(LottieDrawable lottieDrawable, Layer layerModel) {
super(lottieDrawable, layerModel);
lottieImageAsset = lottieDrawable.getLottieImageAssetForId(layerModel.getRefId());

if (getDropShadowEffect() != null) {
dropShadowAnimation = new DropShadowKeyframeAnimation(this, this, getDropShadowEffect());
}
}

@Override public void drawLayer(@NonNull Canvas canvas, Matrix parentMatrix, int parentAlpha) {
Expand All @@ -54,6 +60,10 @@ public class ImageLayer extends BaseLayer {
dst.set(0, 0, (int) (bitmap.getWidth() * density), (int) (bitmap.getHeight() * density));
}

if (dropShadowAnimation != null) {
dropShadowAnimation.applyTo(paint, parentMatrix, parentAlpha);
}

canvas.drawBitmap(bitmap, src, dst, paint);
canvas.restore();
}
Expand Down
7 changes: 7 additions & 0 deletions lottie/src/main/java/com/airbnb/lottie/utils/Utils.java
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,13 @@ public static void saveLayerCompat(Canvas canvas, RectF rect, Paint paint, int f
}
}

/**
* Multiplies 2 opacities that are 0-255.
*/
public static int mixOpacities(int opacity1, int opacity2) {
return (int) ((opacity1 / 255f * opacity2 / 255f) * 255f);
}

/**
* For testing purposes only. DO NOT USE IN PRODUCTION.
*/
Expand Down
Loading
Loading