Merge pull request #232 from herou/zxing_implementation
Implement zxing instead of Google Firebase ML Vision
This commit is contained in:
commit
bad21eda3f
|
@ -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'
|
||||
|
@ -152,7 +151,6 @@ dependencies {
|
|||
// Google
|
||||
implementation Deps.Google.GUAVA
|
||||
implementation Deps.Google.MATERIAL
|
||||
implementation Deps.Google.ML_VISION // QR Scanner
|
||||
|
||||
// Dagger
|
||||
implementation Deps.Dagger.ANDROID_SUPPORT
|
||||
|
@ -173,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,6 +183,7 @@ dependencies {
|
|||
|
||||
androidTestImplementation Deps.Test.Android.JUNIT
|
||||
androidTestImplementation Deps.Test.Android.ESPRESSO
|
||||
|
||||
}
|
||||
|
||||
defaultTasks 'clean', 'assembleZcashmainnetRelease'
|
|
@ -2,62 +2,62 @@ package cash.z.ecc.android.ui.scan
|
|||
|
||||
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.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.*
|
||||
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<out Any>? = null
|
||||
private val reader = MultiFormatReader()
|
||||
|
||||
override fun analyze(image: ImageProxy) {
|
||||
var rotation = image.imageInfo.rotationDegrees % 360
|
||||
if (rotation < 0) {
|
||||
rotation += 360
|
||||
}
|
||||
|
||||
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)
|
||||
image.toBinaryBitmap().let { bitmap ->
|
||||
val qrContent = bitmap.decodeWith(reader) ?: bitmap.flip().decodeWith(reader)
|
||||
if (qrContent == null) {
|
||||
image.close()
|
||||
} else {
|
||||
onImageScan(qrContent, image)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onImageScan(result: List<FirebaseVisionBarcode>, image: ImageProxy) {
|
||||
result.firstOrNull()?.rawValue?.let {
|
||||
scanCallback(it, image)
|
||||
} ?: runCatching { image.close() }
|
||||
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 onImageScanFailure(e: Exception) {
|
||||
twig("Warning: Image scan failed")
|
||||
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)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -96,7 +96,7 @@ class ScanFragment : BaseFragment<FragmentScanBinding>() {
|
|||
|
||||
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)
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -25,13 +25,13 @@ 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"
|
||||
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") {
|
||||
|
@ -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"
|
||||
|
@ -95,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"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue