Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -103,28 +103,54 @@ internal class ViewTransitionSnapshotManager(

@RequiresApi(Build.VERSION_CODES.O)
private fun captureHardwareBitmap(view: View, reactTag: Int, window: Window) {
val bitmap = createBitmap(view.width, view.height)
val location = IntArray(2)
view.getLocationInWindow(location)
val rect = Rect(location[0], location[1], location[0] + view.width, location[1] + view.height)

// The view's rect in window coordinates.
val viewRect =
Rect(location[0], location[1], location[0] + view.width, location[1] + view.height)

// Clamp to window bounds — PixelCopy only captures what's visible on the
// window surface. Without clamping, off-screen portions are black/empty
// and the partial result gets stretched to fill the pseudo-element.
val windowWidth = window.decorView.width
val windowHeight = window.decorView.height
val clampedRect =
Rect(
viewRect.left.coerceAtLeast(0),
viewRect.top.coerceAtLeast(0),
viewRect.right.coerceAtMost(windowWidth),
viewRect.bottom.coerceAtMost(windowHeight),
)

if (clampedRect.isEmpty) {
// Entirely off-screen — nothing to capture.
return
}

val clampedBitmap = createBitmap(clampedRect.width(), clampedRect.height())
// Offset of the clamped region within the full view.
val offsetX = clampedRect.left - viewRect.left
val offsetY = clampedRect.top - viewRect.top

// PixelCopy callback is posted to mainHandler, so onBitmapCaptured may run after
// setViewSnapshot has already recorded the target tag for this source tag.
try {
PixelCopy.request(
window,
rect,
bitmap,
clampedRect,
clampedBitmap,
{ copyResult ->
if (copyResult == PixelCopy.SUCCESS) {
val hwBitmap = bitmap.copy(Bitmap.Config.HARDWARE, false)
if (hwBitmap != null) {
bitmap.recycle()
onBitmapCaptured(reactTag, hwBitmap)
} else {
onBitmapCaptured(reactTag, bitmap)
}
// 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)
Canvas(fullBitmap)
.drawBitmap(clampedBitmap, offsetX.toFloat(), offsetY.toFloat(), null)
clampedBitmap.recycle()
onBitmapCaptured(reactTag, fullBitmap)
} else {
bitmap.recycle()
clampedBitmap.recycle()
onBitmapCaptured(reactTag, captureSoftwareBitmap(view))
}
},
Expand All @@ -133,7 +159,7 @@ internal class ViewTransitionSnapshotManager(
} catch (e: IllegalArgumentException) {
// Window surface may have been destroyed (e.g., device idle/sleep).
// Fall back to software rendering.
bitmap.recycle()
clampedBitmap.recycle()
onBitmapCaptured(reactTag, captureSoftwareBitmap(view))
}
}
Expand Down
Loading