Refactor QR scan and share and add QRS support

This commit is contained in:
世界
2026-01-01 23:50:31 +08:00
parent 4a2ffcd080
commit aaa3ef044a
31 changed files with 2475 additions and 735 deletions

View File

@@ -1,6 +1,6 @@
package io.nekohasekai.sfa.vendor
import android.util.Log
import android.graphics.Bitmap
import androidx.camera.core.ExperimentalGetImage
import androidx.camera.core.ImageAnalysis
import androidx.camera.core.ImageProxy
@@ -17,21 +17,27 @@ class MLKitQRCodeAnalyzer(
) : ImageAnalysis.Analyzer {
private val barcodeScanner =
BarcodeScanning.getClient(
BarcodeScannerOptions.Builder().setBarcodeFormats(Barcode.FORMAT_QR_CODE).build(),
BarcodeScannerOptions.Builder()
.setBarcodeFormats(Barcode.FORMAT_QR_CODE)
.build(),
)
@Volatile
private var failureOccurred = false
private var failureTimestamp = 0L
private var pixelBuffer: IntArray? = null
@ExperimentalGetImage
override fun analyze(image: ImageProxy) {
if (image.image == null) return
if (image.image == null) {
image.close()
return
}
val nowMills = System.currentTimeMillis()
if (failureOccurred && nowMills - failureTimestamp < 5000L) {
failureTimestamp = nowMills
Log.d("MLKitQRCodeAnalyzer", "throttled analysis since error occurred in previous pass")
image.close()
return
}
@@ -39,24 +45,56 @@ class MLKitQRCodeAnalyzer(
failureOccurred = false
barcodeScanner.process(image.toInputImage())
.addOnSuccessListener { codes ->
if (codes.isNotEmpty()) {
val rawValue = codes.firstOrNull()?.rawValue
if (rawValue != null) {
Log.d("MLKitQRCodeAnalyzer", "barcode decode success: $rawValue")
onSuccess(rawValue)
}
val rawValue = codes.firstOrNull()?.rawValue
if (rawValue != null) {
onSuccess(rawValue)
image.close()
} else {
tryInvertedScan(image)
}
}
.addOnFailureListener {
failureOccurred = true
failureTimestamp = System.currentTimeMillis()
onFailure(it)
}
.addOnCompleteListener {
image.close()
}
}
private fun tryInvertedScan(image: ImageProxy) {
val inverted = image.toInvertedBitmap()
barcodeScanner.process(InputImage.fromBitmap(inverted, image.imageInfo.rotationDegrees))
.addOnSuccessListener { codes ->
codes.firstOrNull()?.rawValue?.let { onSuccess(it) }
}
.addOnCompleteListener {
inverted.recycle()
image.close()
}
}
private fun ImageProxy.toInvertedBitmap(): Bitmap {
val yPlane = planes[0]
val yBuffer = yPlane.buffer.duplicate()
val rowStride = yPlane.rowStride
val width = width
val height = height
val size = width * height
val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
val pixels = pixelBuffer?.takeIf { it.size >= size } ?: IntArray(size).also { pixelBuffer = it }
for (row in 0 until height) {
yBuffer.position(row * rowStride)
for (col in 0 until width) {
val y = 255 - (yBuffer.get().toInt() and 0xFF)
pixels[row * width + col] = (0xFF shl 24) or (y shl 16) or (y shl 8) or y
}
}
bitmap.setPixels(pixels, 0, width, 0, 0, width, height)
return bitmap
}
@ExperimentalGetImage
@Suppress("UnsafeCallOnNullableType")
private fun ImageProxy.toInputImage() = InputImage.fromMediaImage(image!!, imageInfo.rotationDegrees)