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

如何用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.02 23:37:45