Merge pull request #232 from herou/zxing_implementation

Implement zxing instead of Google Firebase ML Vision
This commit is contained in:
Kevin Gorham 2021-03-16 14:32:55 -04:00 committed by GitHub
commit bad21eda3f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 54 additions and 57 deletions

View File

@ -4,7 +4,6 @@ apply plugin: 'com.android.application'
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt' apply plugin: 'kotlin-kapt'
apply plugin: 'com.google.gms.google-services'
//apply plugin: 'com.github.ben-manes.versions' //apply plugin: 'com.github.ben-manes.versions'
archivesBaseName = 'zcash-android-wallet' archivesBaseName = 'zcash-android-wallet'
@ -152,7 +151,6 @@ dependencies {
// Google // Google
implementation Deps.Google.GUAVA implementation Deps.Google.GUAVA
implementation Deps.Google.MATERIAL implementation Deps.Google.MATERIAL
implementation Deps.Google.ML_VISION // QR Scanner
// Dagger // Dagger
implementation Deps.Dagger.ANDROID_SUPPORT implementation Deps.Dagger.ANDROID_SUPPORT
@ -173,6 +171,7 @@ dependencies {
// Misc. // Misc.
implementation Deps.Misc.LOTTIE implementation Deps.Misc.LOTTIE
implementation Deps.Misc.CHIPS implementation Deps.Misc.CHIPS
implementation Deps.Misc.Plugins.QR_SCANNER
// Tests // Tests
testImplementation Deps.Test.COROUTINES_TEST testImplementation Deps.Test.COROUTINES_TEST
@ -184,6 +183,7 @@ dependencies {
androidTestImplementation Deps.Test.Android.JUNIT androidTestImplementation Deps.Test.Android.JUNIT
androidTestImplementation Deps.Test.Android.ESPRESSO androidTestImplementation Deps.Test.Android.ESPRESSO
} }
defaultTasks 'clean', 'assembleZcashmainnetRelease' defaultTasks 'clean', 'assembleZcashmainnetRelease'

View File

@ -2,62 +2,62 @@ package cash.z.ecc.android.ui.scan
import androidx.camera.core.ImageAnalysis import androidx.camera.core.ImageAnalysis
import androidx.camera.core.ImageProxy 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 cash.z.ecc.android.sdk.ext.twig
import com.google.android.gms.tasks.Task import com.google.zxing.*
import com.google.firebase.ml.vision.FirebaseVision import com.google.zxing.common.HybridBinarizer
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
class QrAnalyzer(val scanCallback: (qrContent: String, image: ImageProxy) -> Unit) : class QrAnalyzer(val scanCallback: (qrContent: String, image: ImageProxy) -> Unit) :
ImageAnalysis.Analyzer { 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<out Any>? = null private val reader = MultiFormatReader()
override fun analyze(image: ImageProxy) { override fun analyze(image: ImageProxy) {
var rotation = image.imageInfo.rotationDegrees % 360 image.toBinaryBitmap().let { bitmap ->
if (rotation < 0) { val qrContent = bitmap.decodeWith(reader) ?: bitmap.flip().decodeWith(reader)
rotation += 360 if (qrContent == null) {
} image.close()
} else {
retrySimple { onImageScan(qrContent, image)
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)
} }
} }
} }
private fun onImageScan(result: List<FirebaseVisionBarcode>, image: ImageProxy) { private fun ImageProxy.toBinaryBitmap(): BinaryBitmap {
result.firstOrNull()?.rawValue?.let { return planes[0].buffer.let { buffer ->
scanCallback(it, image) ByteArray(buffer.remaining()).also { buffer.get(it) }
} ?: runCatching { image.close() } }.let { bytes ->
PlanarYUVLuminanceSource(bytes, width, height, 0, 0, width, height, false)
}.let { source ->
BinaryBitmap(HybridBinarizer(source))
}
} }
private fun onImageScanFailure(e: Exception) { private fun BinaryBitmap.decodeWith(reader: Reader): String? {
twig("Warning: Image scan failed") 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)
}
} }

View File

@ -96,7 +96,7 @@ class ScanFragment : BaseFragment<FragmentScanBinding>() {
try { try {
cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageAnalysis) cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageAnalysis)
preview.setSurfaceProvider(binding.preview.createSurfaceProvider()) preview.setSurfaceProvider(binding.preview.surfaceProvider)
} catch (t: Throwable) { } catch (t: Throwable) {
// TODO: consider bubbling this up to the user // TODO: consider bubbling this up to the user
mainActivity?.feedback?.report(t) mainActivity?.feedback?.report(t)

View File

@ -10,7 +10,6 @@ buildscript {
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:4.0.1' 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 "org.jetbrains.kotlin:kotlin-gradle-plugin:${Deps.kotlinVersion}"
classpath 'io.fabric.tools:gradle:1.31.2' classpath 'io.fabric.tools:gradle:1.31.2'
classpath 'com.bugsnag:bugsnag-android-gradle-plugin:4.7.5' classpath 'com.bugsnag:bugsnag-android-gradle-plugin:4.7.5'

View File

@ -25,13 +25,13 @@ object Deps {
const val MULTIDEX = "androidx.multidex:multidex:2.0.1" const val MULTIDEX = "androidx.multidex:multidex:2.0.1"
const val PAGING = "androidx.paging:paging-runtime-ktx:2.1.2" const val PAGING = "androidx.paging:paging-runtime-ktx:2.1.2"
const val RECYCLER = "androidx.recyclerview:recyclerview:1.2.0-alpha05" const val RECYCLER = "androidx.recyclerview:recyclerview:1.2.0-alpha05"
object CameraX : Version("1.0.0-beta04") { object CameraX : Version("1.0.0-rc02") {
val CAMERA2 = "androidx.camera:camera-camera2:1.0.0-beta04" val CAMERA2 = "androidx.camera:camera-camera2:1.0.0-rc02"
val CORE = "androidx.camera:camera-core:1.0.0-beta04" val CORE = "androidx.camera:camera-core:1.0.0-rc02"
val LIFECYCLE = "androidx.camera:camera-lifecycle:1.0.0-beta04" val LIFECYCLE = "androidx.camera:camera-lifecycle:1.0.0-rc02"
object View : Version("1.0.0-alpha11") { object View : Version("1.0.0-alpha11") {
val EXT = "androidx.camera:camera-extensions:1.0.0-alpha11" val EXT = "androidx.camera:camera-extensions:1.0.0-alpha21"
val VIEW = "androidx.camera:camera-view:1.0.0-alpha11" val VIEW = "androidx.camera:camera-view:1.0.0-alpha21"
} }
} }
object Lifecycle : Version("2.2.0") { object Lifecycle : Version("2.2.0") {
@ -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 // 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 GUAVA = "com.google.guava:guava:27.0.1-android"
const val MATERIAL = "com.google.android.material:material:1.1.0-beta01" 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") { object Grpc : Version("1.25.0") {
val ANDROID = "io.grpc:grpc-android:$version" val ANDROID = "io.grpc:grpc-android:$version"
@ -95,7 +93,7 @@ object Deps {
const val CHIPS = "com.github.gmale:chips-input-layout:2.3.1" const val CHIPS = "com.github.gmale:chips-input-layout:2.3.1"
object Plugins { object Plugins {
const val SECURE_STORAGE = "com.github.gmale:secure-storage-android:0.0.3"//"de.adorsys.android:securestoragelibrary:1.2.2" 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"
} }
} }