You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 23:35:56