From 8fa8f42a44ff37d2048bccbb436b78432607daa8 Mon Sep 17 00:00:00 2001 From: Eljo Date: Sun, 14 Feb 2021 23:01:19 +0100 Subject: [PATCH 1/3] Implement zxing in progress --- app/build.gradle | 2 + .../cash/z/ecc/android/ui/scan/QrAnalyzer.kt | 79 +++++++++++-------- 2 files changed, 50 insertions(+), 31 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index db18495..fe5839e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -184,6 +184,8 @@ dependencies { androidTestImplementation Deps.Test.Android.JUNIT androidTestImplementation Deps.Test.Android.ESPRESSO + + implementation 'com.journeyapps:zxing-android-embedded:3.6.0' } defaultTasks 'clean', 'assembleZcashmainnetRelease' \ No newline at end of file diff --git a/app/src/main/java/cash/z/ecc/android/ui/scan/QrAnalyzer.kt b/app/src/main/java/cash/z/ecc/android/ui/scan/QrAnalyzer.kt index fbb5de4..685493c 100644 --- a/app/src/main/java/cash/z/ecc/android/ui/scan/QrAnalyzer.kt +++ b/app/src/main/java/cash/z/ecc/android/ui/scan/QrAnalyzer.kt @@ -1,63 +1,80 @@ package cash.z.ecc.android.ui.scan +import android.content.ContentValues.TAG +import android.graphics.ImageFormat +import android.util.Log import androidx.camera.core.ImageAnalysis import androidx.camera.core.ImageProxy import cash.z.ecc.android.sdk.ext.retrySimple import cash.z.ecc.android.sdk.ext.retryUpTo import cash.z.ecc.android.sdk.ext.twig import com.google.android.gms.tasks.Task +import com.google.zxing.PlanarYUVLuminanceSource import com.google.firebase.ml.vision.FirebaseVision import com.google.firebase.ml.vision.barcode.FirebaseVisionBarcode import com.google.firebase.ml.vision.barcode.FirebaseVisionBarcodeDetector import com.google.firebase.ml.vision.barcode.FirebaseVisionBarcodeDetectorOptions import com.google.firebase.ml.vision.common.FirebaseVisionImage import com.google.firebase.ml.vision.common.FirebaseVisionImageMetadata +import com.google.zxing.BinaryBitmap +import com.google.zxing.MultiFormatReader +import com.google.zxing.common.HybridBinarizer + + class QrAnalyzer(val scanCallback: (qrContent: String, image: ImageProxy) -> Unit) : ImageAnalysis.Analyzer { - private val detector: FirebaseVisionBarcodeDetector by lazy { - val options = FirebaseVisionBarcodeDetectorOptions.Builder() - .setBarcodeFormats(FirebaseVisionBarcode.FORMAT_QR_CODE) - .build() - FirebaseVision.getInstance().getVisionBarcodeDetector(options) - } var pendingTask: Task? = null + var enabled = true + var inverted = false + + private val reader = MultiFormatReader() + override fun analyze(image: ImageProxy) { - var rotation = image.imageInfo.rotationDegrees % 360 - if (rotation < 0) { - rotation += 360 + if (!enabled) return + + //YUV_420 is normally the input type here, but other YUV types are also supported in theory + if (ImageFormat.YUV_420_888 != image.format && ImageFormat.YUV_422_888 != image.format && ImageFormat.YUV_444_888 != image.format) { + Log.e(TAG, "Unexpected format: ${image.format}") + // listener.onNoResult() + return + } + val byteBuffer = image?.planes?.firstOrNull()?.buffer + if (byteBuffer == null) { + // listener.onNoResult() + return } - retrySimple { - val mediaImage = FirebaseVisionImage.fromMediaImage( - image.image!!, when (rotation) { - 0 -> FirebaseVisionImageMetadata.ROTATION_0 - 90 -> FirebaseVisionImageMetadata.ROTATION_90 - 180 -> FirebaseVisionImageMetadata.ROTATION_180 - 270 -> FirebaseVisionImageMetadata.ROTATION_270 - else -> { - FirebaseVisionImageMetadata.ROTATION_0 - } - } - ) - pendingTask = detector.detectInImage(mediaImage).also { - it.addOnSuccessListener { result -> - onImageScan(result, image) - } - it.addOnFailureListener(::onImageScanFailure) - } + val data = ByteArray(byteBuffer.remaining()).also { byteBuffer.get(it) } + + val width = image.width + val height = image.height + + val source = PlanarYUVLuminanceSource(data, width, height, 0, 0, width, height, false).let { + if (inverted) it.invert() else it } + val bitmap = BinaryBitmap(HybridBinarizer(source)) + + + try { + val result = reader.decodeWithState(bitmap) + onImageScan(result.toString(), image) + + } catch (e: Exception) { + image.close() + twig("Error Scanning:{${e}}") + } } - private fun onImageScan(result: List, image: ImageProxy) { - result.firstOrNull()?.rawValue?.let { - scanCallback(it, image) - } ?: runCatching { image.close() } + private fun onImageScan(result: String, image: ImageProxy) { + scanCallback(result, image) ?: runCatching { image.close() } } private fun onImageScanFailure(e: Exception) { twig("Warning: Image scan failed") } + + } From 655094d0d382dc7096b4aa455bc0fc09799fc9c3 Mon Sep 17 00:00:00 2001 From: Eljo Date: Sun, 21 Feb 2021 13:47:16 +0100 Subject: [PATCH 2/3] remove redundant libraries/code --- app/build.gradle | 3 +-- .../java/cash/z/ecc/android/ui/scan/QrAnalyzer.kt | 15 +-------------- build.gradle | 2 +- .../main/java/cash/z/ecc/android/Dependencies.kt | 10 ++++------ 4 files changed, 7 insertions(+), 23 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index fe5839e..7214c5c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -4,7 +4,7 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-kapt' -apply plugin: 'com.google.gms.google-services' +// apply plugin: 'com.google.gms.google-services' //apply plugin: 'com.github.ben-manes.versions' archivesBaseName = 'zcash-android-wallet' @@ -152,7 +152,6 @@ dependencies { // Google implementation Deps.Google.GUAVA implementation Deps.Google.MATERIAL - implementation Deps.Google.ML_VISION // QR Scanner // Dagger implementation Deps.Dagger.ANDROID_SUPPORT diff --git a/app/src/main/java/cash/z/ecc/android/ui/scan/QrAnalyzer.kt b/app/src/main/java/cash/z/ecc/android/ui/scan/QrAnalyzer.kt index 685493c..77c282f 100644 --- a/app/src/main/java/cash/z/ecc/android/ui/scan/QrAnalyzer.kt +++ b/app/src/main/java/cash/z/ecc/android/ui/scan/QrAnalyzer.kt @@ -5,28 +5,15 @@ import android.graphics.ImageFormat import android.util.Log import androidx.camera.core.ImageAnalysis import androidx.camera.core.ImageProxy -import cash.z.ecc.android.sdk.ext.retrySimple -import cash.z.ecc.android.sdk.ext.retryUpTo import cash.z.ecc.android.sdk.ext.twig -import com.google.android.gms.tasks.Task import com.google.zxing.PlanarYUVLuminanceSource -import com.google.firebase.ml.vision.FirebaseVision -import com.google.firebase.ml.vision.barcode.FirebaseVisionBarcode -import com.google.firebase.ml.vision.barcode.FirebaseVisionBarcodeDetector -import com.google.firebase.ml.vision.barcode.FirebaseVisionBarcodeDetectorOptions -import com.google.firebase.ml.vision.common.FirebaseVisionImage -import com.google.firebase.ml.vision.common.FirebaseVisionImageMetadata import com.google.zxing.BinaryBitmap import com.google.zxing.MultiFormatReader import com.google.zxing.common.HybridBinarizer - - class QrAnalyzer(val scanCallback: (qrContent: String, image: ImageProxy) -> Unit) : ImageAnalysis.Analyzer { - - var pendingTask: Task? = null - + var enabled = true var inverted = false diff --git a/build.gradle b/build.gradle index 795df34..159cf8c 100644 --- a/build.gradle +++ b/build.gradle @@ -10,7 +10,7 @@ buildscript { } dependencies { classpath 'com.android.tools.build:gradle:4.0.1' - classpath 'com.google.gms:google-services:4.3.3' + // classpath 'com.google.gms:google-services:4.3.3' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${Deps.kotlinVersion}" classpath 'io.fabric.tools:gradle:1.31.2' classpath 'com.bugsnag:bugsnag-android-gradle-plugin:4.7.5' diff --git a/buildSrc/src/main/java/cash/z/ecc/android/Dependencies.kt b/buildSrc/src/main/java/cash/z/ecc/android/Dependencies.kt index a86bd70..c674401 100644 --- a/buildSrc/src/main/java/cash/z/ecc/android/Dependencies.kt +++ b/buildSrc/src/main/java/cash/z/ecc/android/Dependencies.kt @@ -25,10 +25,10 @@ object Deps { const val MULTIDEX = "androidx.multidex:multidex:2.0.1" const val PAGING = "androidx.paging:paging-runtime-ktx:2.1.2" const val RECYCLER = "androidx.recyclerview:recyclerview:1.2.0-alpha05" - object CameraX : Version("1.0.0-beta04") { - val CAMERA2 = "androidx.camera:camera-camera2:1.0.0-beta04" - val CORE = "androidx.camera:camera-core:1.0.0-beta04" - val LIFECYCLE = "androidx.camera:camera-lifecycle:1.0.0-beta04" + object CameraX : Version("1.0.0-rc02") { + val CAMERA2 = "androidx.camera:camera-camera2:1.0.0-rc02" + val CORE = "androidx.camera:camera-core:1.0.0-rc02" + val LIFECYCLE = "androidx.camera:camera-lifecycle:1.0.0-rc02" object View : Version("1.0.0-alpha11") { val EXT = "androidx.camera:camera-extensions:1.0.0-alpha11" val VIEW = "androidx.camera:camera-view:1.0.0-alpha11" @@ -57,8 +57,6 @@ object Deps { // per this recommendation from Chris Povirk, given guava's decision to split ListenableFuture away from Guava: https://groups.google.com/d/msg/guava-discuss/GghaKwusjcY/bCIAKfzOEwAJ const val GUAVA = "com.google.guava:guava:27.0.1-android" const val MATERIAL = "com.google.android.material:material:1.1.0-beta01" - // QR Scanner - const val ML_VISION = "com.google.firebase:firebase-ml-vision:24.0.3" } object Grpc : Version("1.25.0") { val ANDROID = "io.grpc:grpc-android:$version" From 0b5e4047bbc9be6378f76d37a37095322852939c Mon Sep 17 00:00:00 2001 From: Eljo Date: Sun, 28 Feb 2021 18:38:00 +0100 Subject: [PATCH 3/3] fix PR requests --- app/build.gradle | 3 +- .../cash/z/ecc/android/ui/scan/QrAnalyzer.kt | 90 +++++++++---------- .../z/ecc/android/ui/scan/ScanFragment.kt | 2 +- build.gradle | 1 - .../java/cash/z/ecc/android/Dependencies.kt | 6 +- 5 files changed, 48 insertions(+), 54 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 7214c5c..8a29ba0 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -4,7 +4,6 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-kapt' -// apply plugin: 'com.google.gms.google-services' //apply plugin: 'com.github.ben-manes.versions' archivesBaseName = 'zcash-android-wallet' @@ -172,6 +171,7 @@ dependencies { // Misc. implementation Deps.Misc.LOTTIE implementation Deps.Misc.CHIPS + implementation Deps.Misc.Plugins.QR_SCANNER // Tests testImplementation Deps.Test.COROUTINES_TEST @@ -184,7 +184,6 @@ dependencies { androidTestImplementation Deps.Test.Android.JUNIT androidTestImplementation Deps.Test.Android.ESPRESSO - implementation 'com.journeyapps:zxing-android-embedded:3.6.0' } defaultTasks 'clean', 'assembleZcashmainnetRelease' \ No newline at end of file diff --git a/app/src/main/java/cash/z/ecc/android/ui/scan/QrAnalyzer.kt b/app/src/main/java/cash/z/ecc/android/ui/scan/QrAnalyzer.kt index 77c282f..a4ce929 100644 --- a/app/src/main/java/cash/z/ecc/android/ui/scan/QrAnalyzer.kt +++ b/app/src/main/java/cash/z/ecc/android/ui/scan/QrAnalyzer.kt @@ -1,67 +1,63 @@ package cash.z.ecc.android.ui.scan -import android.content.ContentValues.TAG -import android.graphics.ImageFormat -import android.util.Log import androidx.camera.core.ImageAnalysis import androidx.camera.core.ImageProxy import cash.z.ecc.android.sdk.ext.twig -import com.google.zxing.PlanarYUVLuminanceSource -import com.google.zxing.BinaryBitmap -import com.google.zxing.MultiFormatReader +import com.google.zxing.* import com.google.zxing.common.HybridBinarizer + class QrAnalyzer(val scanCallback: (qrContent: String, image: ImageProxy) -> Unit) : ImageAnalysis.Analyzer { - - var enabled = true - var inverted = false - private val reader = MultiFormatReader() + private val reader = MultiFormatReader() override fun analyze(image: ImageProxy) { - if (!enabled) return - - //YUV_420 is normally the input type here, but other YUV types are also supported in theory - if (ImageFormat.YUV_420_888 != image.format && ImageFormat.YUV_422_888 != image.format && ImageFormat.YUV_444_888 != image.format) { - Log.e(TAG, "Unexpected format: ${image.format}") - // listener.onNoResult() - return - } - val byteBuffer = image?.planes?.firstOrNull()?.buffer - if (byteBuffer == null) { - // listener.onNoResult() - return - } - - val data = ByteArray(byteBuffer.remaining()).also { byteBuffer.get(it) } - - val width = image.width - val height = image.height - - val source = PlanarYUVLuminanceSource(data, width, height, 0, 0, width, height, false).let { - if (inverted) it.invert() else it - } - val bitmap = BinaryBitmap(HybridBinarizer(source)) - - - try { - val result = reader.decodeWithState(bitmap) - onImageScan(result.toString(), image) - - } catch (e: Exception) { + image.toBinaryBitmap().let { bitmap -> + val qrContent = bitmap.decodeWith(reader) ?: bitmap.flip().decodeWith(reader) + if (qrContent == null) { image.close() - twig("Error Scanning:{${e}}") + } else { + onImageScan(qrContent, image) } + } + } + + private fun ImageProxy.toBinaryBitmap(): BinaryBitmap { + return planes[0].buffer.let { buffer -> + ByteArray(buffer.remaining()).also { buffer.get(it) } + }.let { bytes -> + PlanarYUVLuminanceSource(bytes, width, height, 0, 0, width, height, false) + }.let { source -> + BinaryBitmap(HybridBinarizer(source)) + } + } + + private fun BinaryBitmap.decodeWith(reader: Reader): String? { + return try { + reader.decode(this).toString() + } catch (e: NotFoundException) { + // these happen frequently. Whenever no QR code is found in the frame. No need to log. + null + } catch (e: Throwable) { + twig("Error while scanning QR: $e") + null + } + } + + private fun BinaryBitmap.flip(): BinaryBitmap { + blackMatrix.apply { + repeat(width) { w -> + repeat(height) { h -> + flip(w, h) + } + } + } + return this } private fun onImageScan(result: String, image: ImageProxy) { - scanCallback(result, image) ?: runCatching { image.close() } + scanCallback(result, image) } - private fun onImageScanFailure(e: Exception) { - twig("Warning: Image scan failed") - } - - } diff --git a/app/src/main/java/cash/z/ecc/android/ui/scan/ScanFragment.kt b/app/src/main/java/cash/z/ecc/android/ui/scan/ScanFragment.kt index 23fca61..c3aa848 100644 --- a/app/src/main/java/cash/z/ecc/android/ui/scan/ScanFragment.kt +++ b/app/src/main/java/cash/z/ecc/android/ui/scan/ScanFragment.kt @@ -96,7 +96,7 @@ class ScanFragment : BaseFragment() { try { cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageAnalysis) - preview.setSurfaceProvider(binding.preview.createSurfaceProvider()) + preview.setSurfaceProvider(binding.preview.surfaceProvider) } catch (t: Throwable) { // TODO: consider bubbling this up to the user mainActivity?.feedback?.report(t) diff --git a/build.gradle b/build.gradle index 159cf8c..68b2a70 100644 --- a/build.gradle +++ b/build.gradle @@ -10,7 +10,6 @@ buildscript { } dependencies { classpath 'com.android.tools.build:gradle:4.0.1' - // classpath 'com.google.gms:google-services:4.3.3' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${Deps.kotlinVersion}" classpath 'io.fabric.tools:gradle:1.31.2' classpath 'com.bugsnag:bugsnag-android-gradle-plugin:4.7.5' diff --git a/buildSrc/src/main/java/cash/z/ecc/android/Dependencies.kt b/buildSrc/src/main/java/cash/z/ecc/android/Dependencies.kt index c674401..7226517 100644 --- a/buildSrc/src/main/java/cash/z/ecc/android/Dependencies.kt +++ b/buildSrc/src/main/java/cash/z/ecc/android/Dependencies.kt @@ -30,8 +30,8 @@ object Deps { val CORE = "androidx.camera:camera-core:1.0.0-rc02" val LIFECYCLE = "androidx.camera:camera-lifecycle:1.0.0-rc02" object View : Version("1.0.0-alpha11") { - val EXT = "androidx.camera:camera-extensions:1.0.0-alpha11" - val VIEW = "androidx.camera:camera-view:1.0.0-alpha11" + val EXT = "androidx.camera:camera-extensions:1.0.0-alpha21" + val VIEW = "androidx.camera:camera-view:1.0.0-alpha21" } } object Lifecycle : Version("2.2.0") { @@ -93,7 +93,7 @@ object Deps { const val CHIPS = "com.github.gmale:chips-input-layout:2.3.1" object Plugins { const val SECURE_STORAGE = "com.github.gmale:secure-storage-android:0.0.3"//"de.adorsys.android:securestoragelibrary:1.2.2" - const val QR_SCANNER = "com.google.zxing:core:3.2.1" + const val QR_SCANNER = "com.google.zxing:core:3.4.1" } }