diff --git a/lottie/src/main/java/com/airbnb/lottie/animation/content/FillContent.java b/lottie/src/main/java/com/airbnb/lottie/animation/content/FillContent.java index 1690989c05..8dc05b877c 100644 --- a/lottie/src/main/java/com/airbnb/lottie/animation/content/FillContent.java +++ b/lottie/src/main/java/com/airbnb/lottie/animation/content/FillContent.java @@ -11,7 +11,6 @@ import android.graphics.RectF; import androidx.annotation.Nullable; -import androidx.core.graphics.PaintCompat; import com.airbnb.lottie.L; import com.airbnb.lottie.LottieDrawable; @@ -68,8 +67,6 @@ public FillContent(final LottieDrawable lottieDrawable, BaseLayer layer, ShapeFi return; } - PaintCompat.setBlendMode(paint, layer.getBlendMode().toNativeBlendMode()); - path.setFillType(fill.getFillType()); colorAnimation = fill.getColor().createAnimation(); diff --git a/lottie/src/main/java/com/airbnb/lottie/model/content/LBlendMode.java b/lottie/src/main/java/com/airbnb/lottie/model/content/LBlendMode.java index e1c91a0acd..4a4012b05d 100644 --- a/lottie/src/main/java/com/airbnb/lottie/model/content/LBlendMode.java +++ b/lottie/src/main/java/com/airbnb/lottie/model/content/LBlendMode.java @@ -33,6 +33,20 @@ public BlendModeCompat toNativeBlendMode() { switch (this) { case NORMAL: return null; + case MULTIPLY: + // BlendModeCompat.MULTIPLY does not exist on Android < Q. Instead, there's + // BlendModeCompat.MODULATE, which maps to PorterDuff.Mode.MODULATE and not + // PorterDuff.Mode.MULTIPLY. + // + // MODULATE differs from MULTIPLY in that it doesn't perform + // any alpha blending. It just does a component-wise multiplication + // of the colors. + // + // For proper results on all platforms, we will map the MULTIPLY + // blend mode to MODULATE, and then do a slight adjustment to + // how we render such layers to still achieve the correct result. + // See BaseLayer.draw(). + return BlendModeCompat.MODULATE; case SCREEN: return BlendModeCompat.SCREEN; case OVERLAY: @@ -48,7 +62,6 @@ public BlendModeCompat toNativeBlendMode() { // To prevent unexpected issues where animations look correct // during development but silently break for users with older devices // we won't support any of these until Q is widely used. - case MULTIPLY: case COLOR_DODGE: case COLOR_BURN: case HARD_LIGHT: diff --git a/lottie/src/main/java/com/airbnb/lottie/model/layer/BaseLayer.java b/lottie/src/main/java/com/airbnb/lottie/model/layer/BaseLayer.java index 5a4afc01b8..5e8c48953a 100644 --- a/lottie/src/main/java/com/airbnb/lottie/model/layer/BaseLayer.java +++ b/lottie/src/main/java/com/airbnb/lottie/model/layer/BaseLayer.java @@ -14,6 +14,7 @@ import androidx.annotation.CallSuper; import androidx.annotation.FloatRange; import androidx.annotation.Nullable; +import androidx.core.graphics.PaintCompat; import com.airbnb.lottie.L; import com.airbnb.lottie.LottieComposition; @@ -117,6 +118,8 @@ static BaseLayer forModel( float blurMaskFilterRadius = 0f; @Nullable BlurMaskFilter blurMaskFilter; + @Nullable LPaint solidWhitePaint; + BaseLayer(LottieDrawable lottieDrawable, Layer layerModel) { this.lottieDrawable = lottieDrawable; this.layerModel = layerModel; @@ -258,7 +261,7 @@ public void draw(Canvas canvas, Matrix parentMatrix, int parentAlpha) { } } int alpha = (int) ((parentAlpha / 255f * (float) opacity / 100f) * 255); - if (!hasMatteOnThisLayer() && !hasMasksOnThisLayer()) { + if (!hasMatteOnThisLayer() && !hasMasksOnThisLayer() && getBlendMode() == LBlendMode.NORMAL) { matrix.preConcat(transform.getMatrix()); if (L.isTraceEnabled()) { L.beginSection("Layer#drawLayer"); @@ -307,13 +310,31 @@ public void draw(Canvas canvas, Matrix parentMatrix, int parentAlpha) { L.beginSection("Layer#saveLayer"); } contentPaint.setAlpha(255); + PaintCompat.setBlendMode(contentPaint, getBlendMode().toNativeBlendMode()); Utils.saveLayerCompat(canvas, rect, contentPaint); if (L.isTraceEnabled()) { L.endSection("Layer#saveLayer"); } // Clear the off screen buffer. This is necessary for some phones. - clearCanvas(canvas); + if (getBlendMode() != LBlendMode.MULTIPLY) { + clearCanvas(canvas); + } else { + // Due to the difference between PorterDuffMode.MULTIPLY (which we use for compatibility + // with Android < Q) and BlendMode.MULTIPLY (which is the correct, alpha-blended mode), + // we will alpha-blend the contents of this layer on top of a white background before + // we multiply it with the opaque substrate below (with canvas.restore()). + // + // Since white is the identity color for multiplication, this will behave as if we + // had correctly performed an alpha-blended multiply (such as BlendMode.MULTIPLY), but + // will work pre-Q as well. + if (solidWhitePaint == null) { + solidWhitePaint = new LPaint(); + solidWhitePaint.setColor(0xffffffff); + } + canvas.drawRect(rect.left - 1, rect.top - 1, rect.right + 1, rect.bottom + 1, solidWhitePaint); + } + if (L.isTraceEnabled()) { L.beginSection("Layer#drawLayer"); }