Jetpack Compose CameraX拍摄后按指定矩形裁剪A4比例图片问题
问题分析与解决方案
你的核心问题是预览画布的裁剪坐标和拍摄原图的尺寸/比例不匹配,直接用预览的绝对像素去裁剪原图,导致得到的是小块区域。需要将预览上的相对裁剪比例映射到原图,同时处理图像旋转问题,以下是具体实现步骤:
1. 保存裁剪区域的相对比例(而非绝对像素)
预览画布的尺寸会随设备屏幕变化,保存相对比例能适配所有屏幕,避免固定像素的偏差。在ViewModel中修改裁剪区域的存储逻辑:
// 定义数据类存储裁剪区域的相对参数(相对于预览画布的比例) data class CropArea( val relativeLeft: Float, val relativeTop: Float, val relativeWidth: Float, val relativeHeight: Float ) class ScanLabelViewModel : ViewModel() { private val _expediteurCrop = mutableStateOf(CropArea(0f,0f,0f,0f)) val expediteurCrop: State<CropArea> = _expediteurCrop // 同理定义destinataireCrop、descriptionCrop fun setCropsMesurements(canvasSize: Size) { // 计算expediteur区域的相对比例 _expediteurCrop.value = CropArea( relativeLeft = (canvasSize.width * 6 / 24) / canvasSize.width, relativeTop = (canvasSize.height * 3 / 29) / canvasSize.height, relativeWidth = ((canvasSize.width / 24) * 11) / canvasSize.width, relativeHeight = ((canvasSize.height / 29) * 4) / canvasSize.height ) // 同理计算并保存destinataire、description的相对比例 } }
2. 处理图像旋转与比例适配
拍摄的原图会带有Exif旋转信息,且原图比例可能和预览的A4比例不一致,需要先校正旋转,再将原图裁剪为和预览一致的A4比例区域:
import android.graphics.Bitmap import android.graphics.BitmapFactory import android.graphics.Matrix import android.media.ExifInterface import java.io.File fun cropExpediteurImage(imageFile: File): Bitmap? { // 1. 获取图像旋转角度 val exif = ExifInterface(imageFile.path) val rotation = exif.getAttributeInt( ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL ) val rotationDegrees = when(rotation) { ExifInterface.ORIENTATION_ROTATE_90 -> 90 ExifInterface.ORIENTATION_ROTATE_180 -> 180 ExifInterface.ORIENTATION_ROTATE_270 -> 270 else -> 0 } // 2. 获取原图尺寸 val options = BitmapFactory.Options().apply { inJustDecodeBounds = true } BitmapFactory.decodeFile(imageFile.path, options) val originalWidth = options.outWidth val originalHeight = options.outHeight val a4Ratio = 1f / 1.414f // 预览的A4宽高比 // 3. 将原图裁剪为A4比例区域(和预览显示区域匹配) val (cropLeft, cropTop, cropWidth, cropHeight) = if (originalWidth.toFloat()/originalHeight > a4Ratio) { // 原图比A4宽,裁剪左右两侧 val targetWidth = (originalHeight * a4Ratio).toInt() Pair((originalWidth - targetWidth)/2, 0) to Pair(targetWidth, originalHeight) } else { // 原图比A4高,裁剪上下两侧 val targetHeight = (originalWidth / a4Ratio).toInt() Pair(0, (originalHeight - targetHeight)/2) to Pair(originalWidth, targetHeight) } // 4. 裁剪A4区域并校正旋转 val a4Bitmap = BitmapFactory.decodeFile(imageFile.path).let { Bitmap.createBitmap(it, cropLeft, cropTop, cropWidth, cropHeight) } val matrix = Matrix().apply { postRotate(rotationDegrees.toFloat()) } val rotatedBitmap = Bitmap.createBitmap( a4Bitmap, 0, 0, a4Bitmap.width, a4Bitmap.height, matrix, true ) // 5. 根据相对比例裁剪目标区域 val cropArea = expediteurCrop.value val targetLeft = (rotatedBitmap.width * cropArea.relativeLeft).toInt() val targetTop = (rotatedBitmap.height * cropArea.relativeTop).toInt() val targetWidth = (rotatedBitmap.width * cropArea.relativeWidth).toInt() val targetHeight = (rotatedBitmap.height * cropArea.relativeHeight).toInt() return Bitmap.createBitmap( rotatedBitmap, targetLeft, targetTop, targetWidth, targetHeight ) }
3. 修正拍照后的裁剪调用逻辑
确保图像保存完成后再执行裁剪,避免空指针或提前裁剪:
// CameraScreenContent中的Button onClick修改 Button( // ...其他属性 onClick = { viewModel.captureImage(cameraController) { imageFile -> // 图像保存成功后执行裁剪 viewModel.cropExpediteurImage(imageFile)?.let { croppedBitmap -> // 处理裁剪后的图片(如上传、本地存储等) } } } )
4. 可选:统一相机预览与拍照比例
为了减少裁剪损耗,可以设置相机拍照的比例和预览一致(A4比例),在setupCamera函数中添加:
private fun setupCamera( cameraController: LifecycleCameraController, lifecycleOwner: LifecycleOwner, previewView: PreviewView, ) { cameraController.bindToLifecycle(lifecycleOwner) // 设置拍照目标比例为A4 val imageCapture = ImageCapture.Builder() .setTargetAspectRatioCustom(Rational(1, 1414)) // 1:1.414 .build() cameraController.imageCapture = imageCapture previewView.controller = cameraController }
内容的提问来源于stack exchange,提问作者Jamal N
相关产品推荐
相关产品推荐

