Skip to content

Commit 51ec52e

Browse files
Abbondanzometa-codesync[bot]
authored andcommitted
Fix image disappearing on Nougat with antialiased border radius clipping (#55527)
Summary: Pull Request resolved: #55527 On API 25 (Nougat), the `DST_OUT` + `EVEN_ODD` inverse path technique in `clipWithAntiAliasing()` doesn't render correctly — it erases all image content instead of just the area outside the rounded rect. The bug manifests specifically with ReactImageView (Fresco-backed) but not plain ImageView. ReactImageView's Fresco hierarchy configures `RoundingParams.RoundingMethod.BITMAP_ONLY`, which causes the drawable chain to draw through a `BitmapShader`-based paint. This shader-based drawing interacts badly with the `EVEN_ODD` fill + `DST_OUT` compositing inside a hardware-accelerated `saveLayer` on API 24's Skia renderer. The fix replaces the `EVEN_ODD` + `DST_OUT` technique with a nested `saveLayer` using `DST_IN` compositing, following the same pattern used by Facebook's Keyframes library (`AbstractLayer.applyClip`). The mask shape is drawn into a separate layer; when restored with `DST_IN`, content is preserved only where the mask is opaque. A `drawColor(CLEAR)` call after `saveLayer` ensures the layer starts fully transparent — without this, on API 24, the layer may retain parent content, causing `DST_IN` to see non-zero alpha everywhere and clip nothing. Changelog: [Android][Fixed] - Fix image content disappearing on API 24 (Nougat) when antialiased border radius clipping is applied Reviewed By: NickGerleman Differential Revision: D92980234 fbshipit-source-id: ea6d05d8f06346371eab1c406538e91bbd78d0ae
1 parent b92d378 commit 51ec52e

1 file changed

Lines changed: 14 additions & 9 deletions

File tree

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

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -590,16 +590,21 @@ public object BackgroundStyleApplicator {
590590
paddingBoxPath.setFillType(Path.FillType.INVERSE_WINDING)
591591
canvas.drawPath(paddingBoxPath, maskPaint)
592592
} else {
593-
// Create an inverse path: outer rect minus the rounded rect (using even-odd fill rule)
594-
val inversePath = Path()
595-
inversePath.addRect(0f, 0f, view.width.toFloat(), view.height.toFloat(), Path.Direction.CW)
596-
inversePath.addPath(paddingBoxPath)
597-
inversePath.setFillType(Path.FillType.EVEN_ODD)
598-
599-
// Use DST_OUT to remove content where the mask is drawn (outside the rounded rect)
600-
maskPaint.xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_OUT)
593+
// API < 28: Use a nested saveLayer with DST_IN compositing to mask content to the
594+
// padding box path. EVEN_ODD fill + DST_OUT has rendering bugs on API 24's hardware
595+
// renderer, so we avoid that technique. Instead, draw the mask shape into a separate
596+
// layer; when restored with DST_IN, content is preserved only where the mask is opaque.
597+
val dstInPaint = Paint()
598+
dstInPaint.xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_IN)
599+
val maskSave =
600+
canvas.saveLayer(0f, 0f, view.width.toFloat(), view.height.toFloat(), dstInPaint)
601+
// Clear the layer to ensure it starts fully transparent. On API 24, saveLayer may not
602+
// initialize the buffer to transparent, causing DST_IN to see non-zero alpha everywhere.
603+
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR)
604+
maskPaint.xfermode = null
601605
maskPaint.color = Color.BLACK
602-
canvas.drawPath(inversePath, maskPaint)
606+
canvas.drawPath(paddingBoxPath, maskPaint)
607+
canvas.restoreToCount(maskSave)
603608
}
604609

605610
// Restore the layer

0 commit comments

Comments
 (0)