# mas-code-scan 自定义识别区域方案 ## 1. 目标 在 Android 扫码页面中提供可配置的实时识别区域(ROI,Region of Interest): - 摄像头打开后显示一个矩形扫描框; - 只有条码位于矩形区域内时才允许识别成功; - 扫描框外的条码不触发单次扫码成功; - 支持自定义扫描框宽度、高度、水平位置和垂直位置; - 支持不同屏幕尺寸和横竖屏适配; - 不破坏现有的单次扫码、连续扫码、相册识图、多码点选、智能变焦和码制过滤能力。 ## 2. 当前实现核对 ### 2.1 现有接口 文件:`uni_modules/mas-code-scan/utssdk/interface.uts` 当前 `CodeScanOptions` 支持: ```ts { smartZoom?: boolean formats?: string[] vibrate?: boolean continuous?: boolean fromAlbum?: boolean success?: (res) => void fail?: (res) => void complete?: (res) => void } ``` 当前没有识别区域配置字段。 ### 2.2 预览层 文件:`uni_modules/mas-code-scan/utssdk/app-android/CodeCaptureActivity.kt` - 使用 `PreviewView.ScaleType.FILL_CENTER` 显示相机预览; - `ScanFrameDecorView` 覆盖在预览层上方绘制扫描动画; - 预览层和装饰层都是全屏 `FrameLayout`; - 触摸缩放绑定在 `PreviewView` 上。 文件:`uni_modules/mas-code-scan/utssdk/app-android/ScanFrameDecorView.kt` 当前扫描框是固定的正方形: ```kotlin val frameWidth = ... val frameHeight = frameWidth ``` 它只负责画扫描线,不参与实际识别。 ### 2.3 识别层 文件:`uni_modules/mas-code-scan/utssdk/app-android/CodeCaptureActivity.kt` 当前把完整的 `ImageProxy` 图像交给 ML Kit: ```kotlin val inputImage = InputImage.fromMediaImage(mediaImage, rotationDegrees) scanner.process(inputImage) ``` 因此当前扫描框只是视觉提示,框外条码仍可能被识别。 ## 3. 推荐接口设计 建议新增 `scanRegion`,使用相对比例配置,不使用固定像素作为公共接口。 ```ts openCodeScan({ scanRegion: { width: 0.92, height: 0.18, centerX: 0.5, centerY: 0.5 } }) ``` 字段定义: | 字段 | 类型 | 默认值 | 范围 | 含义 | |---|---|---:|---|---| | `fullScreen` | `boolean` | `false` | - | 为 `true` 时整画面识别,不绘制扫描框且不进行区域过滤 | | `width` | `number` | `0.74` | `0.1 ~ 1` | 扫描框宽度,占预览宽度的比例 | | `height` | `number` | `0.74` | `0.1 ~ 1` | 扫描框高度,占预览宽度的比例 | | `centerX` | `number` | `0.5` | `0 ~ 1` | 扫描框中心的水平位置比例 | | `centerY` | `number` | `0.5` | `0 ~ 1` | 扫描框中心的垂直位置比例 | 全屏识别示例: ```ts openCodeScan({ scanRegion: { fullScreen: true } }) ``` 二维码区域约束: - `width` 与 `height` 使用相同值,严格锁定 `1:1` 正方形比例; - 默认值为 `width: 0.74`、`height: 0.74`; - 原生扫描框使用直角,不绘制圆角。 推荐默认值: ```ts { width: 0.74, height: 0.74, centerX: 0.5, centerY: 0.5 } ``` 业务页面可针对一维码使用横向区域,并通过 `centerY` 调整上下位置、通过 `height` 调整框高: ```ts { width: 0.96, height: 0.24, centerX: 0.5, centerY: 0.5 } ``` 原型页提供三档横向条码框高度: - 中:`0.24`(24%); - 大:`0.32`(32%); - 特大:`0.40`(40%)。 ### 3.1 为什么使用比例 - 不依赖具体手机分辨率和 density; - 适配不同 Android 设备; - 与当前全屏预览布局一致; - 后续可在页面中通过滑块或预设配置调整; - 避免将前端 `rpx` 直接与原生像素混用。 ## 4. 实现分层 ### 4.1 UTS 接口层 修改:`utssdk/interface.uts` 新增: ```ts export type ScanRegion = { width?: number height?: number centerX?: number centerY?: number } ``` 并在 `CodeScanOptions` 中增加: ```ts scanRegion?: ScanRegion ``` 要求: - 字段全部可选; - 不传时保持现有默认扫描框; - 对非法值进行原生端裁剪和兜底; - 不把 UI 颜色、文案等表现层参数与识别区域参数混在一起。 ### 4.2 UTS 到 Android Intent 修改:`utssdk/app-android/index.uts` 新增一个 JSON 字符串 Extra,例如: ```text mas_scan_region ``` 将 `scanRegion` 序列化后传递给 `CodeCaptureActivity`。 推荐传递 JSON,而不是增加多个 Intent Extra,原因是: - 字段集中; - 后续增加圆角、边框颜色等非识别参数时更容易扩展; - 保持接口和 Activity 参数数量可控。 ### 4.3 Activity 参数解析 修改:`CodeCaptureActivity.kt` 新增: ```kotlin private data class ScanRegionConfig( val width: Float, val height: Float, val centerX: Float, val centerY: Float ) ``` 初始化时: 1. 读取 Intent Extra; 2. 解析 JSON; 3. 将数值限制在安全范围; 4. 对过小区域、越界中心点进行兜底; 5. 将同一份配置传给 `ScanFrameDecorView` 和识别过滤逻辑。 最终必须保证: ```text 视觉扫描框区域 == 实际有效识别区域 ``` ## 5. 坐标体系设计 这是本需求的核心难点。项目中至少存在三套坐标: 1. **屏幕/装饰层坐标**:`ScanFrameDecorView` 的 view 坐标; 2. **预览可视区域坐标**:`PreviewView` 中用户看到的相机画面; 3. **ImageProxy 图像坐标**:ML Kit `Barcode.boundingBox` 使用的图像坐标。 由于预览使用 `FILL_CENTER`,图像可能在屏幕两侧或上下被裁剪,不能简单按照屏幕宽高直接换算。 ### 5.1 统一区域定义 推荐先在预览层坐标中计算扫描框: ```text frameLeft = viewWidth * (centerX - width / 2) frameTop = viewHeight * (centerY - height / 2) frameRight = viewWidth * (centerX + width / 2) frameBottom = viewHeight * (centerY + height / 2) ``` 并限制在预览层范围内。 ### 5.2 预览坐标映射到图像坐标 当前 `PreviewView` 使用 `FILL_CENTER`,映射应考虑: ```text scale = max(viewWidth / rotatedImageWidth, viewHeight / rotatedImageHeight) ``` 相机图像在屏幕上的实际显示尺寸为: ```text displayWidth = rotatedImageWidth * scale displayHeight = rotatedImageHeight * scale offsetX = (viewWidth - displayWidth) / 2 offsetY = (viewHeight - displayHeight) / 2 ``` 扫描框转回旋转后的图像坐标: ```text imageLeft = (frameLeft - offsetX) / scale imageTop = (frameTop - offsetY) / scale imageRight = (frameRight - offsetX) / scale imageBottom = (frameBottom - offsetY) / scale ``` 然后再根据 `rotationDegrees` 转换到 `ImageProxy` 原始坐标。 注意:实际代码需要以 `PreviewView` 的 `viewPort`、绑定的 UseCase 分辨率和设备方向为准验证,不能只凭静态公式上线。 ## 6. 识别限制策略 ### 6.1 第一阶段:结果过滤,推荐先实现 ML Kit 仍分析整帧,但只接受扫描区域内的条码: ```kotlin val validBarcodes = barcodes.filter { barcode -> val box = barcode.boundingBox ?: return@filter false isBarcodeInsideScanRegion(box, imageProxy, rotationDegrees) } ``` 区域判断建议使用条码边界框中心点,默认规则: ```text 条码中心点位于扫描框内 => 允许识别 条码中心点位于扫描框外 => 忽略 ``` 对于严格业务场景,可以改为: ```text 条码边界框完全位于扫描框内 => 允许识别 ``` 本项目建议默认使用“中心点在区域内”,原因是: - 用户将条码移入框内时体验更自然; - 条码边缘轻微超出时不会频繁丢失; - 对一维横向条码更友好。 ### 6.2 多码识别 过滤应发生在多码判断之前: ```text 原始 barcodes -> 过滤无 boundingBox 的结果 -> 过滤框外结果 -> 再执行单码 / 多码判断 ``` 这样框外多码不会触发点选页面,也不会影响智能变焦。 ### 6.3 相册识图 实时摄像头的 `scanRegion` 不应默认限制相册识图: - 相册图片没有实时预览框坐标; - 用户选择的是整张图片; - 继续保持相册识别整图的现有行为。 如果未来确实需要对相册图片裁剪,应单独设计 `albumRegion`,不要复用实时预览坐标。 ### 6.4 识别前裁剪 第二阶段可选做真正的 ROI 图像裁剪,只将区域图像提交给 ML Kit。 暂不作为第一阶段实现,原因: - 需要处理 ImageProxy YUV 裁剪; - 需要处理旋转和 cropRect; - 需要重新映射多码点选坐标; - 可能影响智能变焦依据和现有连续扫码稳定性; - 设备兼容性验证成本更高。 ## 7. 扫描框视觉设计 修改:`ScanFrameDecorView.kt` 扫描框应从固定正方形改为配置矩形: - 宽度由 `width` 决定; - 高度由 `height` 决定; - 位置由 `centerX`、`centerY` 决定; - 扫描线只在矩形内部移动; - 扫描线左右留出固定 inset; - 其他区域可增加半透明遮罩,增强用户对有效范围的理解。 建议提供清晰的 UI 语义: ```text 框内:有效识别区域 框外:不识别 ``` 当前截图的横向条码场景推荐: ```ts scanRegion: { width: 0.96, height: 0.16, centerX: 0.5, centerY: 0.5 } ``` ## 8. 原型页面示例规划 文件:`pages/scan/prototype.vue` 原型页增加“识别区域”配置区,但不直接实现摄像头 UI。页面负责展示和传递配置: ### 8.1 预设 提供三个预设: - 默认正方形:适合二维码; - 横向条码:适合设备码、物料码; - 自定义:通过宽度、高度、水平位置、垂直位置调整。 ### 8.2 示例数据 ```js scanRegion: { width: 0.96, height: 0.16, centerX: 0.5, centerY: 0.5 } ``` ### 8.3 原型页交互 - 使用滑块或步进按钮调整四个比例参数; - 页面实时显示当前区域参数; - 单次扫码、连续扫码都使用当前 `scanRegion`; - 相册识图按钮不传实时扫描区域,继续整图识别; - 在 Web 预览中展示“仅 Android App-Plus 支持原生扫码”的提示。 ## 9. 兼容性策略 ### 9.1 不传配置 现有调用全部保持兼容: ```ts openCodeScan({ smartZoom: true, vibrate: true }) ``` 行为继续使用默认正方形区域。 ### 9.2 旧构建包 如果前端代码传入 `scanRegion`,但运行的是旧插件包,旧原生代码会忽略未知 Extra,仍可正常扫码,但不会限制区域。因此必须重新制作自定义基座或正式 APK。 ### 9.3 非 Android 平台 继续由 `#ifdef APP-PLUS` 控制插件导入和调用,Web、iOS、小程序不加载 Android UTS 实现。 ### 9.4 参数安全 原生端必须再次校验参数,不能只依赖前端: - `width`、`height` 限制在 `0.1 ~ 1`; - `centerX`、`centerY` 限制在 `0 ~ 1`; - 区域超出屏幕时进行边界收敛; - 宽高过小导致不可用时回退默认值。 ## 10. 实施顺序 ### 阶段一:方案验证 1. 增加 `scanRegion` 接口定义; 2. 将参数传入 Android Activity; 3. 扫描框支持矩形和位置配置; 4. 根据条码中心点过滤框外结果; 5. 原型页提供横向条码预设; 6. 保持相册整图识别。 ### 阶段二:真机验证 至少验证: - 二维码默认正方形识别; - 横向一维码识别; - 框内识别成功; - 框外完全不识别; - 条码横跨边界时的行为; - 多个条码一内一外时只处理框内条码; - 连续扫码只回传框内结果; - 智能变焦不会被框外条码触发; - 横竖屏和不同分辨率设备; - 相册识图不受实时区域影响; - 关闭、取消、权限拒绝和页面销毁流程。 ### 阶段三:可选优化 根据真机效果再决定是否: - 改为识别前裁剪 ROI; - 增加边框颜色、圆角、遮罩透明度配置; - 增加“中心点在框内 / 完全包含”两种命中规则; - 增加一维码专用默认模板。 ## 11. 验收标准 功能验收: - 扫描框宽高和位置可以通过 `openCodeScan` 参数调整; - 用户看到的扫描框与实际识别区域一致; - 框外条码不会触发成功回调; - 框内条码可以正常触发成功回调; - 多码点选只展示有效区域内的条码; - 旧调用不传 `scanRegion` 时行为不变; - 单次、连续、相册三种模式互不回归。 工程验收: - UTS 接口类型、Intent Extra、Activity 解析三层字段一致; - 不在页面中直接修改原生插件内部逻辑; - 所有新增配置有默认值和越界保护; - 真机重新构建自定义基座后验证; - 不以 Web 预览结果代替 Android 真机验证。 ## 12. 结论 本项目适合采用“**比例配置 + 原生扫描框绘制 + 条码结果区域过滤**”作为第一版实现。 该方案能满足截图所示的横向矩形识别场景,改动范围可控,且能保留当前插件已有能力。真正的图像裁剪 ROI 作为后续优化,不在第一版中引入,以降低旋转、坐标映射和多码点选的回归风险。