Convert FilterHelper.java to Kotlin (#43535)

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

tsia

Changelog: [Internal]

Reviewed By: tdn120

Differential Revision: D54914028

fbshipit-source-id: 78d3dd20a8e98b4d4a2335b5542e4879a4cf3e41
This commit is contained in:
Joe Vilches
2024-03-20 10:43:09 -07:00
committed by Facebook GitHub Bot
parent 9c48121b4e
commit 21b008ed40
2 changed files with 254 additions and 246 deletions
@@ -1,246 +0,0 @@
/*
* 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.ColorFilter;
import android.graphics.ColorMatrix;
import android.graphics.ColorMatrixColorFilter;
import android.graphics.RenderEffect;
import android.graphics.Shader;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
@TargetApi(31)
class FilterHelper {
static @Nullable RenderEffect parseFilters(@Nullable ReadableArray filters) {
if (filters == null) {
return null;
}
RenderEffect chainedEffects = null;
for (int i = 0; i < filters.size(); i++) {
ReadableMap filter = filters.getMap(i);
@Nullable String filterName = filter.getString("name");
if (filterName == null) {
continue;
}
switch (filterName) {
case "brightness":
float brightnessAmount = (float) filter.getDouble("amount");
ColorFilter brightnessFilter =
new ColorMatrixColorFilter(getBrightnessColorMatrix(brightnessAmount));
chainedEffects = chainColorFilterEffect(chainedEffects, brightnessFilter);
break;
case "contrast":
float contrastAmount = (float) filter.getDouble("amount");
ColorFilter contrastFilter =
new ColorMatrixColorFilter(getContrastColorMatrix(contrastAmount));
chainedEffects = chainColorFilterEffect(chainedEffects, contrastFilter);
break;
case "grayscale":
float grayscaleAmount = (float) filter.getDouble("amount");
ColorFilter grayscaleFilter =
new ColorMatrixColorFilter(getGrayscaleColorMatrix(grayscaleAmount));
chainedEffects = chainColorFilterEffect(chainedEffects, grayscaleFilter);
break;
case "sepia":
float sepiaAmount = (float) filter.getDouble("amount");
ColorFilter sepiaFilter = new ColorMatrixColorFilter(getSepiaColorMatrix(sepiaAmount));
chainedEffects = chainColorFilterEffect(chainedEffects, sepiaFilter);
break;
case "saturate":
float saturateAmount = (float) filter.getDouble("amount");
ColorFilter saturateFilter =
new ColorMatrixColorFilter(getSaturateColorMatrix(saturateAmount));
chainedEffects = chainColorFilterEffect(chainedEffects, saturateFilter);
break;
case "hue-rotate":
float hueRotateAmount = (float) filter.getDouble("amount");
ColorFilter hueRotateFilter =
new ColorMatrixColorFilter(getHueRotateColorMatrix(hueRotateAmount));
chainedEffects = chainColorFilterEffect(chainedEffects, hueRotateFilter);
break;
case "invert":
float invertAmount = (float) filter.getDouble("amount");
ColorFilter invertColorFilter =
new ColorMatrixColorFilter(getInvertColorMatrix(invertAmount));
chainedEffects = chainColorFilterEffect(chainedEffects, invertColorFilter);
break;
case "blur":
float blurAmount = (float) filter.getDouble("amount");
chainedEffects = chainBlurFilterEffect(chainedEffects, blurAmount);
break;
default:
throw new IllegalArgumentException("Invalid filter name: " + filterName);
}
}
return chainedEffects;
}
// https://www.w3.org/TR/filter-effects-1/#blurEquivalent
private static RenderEffect chainBlurFilterEffect(
@Nullable RenderEffect chainedEffects, float std) {
// Android takes blur amount as a radius while web takes a sigma. This value
// is used under the hood to convert between them on Android
// https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/libs/hwui/utils/Blur.cpp
float sigmaToRadiusRatio = 0.57735f;
float radius = (std - 0.5f) / sigmaToRadiusRatio;
float scaledRadius = PixelUtil.toPixelFromDIP(radius);
return chainedEffects == null
? RenderEffect.createBlurEffect(scaledRadius, scaledRadius, Shader.TileMode.DECAL)
: RenderEffect.createBlurEffect(
scaledRadius, scaledRadius, chainedEffects, Shader.TileMode.DECAL);
}
private static RenderEffect chainColorFilterEffect(
@Nullable RenderEffect chainedEffects, ColorFilter colorFilter) {
return chainedEffects == null
? RenderEffect.createColorFilterEffect(colorFilter)
: RenderEffect.createColorFilterEffect(colorFilter, chainedEffects);
}
// https://www.w3.org/TR/filter-effects-1/#brightnessEquivalent
private static ColorMatrix getBrightnessColorMatrix(float amount) {
ColorMatrix matrix = new ColorMatrix();
matrix.setScale(amount, amount, amount, 1);
return matrix;
}
// https://www.w3.org/TR/filter-effects-1/#contrastEquivalent
private static ColorMatrix getContrastColorMatrix(float amount) {
// Multiply by 255 as Android operates in [0, 255] while the spec operates in [0, 1].
// This really only matters if there is an intercept that needs to be added
float intercept = 255 * (-(amount / 2.0f) + 0.5f);
float[] colorMatrix = {
amount, 0, 0, 0, intercept, //
0, amount, 0, 0, intercept, //
0, 0, amount, 0, intercept, //
0, 0, 0, 1, 0
};
return new ColorMatrix(colorMatrix);
}
// https://www.w3.org/TR/filter-effects-1/#grayscaleEquivalent
private static float[] getGrayscaleColorMatrix(float amount) {
float inverseAmount = 1 - amount;
float[] colorMatrix = {
0.2126f + 0.7874f * inverseAmount,
0.7152f - 0.7152f * inverseAmount,
0.0722f - 0.0722f * inverseAmount,
0,
0,
0.2126f - 0.2126f * inverseAmount,
0.7152f + 0.2848f * inverseAmount,
0.0722f - 0.0722f * inverseAmount,
0,
0,
0.2126f - 0.2126f * inverseAmount,
0.7152f - 0.7152f * inverseAmount,
0.0722f + 0.9278f * inverseAmount,
0,
0,
0,
0,
0,
1,
0
};
return colorMatrix;
}
// https://www.w3.org/TR/filter-effects-1/#sepiaEquivalent
private static float[] getSepiaColorMatrix(float amount) {
float inverseAmount = 1 - amount;
float[] colorMatrix = {
0.393f + 0.607f * inverseAmount,
0.769f - 0.769f * inverseAmount,
0.189f - 0.189f * inverseAmount,
0,
0,
0.349f - 0.349f * inverseAmount,
0.686f + 0.314f * inverseAmount,
0.168f - 0.168f * inverseAmount,
0,
0,
0.272f - 0.272f * inverseAmount,
0.534f - 0.534f * inverseAmount,
0.131f + 0.869f * inverseAmount,
0,
0,
0,
0,
0,
1,
0
};
return colorMatrix;
}
// https://www.w3.org/TR/filter-effects-1/#saturateEquivalent
private static ColorMatrix getSaturateColorMatrix(float amount) {
ColorMatrix matrix = new ColorMatrix();
matrix.setSaturation(amount);
return matrix;
}
// https://www.w3.org/TR/filter-effects-1/#huerotateEquivalent
private static float[] getHueRotateColorMatrix(float amount) {
double amountRads = Math.toRadians(amount);
float cos = (float) Math.cos(amountRads);
float sin = (float) Math.sin(amountRads);
float[] matrix = {
0.213f + 0.787f * cos - 0.213f * sin,
0.715f - 0.715f * cos - 0.715f * sin,
0.072f - 0.072f * cos + 0.928f * sin,
0,
0,
0.213f - 0.213f * cos + 0.143f * sin,
0.715f + 0.285f * cos + 0.140f * sin,
0.072f - 0.072f * cos - 0.283f * sin,
0,
0,
0.213f - 0.213f * cos - 0.787f * sin,
0.715f - 0.715f * cos + 0.715f * sin,
0.072f + 0.928f * cos + 0.072f * sin,
0,
0,
0,
0,
0,
1,
0
};
return matrix;
}
// https://www.w3.org/TR/filter-effects-1/#invertEquivalent
private static float[] getInvertColorMatrix(float amount) {
float slope = 1 - 2 * amount;
float intercept = amount * 255;
float[] matrix = {
slope, 0, 0, 0, intercept, //
0, slope, 0, 0, intercept, //
0, 0, slope, 0, intercept, //
0, 0, 0, 1, 0
};
return matrix;
}
}
@@ -0,0 +1,254 @@
/*
* 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.ColorFilter
import android.graphics.ColorMatrix
import android.graphics.ColorMatrixColorFilter
import android.graphics.RenderEffect
import android.graphics.Shader
import android.graphics.Shader.TileMode
import com.facebook.react.bridge.ReadableArray
@TargetApi(31)
internal object FilterHelper {
@JvmStatic
fun parseFilters(filters: ReadableArray?): RenderEffect? {
filters ?: return null
var chainedEffects: RenderEffect? = null
for (i in 0 until filters.size()) {
val filter = filters.getMap(i)
val filterName = filter.getString("name") ?: continue
chainedEffects =
when (filterName) {
"brightness" -> {
val brightnessAmount = filter.getDouble("amount").toFloat()
val brightnessFilter =
ColorMatrixColorFilter(getBrightnessColorMatrix(brightnessAmount))
chainColorFilterEffect(chainedEffects, brightnessFilter)
}
"contrast" -> {
val contrastAmount = filter.getDouble("amount").toFloat()
val contrastFilter = ColorMatrixColorFilter(getContrastColorMatrix(contrastAmount))
chainColorFilterEffect(chainedEffects, contrastFilter)
}
"grayscale" -> {
val grayscaleAmount = filter.getDouble("amount").toFloat()
val grayscaleFilter = ColorMatrixColorFilter(getGrayscaleColorMatrix(grayscaleAmount))
chainColorFilterEffect(chainedEffects, grayscaleFilter)
}
"sepia" -> {
val sepiaAmount = filter.getDouble("amount").toFloat()
val sepiaFilter = ColorMatrixColorFilter(getSepiaColorMatrix(sepiaAmount))
chainColorFilterEffect(chainedEffects, sepiaFilter)
}
"saturate" -> {
val saturateAmount = filter.getDouble("amount").toFloat()
val saturateFilter = ColorMatrixColorFilter(getSaturateColorMatrix(saturateAmount))
chainColorFilterEffect(chainedEffects, saturateFilter)
}
"hue-rotate" -> {
val hueRotateAmount = filter.getDouble("amount").toFloat()
val hueRotateFilter = ColorMatrixColorFilter(getHueRotateColorMatrix(hueRotateAmount))
chainColorFilterEffect(chainedEffects, hueRotateFilter)
}
"invert" -> {
val invertAmount = filter.getDouble("amount").toFloat()
val invertColorFilter = ColorMatrixColorFilter(getInvertColorMatrix(invertAmount))
chainColorFilterEffect(chainedEffects, invertColorFilter)
}
"blur" -> {
val blurAmount = filter.getDouble("amount").toFloat()
chainBlurFilterEffect(chainedEffects, blurAmount)
}
else -> throw IllegalArgumentException("Invalid filter name: $filterName")
}
}
return chainedEffects
}
// https://www.w3.org/TR/filter-effects-1/#blurEquivalent
private fun chainBlurFilterEffect(chainedEffects: RenderEffect?, std: Float): RenderEffect {
// Android takes blur amount as a radius while web takes a sigma. This value
// is used under the hood to convert between them on Android
// https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/libs/hwui/utils/Blur.cpp
val sigmaToRadiusRatio = 0.57_735f
val radius = (std - 0.5f) / sigmaToRadiusRatio
val scaledRadius = PixelUtil.toPixelFromDIP(radius)
return if (chainedEffects == null) {
RenderEffect.createBlurEffect(scaledRadius, scaledRadius, Shader.TileMode.DECAL)
} else {
RenderEffect.createBlurEffect(
scaledRadius, scaledRadius, chainedEffects, Shader.TileMode.DECAL)
}
}
private fun chainColorFilterEffect(
chainedEffects: RenderEffect?,
colorFilter: ColorFilter
): RenderEffect {
return if (chainedEffects == null) {
RenderEffect.createColorFilterEffect(colorFilter)
} else {
RenderEffect.createColorFilterEffect(colorFilter, chainedEffects)
}
}
// https://www.w3.org/TR/filter-effects-1/#brightnessEquivalent
private fun getBrightnessColorMatrix(amount: Float): ColorMatrix {
val matrix = ColorMatrix()
matrix.setScale(amount, amount, amount, 1f)
return matrix
}
// https://www.w3.org/TR/filter-effects-1/#contrastEquivalent
private fun getContrastColorMatrix(amount: Float): ColorMatrix {
// Multiply by 255 as Android operates in [0, 255] while the spec operates in [0, 1].
// This really only matters if there is an intercept that needs to be added
val intercept = 255 * (-(amount / 2.0f) + 0.5f)
val colorMatrix =
floatArrayOf(
amount,
0f,
0f,
0f,
intercept,
0f,
amount,
0f,
0f,
intercept,
0f,
0f,
amount,
0f,
intercept,
0f,
0f,
0f,
1f,
0f)
return ColorMatrix(colorMatrix)
}
// https://www.w3.org/TR/filter-effects-1/#grayscaleEquivalent
private fun getGrayscaleColorMatrix(amount: Float): FloatArray {
val inverseAmount = 1 - amount
return floatArrayOf(
0.2_126f + 0.7_874f * inverseAmount,
0.7_152f - 0.7_152f * inverseAmount,
0.0_722f - 0.0_722f * inverseAmount,
0f,
0f,
0.2_126f - 0.2_126f * inverseAmount,
0.7_152f + 0.2_848f * inverseAmount,
0.0_722f - 0.0_722f * inverseAmount,
0f,
0f,
0.2_126f - 0.2_126f * inverseAmount,
0.7_152f - 0.7_152f * inverseAmount,
0.0_722f + 0.9_278f * inverseAmount,
0f,
0f,
0f,
0f,
0f,
1f,
0f)
}
// https://www.w3.org/TR/filter-effects-1/#sepiaEquivalent
private fun getSepiaColorMatrix(amount: Float): FloatArray {
val inverseAmount = 1 - amount
return floatArrayOf(
0.393f + 0.607f * inverseAmount,
0.769f - 0.769f * inverseAmount,
0.189f - 0.189f * inverseAmount,
0f,
0f,
0.349f - 0.349f * inverseAmount,
0.686f + 0.314f * inverseAmount,
0.168f - 0.168f * inverseAmount,
0f,
0f,
0.272f - 0.272f * inverseAmount,
0.534f - 0.534f * inverseAmount,
0.131f + 0.869f * inverseAmount,
0f,
0f,
0f,
0f,
0f,
1f,
0f)
}
// https://www.w3.org/TR/filter-effects-1/#saturateEquivalent
private fun getSaturateColorMatrix(amount: Float): ColorMatrix {
val matrix = ColorMatrix()
matrix.setSaturation(amount)
return matrix
}
// https://www.w3.org/TR/filter-effects-1/#huerotateEquivalent
private fun getHueRotateColorMatrix(amount: Float): FloatArray {
val amountRads = Math.toRadians(amount.toDouble())
val cos = Math.cos(amountRads).toFloat()
val sin = Math.sin(amountRads).toFloat()
return floatArrayOf(
0.213f + 0.787f * cos - 0.213f * sin,
0.715f - 0.715f * cos - 0.715f * sin,
0.072f - 0.072f * cos + 0.928f * sin,
0f,
0f,
0.213f - 0.213f * cos + 0.143f * sin,
0.715f + 0.285f * cos + 0.140f * sin,
0.072f - 0.072f * cos - 0.283f * sin,
0f,
0f,
0.213f - 0.213f * cos - 0.787f * sin,
0.715f - 0.715f * cos + 0.715f * sin,
0.072f + 0.928f * cos + 0.072f * sin,
0f,
0f,
0f,
0f,
0f,
1f,
0f)
}
// https://www.w3.org/TR/filter-effects-1/#invertEquivalent
private fun getInvertColorMatrix(amount: Float): FloatArray {
val slope = 1 - 2 * amount
val intercept = amount * 255
return floatArrayOf(
slope,
0f,
0f,
0f,
intercept,
0f,
slope,
0f,
0f,
intercept,
0f,
0f,
slope,
0f,
intercept,
0f,
0f,
0f,
1f,
0f)
}
}