You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Android Jetpack Compose:如何仅捕获Box组件内或相机遮罩中显示的图像

解决Jetpack Compose中Box区域图像捕获的两个场景

嘿,你已经搞定了最关键的两步——拿到目标Bitmap和Box的屏幕坐标,剩下的就是把这两者结合起来做精准裁剪啦!我分两个场景给你详细说:

场景1:捕获Box组件内显示的静态图像

你已经通过onGloballyPositioned拿到了Box(也就是你的Image组件)的屏幕坐标rect,接下来要把这个屏幕区域映射到原始Bitmap的像素位置,因为Image组件可能会对图像做缩放、居中处理(比如默认的ContentScale.Fit)。

具体步骤&代码:

  1. 先准备好你的原始Bitmap和Box的坐标:
val originalBitmap: Bitmap = ... // 你的原始图像Bitmap
val boxRect: Rect = ... // 从boundsInRoot获取的Box屏幕坐标
val imageCoordinates: LayoutCoordinates = ... // onGloballyPositioned回调里的coordinates
  1. 计算图像在Image组件内的缩放比例和偏移量(适配ContentScale.Fit的情况):
// 原始Bitmap的尺寸
val bitmapWidth = originalBitmap.width.toFloat()
val bitmapHeight = originalBitmap.height.toFloat()

// Image组件的显示尺寸
val imageViewWidth = imageCoordinates.size.width.toFloat()
val imageViewHeight = imageCoordinates.size.height.toFloat()

// 计算缩放比例(保持图像比例,取最小的缩放值)
val scaleX = imageViewWidth / bitmapWidth
val scaleY = imageViewHeight / bitmapHeight
val actualScale = min(scaleX, scaleY)

// 计算图像在Image组件内的居中偏移量
val offsetX = (imageViewWidth - bitmapWidth * actualScale) / 2f
val offsetY = (imageViewHeight - bitmapHeight * actualScale) / 2f
  1. 将Box的屏幕区域转换为Bitmap上的像素区域,然后裁剪:
// 因为Box就是Image组件本身,所以Box的区域对应Image内的全区域,转换为Bitmap的坐标
val bitmapLeft = (offsetX / actualScale).toInt()
val bitmapTop = (offsetY / actualScale).toInt()
val cropWidth = (imageViewWidth / actualScale).toInt()
val cropHeight = (imageViewHeight / actualScale).toInt()

// 做边界检查,避免超出Bitmap范围报错
val safeLeft = max(0, bitmapLeft)
val safeTop = max(0, bitmapTop)
val safeWidth = min(originalBitmap.width - safeLeft, cropWidth)
val safeHeight = min(originalBitmap.height - safeTop, cropHeight)

// 最终裁剪出的Bitmap
val croppedBitmap = Bitmap.createBitmap(originalBitmap, safeLeft, safeTop, safeWidth, safeHeight)

场景2:相机遮罩中捕获Box内的图像

这个场景要额外处理相机预览的方向和缩放问题,因为相机捕获的Bitmap方向可能和屏幕显示不一致,而且预览画面会适配屏幕尺寸做缩放。

具体步骤&代码:

  1. 准备好相机捕获的Bitmap、遮罩Box的屏幕坐标,以及相机预览组件的尺寸和方向:
val cameraCapturedBitmap: Bitmap = ... // 相机捕获的原始Bitmap
val maskBoxRect: Rect = ... // 遮罩Box的屏幕坐标
val previewViewSize: Size = ... // 屏幕上相机预览组件的尺寸(比如PreviewView的宽高)
val cameraOrientation: Int = ... // 相机的旋转角度(比如竖屏时是90度)
val previewViewTopLeft: Offset = ... // 预览组件在屏幕上的左上角坐标
  1. 先把相机Bitmap旋转到和屏幕一致的方向:
val rotatedBitmap = if (cameraOrientation != 0) {
    val matrix = Matrix()
    matrix.postRotate(cameraOrientation.toFloat())
    Bitmap.createBitmap(
        cameraCapturedBitmap,
        0,
        0,
        cameraCapturedBitmap.width,
        cameraCapturedBitmap.height,
        matrix,
        true
    )
} else {
    cameraCapturedBitmap
}
  1. 计算预览画面到Bitmap的缩放比例:
val rotatedBitmapWidth = rotatedBitmap.width.toFloat()
val rotatedBitmapHeight = rotatedBitmap.height.toFloat()

// 缩放比例:Bitmap尺寸 / 预览组件显示尺寸
val scaleX = rotatedBitmapWidth / previewViewSize.width
val scaleY = rotatedBitmapHeight / previewViewSize.height
  1. 将遮罩Box的屏幕坐标转换为相对于预览组件的坐标,再映射到Bitmap:
// 遮罩Box相对于预览组件的坐标
val maskRelativeLeft = maskBoxRect.left - previewViewTopLeft.x
val maskRelativeTop = maskBoxRect.top - previewViewTopLeft.y
val maskRelativeWidth = maskBoxRect.width
val maskRelativeHeight = maskBoxRect.height

// 转换为Bitmap上的像素坐标
val bitmapLeft = (maskRelativeLeft * scaleX).toInt()
val bitmapTop = (maskRelativeTop * scaleY).toInt()
val cropWidth = (maskRelativeWidth * scaleX).toInt()
val cropHeight = (maskRelativeHeight * scaleY).toInt()

// 边界检查
val safeLeft = max(0, bitmapLeft)
val safeTop = max(0, bitmapTop)
val safeWidth = min(rotatedBitmap.width - safeLeft, cropWidth)
val safeHeight = min(rotatedBitmap.height - safeTop, cropHeight)

// 最终裁剪的Bitmap
val croppedCameraBitmap = Bitmap.createBitmap(rotatedBitmap, safeLeft, safeTop, safeWidth, safeHeight)

额外注意事项:

  • 记得在使用完Bitmap后调用recycle(),避免内存泄漏。
  • 如果你的Image组件用了其他ContentScale(比如Fill),需要调整缩放比例的计算逻辑。
  • 相机场景中,如果你用的是CameraX,也可以通过ImageAnalysis直接在分析阶段裁剪,效率更高。

内容的提问来源于stack exchange,提问作者Kunal Kalwar

火山引擎 最新活动