Add mix-blend-mode effects to Android (#45053)

Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/45053

Add support for most keyword values of mix-blend-mode on Android

Missing compositing operators and global values

Changelog: [Internal]

Reviewed By: NickGerleman

Differential Revision: D58752052

fbshipit-source-id: e63e01d45a7e0924f3853f08dff5cec7e2f1ceaf
This commit is contained in:
Jorge Cabiedes Acosta
2024-07-03 20:55:27 -07:00
committed by Facebook GitHub Bot
parent 70bacfe098
commit ec95533609
9 changed files with 102 additions and 20 deletions
@@ -4003,6 +4003,7 @@ public abstract class com/facebook/react/uimanager/BaseViewManager : com/faceboo
public fun setElevation (Landroid/view/View;F)V
public fun setFilter (Landroid/view/View;Lcom/facebook/react/bridge/ReadableArray;)V
public fun setImportantForAccessibility (Landroid/view/View;Ljava/lang/String;)V
public fun setMixBlendMode (Landroid/view/View;Ljava/lang/String;)V
public fun setMoveShouldSetResponder (Landroid/view/View;Z)V
public fun setMoveShouldSetResponderCapture (Landroid/view/View;Z)V
public fun setNativeId (Landroid/view/View;Ljava/lang/String;)V
@@ -4073,6 +4074,7 @@ public abstract interface class com/facebook/react/uimanager/BaseViewManagerInte
public abstract fun setElevation (Landroid/view/View;F)V
public abstract fun setFilter (Landroid/view/View;Lcom/facebook/react/bridge/ReadableArray;)V
public abstract fun setImportantForAccessibility (Landroid/view/View;Ljava/lang/String;)V
public abstract fun setMixBlendMode (Landroid/view/View;Ljava/lang/String;)V
public abstract fun setNativeId (Landroid/view/View;Ljava/lang/String;)V
public abstract fun setOpacity (Landroid/view/View;F)V
public abstract fun setRenderToHardwareTexture (Landroid/view/View;Z)V
@@ -5449,6 +5451,7 @@ public final class com/facebook/react/uimanager/ViewProps {
public static final field MINIMUM_FONT_SCALE Ljava/lang/String;
public static final field MIN_HEIGHT Ljava/lang/String;
public static final field MIN_WIDTH Ljava/lang/String;
public static final field MIX_BLEND_MODE Ljava/lang/String;
public static final field NATIVE_ID Ljava/lang/String;
public static final field NEEDS_OFFSCREEN_ALPHA_COMPOSITING Ljava/lang/String;
public static final field NONE Ljava/lang/String;
@@ -7,6 +7,7 @@
package com.facebook.react.uimanager;
import android.graphics.BlendMode;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.Build;
@@ -107,7 +108,7 @@ public abstract class BaseViewManager<T extends View, C extends LayoutShadowNode
view.setTag(R.id.use_hardware_layer, null);
view.setTag(R.id.filter, null);
applyFilter(view, null);
applyLayerEffects(view, null, null, null);
// setShadowColor
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
@@ -192,6 +193,12 @@ public abstract class BaseViewManager<T extends View, C extends LayoutShadowNode
view.setTag(R.id.filter, filter);
}
@Override
@ReactProp(name = ViewProps.MIX_BLEND_MODE)
public void setMixBlendMode(@NonNull T view, @Nullable String mixBlendMode) {
view.setTag(R.id.mix_blend_mode, BlendModeHelper.parseMixBlendMode(mixBlendMode));
}
@Override
@ReactProp(name = ViewProps.TRANSFORM)
public void setTransform(@NonNull T view, @Nullable ReadableArray matrix) {
@@ -497,25 +504,37 @@ public abstract class BaseViewManager<T extends View, C extends LayoutShadowNode
}
}
private void applyFilter(@NonNull T view, @Nullable ReadableArray filter) {
private void applyLayerEffects(
@NonNull T view,
@Nullable ReadableArray filter,
@Nullable BlendMode mixBlendMode,
@Nullable Boolean useHWLayer) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
view.setRenderEffect(null);
}
Boolean useHWLayer = (Boolean) view.getTag(R.id.use_hardware_layer);
int layerType =
useHWLayer != null && useHWLayer ? View.LAYER_TYPE_HARDWARE : View.LAYER_TYPE_NONE;
view.setLayerType(layerType, null);
if (filter == null) {
return;
@Nullable Paint p = null;
if (filter != null) {
if (FilterHelper.isOnlyColorMatrixFilters(filter)) {
p = new Paint();
p.setColorFilter(FilterHelper.parseColorMatrixFilters(filter));
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
view.setRenderEffect(FilterHelper.parseFilters(filter));
}
}
if (FilterHelper.isOnlyColorMatrixFilters(filter)) {
Paint p = new Paint();
p.setColorFilter(FilterHelper.parseColorMatrixFilters(filter));
if (mixBlendMode != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
p = p == null ? new Paint() : p;
p.setBlendMode(mixBlendMode);
}
if (p == null) {
int layerType =
useHWLayer != null && useHWLayer ? View.LAYER_TYPE_HARDWARE : View.LAYER_TYPE_NONE;
view.setLayerType(layerType, null);
} else {
view.setLayerType(View.LAYER_TYPE_HARDWARE, p);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
view.setRenderEffect(FilterHelper.parseFilters(filter));
}
}
@@ -625,13 +644,14 @@ public abstract class BaseViewManager<T extends View, C extends LayoutShadowNode
view.setTag(R.id.invalidate_transform, false);
}
Boolean useHWLayer = (Boolean) view.getTag(R.id.use_hardware_layer);
if (useHWLayer != null) {
view.setLayerType(useHWLayer ? View.LAYER_TYPE_HARDWARE : View.LAYER_TYPE_NONE, null);
}
ReadableArray filter = (ReadableArray) view.getTag(R.id.filter);
applyFilter(view, filter);
BlendMode mixBlendMode =
Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
? (BlendMode) view.getTag(R.id.mix_blend_mode)
: null;
Boolean useHWLayer = (Boolean) view.getTag(R.id.use_hardware_layer);
applyLayerEffects(view, filter, mixBlendMode, useHWLayer);
}
@Override
@@ -50,6 +50,8 @@ public interface BaseViewManagerInterface<T extends View> {
void setFilter(T view, ReadableArray filter);
void setMixBlendMode(T view, String setMixBlendMode);
void setShadowColor(T view, int shadowColor);
void setImportantForAccessibility(T view, @Nullable String importantForAccessibility);
@@ -0,0 +1,41 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.uimanager
import android.annotation.TargetApi
import android.graphics.BlendMode
@TargetApi(29)
internal object BlendModeHelper {
/** @see https://www.w3.org/TR/compositing-1/#mix-blend-mode */
@JvmStatic
public fun parseMixBlendMode(mixBlendMode: String?): BlendMode? {
mixBlendMode ?: return null
return when (mixBlendMode) {
"normal" -> null
"multiply" -> BlendMode.MULTIPLY
"screen" -> BlendMode.SCREEN
"overlay" -> BlendMode.OVERLAY
"darken" -> BlendMode.DARKEN
"lighten" -> BlendMode.LIGHTEN
"color-dodge" -> BlendMode.COLOR_DODGE
"color-burn" -> BlendMode.COLOR_BURN
"hard-light" -> BlendMode.HARD_LIGHT
"soft-light" -> BlendMode.SOFT_LIGHT
"difference" -> BlendMode.DIFFERENCE
"exclusion" -> BlendMode.EXCLUSION
"hue" -> BlendMode.HUE
"saturation" -> BlendMode.SATURATION
"color" -> BlendMode.COLOR
"luminosity" -> BlendMode.LUMINOSITY
else -> throw IllegalArgumentException("Invalid mix-blend-mode name: $mixBlendMode")
}
}
}
@@ -136,6 +136,7 @@ public object ViewProps {
public const val BORDER_END_COLOR: String = "borderEndColor"
public const val ON_LAYOUT: String = "onLayout"
public const val FILTER: String = "experimental_filter"
public const val MIX_BLEND_MODE: String = "experimental_mixBlendMode"
public const val TRANSFORM: String = "transform"
public const val TRANSFORM_ORIGIN: String = "transformOrigin"
public const val ELEVATION: String = "elevation"
@@ -62,4 +62,7 @@
<!-- tag is used to store graphical filter effects to apply to the view -->
<item type="id" name="filter"/>
<!-- tag is used to store mixBlendMode effects to apply to the view -->
<item type="id" name="mix_blend_mode"/>
</resources>
@@ -166,6 +166,15 @@ BaseViewProps::BaseViewProps(
"experimental_filter",
sourceProps.filter,
{})),
mixBlendMode(
CoreFeatures::enablePropIteratorSetter
? sourceProps.mixBlendMode
: convertRawProp(
context,
rawProps,
"experimental_mixBlendMode",
sourceProps.mixBlendMode,
{})),
transform(
CoreFeatures::enablePropIteratorSetter ? sourceProps.transform
: convertRawProp(
@@ -62,6 +62,9 @@ class BaseViewProps : public YogaStylableProps, public AccessibilityProps {
// Filter
std::vector<FilterPrimitive> filter{};
// MixBlendMode
std::string mixBlendMode;
// Transform
Transform transform{};
TransformOrigin transformOrigin{
@@ -61,7 +61,7 @@ void ViewShadowNode::initialize() noexcept {
viewProps.accessibilityViewIsModal ||
viewProps.importantForAccessibility != ImportantForAccessibility::Auto ||
viewProps.removeClippedSubviews || viewProps.cursor != Cursor::Auto ||
!viewProps.filter.empty() ||
!viewProps.filter.empty() || !viewProps.mixBlendMode.empty() ||
HostPlatformViewTraitsInitializer::formsStackingContext(viewProps);
bool formsView = formsStackingContext ||