From 21b008ed404cc5f9b6595e5d61b64fde7d3ef97d Mon Sep 17 00:00:00 2001 From: Joe Vilches Date: Wed, 20 Mar 2024 10:43:09 -0700 Subject: [PATCH] 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 --- .../react/uimanager/FilterHelper.java | 246 ----------------- .../facebook/react/uimanager/FilterHelper.kt | 254 ++++++++++++++++++ 2 files changed, 254 insertions(+), 246 deletions(-) delete mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/FilterHelper.java create mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/FilterHelper.kt diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/FilterHelper.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/FilterHelper.java deleted file mode 100644 index 56bfa21ee67..00000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/FilterHelper.java +++ /dev/null @@ -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; - } -} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/FilterHelper.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/FilterHelper.kt new file mode 100644 index 00000000000..a451c9f5f3b --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/FilterHelper.kt @@ -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) + } +}