diff --git a/README.md b/README.md index d9a675a..fd9e30a 100644 --- a/README.md +++ b/README.md @@ -55,9 +55,9 @@ OpenCV二维码识别:封装好的API,通过 **OpenCVQRCodeDetector** 你可 ### [opencv-qrcode-scanning](opencv-qrcode-scanning) -OpenCV二维码扫码:有了上面的OpenCV二维码识别功能,基本的扫码相关界面还是需要有个的,扫码加识别完美搭配,依赖[MLKit](https://github.com/jenly1314/MLKit)中的 **mlkit-camera-core**; +OpenCV二维码扫码:有了上面的OpenCV二维码识别功能,基本的扫码相关界面还是需要有个的,扫码加识别完美搭配; -**opencv-qrcode-scanning** 相当于[MLKit](https://github.com/jenly1314/MLKit)中的 **mlkit-camera-core**的衍生库。 +**opencv-qrcode-scanning** 相当于[CameraScan](https://github.com/jenly1314/CameraScan)的衍生库。 ### [wechat-qrcode](wechat-qrcode) @@ -65,25 +65,23 @@ OpenCV二维码扫码:有了上面的OpenCV二维码识别功能,基本的 ### [wechat-qrcode-scanning](wechat-qrcode-scanning) -微信二维码扫码:有了上面的微信二维码识别功能,基本的扫码相关界面还是需要有个的,扫码加识别完美搭配,依赖[MLKit](https://github.com/jenly1314/MLKit)中的 **mlkit-camera-core**; +微信二维码扫码:有了上面的微信二维码识别功能,基本的扫码相关界面还是需要有个的,扫码加识别完美搭配; -**wechat-qrcode-scanning** 相当于[MLKit](https://github.com/jenly1314/MLKit)中的 **mlkit-camera-core**的衍生库。 - -### [Java版本(点击查看java分支)](https://github.com/jenly1314/WeChatQrCode/tree/java) +**wechat-qrcode-scanning** 相当于[CameraScan](https://github.com/jenly1314/CameraScan)的衍生库。 +### [Java版本(点击查看java分支)](https://github.com/jenly1314/WeChatQrCode/tree/java) ## 引入 ### Gradle: -1. 在Project的 **build.gradle** 里面添加远程仓库 - +1. 在Project的 **build.gradle** 或 **setting.gradle** 中添加远程仓库 + ```gradle -allprojects { - repositories { - //... - mavenCentral() - } +repositories { + //... + mavenCentral() + maven { url 'https://jitpack.io' } } ``` @@ -91,41 +89,23 @@ allprojects { ```gradle // OpenCV基础库(*必须) -implementation 'com.github.jenly1314.WeChatQRCode:opencv:1.3.0' -implementation 'com.github.jenly1314.WeChatQRCode:opencv-armv7a:1.3.0' - -// OpenCV二维码识别功能(可选) -implementation 'com.github.jenly1314.WeChatQRCode:opencv-qrcode:1.3.0' -// OpenCV二维码扫码功能(可选) -implementation 'com.github.jenly1314.WeChatQRCode:opencv-qrcode-scanning:1.3.0' - -// 微信二维码识别功能(可选) -implementation 'com.github.jenly1314.WeChatQRCode:wechat-qrcode:1.3.0' -// 微信二维码扫码功能(可选) -implementation 'com.github.jenly1314.WeChatQRCode:wechat-qrcode-scanning:1.3.0' +implementation 'com.github.jenly1314.WeChatQRCode:opencv:2.0.0' +implementation 'com.github.jenly1314.WeChatQRCode:opencv-armv7a:2.0.0' -``` - -根据需要选择支持的 SO 库架构 -```gradle -// OpenCV基础库(*必须) -implementation 'com.github.jenly1314.WeChatQRCode:opencv:1.3.0' -implementation 'com.github.jenly1314.WeChatQRCode:opencv-armv7a:1.3.0' - -// OpenCV的其他ABI(可选),根据你的需求选择想要的so支持 -implementation 'com.github.jenly1314.WeChatQRCode:opencv-armv64:1.3.0' -implementation 'com.github.jenly1314.WeChatQRCode:opencv-x86:1.3.0' -implementation 'com.github.jenly1314.WeChatQRCode:opencv-x86_64:1.3.0' +// OpenCV的其他ABI(可选),根据你的需要选择想要支持的SO库架构 +implementation 'com.github.jenly1314.WeChatQRCode:opencv-armv64:2.0.0' +implementation 'com.github.jenly1314.WeChatQRCode:opencv-x86:2.0.0' +implementation 'com.github.jenly1314.WeChatQRCode:opencv-x86_64:2.0.0' // OpenCV二维码识别功能(可选) -implementation 'com.github.jenly1314.WeChatQRCode:opencv-qrcode:1.3.0' +implementation 'com.github.jenly1314.WeChatQRCode:opencv-qrcode:2.0.0' // OpenCV二维码扫码功能(可选) -implementation 'com.github.jenly1314.WeChatQRCode:opencv-qrcode-scanning:1.3.0' +implementation 'com.github.jenly1314.WeChatQRCode:opencv-qrcode-scanning:2.0.0' // 微信二维码识别功能(可选) -implementation 'com.github.jenly1314.WeChatQRCode:wechat-qrcode:1.3.0' +implementation 'com.github.jenly1314.WeChatQRCode:wechat-qrcode:2.0.0' // 微信二维码扫码功能(可选) -implementation 'com.github.jenly1314.WeChatQRCode:wechat-qrcode-scanning:1.3.0' +implementation 'com.github.jenly1314.WeChatQRCode:wechat-qrcode-scanning:2.0.0' ``` @@ -135,7 +115,8 @@ implementation 'com.github.jenly1314.WeChatQRCode:wechat-qrcode-scanning:1.3.0' > 使用 v1.3.x 以上版本时,要求 compileSdkVersion >= 33 -> 如果 compileSdkVersion < 33 请使用 v1.3.x 以前的版本(如:v1.2.1) +> 如果 **compileSdkVersion < 33** 请使用 [**v1.x版本**](https://github.com/jenly1314/WeChatQRCode/tree/1.x/) + #### ABI过滤 @@ -232,7 +213,7 @@ class WeChatQRCodeActivity : WeChatCameraScanActivity() { if (result is WeChatScanningAnalyzer.QRCodeAnalyzeResult) { // 如果需要处理结果二维码的位置信息 val buffer = StringBuilder() - val bitmap = result.bitmap.drawRect { canvas, paint -> + val bitmap = result.bitmap?.drawRect { canvas, paint -> // 扫码结果可能有多个 for ((index, data) in result.result.withIndex()) { buffer.append("[$index] ").append(data).append("\n") @@ -308,63 +289,43 @@ class WeChatQRCodeActivity : WeChatCameraScanActivity() { **wechat-qrcode-scanning** 扫描识别二维码实现示例:通过直接继承 OpenCVCameraScanActivity 实现的示例 [OpenCVQRCodeActivity](app/src/main/java/com/king/wechat/qrcode/app/OpenCVQRCodeActivity.kt) - - + ### 特别说明 -因为 **wechat-qrcode-scanning** 依赖了 [MLKit](https://github.com/jenly1314/MLKit) 中的 **mlkit-camera-core**, -所以关于 **CameraScan** 的和界面布局在使用上完全遵循 **mlkit-camera-core** 的使用方式。 +因为 **wechat-qrcode-scanning** 和 **opencv-qrcode-scanning** 都是以[CameraScan](https://github.com/jenly1314/CameraScan)作为基础库去实现具体的分析检测功能,所以关于 **CameraScan** 的使用和自定义扫码界面布局都完全遵循[CameraScan](https://github.com/jenly1314/CameraScan) 的使用方式。 -这里贴出一部分主要的使用示例: +> 关于**CameraScan**的使用,你可以直接去看[CameraScan](https://github.com/jenly1314/CameraScan)的使用说明; -#### CameraScan配置示例 +> 关于扫描框动画,你可以查看[ViewfinderView](https://github.com/jenly1314/ViewfinderView)的使用说明; -**CameraScan** 里面包含部分支持链式调用的方法,即调用返回是 **CameraScan** 本身的一些配置建议在调用 **startCamera()** 方法之前调用。 +### 2.x版本的变化 -> 如果是通过继承 **BaseCameraScanActivity** 或者 **BaseCameraScanFragment** 或其子类实现的相机扫描,可以在 -**initCameraScan()** 方法中获取 **CameraScan** ,然后根据需要修改相关配置。 +从 **1.x** 到 **2.x** 主要变化如下: +* 1.x版本中 **wechat-qrcode-scanning** 和 **opencv-qrcode-scanning** 默认依赖的 **mlkit-camera-core** 被移除了; +> 从2.0.0版本开始 **wechat-qrcode-scanning** 和 **opencv-qrcode-scanning** 都改为依赖[CameraScan](https://github.com/jenly1314/CameraScan);([CameraScan](https://github.com/jenly1314/CameraScan)是一个独立的库,单独进行维护) -```java -// 获取CameraScan,根据需要修改相关配置 -getCameraScan().setPlayBeep(true)//设置是否播放音效,默认为false - .setVibrate(true)//设置是否震动,默认为false - .setCameraConfig(new ResolutionCameraConfig(this))//设置相机配置信息,CameraConfig可覆写options方法自定义配置 - .setNeedTouchZoom(true)//支持多指触摸捏合缩放,默认为true - .setDarkLightLux(45f)//设置光线足够暗的阈值(单位:lux),需要通过{@link #bindFlashlightView(View)}绑定手电筒才有效 - .setBrightLightLux(100f)//设置光线足够明亮的阈值(单位:lux),需要通过{@link #bindFlashlightView(View)}绑定手电筒才有效 - .bindFlashlightView(ivFlashlight)//绑定手电筒,绑定后可根据光线传感器,动态显示或隐藏手电筒按钮 - .setOnScanResultCallback(this)//设置扫码结果回调,需要自己处理或者需要连扫时,可设置回调,自己去处理相关逻辑 - .setAnalyzer(new BarcodeScanningAnalyzer())//设置分析器,如这里使用条码分析器,BarcodeScanningAnalyzer是mlkit-barcode-scanning中的 - .setAnalyzeImage(true)//设置是否分析图片,默认为true。如果设置为false,相当于关闭了扫码识别功能 +> 从2.0.0版本开始 **wechat-qrcode-scanning** 和 **opencv-qrcode-scanning** 都已默认依赖 **ViewfinderView**[ViewfinderView](https://github.com/jenly1314/ViewfinderView) - // 启动预览(如果是通过直接或间接继承BaseCameraScanActivity或BaseCameraScanFragment实现的则无需调用startCamera) - getCameraScan().startCamera(); +> 从2.0.0版本开始默认布局包含了扫描动画 **ViewfinderView** 和手电筒按钮,集成步骤更简单。 +基于以上两点主要差异:2.x的主要使用方式和1.x基本类似,部分细节有所变更。 - // 设置闪光灯(手电筒)是否开启,需在startCamera之后调用才有效 - getCameraScan().enableTorch(torch); -``` +> 如果你是从 **1.x** 版本升级至 **2.x** 版本,那么你需要知道上面所说的差异;特别是独立出去单独维护的库,其包名都有所变化,这一点需要注意一下,大部分变动只需变更导入的包名即可完成升级。 -#### 布局示例 +> 如果你使用的是1.x版本的话请直接[查看v1.x分支版本](https://github.com/jenly1314/WeChatQRCode/tree/1.x/) -**PreviewView** 用来预览,布局内至少要保证有 **PreviewView**;如果是继承 **BaseCameraScanActivity** 或 -**BaseCameraScanFragment** 或其子类实现的相机扫描;快速实现扫描功能; +### 2.x版本的使用 -需自定义布局时,通过覆写getLayoutId方法即可;更多代码用法可直接查看 **BaseCameraScanActivity** 源码或参见下面的使用示例。 +2.x版本的实现主要是以[CameraScan](https://github.com/jenly1314/CameraScan)作为基础库去实现具体的分析检测功能,所以你可以直接去看[CameraScan](https://github.com/jenly1314/CameraScan)的使用说明,只要知道了[CameraScan](https://github.com/jenly1314/CameraScan)的基本使用方式,自然就会使用 **wechat-qrcode-scanning** 和 **opencv-qrcode-scanning**了。 -```Xml - - - - -``` +### 二维码扫码识别 + +下面就列一下OpenCVQRCode和WeChatQRCode实现扫二维码功能的核心类;主要包括实现扫描二维码的**Analyzer** 和便于快速实现扫描检测的 **BaseCameraScanActivity** 或 **BaseCameraScanFragment** 的子类。 -> 关于扫描框动画:你暂时可以参考[app](app)中的源码示例,直接使用[ViewfinderView](https://github.com/jenly1314/ViewfinderView);(后续发布新版本时,计划自动依赖 **ViewfinderView**) +| 功能 | 所属子模块 | 对应的Analyzer实现 | 对应的BaseCameraScanActivity子类 | +|:--------|:-----------------------|:-----------------------|:--------------------------------------------------| +| 二维码扫码识别 | opencv-qrcode-scanning | OpenCVScanningAnalyzer | OpenCVCameraScanActivity/OpenCVCameraScanFragment | +| 二维码扫码识别 | wechat-qrcode-scanning | WeChatScanningAnalyzer | WeChatCameraScanActivity/WeChatCameraScanFragment | 更多使用详情,请查看[app](app)中的源码使用示例或直接查看 [API帮助文档](https://jitpack.io/com/github/jenly1314/WeChatQRCode/latest/javadoc/) @@ -389,11 +350,17 @@ getCameraScan().setPlayBeep(true)//设置是否播放音效,默认为false ## 相关推荐 #### [MLKit](https://github.com/jenly1314/MLKit) 一个强大易用的工具包。通过ML Kit您可以很轻松的实现文字识别、条码识别、图像标记、人脸检测、对象检测等功能。 -#### [ZXingLite](https://github.com/jenly1314/ZXingLite) 基于ZXing库优化扫码和生成二维码/条形码功能,扫码界面完全支持自定义。 - +#### [ZXingLite](https://github.com/jenly1314/ZXingLite) 基于zxing实现的扫码库,优化扫码和生成二维码/条形码功能。 +#### [CameraScan](https://github.com/jenly1314/CameraScan) 一个简化扫描识别流程的通用基础库。 +#### [ViewfinderView](https://github.com/jenly1314/ViewfinderView) ViewfinderView一个取景视图:主要用于渲染扫描相关的动画效果。 ## 版本记录 +#### v2.0.0:2023-8-14 +* **wechat-qrcode-scanning** 和 **opencv-qrcode-scanning** 中移除原依赖(**mlkit-camera-core**),现改为依赖[CameraScan](https://github.com/jenly1314/CameraScan) +* **wechat-qrcode-scanning** 和 **opencv-qrcode-scanning** 添加默认依赖[ViewfinderView](https://github.com/jenly1314/ViewfinderView) +* 优化扫描分析过程的性能体验(优化帧数据分析过程) + #### v1.3.0:2023-4-16 * 新增OpenCV二维码扫码识别库(opencv-qrcode和opencv-qrcode-scanning) * 更新mlkit-camera-core至v1.4.0 diff --git a/app/build.gradle b/app/build.gradle index 94d87b5..902302d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -36,13 +36,15 @@ android { kotlinOptions { jvmTarget = JavaVersion.VERSION_11.toString() } - - lintOptions { - abortOnError false + packagingOptions { + resources { + excludes += ['META-INF/*.kotlin_module'] + } } - packagingOptions { - exclude 'META-INF/*.kotlin_module' + + lint { + abortOnError false } } @@ -59,11 +61,8 @@ dependencies { implementation "androidx.lifecycle:lifecycle-runtime-ktx:$versions.lifecycleKtx" - implementation "com.github.jenly1314:viewfinderview:$versions.viewfinderview" - implementation "com.github.jenly1314.AppUpdater:app-dialog:$versions.appDialog" - implementation project(path: ':opencv') implementation project(path: ':opencv-armv7a') // implementation project(path: ':opencv-armv64') diff --git a/app/release/app-release.apk b/app/release/app-release.apk index 60cf507..31ac50f 100644 Binary files a/app/release/app-release.apk and b/app/release/app-release.apk differ diff --git a/app/release/output-metadata.json b/app/release/output-metadata.json index 8c2cb44..7a796f2 100644 --- a/app/release/output-metadata.json +++ b/app/release/output-metadata.json @@ -11,8 +11,8 @@ "type": "SINGLE", "filters": [], "attributes": [], - "versionCode": 7, - "versionName": "1.3.0", + "versionCode": 8, + "versionName": "2.0.0", "outputFile": "app-release.apk" } ], diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 77dd3fb..7664904 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,6 +1,5 @@ - + diff --git a/app/src/main/java/com/king/wechat/qrcode/app/Function.kt b/app/src/main/java/com/king/wechat/qrcode/app/Function.kt index 0860dd5..fee5683 100644 --- a/app/src/main/java/com/king/wechat/qrcode/app/Function.kt +++ b/app/src/main/java/com/king/wechat/qrcode/app/Function.kt @@ -4,7 +4,7 @@ import android.graphics.Bitmap import android.graphics.Canvas import android.graphics.Color import android.graphics.Paint -import com.king.mlkit.vision.camera.util.LogUtils +import com.king.camera.scan.util.LogUtils /** * @author Jenly diff --git a/app/src/main/java/com/king/wechat/qrcode/app/MainActivity.kt b/app/src/main/java/com/king/wechat/qrcode/app/MainActivity.kt index aae70c8..2ba88c2 100644 --- a/app/src/main/java/com/king/wechat/qrcode/app/MainActivity.kt +++ b/app/src/main/java/com/king/wechat/qrcode/app/MainActivity.kt @@ -10,9 +10,9 @@ import android.widget.Toast import androidx.appcompat.app.AppCompatActivity import androidx.core.app.ActivityOptionsCompat import androidx.lifecycle.lifecycleScope -import com.king.mlkit.vision.camera.CameraScan -import com.king.mlkit.vision.camera.util.LogUtils -import com.king.mlkit.vision.camera.util.PermissionUtils +import com.king.camera.scan.CameraScan +import com.king.camera.scan.util.LogUtils +import com.king.camera.scan.util.PermissionUtils import com.king.opencv.qrcode.OpenCVQRCodeDetector import com.king.wechat.qrcode.WeChatQRCodeDetector import kotlinx.coroutines.Dispatchers diff --git a/app/src/main/java/com/king/wechat/qrcode/app/OpenCVQRCodeActivity.kt b/app/src/main/java/com/king/wechat/qrcode/app/OpenCVQRCodeActivity.kt index 560bc26..8755c25 100644 --- a/app/src/main/java/com/king/wechat/qrcode/app/OpenCVQRCodeActivity.kt +++ b/app/src/main/java/com/king/wechat/qrcode/app/OpenCVQRCodeActivity.kt @@ -7,9 +7,9 @@ import android.view.View import android.widget.ImageView import com.king.app.dialog.AppDialog import com.king.app.dialog.AppDialogConfig -import com.king.mlkit.vision.camera.AnalyzeResult -import com.king.mlkit.vision.camera.CameraScan -import com.king.mlkit.vision.camera.analyze.Analyzer +import com.king.camera.scan.AnalyzeResult +import com.king.camera.scan.CameraScan +import com.king.camera.scan.analyze.Analyzer import com.king.opencv.qrcode.scanning.OpenCVCameraScanActivity import com.king.opencv.qrcode.scanning.analyze.OpenCVScanningAnalyzer @@ -83,10 +83,6 @@ class OpenCVQRCodeActivity : OpenCVCameraScanActivity() { return OpenCVScanningAnalyzer(true) } - override fun getLayoutId(): Int { - return R.layout.activity_opencv_qrcode - } - companion object { const val TAG = "OpenCVQRCodeActivity" } diff --git a/app/src/main/java/com/king/wechat/qrcode/app/WeChatMultiQRCodeActivity.kt b/app/src/main/java/com/king/wechat/qrcode/app/WeChatMultiQRCodeActivity.kt index e8fbd92..7380d5d 100644 --- a/app/src/main/java/com/king/wechat/qrcode/app/WeChatMultiQRCodeActivity.kt +++ b/app/src/main/java/com/king/wechat/qrcode/app/WeChatMultiQRCodeActivity.kt @@ -1,5 +1,6 @@ package com.king.wechat.qrcode.app +import android.annotation.SuppressLint import android.content.Intent import android.graphics.Path import android.util.Log @@ -7,9 +8,9 @@ import android.view.View import android.widget.ImageView import com.king.app.dialog.AppDialog import com.king.app.dialog.AppDialogConfig -import com.king.mlkit.vision.camera.AnalyzeResult -import com.king.mlkit.vision.camera.CameraScan -import com.king.mlkit.vision.camera.analyze.Analyzer +import com.king.camera.scan.AnalyzeResult +import com.king.camera.scan.CameraScan +import com.king.camera.scan.analyze.Analyzer import com.king.wechat.qrcode.scanning.WeChatCameraScanActivity import com.king.wechat.qrcode.scanning.analyze.WeChatScanningAnalyzer @@ -20,6 +21,7 @@ import com.king.wechat.qrcode.scanning.analyze.WeChatScanningAnalyzer */ class WeChatMultiQRCodeActivity : WeChatCameraScanActivity() { + @SuppressLint("LongLogTag") override fun onScanResultCallback(result: AnalyzeResult>) { // 停止分析 cameraScan.setAnalyzeImage(false) @@ -28,7 +30,7 @@ class WeChatMultiQRCodeActivity : WeChatCameraScanActivity() { if (result is WeChatScanningAnalyzer.QRCodeAnalyzeResult) { // 如果需要处理结果二维码的位置信息 val buffer = StringBuilder() - val bitmap = result.bitmap.drawRect { canvas, paint -> + val bitmap = result.bitmap?.drawRect { canvas, paint -> // 扫码结果可能有多个 result.result.forEachIndexed { index, data -> buffer.append("[$index] ").append(data).append("\n") @@ -86,10 +88,6 @@ class WeChatMultiQRCodeActivity : WeChatCameraScanActivity() { return WeChatScanningAnalyzer(true) } - override fun getLayoutId(): Int { - return R.layout.activity_wechat_multi_qrcode - } - companion object { const val TAG = "WeChatMultiQRCodeActivity" } diff --git a/app/src/main/java/com/king/wechat/qrcode/app/WeChatQRCodeActivity.kt b/app/src/main/java/com/king/wechat/qrcode/app/WeChatQRCodeActivity.kt index 2025320..92dbaa0 100644 --- a/app/src/main/java/com/king/wechat/qrcode/app/WeChatQRCodeActivity.kt +++ b/app/src/main/java/com/king/wechat/qrcode/app/WeChatQRCodeActivity.kt @@ -4,10 +4,10 @@ import android.content.Intent import android.graphics.Point import android.util.Log import android.widget.ImageView -import com.king.mlkit.vision.camera.AnalyzeResult -import com.king.mlkit.vision.camera.CameraScan -import com.king.mlkit.vision.camera.analyze.Analyzer -import com.king.mlkit.vision.camera.util.PointUtils +import com.king.camera.scan.AnalyzeResult +import com.king.camera.scan.CameraScan +import com.king.camera.scan.analyze.Analyzer +import com.king.camera.scan.util.PointUtils import com.king.view.viewfinderview.ViewfinderView import com.king.wechat.qrcode.scanning.WeChatCameraScanActivity import com.king.wechat.qrcode.scanning.analyze.WeChatScanningAnalyzer @@ -20,19 +20,23 @@ import com.king.wechat.qrcode.scanning.analyze.WeChatScanningAnalyzer class WeChatQRCodeActivity : WeChatCameraScanActivity() { private lateinit var ivResult: ImageView - private lateinit var viewfinderView: ViewfinderView override fun initUI() { super.initUI() ivResult = findViewById(R.id.ivResult) - viewfinderView = findViewById(R.id.viewfinderView) } override fun onScanResultCallback(result: AnalyzeResult>) { // 停止分析 cameraScan.setAnalyzeImage(false) Log.d(TAG, result.result.toString()) - + val frameMetadata = result.frameMetadata + var width = frameMetadata.width + var height = frameMetadata.height + if(frameMetadata.rotation == 90 || frameMetadata.rotation == 270) { + width = frameMetadata.height + height = frameMetadata.width + } // 当初始化 WeChatScanningAnalyzer 时,如果是需要二维码的位置信息,则会返回 WeChatScanningAnalyzer.QRCodeAnalyzeResult if (result is WeChatScanningAnalyzer.QRCodeAnalyzeResult) { // 如果需要处理结果二维码的位置信息 //取预览当前帧图片并显示,为结果点提供参照 @@ -57,8 +61,8 @@ class WeChatQRCodeActivity : WeChatCameraScanActivity() { val point = PointUtils.transform( centerX, centerY, - result.bitmap.width, - result.bitmap.height, + width, + height, viewfinderView.width, viewfinderView.height ) diff --git a/app/src/main/res/layout/activity_opencv_qrcode.xml b/app/src/main/res/layout/activity_opencv_qrcode.xml deleted file mode 100644 index 369a093..0000000 --- a/app/src/main/res/layout/activity_opencv_qrcode.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/activity_wechat_multi_qrcode.xml b/app/src/main/res/layout/activity_wechat_multi_qrcode.xml deleted file mode 100644 index 369a093..0000000 --- a/app/src/main/res/layout/activity_wechat_multi_qrcode.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/activity_wechat_qrcode.xml b/app/src/main/res/layout/activity_wechat_qrcode.xml index 516611d..927c8e5 100644 --- a/app/src/main/res/layout/activity_wechat_qrcode.xml +++ b/app/src/main/res/layout/activity_wechat_qrcode.xml @@ -19,4 +19,12 @@ app:vvLaserStyle="image" app:vvLaserDrawableRatio="0.8" app:vvLaserDrawable="@drawable/ic_laser_line"/> + \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 5397f3b..b911c69 100644 --- a/gradle.properties +++ b/gradle.properties @@ -18,8 +18,8 @@ android.useAndroidX = true # Kotlin code style for this project: "official" or "obsolete": kotlin.code.style = official -VERSION_NAME=1.3.0 -VERSION_CODE=7 +VERSION_NAME=2.0.0 +VERSION_CODE=8 GROUP=com.github.jenly1314.WeChatQRCode POM_DESCRIPTION=Wechat QRCode @@ -44,6 +44,4 @@ SONATYPE_HOST=S01 RELEASE_REPOSITORY_URL=https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/ SNAPSHOT_REPOSITORY_URL=https://s01.oss.sonatype.org/content/repositories/snapshots/ -RELEASE_SIGNING_ENABLED=false - - +RELEASE_SIGNING_ENABLED=false \ No newline at end of file diff --git a/opencv-armv64/build.gradle b/opencv-armv64/build.gradle index 6110355..93d3c81 100644 --- a/opencv-armv64/build.gradle +++ b/opencv-armv64/build.gradle @@ -10,15 +10,14 @@ android { defaultConfig { minSdk build_versions.minSdk targetSdk build_versions.targetSdk - versionCode opencv_version.versionCode - versionName opencv_version.versionName + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles "consumer-rules.pro" } - buildTypes { release { minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } @@ -26,10 +25,10 @@ android { sourceCompatibility JavaVersion.VERSION_11 targetCompatibility JavaVersion.VERSION_11 } - - lintOptions { + lint { abortOnError false } + } dependencies { diff --git a/opencv-armv64/src/main/AndroidManifest.xml b/opencv-armv64/src/main/AndroidManifest.xml index 509d6d2..9a40236 100644 --- a/opencv-armv64/src/main/AndroidManifest.xml +++ b/opencv-armv64/src/main/AndroidManifest.xml @@ -1,4 +1,3 @@ - + diff --git a/opencv-armv7a/build.gradle b/opencv-armv7a/build.gradle index 94c6a2c..3efaa58 100644 --- a/opencv-armv7a/build.gradle +++ b/opencv-armv7a/build.gradle @@ -10,15 +10,14 @@ android { defaultConfig { minSdk build_versions.minSdk targetSdk build_versions.targetSdk - versionCode opencv_version.versionCode - versionName opencv_version.versionName + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles "consumer-rules.pro" } - buildTypes { release { minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } @@ -26,10 +25,10 @@ android { sourceCompatibility JavaVersion.VERSION_11 targetCompatibility JavaVersion.VERSION_11 } - - lintOptions { + lint { abortOnError false } + } dependencies { diff --git a/opencv-armv7a/src/main/AndroidManifest.xml b/opencv-armv7a/src/main/AndroidManifest.xml index f3e54fc..9a40236 100644 --- a/opencv-armv7a/src/main/AndroidManifest.xml +++ b/opencv-armv7a/src/main/AndroidManifest.xml @@ -1,4 +1,3 @@ - + diff --git a/opencv-qrcode-scanning/build.gradle b/opencv-qrcode-scanning/build.gradle index 78d75b6..24412aa 100644 --- a/opencv-qrcode-scanning/build.gradle +++ b/opencv-qrcode-scanning/build.gradle @@ -10,17 +10,14 @@ android { defaultConfig { minSdk build_versions.minSdk targetSdk build_versions.targetSdk - versionCode app_version.versionCode - versionName app_version.versionName testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles "consumer-rules.pro" } - buildTypes { release { minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } @@ -28,10 +25,10 @@ android { sourceCompatibility JavaVersion.VERSION_11 targetCompatibility JavaVersion.VERSION_11 } - - lintOptions { + lint { abortOnError false } + } dependencies { @@ -42,7 +39,8 @@ dependencies { androidTestImplementation "androidx.test.ext:junit:$versions.androidExtJunit" androidTestImplementation "androidx.test.espresso:espresso-core:$versions.espressoCore" - api "com.github.jenly1314.MLKit:mlkit-camera-core:$versions.mlkit" + api "com.github.jenly1314:CameraScan:$versions.cameraScan" + api "com.github.jenly1314:viewfinderview:$versions.viewfinderview" compileOnly project(path: ':opencv') compileOnly project(path: ':opencv-qrcode') diff --git a/opencv-qrcode-scanning/src/main/java/com/king/opencv/qrcode/scanning/OpenCVCameraScanActivity.java b/opencv-qrcode-scanning/src/main/java/com/king/opencv/qrcode/scanning/OpenCVCameraScanActivity.java index ee024da..5a78e3d 100644 --- a/opencv-qrcode-scanning/src/main/java/com/king/opencv/qrcode/scanning/OpenCVCameraScanActivity.java +++ b/opencv-qrcode-scanning/src/main/java/com/king/opencv/qrcode/scanning/OpenCVCameraScanActivity.java @@ -1,8 +1,11 @@ package com.king.opencv.qrcode.scanning; -import com.king.mlkit.vision.camera.BaseCameraScanActivity; -import com.king.mlkit.vision.camera.analyze.Analyzer; +import android.view.View; + +import com.king.camera.scan.BaseCameraScanActivity; +import com.king.camera.scan.analyze.Analyzer; import com.king.opencv.qrcode.scanning.analyze.OpenCVScanningAnalyzer; +import com.king.view.viewfinderview.ViewfinderView; import java.util.List; @@ -16,9 +19,41 @@ * @author Jenly */ public abstract class OpenCVCameraScanActivity extends BaseCameraScanActivity> { + + protected ViewfinderView viewfinderView; + + @Override + public void initUI() { + int viewfinderViewId = getViewfinderViewId(); + if (viewfinderViewId != View.NO_ID && viewfinderViewId != 0) { + viewfinderView = findViewById(viewfinderViewId); + } + super.initUI(); + } + @Nullable @Override public Analyzer> createAnalyzer() { return new OpenCVScanningAnalyzer(); } + + /** + * 布局ID;通过覆写此方法可以自定义布局 + * + * @return 布局ID + */ + @Override + public int getLayoutId() { + return R.layout.opencv_camera_scan; + } + + /** + * {@link #viewfinderView} 的 ID + * + * @return 默认返回{@code R.id.viewfinderView}, 如果不需要扫码框可以返回{@link View#NO_ID} + */ + + public int getViewfinderViewId() { + return R.id.viewfinderView; + } } diff --git a/opencv-qrcode-scanning/src/main/java/com/king/opencv/qrcode/scanning/OpenCVCameraScanFragment.java b/opencv-qrcode-scanning/src/main/java/com/king/opencv/qrcode/scanning/OpenCVCameraScanFragment.java index 5063266..e571721 100644 --- a/opencv-qrcode-scanning/src/main/java/com/king/opencv/qrcode/scanning/OpenCVCameraScanFragment.java +++ b/opencv-qrcode-scanning/src/main/java/com/king/opencv/qrcode/scanning/OpenCVCameraScanFragment.java @@ -1,8 +1,11 @@ package com.king.opencv.qrcode.scanning; -import com.king.mlkit.vision.camera.BaseCameraScanFragment; -import com.king.mlkit.vision.camera.analyze.Analyzer; +import android.view.View; + +import com.king.camera.scan.BaseCameraScanFragment; +import com.king.camera.scan.analyze.Analyzer; import com.king.opencv.qrcode.scanning.analyze.OpenCVScanningAnalyzer; +import com.king.view.viewfinderview.ViewfinderView; import java.util.List; @@ -16,9 +19,41 @@ * @author Jenly */ public abstract class OpenCVCameraScanFragment extends BaseCameraScanFragment> { + + protected ViewfinderView viewfinderView; + + @Override + public void initUI() { + int viewfinderViewId = getViewfinderViewId(); + if (viewfinderViewId != View.NO_ID && viewfinderViewId != 0) { + viewfinderView = getRootView().findViewById(viewfinderViewId); + } + super.initUI(); + } + @Nullable @Override public Analyzer> createAnalyzer() { return new OpenCVScanningAnalyzer(); } + + /** + * 布局ID;通过覆写此方法可以自定义布局 + * + * @return 布局ID + */ + @Override + public int getLayoutId() { + return R.layout.opencv_camera_scan; + } + + /** + * {@link #viewfinderView} 的 ID + * + * @return 默认返回{@code R.id.viewfinderView}, 如果不需要扫码框可以返回{@link View#NO_ID} + */ + + public int getViewfinderViewId() { + return R.id.viewfinderView; + } } diff --git a/opencv-qrcode-scanning/src/main/java/com/king/opencv/qrcode/scanning/analyze/OpenCVScanningAnalyzer.java b/opencv-qrcode-scanning/src/main/java/com/king/opencv/qrcode/scanning/analyze/OpenCVScanningAnalyzer.java index 5973768..a676265 100644 --- a/opencv-qrcode-scanning/src/main/java/com/king/opencv/qrcode/scanning/analyze/OpenCVScanningAnalyzer.java +++ b/opencv-qrcode-scanning/src/main/java/com/king/opencv/qrcode/scanning/analyze/OpenCVScanningAnalyzer.java @@ -1,17 +1,24 @@ package com.king.opencv.qrcode.scanning.analyze; -import android.graphics.Bitmap; +import android.graphics.ImageFormat; -import com.king.mlkit.vision.camera.AnalyzeResult; -import com.king.mlkit.vision.camera.analyze.Analyzer; -import com.king.mlkit.vision.camera.util.BitmapUtils; -import com.king.mlkit.vision.camera.util.LogUtils; +import com.king.camera.scan.AnalyzeResult; +import com.king.camera.scan.FrameMetadata; +import com.king.camera.scan.analyze.Analyzer; +import com.king.camera.scan.util.LogUtils; import com.king.opencv.qrcode.OpenCVQRCodeDetector; +import org.opencv.core.Core; +import org.opencv.core.CvType; import org.opencv.core.Mat; +import org.opencv.imgproc.Imgproc; +import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.atomic.AtomicBoolean; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -24,11 +31,19 @@ */ public class OpenCVScanningAnalyzer implements Analyzer> { - private OpenCVQRCodeDetector mDetector = new OpenCVQRCodeDetector(); + private static final int ROTATION_90 = 90; + private static final int ROTATION_180 = 180; + private static final int ROTATION_270 = 270; + + private final Queue queue = new ConcurrentLinkedQueue<>(); + + private final AtomicBoolean joinQueue = new AtomicBoolean(false); + + private final OpenCVQRCodeDetector mDetector = new OpenCVQRCodeDetector(); /** * 是否需要输出二维码的各个顶点 */ - private boolean isOutputVertices; + private final boolean isOutputVertices; public OpenCVScanningAnalyzer() { this(false); @@ -44,17 +59,33 @@ public OpenCVScanningAnalyzer(boolean isOutputVertices) { } @Override - public void analyze(@NonNull ImageProxy imageProxy, @NonNull OnAnalyzeListener>> listener) { + public void analyze(@NonNull ImageProxy imageProxy, @NonNull Analyzer.OnAnalyzeListener>> listener) { + if (!joinQueue.get()) { + int imageSize = imageProxy.getWidth() * imageProxy.getHeight(); + byte[] bytes = new byte[imageSize + 2 * (imageSize / 4)]; + queue.add(bytes); + joinQueue.set(true); + } + if (queue.isEmpty()) { + return; + } + final byte[] nv21Data = queue.poll(); AnalyzeResult> result = null; try { - final Bitmap bitmap = BitmapUtils.getBitmap(imageProxy); - result = detectAndDecode(bitmap, isOutputVertices); + yuv_420_888toNv21(imageProxy, nv21Data); + FrameMetadata frameMetadata = new FrameMetadata( + imageProxy.getWidth(), + imageProxy.getHeight(), + imageProxy.getImageInfo().getRotationDegrees()); + result = detectAndDecode(nv21Data, frameMetadata, isOutputVertices); } catch (Exception e) { LogUtils.w(e); } - if (result != null && !result.getResult().isEmpty()) { + if (result != null) { + joinQueue.set(false); listener.onSuccess(result); } else { + queue.add(nv21Data); listener.onFailure(null); } } @@ -62,33 +93,120 @@ public void analyze(@NonNull ImageProxy imageProxy, @NonNull OnAnalyzeListener> detectAndDecode(Bitmap bitmap, boolean isOutputVertices) { + private AnalyzeResult> detectAndDecode(byte[] nv21, FrameMetadata frameMetadata, boolean isOutputVertices) { + Mat mat = new Mat(frameMetadata.getHeight() + frameMetadata.getHeight() / 2, frameMetadata.getWidth(), CvType.CV_8UC1); + mat.put(0,0, nv21); + Mat bgrMat = new Mat(); + Imgproc.cvtColor(mat, bgrMat, Imgproc.COLOR_YUV2BGR_NV21); + mat.release(); + rotation(bgrMat, frameMetadata.getRotation()); if (isOutputVertices) { // 如果需要返回二维码的各个顶点 final Mat points = new Mat(); - String result = mDetector.detectAndDecode(bitmap, points); + String result = mDetector.detectAndDecode(bgrMat, points); + bgrMat.release(); if (result != null && !result.isEmpty()) { List list = new ArrayList(); list.add(result); - return new QRCodeAnalyzeResult<>(bitmap, list, points); + return new QRCodeAnalyzeResult<>(nv21, ImageFormat.NV21, frameMetadata, list, points); } } else { // 反之则需识别结果即可 - String result = mDetector.detectAndDecode(bitmap); + String result = mDetector.detectAndDecode(bgrMat); + bgrMat.release(); if (result != null && !result.isEmpty()) { List list = new ArrayList(); list.add(result); - return new QRCodeAnalyzeResult(bitmap, list); + return new QRCodeAnalyzeResult<>(nv21, ImageFormat.NV21, frameMetadata, list); } } return null; } + /** + * YUV420_888转NV21 + * + * @param image + * @param nv21 + */ + private void yuv_420_888toNv21(@NonNull ImageProxy image, byte[] nv21) { + ImageProxy.PlaneProxy yPlane = image.getPlanes()[0]; + ImageProxy.PlaneProxy uPlane = image.getPlanes()[1]; + ImageProxy.PlaneProxy vPlane = image.getPlanes()[2]; + + ByteBuffer yBuffer = yPlane.getBuffer(); + ByteBuffer uBuffer = uPlane.getBuffer(); + ByteBuffer vBuffer = vPlane.getBuffer(); + yBuffer.rewind(); + uBuffer.rewind(); + vBuffer.rewind(); + + int ySize = yBuffer.remaining(); + + int position = 0; + + // Add the full y buffer to the array. If rowStride > 1, some padding may be skipped. + for (int row = 0; row < image.getHeight(); row++) { + yBuffer.get(nv21, position, image.getWidth()); + position += image.getWidth(); + yBuffer.position(Math.min(ySize, yBuffer.position() - image.getWidth() + yPlane.getRowStride())); + } + + int chromaHeight = image.getHeight() / 2; + int chromaWidth = image.getWidth() / 2; + int vRowStride = vPlane.getRowStride(); + int uRowStride = uPlane.getRowStride(); + int vPixelStride = vPlane.getPixelStride(); + int uPixelStride = uPlane.getPixelStride(); + + // Interleave the u and v frames, filling up the rest of the buffer. Use two line buffers to + // perform faster bulk gets from the byte buffers. + byte[] vLineBuffer = new byte[vRowStride]; + byte[] uLineBuffer = new byte[uRowStride]; + for (int row = 0; row < chromaHeight; row++) { + vBuffer.get(vLineBuffer, 0, Math.min(vRowStride, vBuffer.remaining())); + uBuffer.get(uLineBuffer, 0, Math.min(uRowStride, uBuffer.remaining())); + int vLineBufferPosition = 0; + int uLineBufferPosition = 0; + for (int col = 0; col < chromaWidth; col++) { + nv21[position++] = vLineBuffer[vLineBufferPosition]; + nv21[position++] = uLineBuffer[uLineBufferPosition]; + vLineBufferPosition += vPixelStride; + uLineBufferPosition += uPixelStride; + } + } + } + + /** + * 旋转指定角度 + * @param mat + * @param rotation + */ + private void rotation(Mat mat, int rotation) { + // 旋转90° + if (rotation == ROTATION_90) { + // 将图像逆时针旋转90°,然后再关于x轴对称 + Core.transpose(mat, mat); + // 然后再绕Y轴旋转180° (顺时针) + Core.flip(mat, mat, 1); + } else if (rotation == ROTATION_180) { + //将图片绕X轴旋转180°(顺时针) + Core.flip(mat, mat, 0); + //将图片绕Y轴旋转180°(顺时针) + Core.flip(mat, mat, 1); + } else if (rotation == ROTATION_270) { + // 将图像逆时针旋转90°,然后再关于x轴对称 + Core.transpose(mat, mat); + // 将图片绕X轴旋转180°(顺时针) + Core.flip(mat, mat, 0); + } + } + /** * 二维码分析结果 * @@ -101,12 +219,12 @@ public static class QRCodeAnalyzeResult extends AnalyzeResult { */ private Mat points; - public QRCodeAnalyzeResult(Bitmap bitmap, T result) { - super(bitmap, result); + public QRCodeAnalyzeResult(@NonNull byte[] imageData, int imageFormat, @NonNull FrameMetadata frameMetadata, @NonNull T result) { + super(imageData, imageFormat, frameMetadata, result); } - public QRCodeAnalyzeResult(Bitmap bitmap, T result, Mat points) { - super(bitmap, result); + public QRCodeAnalyzeResult(@NonNull byte[] imageData, int imageFormat, @NonNull FrameMetadata frameMetadata, @NonNull T result, @Nullable Mat points) { + super(imageData, imageFormat, frameMetadata, result); this.points = points; } @@ -119,9 +237,5 @@ public Mat getPoints() { return points; } - @Deprecated - public void setPoints(Mat points) { - this.points = points; - } } } diff --git a/opencv-qrcode-scanning/src/main/res/layout/opencv_camera_scan.xml b/opencv-qrcode-scanning/src/main/res/layout/opencv_camera_scan.xml new file mode 100644 index 0000000..399bef4 --- /dev/null +++ b/opencv-qrcode-scanning/src/main/res/layout/opencv_camera_scan.xml @@ -0,0 +1,24 @@ + + + + + + + + + \ No newline at end of file diff --git a/opencv-qrcode/build.gradle b/opencv-qrcode/build.gradle index 0150d4b..60335ad 100644 --- a/opencv-qrcode/build.gradle +++ b/opencv-qrcode/build.gradle @@ -10,17 +10,14 @@ android { defaultConfig { minSdk build_versions.minSdk targetSdk build_versions.targetSdk - versionCode app_version.versionCode - versionName app_version.versionName testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles "consumer-rules.pro" } - buildTypes { release { minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } @@ -28,10 +25,10 @@ android { sourceCompatibility JavaVersion.VERSION_11 targetCompatibility JavaVersion.VERSION_11 } - - lintOptions { + lint { abortOnError false } + } dependencies { diff --git a/opencv-x86/build.gradle b/opencv-x86/build.gradle index 53c0de2..5fea5b1 100644 --- a/opencv-x86/build.gradle +++ b/opencv-x86/build.gradle @@ -10,15 +10,14 @@ android { defaultConfig { minSdk build_versions.minSdk targetSdk build_versions.targetSdk - versionCode opencv_version.versionCode - versionName opencv_version.versionName + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles "consumer-rules.pro" } - buildTypes { release { minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } @@ -26,10 +25,10 @@ android { sourceCompatibility JavaVersion.VERSION_11 targetCompatibility JavaVersion.VERSION_11 } - - lintOptions { + lint { abortOnError false } + } dependencies { diff --git a/opencv-x86/src/main/AndroidManifest.xml b/opencv-x86/src/main/AndroidManifest.xml index 0130f42..9a40236 100644 --- a/opencv-x86/src/main/AndroidManifest.xml +++ b/opencv-x86/src/main/AndroidManifest.xml @@ -1,4 +1,3 @@ - + diff --git a/opencv-x86_64/build.gradle b/opencv-x86_64/build.gradle index d40dd0c..ab7cb5e 100644 --- a/opencv-x86_64/build.gradle +++ b/opencv-x86_64/build.gradle @@ -10,15 +10,14 @@ android { defaultConfig { minSdk build_versions.minSdk targetSdk build_versions.targetSdk - versionCode opencv_version.versionCode - versionName opencv_version.versionName + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles "consumer-rules.pro" } - buildTypes { release { minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } @@ -26,10 +25,10 @@ android { sourceCompatibility JavaVersion.VERSION_11 targetCompatibility JavaVersion.VERSION_11 } - - lintOptions { + lint { abortOnError false } + } dependencies { diff --git a/opencv-x86_64/src/main/AndroidManifest.xml b/opencv-x86_64/src/main/AndroidManifest.xml index 71ee8fe..9a40236 100644 --- a/opencv-x86_64/src/main/AndroidManifest.xml +++ b/opencv-x86_64/src/main/AndroidManifest.xml @@ -1,4 +1,3 @@ - + diff --git a/opencv/build.gradle b/opencv/build.gradle index c1efce0..1c0666a 100644 --- a/opencv/build.gradle +++ b/opencv/build.gradle @@ -101,8 +101,6 @@ android { defaultConfig { minSdk build_versions.minSdk targetSdk build_versions.targetSdk - versionCode opencv_version.versionCode - versionName opencv_version.versionName externalNativeBuild { cmake { @@ -135,7 +133,6 @@ android { lintOptions { abortOnError false - warning 'InvalidPackage' } sourceSets { @@ -155,6 +152,10 @@ android { } ndkVersion '22.1.7171670' + lint { + abortOnError false + warning 'InvalidPackage' + } } dependencies { diff --git a/settings.gradle b/settings.gradle index 23a6012..72f30de 100644 --- a/settings.gradle +++ b/settings.gradle @@ -10,6 +10,7 @@ dependencyResolutionManagement { repositories { google() mavenCentral() + maven { url 'https://jitpack.io' } } } diff --git a/versions.gradle b/versions.gradle index 517e533..84d2a65 100644 --- a/versions.gradle +++ b/versions.gradle @@ -1,7 +1,7 @@ //App def app_version = [:] -app_version.versionCode = 7 -app_version.versionName = "1.3.0" +app_version.versionCode = 8 +app_version.versionName = "2.0.0" ext.app_version = app_version def opencv_version = [:] @@ -36,8 +36,8 @@ versions.androidExtJunit = "1.1.3" versions.espressoCore = "3.4.0" versions.lifecycleKtx="2.3.1" -versions.mlkit="1.4.0" versions.viewfinderview="1.0.0" +versions.cameraScan="1.0.0" versions.appDialog="1.1.4" diff --git a/wechat-qrcode-scanning/build.gradle b/wechat-qrcode-scanning/build.gradle index 3516239..ab0f370 100644 --- a/wechat-qrcode-scanning/build.gradle +++ b/wechat-qrcode-scanning/build.gradle @@ -10,17 +10,14 @@ android { defaultConfig { minSdk build_versions.minSdk targetSdk build_versions.targetSdk - versionCode app_version.versionCode - versionName app_version.versionName testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles "consumer-rules.pro" } - buildTypes { release { minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } @@ -28,10 +25,10 @@ android { sourceCompatibility JavaVersion.VERSION_11 targetCompatibility JavaVersion.VERSION_11 } - - lintOptions { + lint { abortOnError false } + } dependencies { @@ -42,7 +39,8 @@ dependencies { androidTestImplementation "androidx.test.ext:junit:$versions.androidExtJunit" androidTestImplementation "androidx.test.espresso:espresso-core:$versions.espressoCore" - api "com.github.jenly1314.MLKit:mlkit-camera-core:$versions.mlkit" + api "com.github.jenly1314:CameraScan:$versions.cameraScan" + api "com.github.jenly1314:viewfinderview:$versions.viewfinderview" compileOnly project(path: ':opencv') compileOnly project(path: ':wechat-qrcode') diff --git a/wechat-qrcode-scanning/src/main/AndroidManifest.xml b/wechat-qrcode-scanning/src/main/AndroidManifest.xml index 2ffb9d0..a5918e6 100644 --- a/wechat-qrcode-scanning/src/main/AndroidManifest.xml +++ b/wechat-qrcode-scanning/src/main/AndroidManifest.xml @@ -1,5 +1,4 @@ - + \ No newline at end of file diff --git a/wechat-qrcode-scanning/src/main/java/com/king/wechat/qrcode/scanning/WeChatCameraScanActivity.java b/wechat-qrcode-scanning/src/main/java/com/king/wechat/qrcode/scanning/WeChatCameraScanActivity.java index 273305c..8ff9652 100644 --- a/wechat-qrcode-scanning/src/main/java/com/king/wechat/qrcode/scanning/WeChatCameraScanActivity.java +++ b/wechat-qrcode-scanning/src/main/java/com/king/wechat/qrcode/scanning/WeChatCameraScanActivity.java @@ -1,7 +1,10 @@ package com.king.wechat.qrcode.scanning; -import com.king.mlkit.vision.camera.BaseCameraScanActivity; -import com.king.mlkit.vision.camera.analyze.Analyzer; +import android.view.View; + +import com.king.camera.scan.BaseCameraScanActivity; +import com.king.camera.scan.analyze.Analyzer; +import com.king.view.viewfinderview.ViewfinderView; import com.king.wechat.qrcode.scanning.analyze.WeChatScanningAnalyzer; import java.util.List; @@ -16,9 +19,41 @@ * @author Jenly */ public abstract class WeChatCameraScanActivity extends BaseCameraScanActivity> { + + protected ViewfinderView viewfinderView; + + @Override + public void initUI() { + int viewfinderViewId = getViewfinderViewId(); + if (viewfinderViewId != View.NO_ID && viewfinderViewId != 0) { + viewfinderView = findViewById(viewfinderViewId); + } + super.initUI(); + } + @Nullable @Override public Analyzer> createAnalyzer() { return new WeChatScanningAnalyzer(); } + + /** + * 布局ID;通过覆写此方法可以自定义布局 + * + * @return 布局ID + */ + @Override + public int getLayoutId() { + return R.layout.wechat_camera_scan; + } + + /** + * {@link #viewfinderView} 的 ID + * + * @return 默认返回{@code R.id.viewfinderView}, 如果不需要扫码框可以返回{@link View#NO_ID} + */ + + public int getViewfinderViewId() { + return R.id.viewfinderView; + } } \ No newline at end of file diff --git a/wechat-qrcode-scanning/src/main/java/com/king/wechat/qrcode/scanning/WeChatCameraScanFragment.java b/wechat-qrcode-scanning/src/main/java/com/king/wechat/qrcode/scanning/WeChatCameraScanFragment.java index 00f707f..aa24c2f 100644 --- a/wechat-qrcode-scanning/src/main/java/com/king/wechat/qrcode/scanning/WeChatCameraScanFragment.java +++ b/wechat-qrcode-scanning/src/main/java/com/king/wechat/qrcode/scanning/WeChatCameraScanFragment.java @@ -1,7 +1,10 @@ package com.king.wechat.qrcode.scanning; -import com.king.mlkit.vision.camera.BaseCameraScanFragment; -import com.king.mlkit.vision.camera.analyze.Analyzer; +import android.view.View; + +import com.king.camera.scan.BaseCameraScanFragment; +import com.king.camera.scan.analyze.Analyzer; +import com.king.view.viewfinderview.ViewfinderView; import com.king.wechat.qrcode.scanning.analyze.WeChatScanningAnalyzer; import java.util.List; @@ -16,9 +19,40 @@ * @author Jenly */ public abstract class WeChatCameraScanFragment extends BaseCameraScanFragment> { + protected ViewfinderView viewfinderView; + + @Override + public void initUI() { + int viewfinderViewId = getViewfinderViewId(); + if (viewfinderViewId != View.NO_ID && viewfinderViewId != 0) { + viewfinderView = getRootView().findViewById(viewfinderViewId); + } + super.initUI(); + } + @Nullable @Override public Analyzer> createAnalyzer() { return new WeChatScanningAnalyzer(); } + + /** + * 布局ID;通过覆写此方法可以自定义布局 + * + * @return 布局ID + */ + @Override + public int getLayoutId() { + return R.layout.wechat_camera_scan; + } + + /** + * {@link #viewfinderView} 的 ID + * + * @return 默认返回{@code R.id.viewfinderView}, 如果不需要扫码框可以返回{@link View#NO_ID} + */ + + public int getViewfinderViewId() { + return R.id.viewfinderView; + } } diff --git a/wechat-qrcode-scanning/src/main/java/com/king/wechat/qrcode/scanning/analyze/WeChatScanningAnalyzer.java b/wechat-qrcode-scanning/src/main/java/com/king/wechat/qrcode/scanning/analyze/WeChatScanningAnalyzer.java index b633cde..acf5d2b 100644 --- a/wechat-qrcode-scanning/src/main/java/com/king/wechat/qrcode/scanning/analyze/WeChatScanningAnalyzer.java +++ b/wechat-qrcode-scanning/src/main/java/com/king/wechat/qrcode/scanning/analyze/WeChatScanningAnalyzer.java @@ -1,17 +1,24 @@ package com.king.wechat.qrcode.scanning.analyze; -import android.graphics.Bitmap; +import android.graphics.ImageFormat; -import com.king.mlkit.vision.camera.AnalyzeResult; -import com.king.mlkit.vision.camera.analyze.Analyzer; -import com.king.mlkit.vision.camera.util.BitmapUtils; -import com.king.mlkit.vision.camera.util.LogUtils; +import com.king.camera.scan.AnalyzeResult; +import com.king.camera.scan.FrameMetadata; +import com.king.camera.scan.analyze.Analyzer; +import com.king.camera.scan.util.LogUtils; import com.king.wechat.qrcode.WeChatQRCodeDetector; +import org.opencv.core.Core; +import org.opencv.core.CvType; import org.opencv.core.Mat; +import org.opencv.imgproc.Imgproc; +import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.atomic.AtomicBoolean; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -24,10 +31,17 @@ */ public class WeChatScanningAnalyzer implements Analyzer> { + private static final int ROTATION_90 = 90; + private static final int ROTATION_180 = 180; + private static final int ROTATION_270 = 270; + private final Queue queue = new ConcurrentLinkedQueue<>(); + + private final AtomicBoolean joinQueue = new AtomicBoolean(false); + /** * 是否需要输出二维码的各个顶点 */ - private boolean isOutputVertices; + private final boolean isOutputVertices; public WeChatScanningAnalyzer() { this(false); @@ -44,16 +58,32 @@ public WeChatScanningAnalyzer(boolean isOutputVertices) { @Override public void analyze(@NonNull ImageProxy imageProxy, @NonNull Analyzer.OnAnalyzeListener>> listener) { + if (!joinQueue.get()) { + int imageSize = imageProxy.getWidth() * imageProxy.getHeight(); + byte[] bytes = new byte[imageSize + 2 * (imageSize / 4)]; + queue.add(bytes); + joinQueue.set(true); + } + if (queue.isEmpty()) { + return; + } + final byte[] nv21Data = queue.poll(); AnalyzeResult> result = null; try { - final Bitmap bitmap = BitmapUtils.getBitmap(imageProxy); - result = detectAndDecode(bitmap, isOutputVertices); + yuv_420_888toNv21(imageProxy, nv21Data); + FrameMetadata frameMetadata = new FrameMetadata( + imageProxy.getWidth(), + imageProxy.getHeight(), + imageProxy.getImageInfo().getRotationDegrees()); + result = detectAndDecode(nv21Data, frameMetadata, isOutputVertices); } catch (Exception e) { LogUtils.w(e); } - if (result != null && !result.getResult().isEmpty()) { + if (result != null) { + joinQueue.set(false); listener.onSuccess(result); } else { + queue.add(nv21Data); listener.onFailure(null); } } @@ -61,30 +91,117 @@ public void analyze(@NonNull ImageProxy imageProxy, @NonNull Analyzer.OnAnalyzeL /** * 检测并识别二维码 * - * @param bitmap + * @param nv21 * @param isOutputVertices * @return */ @Nullable - private AnalyzeResult> detectAndDecode(Bitmap bitmap, boolean isOutputVertices) { + private AnalyzeResult> detectAndDecode(byte[] nv21, FrameMetadata frameMetadata, boolean isOutputVertices) { + Mat mat = new Mat(frameMetadata.getHeight() + frameMetadata.getHeight() / 2, frameMetadata.getWidth(), CvType.CV_8UC1); + mat.put(0,0, nv21); + Mat bgrMat = new Mat(); + Imgproc.cvtColor(mat, bgrMat, Imgproc.COLOR_YUV2BGR_NV21); + mat.release(); + rotation(bgrMat, frameMetadata.getRotation()); if (isOutputVertices) { // 如果需要返回二维码的各个顶点 final List points = new ArrayList<>(); - List result = WeChatQRCodeDetector.detectAndDecode(bitmap, points); + List result = WeChatQRCodeDetector.detectAndDecode(bgrMat, points); + bgrMat.release(); if (result != null && !result.isEmpty()) { - return new QRCodeAnalyzeResult<>(bitmap, result, points); + return new QRCodeAnalyzeResult<>(nv21, ImageFormat.NV21, frameMetadata, result, points); } } else { // 反之则需识别结果即可 - List result = WeChatQRCodeDetector.detectAndDecode(bitmap); + List result = WeChatQRCodeDetector.detectAndDecode(bgrMat); + bgrMat.release(); if (result != null && !result.isEmpty()) { - return new QRCodeAnalyzeResult<>(bitmap, result); + return new QRCodeAnalyzeResult<>(nv21, ImageFormat.NV21, frameMetadata, result); } } return null; } + /** + * 旋转指定角度 + * @param mat + * @param rotation + */ + private void rotation(Mat mat, int rotation) { + // 旋转90° + if (rotation == ROTATION_90) { + // 将图像逆时针旋转90°,然后再关于x轴对称 + Core.transpose(mat, mat); + // 然后再绕Y轴旋转180° (顺时针) + Core.flip(mat, mat, 1); + } else if (rotation == ROTATION_180) { + //将图片绕X轴旋转180°(顺时针) + Core.flip(mat, mat, 0); + //将图片绕Y轴旋转180°(顺时针) + Core.flip(mat, mat, 1); + } else if (rotation == ROTATION_270) { + // 将图像逆时针旋转90°,然后再关于x轴对称 + Core.transpose(mat, mat); + // 将图片绕X轴旋转180°(顺时针) + Core.flip(mat, mat, 0); + } + } + + /** + * YUV420_888转NV21 + * + * @param image + * @param nv21 + */ + private void yuv_420_888toNv21(@NonNull ImageProxy image, byte[] nv21) { + ImageProxy.PlaneProxy yPlane = image.getPlanes()[0]; + ImageProxy.PlaneProxy uPlane = image.getPlanes()[1]; + ImageProxy.PlaneProxy vPlane = image.getPlanes()[2]; + + ByteBuffer yBuffer = yPlane.getBuffer(); + ByteBuffer uBuffer = uPlane.getBuffer(); + ByteBuffer vBuffer = vPlane.getBuffer(); + yBuffer.rewind(); + uBuffer.rewind(); + vBuffer.rewind(); + + int ySize = yBuffer.remaining(); + + int position = 0; + + // Add the full y buffer to the array. If rowStride > 1, some padding may be skipped. + for (int row = 0; row < image.getHeight(); row++) { + yBuffer.get(nv21, position, image.getWidth()); + position += image.getWidth(); + yBuffer.position(Math.min(ySize, yBuffer.position() - image.getWidth() + yPlane.getRowStride())); + } + + int chromaHeight = image.getHeight() / 2; + int chromaWidth = image.getWidth() / 2; + int vRowStride = vPlane.getRowStride(); + int uRowStride = uPlane.getRowStride(); + int vPixelStride = vPlane.getPixelStride(); + int uPixelStride = uPlane.getPixelStride(); + + // Interleave the u and v frames, filling up the rest of the buffer. Use two line buffers to + // perform faster bulk gets from the byte buffers. + byte[] vLineBuffer = new byte[vRowStride]; + byte[] uLineBuffer = new byte[uRowStride]; + for (int row = 0; row < chromaHeight; row++) { + vBuffer.get(vLineBuffer, 0, Math.min(vRowStride, vBuffer.remaining())); + uBuffer.get(uLineBuffer, 0, Math.min(uRowStride, uBuffer.remaining())); + int vLineBufferPosition = 0; + int uLineBufferPosition = 0; + for (int col = 0; col < chromaWidth; col++) { + nv21[position++] = vLineBuffer[vLineBufferPosition]; + nv21[position++] = uLineBuffer[uLineBufferPosition]; + vLineBufferPosition += vPixelStride; + uLineBufferPosition += uPixelStride; + } + } + } + /** * 二维码分析结果 * @@ -97,12 +214,12 @@ public static class QRCodeAnalyzeResult extends AnalyzeResult { */ private List points; - public QRCodeAnalyzeResult(Bitmap bitmap, T result) { - super(bitmap, result); + public QRCodeAnalyzeResult(@NonNull byte[] imageData, int imageFormat, @NonNull FrameMetadata frameMetadata, @NonNull T result) { + super(imageData, imageFormat, frameMetadata, result); } - public QRCodeAnalyzeResult(Bitmap bitmap, T result, List points) { - super(bitmap, result); + public QRCodeAnalyzeResult(@NonNull byte[] imageData, int imageFormat, @NonNull FrameMetadata frameMetadata, @NonNull T result, @Nullable List points) { + super(imageData, imageFormat, frameMetadata, result); this.points = points; } @@ -111,13 +228,10 @@ public QRCodeAnalyzeResult(Bitmap bitmap, T result, List points) { * * @return */ + @Nullable public List getPoints() { return points; } - @Deprecated - public void setPoints(List points) { - this.points = points; - } } } diff --git a/wechat-qrcode-scanning/src/main/res/layout/wechat_camera_scan.xml b/wechat-qrcode-scanning/src/main/res/layout/wechat_camera_scan.xml new file mode 100644 index 0000000..399bef4 --- /dev/null +++ b/wechat-qrcode-scanning/src/main/res/layout/wechat_camera_scan.xml @@ -0,0 +1,24 @@ + + + + + + + + + \ No newline at end of file diff --git a/wechat-qrcode/build.gradle b/wechat-qrcode/build.gradle index dd90665..201b847 100644 --- a/wechat-qrcode/build.gradle +++ b/wechat-qrcode/build.gradle @@ -10,23 +10,14 @@ android { defaultConfig { minSdk build_versions.minSdk targetSdk build_versions.targetSdk - versionCode app_version.versionCode - versionName app_version.versionName testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles "consumer-rules.pro" - - externalNativeBuild { - cmake { - arguments "-DANDROID_STL=c++_shared" - } - } } - buildTypes { release { minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } @@ -34,10 +25,10 @@ android { sourceCompatibility JavaVersion.VERSION_11 targetCompatibility JavaVersion.VERSION_11 } - - lintOptions { + lint { abortOnError false } + } dependencies { diff --git a/wechat-qrcode/src/main/AndroidManifest.xml b/wechat-qrcode/src/main/AndroidManifest.xml index d08ab3c..a5918e6 100644 --- a/wechat-qrcode/src/main/AndroidManifest.xml +++ b/wechat-qrcode/src/main/AndroidManifest.xml @@ -1,5 +1,4 @@ - + \ No newline at end of file diff --git a/wechat-qrcode/src/main/java/com/king/wechat/qrcode/WeChatQRCodeDetector.java b/wechat-qrcode/src/main/java/com/king/wechat/qrcode/WeChatQRCodeDetector.java index a0e64e9..651ea63 100644 --- a/wechat-qrcode/src/main/java/com/king/wechat/qrcode/WeChatQRCodeDetector.java +++ b/wechat-qrcode/src/main/java/com/king/wechat/qrcode/WeChatQRCodeDetector.java @@ -106,8 +106,11 @@ private static String getExternalFilesDir(Context context, String path) { if (files != null && files.length > 0) { return files[0].getAbsolutePath(); } - return context.getExternalFilesDir(path).getAbsolutePath(); - + File file = context.getExternalFilesDir(path); + if(file == null) { + file = new File(context.getFilesDir(), path); + } + return file.getAbsolutePath(); } /**