mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Use BlurMaskFilter in outset shadows instead of Blur RenderEffect (#45986)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/45986 This uses SkBlurMaskFilter under the hood, to draw geometry of a solid color with alpha blur, without going the route of full image filter/rasterization. It was not supported under hardware accelerated canvases for a while, but seems to fully work as of API 29. Requiring Android 10 instead of 12 makes box shadows a lot more palatable (80% support vs 50%), and we see drastically better performance in one case with many large shadows, where creating many large hardware layers previously drastically hurt framerates. {F1801807696} At this point, the RenderNode may be redundant, though I think it can technically save us some work on redraws still. It is kept around for now. I simplified some of the math around here as well. Changelog: [Internal] Reviewed By: joevilches Differential Revision: D61162637 fbshipit-source-id: 8f6ff486e655e64a0665c31391359c499c374c8f
This commit is contained in:
committed by
Facebook GitHub Bot
parent
9e48976bc2
commit
6c0710620f
+21
-18
@@ -8,6 +8,7 @@
|
||||
package com.facebook.react.uimanager.drawable
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.BlurMaskFilter
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.ColorFilter
|
||||
import android.graphics.Paint
|
||||
@@ -18,12 +19,10 @@ import android.graphics.RenderNode
|
||||
import android.graphics.drawable.Drawable
|
||||
import androidx.annotation.RequiresApi
|
||||
import com.facebook.common.logging.FLog
|
||||
import com.facebook.react.common.annotations.UnstableReactNativeAPI
|
||||
import com.facebook.react.uimanager.FilterHelper
|
||||
import com.facebook.react.uimanager.PixelUtil
|
||||
import com.facebook.react.uimanager.style.BorderRadiusStyle
|
||||
import com.facebook.react.uimanager.style.ComputedBorderRadius
|
||||
import kotlin.math.ceil
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
private const val TAG = "OutsetBoxShadowDrawable"
|
||||
@@ -34,8 +33,7 @@ private const val TAG = "OutsetBoxShadowDrawable"
|
||||
private const val BLUR_RADIUS_SIGMA_SCALE = 0.5f
|
||||
|
||||
/** Draws an outer box-shadow https://www.w3.org/TR/css-backgrounds-3/#shadow-shape */
|
||||
@RequiresApi(31)
|
||||
@OptIn(UnstableReactNativeAPI::class)
|
||||
@RequiresApi(29)
|
||||
internal class OutsetBoxShadowDrawable(
|
||||
private val context: Context,
|
||||
borderRadius: BorderRadiusStyle? = null,
|
||||
@@ -53,16 +51,23 @@ internal class OutsetBoxShadowDrawable(
|
||||
}
|
||||
}
|
||||
|
||||
private val renderNode =
|
||||
RenderNode(TAG).apply {
|
||||
clipToBounds = true
|
||||
setRenderEffect(FilterHelper.createBlurEffect(blurRadius * BLUR_RADIUS_SIGMA_SCALE))
|
||||
}
|
||||
private val renderNode = RenderNode(TAG).apply { clipToBounds = true }
|
||||
|
||||
private val shadowClipOutPath: Path = Path()
|
||||
private val shadowOuterPath: Path = Path()
|
||||
private val shadowOuterRect: RectF = RectF()
|
||||
private val shadowPaint = Paint().apply { color = shadowColor }
|
||||
private val shadowPaint =
|
||||
Paint().apply {
|
||||
color = shadowColor
|
||||
|
||||
// Mask filters are not documented to be supported under hardware accelerated canvases, but
|
||||
// they seem to work, mapped to SkMaskFilter, as of API 29.
|
||||
if (blurRadius > 0) {
|
||||
maskFilter =
|
||||
BlurMaskFilter(
|
||||
FilterHelper.sigmaToRadius(blurRadius * BLUR_RADIUS_SIGMA_SCALE),
|
||||
BlurMaskFilter.Blur.NORMAL)
|
||||
}
|
||||
}
|
||||
|
||||
private var lastBounds: Rect? = null
|
||||
private var lastBorderRadius: ComputedBorderRadius? = null
|
||||
@@ -82,7 +87,7 @@ internal class OutsetBoxShadowDrawable(
|
||||
}
|
||||
|
||||
val spreadExtent = PixelUtil.toPixelFromDIP(spread).roundToInt().coerceAtLeast(0)
|
||||
val shadowOutset = ceil(PixelUtil.toPixelFromDIP(blurRadius))
|
||||
val shadowOutset = FilterHelper.sigmaToRadius(blurRadius)
|
||||
val shadowShapeFrame = Rect(bounds).apply { inset(-spreadExtent, -spreadExtent) }
|
||||
val shadowShapeBounds = Rect(0, 0, shadowShapeFrame.width(), shadowShapeFrame.height())
|
||||
|
||||
@@ -112,8 +117,6 @@ internal class OutsetBoxShadowDrawable(
|
||||
lastBorderRadius != shadowBorderRadii) {
|
||||
lastBounds = shadowShapeBounds
|
||||
lastBorderRadius = shadowBorderRadii
|
||||
shadowOuterRect.set(
|
||||
RectF(bounds).apply { inset(-spreadExtent.toFloat(), -spreadExtent.toFloat()) })
|
||||
|
||||
// We remove the portion of the shadow which overlaps the background border box, to avoid
|
||||
// showing the shadow shape e.g. behind a transparent background. There may be a subpixel gap
|
||||
@@ -139,7 +142,7 @@ internal class OutsetBoxShadowDrawable(
|
||||
|
||||
shadowOuterPath.rewind()
|
||||
shadowOuterPath.addRoundRect(
|
||||
shadowOuterRect,
|
||||
RectF(shadowShapeBounds),
|
||||
floatArrayOf(
|
||||
shadowBorderRadii.topLeft,
|
||||
shadowBorderRadii.topLeft,
|
||||
@@ -156,8 +159,8 @@ internal class OutsetBoxShadowDrawable(
|
||||
setPosition(
|
||||
Rect(shadowShapeFrame).apply {
|
||||
offset(
|
||||
PixelUtil.toPixelFromDIP(offsetX).roundToInt() - shadowShapeFrame.left,
|
||||
PixelUtil.toPixelFromDIP(offsetY).roundToInt() - shadowShapeFrame.top)
|
||||
PixelUtil.toPixelFromDIP(offsetX).roundToInt(),
|
||||
PixelUtil.toPixelFromDIP(offsetY).roundToInt())
|
||||
inset(-shadowOutset.roundToInt(), -shadowOutset.roundToInt())
|
||||
})
|
||||
|
||||
@@ -166,7 +169,7 @@ internal class OutsetBoxShadowDrawable(
|
||||
if (shadowBorderRadii?.hasRoundedBorders() == true) {
|
||||
renderNodeCanvas.drawPath(shadowOuterPath, shadowPaint)
|
||||
} else {
|
||||
renderNodeCanvas.drawRect(shadowOuterRect, shadowPaint)
|
||||
renderNodeCanvas.drawRect(shadowShapeBounds, shadowPaint)
|
||||
}
|
||||
endRecording()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user