如何用Android CameraX实现仅捕获相机矩形区域图像(无第三方库)
Android CameraX 原生实现矩形框区域图像捕获
当然可以实现这个需求,核心分为界面叠加矩形遮罩和捕获指定区域图像两部分,以下是具体实现步骤:
一、界面绘制矩形遮罩
通过自定义View叠加在CameraX的Preview之上,实现半透明遮罩+中间透明矩形框的效果:
1. 自定义遮罩View
class CaptureOverlayView(context: Context, attrs: AttributeSet?) : View(context, attrs) { private val maskPaint = Paint().apply { color = Color.parseColor("#80000000") // 半透明黑色遮罩 style = Paint.Style.FILL } private val borderPaint = Paint().apply { color = Color.WHITE style = Paint.Style.STROKE strokeWidth = 4f isAntiAlias = true } private var captureRect: RectF = RectF() override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { super.onSizeChanged(w, h, oldw, oldh) // 计算居中的矩形框(示例为屏幕最短边的60%) val rectSize = min(w, h) * 0.6f captureRect = RectF( (w - rectSize) / 2, (h - rectSize) / 2, (w + rectSize) / 2, (h + rectSize) / 2 ) } override fun onDraw(canvas: Canvas) { super.onDraw(canvas) // 绘制全屏遮罩 canvas.drawRect(0f, 0f, width.toFloat(), height.toFloat(), maskPaint) // 清除矩形区域的遮罩(透明显示) maskPaint.xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR) canvas.drawRect(captureRect, maskPaint) // 绘制矩形边框 maskPaint.xfermode = null canvas.drawRect(captureRect, borderPaint) } // 对外提供矩形区域坐标 fun getCaptureRect(): RectF = captureRect }
2. 布局中叠加遮罩
使用FrameLayout将遮罩View覆盖在PreviewView之上:
<FrameLayout android:layout_width="match_parent" android:layout_height="match_parent"> <androidx.camera.view.PreviewView android:id="@+id/previewView" android:layout_width="match_parent" android:layout_height="match_parent"/> <com.yourpackage.CaptureOverlayView android:id="@+id/overlayView" android:layout_width="match_parent" android:layout_height="match_parent"/> </FrameLayout>
二、捕获矩形区域内的图像
需要将UI上的矩形坐标转换为相机图像的实际坐标,再对捕获的图像进行裁剪:
1. 初始化ImageCapture
val imageCapture = ImageCapture.Builder() .setTargetResolution(Size(1920, 1080)) // 自定义目标分辨率 .build()
2. 拍照并裁剪指定区域
captureBtn.setOnClickListener { val outputFile = File(externalMediaDirs.first(), "${System.currentTimeMillis()}.jpg") val outputOptions = ImageCapture.OutputFileOptions.Builder(outputFile).build() imageCapture.takePicture( outputOptions, ContextCompat.getMainExecutor(this), object : ImageCapture.OnImageSavedCallback { override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) { // 读取原始图像 val originalBitmap = BitmapFactory.decodeFile(outputFile.absolutePath) val uiRect = overlayView.getCaptureRect() val previewView = findViewById<PreviewView>(R.id.previewView) // 转换UI坐标到图像坐标(适配屏幕旋转和缩放) val scaleX = originalBitmap.width.toFloat() / previewView.width.toFloat() val scaleY = originalBitmap.height.toFloat() / previewView.height.toFloat() val imageRect = when (resources.configuration.orientation) { Configuration.ORIENTATION_PORTRAIT -> { // 竖屏时需要交换X/Y轴缩放比例 RectF( uiRect.top * scaleX, uiRect.left * scaleY, uiRect.bottom * scaleX, uiRect.right * scaleY ) } else -> { RectF( uiRect.left * scaleX, uiRect.top * scaleY, uiRect.right * scaleX, uiRect.bottom * scaleY ) } } // 裁剪图像(确保坐标在图像范围内) val safeLeft = max(0f, imageRect.left).toInt() val safeTop = max(0f, imageRect.top).toInt() val safeWidth = min(imageRect.width().toInt(), originalBitmap.width - safeLeft) val safeHeight = min(imageRect.height().toInt(), originalBitmap.height - safeTop) val croppedBitmap = Bitmap.createBitmap( originalBitmap, safeLeft, safeTop, safeWidth, safeHeight ) // 保存裁剪后的图像 val croppedFile = File(externalMediaDirs.first(), "cropped_${System.currentTimeMillis()}.jpg") FileOutputStream(croppedFile).use { out -> croppedBitmap.compress(Bitmap.CompressFormat.JPEG, 90, out) } // 释放内存 originalBitmap.recycle() croppedBitmap.recycle() } override fun onError(exception: ImageCaptureException) { // 处理拍照错误 } } ) }
注意事项
- 坐标转换需适配设备旋转方向和PreviewView的缩放模式(如
ScaleType.FILL_CENTER) - 裁剪时要做边界校验,避免数组越界
- 处理Bitmap后及时调用
recycle()释放内存,防止OOM
内容的提问来源于stack exchange,提问作者F_Z
相关产品推荐
相关产品推荐

