From ad8bb8b33001c76c976dfa03dcbff177f7862ce9 Mon Sep 17 00:00:00 2001 From: Zeya Peng Date: Mon, 27 Apr 2026 14:45:03 -0700 Subject: [PATCH] make sure view width and height are non 0 before calling createBitmap() (#56627) Summary: ## Changelog: [Android] [Fixed] - make sure view width and height are non 0 before calling createBitmap() Reviewed By: Abbondanzo Differential Revision: D102657602 --- .../fabric/ViewTransitionSnapshotManager.kt | 33 ++++++++++++------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/ViewTransitionSnapshotManager.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/ViewTransitionSnapshotManager.kt index 6c21fbc4d45..29c987a4960 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/ViewTransitionSnapshotManager.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/ViewTransitionSnapshotManager.kt @@ -38,10 +38,13 @@ internal class ViewTransitionSnapshotManager( ) : UIManagerListener { companion object { - private fun captureSoftwareBitmap(view: View): Bitmap { - val bitmap = createBitmap(view.width, view.height) - view.draw(Canvas(bitmap)) - return bitmap + private fun captureSoftwareBitmap(view: View): Bitmap? { + if (view.width > 0 && view.height > 0) { + val bitmap = createBitmap(view.width, view.height) + view.draw(Canvas(bitmap)) + return bitmap + } + return null } } @@ -96,7 +99,7 @@ internal class ViewTransitionSnapshotManager( } else { // Software fallback runs synchronously, so onBitmapCaptured always // completes before setViewSnapshot is called. - onBitmapCaptured(reactTag, captureSoftwareBitmap(view)) + captureSoftwareBitmap(view)?.let { onBitmapCaptured(reactTag, it) } } } } @@ -106,9 +109,16 @@ internal class ViewTransitionSnapshotManager( val location = IntArray(2) view.getLocationInWindow(location) + val viewWidth = view.width + val viewHeight = view.height + + if (viewWidth <= 0 || viewHeight <= 0) { + // Nothing to capture. + return + } + // The view's rect in window coordinates. - val viewRect = - Rect(location[0], location[1], location[0] + view.width, location[1] + view.height) + val viewRect = Rect(location[0], location[1], location[0] + viewWidth, location[1] + viewHeight) // Clamp to window bounds — PixelCopy only captures what's visible on the // window surface. Without clamping, off-screen portions are black/empty @@ -144,23 +154,24 @@ internal class ViewTransitionSnapshotManager( if (copyResult == PixelCopy.SUCCESS) { // Compose the clamped capture into a full-size bitmap at the // correct offset so it aligns with the pseudo-element's bounds. - val fullBitmap = createBitmap(view.width, view.height) + val fullBitmap = createBitmap(viewWidth, viewHeight) Canvas(fullBitmap) .drawBitmap(clampedBitmap, offsetX.toFloat(), offsetY.toFloat(), null) clampedBitmap.recycle() onBitmapCaptured(reactTag, fullBitmap) } else { + // Fall back to software rendering if the view is still valid. clampedBitmap.recycle() - onBitmapCaptured(reactTag, captureSoftwareBitmap(view)) + captureSoftwareBitmap(view)?.let { onBitmapCaptured(reactTag, it) } } }, mainHandler, ) } catch (e: IllegalArgumentException) { // Window surface may have been destroyed (e.g., device idle/sleep). - // Fall back to software rendering. + // Fall back to software rendering if the view is still valid. clampedBitmap.recycle() - onBitmapCaptured(reactTag, captureSoftwareBitmap(view)) + captureSoftwareBitmap(view)?.let { onBitmapCaptured(reactTag, it) } } }