如何让Android相机拍摄尺寸与PreviewView预览区域一致
解决CameraX拍摄图片与PreviewView预览区域尺寸一致的问题
问题说明
拍摄得到的图片尺寸及比例与相机预览界面显示的不一致,需要拍摄出与PreviewView预览区域(非全屏的约束布局区域)尺寸完全匹配的图片。当前拍摄的图片为全屏尺寸,布局代码和拍照代码如下:
布局代码
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/black"> <androidx.camera.view.PreviewView android:id="@+id/preview" android:layout_width="0dp" android:layout_height="0dp" android:layout_marginStart="10dp" android:layout_marginTop="10dp" android:layout_marginEnd="10dp" android:layout_marginBottom="120dp" app:layout_constraintBottom_toTopOf="@id/bottom" app:layout_constraintEnd_toStartOf="@id/right" app:layout_constraintStart_toEndOf="@id/left" app:layout_constraintTop_toBottomOf="@id/top" /> <ImageView android:id="@+id/photo_preview" android:layout_width="0dp" android:layout_height="0dp" android:layout_marginStart="10dp" android:layout_marginTop="10dp" android:layout_marginEnd="10dp" android:layout_marginBottom="120dp" android:visibility="gone" app:layout_constraintBottom_toTopOf="@id/bottom" app:layout_constraintEnd_toStartOf="@id/right" app:layout_constraintStart_toEndOf="@id/left" app:layout_constraintTop_toBottomOf="@id/top" /> <ImageView android:id="@+id/top" android:layout_width="100dp" android:layout_height="100dp" android:src="@drawable/camera" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:tint="@color/white" /> <ImageView android:id="@+id/left" android:layout_width="100dp" android:layout_height="100dp" android:src="@drawable/camera" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:tint="@color/white" /> <ImageView android:id="@+id/right" android:layout_width="100dp" android:layout_height="100dp" android:src="@drawable/camera" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" app:tint="@color/white" /> <ImageView android:id="@+id/bottom" android:layout_width="100dp" android:layout_height="100dp" android:src="@drawable/camera" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:tint="@color/white" /> </androidx.constraintlayout.widget.ConstraintLayout>
拍照代码
private fun takePhoto() { imageCapture.takePicture(ContextCompat.getMainExecutor(this), object : ImageCapture.OnImageCapturedCallback() { override fun onCaptureSuccess(image: ImageProxy) { val buffer = image.planes[0].buffer val bytes = ByteArray(buffer.remaining()) buffer.get(bytes) val bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.size) // how to resize?? binding.photoPreview.isVisible = true binding.photoPreview.post { binding.photoPreview.setImageBitmap(bitmap) } image.close() } override fun onError(exception: ImageCaptureException) { Log.e(TAG, "Photo capture failed: ${exception.message}", exception) } }) }
解决步骤
1. 处理图片旋转,匹配预览方向
CameraX输出的ImageProxy带有方向信息,必须先将图片旋转到与预览一致的方向,否则裁剪会出现错位。
2. 获取PreviewView的实际显示尺寸
PreviewView是约束布局,需要确保布局完成后再获取真实宽高,避免拿到0值。
3. 计算裁剪区域并缩放至预览尺寸
相机拍摄的原图比例和预览区域比例一致,但尺寸更大,需要先裁剪出和预览比例匹配的区域,再缩放至预览的精确尺寸。
修改后的完整拍照代码如下:
private fun takePhoto() { imageCapture.takePicture(ContextCompat.getMainExecutor(this), object : ImageCapture.OnImageCapturedCallback() { override fun onCaptureSuccess(image: ImageProxy) { // 处理图片旋转,得到和预览同方向的Bitmap val rotationDegrees = image.imageInfo.rotationDegrees val buffer = image.planes[0].buffer val bytes = ByteArray(buffer.remaining()) buffer.get(bytes) var sourceBitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.size) val matrix = Matrix() matrix.postRotate(rotationDegrees.toFloat()) sourceBitmap = Bitmap.createBitmap(sourceBitmap, 0, 0, sourceBitmap.width, sourceBitmap.height, matrix, true) // 获取PreviewView的实际尺寸 val previewWidth = binding.preview.width val previewHeight = binding.preview.height if (previewWidth == 0 || previewHeight == 0) { // 布局未完成,延迟处理 binding.preview.post { processBitmapToPreviewSize(sourceBitmap, binding.preview.width, binding.preview.height) } } else { processBitmapToPreviewSize(sourceBitmap, previewWidth, previewHeight) } image.close() } override fun onError(exception: ImageCaptureException) { Log.e(TAG, "Photo capture failed: ${exception.message}", exception) } }) } private fun processBitmapToPreviewSize(sourceBitmap: Bitmap, previewWidth: Int, previewHeight: Int) { val sourceWidth = sourceBitmap.width val sourceHeight = sourceBitmap.height // 计算预览和原图的比例 val previewRatio = previewWidth.toFloat() / previewHeight.toFloat() val sourceRatio = sourceWidth.toFloat() / sourceHeight.toFloat() var targetBitmap: Bitmap if (sourceRatio > previewRatio) { // 原图更宽,裁剪左右两侧 val cropWidth = (sourceHeight * previewRatio).toInt() val startX = (sourceWidth - cropWidth) / 2 targetBitmap = Bitmap.createBitmap(sourceBitmap, startX, 0, cropWidth, sourceHeight) } else { // 原图更高,裁剪上下两侧 val cropHeight = (sourceWidth / previewRatio).toInt() val startY = (sourceHeight - cropHeight) / 2 targetBitmap = Bitmap.createBitmap(sourceBitmap, 0, startY, sourceWidth, cropHeight) } // 缩放至PreviewView的精确尺寸 targetBitmap = Bitmap.createScaledBitmap(targetBitmap, previewWidth, previewHeight, true) // 显示结果 binding.photoPreview.isVisible = true binding.photoPreview.setImageBitmap(targetBitmap) // 回收临时Bitmap避免内存泄漏 sourceBitmap.recycle() }
额外优化(可选)
初始化ImageCapture时可以尝试指定目标分辨率,减少后续裁剪的计算量,但由于设备支持的分辨率有限,裁剪仍是更可靠的方案:
val imageCapture = ImageCapture.Builder() .setTargetResolution(Size(previewWidth, previewHeight)) .build()
内容的提问来源于stack exchange,提问作者papag
相关产品推荐
相关产品推荐

