Skip to content

Apply blend modes on layer level and add Multiply blend mode#2519

Merged
gpeal merged 4 commits intoairbnb:masterfrom
geomaster:master
Jul 24, 2024
Merged

Apply blend modes on layer level and add Multiply blend mode#2519
gpeal merged 4 commits intoairbnb:masterfrom
geomaster:master

Conversation

@geomaster
Copy link
Copy Markdown
Contributor

@geomaster geomaster commented Jul 23, 2024

This commit improves blend mode support in lottie-android in two ways:

  • Applying blend modes on layer-level, instead of fill level
  • Adding support for the Multiply blend mode

Applying blend modes on layer level

The Lottie format defines blend modes as attributes on a layer. However, lottie-android is presently applying the layer blend modes on a solid color fill only. Notably, this causes any stroked or gradient-filled shapes or image layers to blend incorrectly, such as in this file:

stroke-blending-test.json

(The file contains a filled + stroked shape that renders as a pink square on other platforms, but renders with a visible stroke on lottie-android since its blend mode is applied only on the fill.)

Instead, we move this decision to BaseLayer by analogy to transparent layer handling, which is closer to how the format specifies the property and fixes these cases.

Multiply support

BlendModeCompat is designed to resolve to either a BlendMode (added in Android Q, supporting most modern blend modes) or PorterDuff.Mode (always available, but a smaller choice of modes as it is mostly focused on alpha compositing).

We use BlendModeCompat to support Lottie layer blend modes (bm key) to ensure compatibility on all platforms. For consistency, we don't support values which don't have a PorterDuff.Mode equivalent.

Our support for Lottie blend modes did not include Multiply due to a slightly different behavior between the PorterDuff.MULTIPLY (exposed as BlendModeCompat.MODULATE) and BlendModeCompat.MULTIPLY variants. Namely, the formula used for PorterDuff.MODULATE, combined with alpha-premultiplication done by Skia, means that a layer with an alpha < 1.0 and multiply blend mode will also darken the destination:

Incorrect-Blend

(Multiply-blended layers with < 1.0 alpha on the left, Screen-blended layers with < 1.0 alpha on the right)

However, what we can do instead is clear the canvas with a solid white color instead of transparent before drawing the layer's contents as normal. When blending the resulting bitmap over an opaque background using PorterDuff.MULTIPLY (i.e. BlendModeCompat.MODULATE), the end result will be as if we had used BlendModeCompat.MULTIPLY, since all-1.0 (white) is a multiplication identity:

Correct-Blend

This PR implements the latter solution and adds a consistent support for the Multiply blend mode for all Android versions.

Test file used: blendmode-tests-multiply+screen+bg.zip

BlendModeCompat is designed to use either a BlendMode (added in Android
Q) or PorterDuff.Mode (always available).

Our support for Lottie blend modes did not include Multiply due to a
slightly different formula between the PorterDuff and BlendMode
variants.

However, we did include support for Screen, which suffers from a similar
behavior. Therefore, we are not breaking any consistency by including
Multiply too, and including it provides benefits in terms of more
complete support, as Multiply is a foundational blend mode.
Copy link
Copy Markdown
Collaborator

@gpeal gpeal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

@github-actions
Copy link
Copy Markdown

Snapshot Tests
API 23: Report Diff
API 31: Report Diff

"Proper" MULTIPLY seems to have been added in Android Q, so use the
older MODULATE with a slight hack to ensure proper rendering in our
usecase.
@github-actions
Copy link
Copy Markdown

Snapshot Tests
API 23: Report Diff
API 31: Report Diff

@geomaster
Copy link
Copy Markdown
Contributor Author

@gpeal Thanks for the review! Addressed comment. Let me know if there's anything else you see that needs improvement!

Copy link
Copy Markdown
Collaborator

@gpeal gpeal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the investigative work here!

@github-actions
Copy link
Copy Markdown

Snapshot Tests
API 23: Report Diff
API 31: Report Diff

@gpeal gpeal merged commit 328fc72 into airbnb:master Jul 24, 2024
allenchen1154 pushed a commit that referenced this pull request Sep 13, 2025
This commit improves blend mode support in lottie-android in two ways:
* Applying blend modes on layer-level, instead of fill level
* Adding support for the Multiply blend mode

## Applying blend modes on layer level

The Lottie format defines blend modes as attributes on a layer. However, lottie-android is presently applying the layer blend modes on a solid color fill only. Notably, this causes any stroked or gradient-filled shapes or image layers to blend incorrectly, such as in this file:

[stroke-blending-test.json](https://github.com/user-attachments/files/16346206/stroke-blending-test.json)

(The file contains a filled + stroked shape that renders as a pink square on other platforms, but renders with a visible stroke on lottie-android since its blend mode is applied only on the fill.)

Instead, we move this decision to `BaseLayer` by analogy to transparent layer handling, which is closer to how the format specifies the property and fixes these cases.

## Multiply support

`BlendModeCompat` is designed to resolve to either a `BlendMode` (added in Android Q, supporting most modern blend modes) or `PorterDuff.Mode` (always available, but a smaller choice of modes as it is mostly focused on alpha compositing).

We use `BlendModeCompat` to support Lottie layer blend modes (`bm` key) to ensure compatibility on all platforms. For consistency, we don't  support values which don't have a `PorterDuff.Mode` equivalent.

Our support for Lottie blend modes did not include Multiply due to a slightly different behavior between the `PorterDuff.MULTIPLY` (exposed as `BlendModeCompat.MODULATE`) and `BlendModeCompat.MULTIPLY` variants. Namely, the formula used for `PorterDuff.MODULATE`, combined with alpha-premultiplication done by Skia, means that a layer with an alpha < 1.0 and multiply blend mode will also darken the destination:

![Incorrect-Blend](https://github.com/user-attachments/assets/6a2113ef-4bac-4bbc-830b-1353adf4ee2b)

(Multiply-blended layers with < 1.0 alpha on the left, Screen-blended layers with < 1.0 alpha on the right)

However, what we can do instead is clear the canvas with a solid white color instead of transparent before drawing the layer's contents as normal. When blending the resulting bitmap over an opaque background using `PorterDuff.MULTIPLY` (i.e. `BlendModeCompat.MODULATE`), the end result will be as if we had used `BlendModeCompat.MULTIPLY`, since all-1.0 (white) is a multiplication identity:

![Correct-Blend](https://github.com/user-attachments/assets/126022ef-6e47-48ee-b803-1d9800ca2c75)

This PR implements the latter solution and adds a consistent support for the Multiply blend mode for all Android versions.

*Test file used:*  [blendmode-tests-multiply+screen+bg.zip](https://github.com/user-attachments/files/16365843/blendmode-tests-multiply%2Bscreen%2Bbg.zip)
allenchen1154 added a commit that referenced this pull request Sep 13, 2025
…oid 34+

The workaround in #2519 for Multiply blend modes doesn't work on Android 34+. This changes the implementation to use `BlemdModeCompat.MULTIPLY` on Android versions Q (29) and higher where it is supported, while retaining the workaround for older versions.

Fixes #2636
allenchen1154 added a commit that referenced this pull request Sep 13, 2025
…oid 34+

The workaround in #2519 for Multiply blend modes doesn't work on Android 34+. This changes the implementation to use `BlemdModeCompat.MULTIPLY` on Android versions Q (29) and higher where it is supported, while retaining the workaround for older versions.

Fixes #2636
and #2593
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants