Skip to content

Commit d11a7b0

Browse files
jorge-cabfacebook-github-bot
authored andcommitted
Add DropShadow effect on Android (#44937)
Summary: Pull Request resolved: #44937 Continuing CSS filters support efforts for Android this diff implements support for dropShadow. Changelog: [Internal] Reviewed By: joevilches Differential Revision: D58488573 fbshipit-source-id: 393e43ba1e24705d8f1fc4ba3df8e12272d874b3
1 parent b56ce9d commit d11a7b0

File tree

1 file changed

+86
-17
lines changed
  • packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager

1 file changed

+86
-17
lines changed

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/FilterHelper.kt

Lines changed: 86 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,15 @@
88
package com.facebook.react.uimanager
99

1010
import android.annotation.TargetApi
11+
import android.graphics.BlendMode
12+
import android.graphics.BlendModeColorFilter
13+
import android.graphics.Color
1114
import android.graphics.ColorMatrix
1215
import android.graphics.ColorMatrixColorFilter
1316
import android.graphics.RenderEffect
1417
import android.graphics.Shader
1518
import com.facebook.react.bridge.ReadableArray
19+
import com.facebook.react.bridge.ReadableMap
1620
import kotlin.math.cos
1721
import kotlin.math.sin
1822

@@ -26,19 +30,22 @@ internal object FilterHelper {
2630
for (i in 0 until filters.size()) {
2731
val filter = filters.getMap(i).getEntryIterator().next()
2832
val filterName = filter.key
29-
val amount = (filter.value as Double).toFloat()
3033

3134
chainedEffects =
3235
when (filterName) {
33-
"brightness" -> createBrightnessEffect(amount, chainedEffects)
34-
"contrast" -> createContrastEffect(amount, chainedEffects)
35-
"grayscale" -> createGrayscaleEffect(amount, chainedEffects)
36-
"sepia" -> createSepiaEffect(amount, chainedEffects)
37-
"saturate" -> createSaturateEffect(amount, chainedEffects)
38-
"hueRotate" -> createHueRotateEffect(amount, chainedEffects)
39-
"invert" -> createInvertEffect(amount, chainedEffects)
40-
"blur" -> createBlurEffect(amount, chainedEffects)
41-
"opacity" -> createOpacityEffect(amount, chainedEffects)
36+
"brightness" ->
37+
createBrightnessEffect((filter.value as Double).toFloat(), chainedEffects)
38+
"contrast" -> createContrastEffect((filter.value as Double).toFloat(), chainedEffects)
39+
"grayscale" -> createGrayscaleEffect((filter.value as Double).toFloat(), chainedEffects)
40+
"sepia" -> createSepiaEffect((filter.value as Double).toFloat(), chainedEffects)
41+
"saturate" -> createSaturateEffect((filter.value as Double).toFloat(), chainedEffects)
42+
"hue-rotate" ->
43+
createHueRotateEffect((filter.value as Double).toFloat(), chainedEffects)
44+
"invert" -> createInvertEffect((filter.value as Double).toFloat(), chainedEffects)
45+
"blur" -> createBlurEffect((filter.value as Double).toFloat(), chainedEffects)
46+
"opacity" -> createOpacityEffect((filter.value as Double).toFloat(), chainedEffects)
47+
"drop-shadow" ->
48+
parseAndCreateDropShadowEffect(filter.value as ReadableMap, chainedEffects)
4249
else -> throw IllegalArgumentException("Invalid filter name: $filterName")
4350
}
4451
}
@@ -62,7 +69,7 @@ internal object FilterHelper {
6269
"grayscale" -> createGrayscaleColorMatrix(amount)
6370
"sepia" -> createSepiaColorMatrix(amount)
6471
"saturate" -> createSaturateColorMatrix(amount)
65-
"hueRotate" -> createHueRotateColorMatrix(amount)
72+
"hue-rotate" -> createHueRotateColorMatrix(amount)
6673
"invert" -> createInvertColorMatrix(amount)
6774
"opacity" -> createOpacityColorMatrix(amount)
6875
else -> throw IllegalArgumentException("Invalid color matrix filter: $filterName")
@@ -80,7 +87,7 @@ internal object FilterHelper {
8087
for (i in 0 until filters.size()) {
8188
val filter = filters.getMap(i).getEntryIterator().next()
8289
val filterName = filter.key
83-
if (filterName == "blur") {
90+
if (filterName == "blur" || filterName == "drop-shadow") {
8491
return false
8592
}
8693
}
@@ -93,11 +100,7 @@ internal object FilterHelper {
93100
return null
94101
}
95102

96-
// Android takes blur amount as a radius while web takes a sigma. This value
97-
// is used under the hood to convert between them on Android
98-
// https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/libs/hwui/jni/RenderEffect.cpp
99-
val sigmaToRadiusRatio = 0.57_735f
100-
val radius = (PixelUtil.toPixelFromDIP(sigma) - 0.5f) / sigmaToRadiusRatio
103+
val radius: Float = sigmaToRadius(sigma)
101104
return if (chainedEffects == null) {
102105
RenderEffect.createBlurEffect(radius, radius, Shader.TileMode.DECAL)
103106
} else {
@@ -127,6 +130,64 @@ internal object FilterHelper {
127130
return createColorMatrixEffect(createOpacityColorMatrix(amount), chainedEffects)
128131
}
129132

133+
// https://www.w3.org/TR/filter-effects-1/#dropshadowEquivalent
134+
public fun createDropShadowEffect(
135+
offsetX: Float,
136+
offsetY: Float,
137+
blurRadius: Float,
138+
color: Int,
139+
chainedEffects: RenderEffect? = null
140+
): RenderEffect {
141+
val identity: RenderEffect
142+
val offsetEffect: RenderEffect
143+
144+
/*
145+
* identity is set to an offset RenderEffect of 0 to keep track of the identity/original content.
146+
* we then create offset effect for the first step of the shadow
147+
*/
148+
if (chainedEffects == null) {
149+
identity = RenderEffect.createOffsetEffect(0f, 0f)
150+
offsetEffect = RenderEffect.createOffsetEffect(offsetX, offsetY)
151+
} else {
152+
identity = RenderEffect.createOffsetEffect(0f, 0f, chainedEffects)
153+
offsetEffect = RenderEffect.createOffsetEffect(offsetX, offsetY, chainedEffects)
154+
}
155+
156+
/*
157+
* create color effect, blend with offset effect with SRC_IN and apply blur on top
158+
* SRC_IN finds the alpha intersection of colorEffect and offset
159+
* https://developer.android.com/reference/android/graphics/BlendMode#SRC_IN
160+
*/
161+
val colorEffect: RenderEffect =
162+
RenderEffect.createColorFilterEffect(
163+
BlendModeColorFilter(color, BlendMode.SRC_IN), offsetEffect)
164+
val blurEffect: RenderEffect =
165+
RenderEffect.createBlurEffect(blurRadius, blurRadius, colorEffect, Shader.TileMode.DECAL)
166+
167+
/*
168+
* at this point blurEffect contains all of drop-shadow's combined effects (offset, color & blur)
169+
* we then blend it with identity/original content with SRC_OVER
170+
* SRC_OVER layers blurEffect's alpha behind identity for the desired result
171+
* https://developer.android.com/reference/android/graphics/BlendMode#SRC_OVER
172+
*/
173+
return RenderEffect.createBlendModeEffect(blurEffect, identity, BlendMode.SRC_OVER)
174+
}
175+
176+
public fun parseAndCreateDropShadowEffect(
177+
filterValues: ReadableMap,
178+
chainedEffects: RenderEffect? = null
179+
): RenderEffect {
180+
val offsetX: Float = PixelUtil.toPixelFromDIP(filterValues.getDouble("offsetX").toFloat())
181+
val offsetY: Float = PixelUtil.toPixelFromDIP(filterValues.getDouble("offsetY").toFloat())
182+
val color: Int = if (filterValues.hasKey("color")) filterValues.getInt("color") else Color.BLACK
183+
val radius: Float =
184+
if (filterValues.hasKey("standardDeviation"))
185+
sigmaToRadius(filterValues.getDouble("standardDeviation").toFloat())
186+
else 0f
187+
188+
return createDropShadowEffect(offsetX, offsetY, radius, color, chainedEffects)
189+
}
190+
130191
public fun createOpacityColorMatrix(amount: Float): ColorMatrix {
131192
val matrix = ColorMatrix()
132193
matrix.setScale(1f, 1f, 1f, amount)
@@ -326,4 +387,12 @@ internal object FilterHelper {
326387
RenderEffect.createColorFilterEffect(ColorMatrixColorFilter(colorMatrix), chainedEffects)
327388
}
328389
}
390+
391+
private fun sigmaToRadius(sigma: Float): Float {
392+
// Android takes blur amount as a radius while web takes a sigma. This value
393+
// is used under the hood to convert between them on Android
394+
// https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/libs/hwui/jni/RenderEffect.cpp
395+
val sigmaToRadiusRatio = 0.57_735f
396+
return (PixelUtil.toPixelFromDIP(sigma) - 0.5f) / sigmaToRadiusRatio
397+
}
329398
}

0 commit comments

Comments
 (0)