8
8
package com.facebook.react.uimanager
9
9
10
10
import android.annotation.TargetApi
11
+ import android.graphics.BlendMode
12
+ import android.graphics.BlendModeColorFilter
13
+ import android.graphics.Color
11
14
import android.graphics.ColorMatrix
12
15
import android.graphics.ColorMatrixColorFilter
13
16
import android.graphics.RenderEffect
14
17
import android.graphics.Shader
15
18
import com.facebook.react.bridge.ReadableArray
19
+ import com.facebook.react.bridge.ReadableMap
16
20
import kotlin.math.cos
17
21
import kotlin.math.sin
18
22
@@ -26,19 +30,22 @@ internal object FilterHelper {
26
30
for (i in 0 until filters.size()) {
27
31
val filter = filters.getMap(i).getEntryIterator().next()
28
32
val filterName = filter.key
29
- val amount = (filter.value as Double ).toFloat()
30
33
31
34
chainedEffects =
32
35
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)
42
49
else -> throw IllegalArgumentException (" Invalid filter name: $filterName " )
43
50
}
44
51
}
@@ -62,7 +69,7 @@ internal object FilterHelper {
62
69
" grayscale" -> createGrayscaleColorMatrix(amount)
63
70
" sepia" -> createSepiaColorMatrix(amount)
64
71
" saturate" -> createSaturateColorMatrix(amount)
65
- " hueRotate " -> createHueRotateColorMatrix(amount)
72
+ " hue-rotate " -> createHueRotateColorMatrix(amount)
66
73
" invert" -> createInvertColorMatrix(amount)
67
74
" opacity" -> createOpacityColorMatrix(amount)
68
75
else -> throw IllegalArgumentException (" Invalid color matrix filter: $filterName " )
@@ -80,7 +87,7 @@ internal object FilterHelper {
80
87
for (i in 0 until filters.size()) {
81
88
val filter = filters.getMap(i).getEntryIterator().next()
82
89
val filterName = filter.key
83
- if (filterName == " blur" ) {
90
+ if (filterName == " blur" || filterName == " drop-shadow " ) {
84
91
return false
85
92
}
86
93
}
@@ -93,11 +100,7 @@ internal object FilterHelper {
93
100
return null
94
101
}
95
102
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)
101
104
return if (chainedEffects == null ) {
102
105
RenderEffect .createBlurEffect(radius, radius, Shader .TileMode .DECAL )
103
106
} else {
@@ -127,6 +130,64 @@ internal object FilterHelper {
127
130
return createColorMatrixEffect(createOpacityColorMatrix(amount), chainedEffects)
128
131
}
129
132
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
+
130
191
public fun createOpacityColorMatrix (amount : Float ): ColorMatrix {
131
192
val matrix = ColorMatrix ()
132
193
matrix.setScale(1f , 1f , 1f , amount)
@@ -326,4 +387,12 @@ internal object FilterHelper {
326
387
RenderEffect .createColorFilterEffect(ColorMatrixColorFilter (colorMatrix), chainedEffects)
327
388
}
328
389
}
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
+ }
329
398
}
0 commit comments