Замена mlkit на свободную библиотеку
This commit is contained in:
@@ -26,7 +26,7 @@ okhttp = "5.3.2"
|
||||
workRuntime = "2.11.2"
|
||||
hiltWork = "1.3.0"
|
||||
cameraX = "1.6.1"
|
||||
mlkitBarcode = "17.3.0"
|
||||
zxing = "3.5.3"
|
||||
javaOtp = "0.4.0"
|
||||
appcompat = "1.7.1"
|
||||
datastore = "1.2.1"
|
||||
@@ -90,7 +90,7 @@ androidx-camera-core = { group = "androidx.camera", name = "camera-core", versio
|
||||
androidx-camera-camera2 = { group = "androidx.camera", name = "camera-camera2", version.ref = "cameraX" }
|
||||
androidx-camera-lifecycle = { group = "androidx.camera", name = "camera-lifecycle", version.ref = "cameraX" }
|
||||
androidx-camera-view = { group = "androidx.camera", name = "camera-view", version.ref = "cameraX" }
|
||||
mlkit-barcode-scanning = { group = "com.google.mlkit", name = "barcode-scanning", version.ref = "mlkitBarcode" }
|
||||
zxing-core = { group = "com.google.zxing", name = "core", version.ref = "zxing" }
|
||||
java-otp = { group = "com.eatthepath", name = "java-otp", version.ref = "javaOtp" }
|
||||
|
||||
androidx-ui = { group = "androidx.compose.ui", name = "ui" }
|
||||
|
||||
@@ -80,7 +80,7 @@ dependencies {
|
||||
implementation(libs.androidx.camera.camera2)
|
||||
implementation(libs.androidx.camera.lifecycle)
|
||||
implementation(libs.androidx.camera.view)
|
||||
implementation(libs.mlkit.barcode.scanning)
|
||||
implementation(libs.zxing.core)
|
||||
|
||||
testImplementation(libs.junit)
|
||||
testImplementation(libs.kotlinx.coroutines.test)
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
package com.github.nullptroma.wallenc.ui.elements
|
||||
|
||||
import androidx.camera.core.ImageProxy
|
||||
import com.google.zxing.BarcodeFormat
|
||||
import com.google.zxing.BinaryBitmap
|
||||
import com.google.zxing.ChecksumException
|
||||
import com.google.zxing.DecodeHintType
|
||||
import com.google.zxing.FormatException
|
||||
import com.google.zxing.NotFoundException
|
||||
import com.google.zxing.PlanarYUVLuminanceSource
|
||||
import com.google.zxing.common.HybridBinarizer
|
||||
import com.google.zxing.qrcode.QRCodeReader
|
||||
|
||||
internal object QrImageDecoder {
|
||||
private val hints = mapOf(
|
||||
DecodeHintType.POSSIBLE_FORMATS to listOf(BarcodeFormat.QR_CODE),
|
||||
DecodeHintType.CHARACTER_SET to "UTF-8",
|
||||
)
|
||||
private val reader = QRCodeReader()
|
||||
|
||||
fun decode(imageProxy: ImageProxy): String? {
|
||||
val yPlane = imageProxy.planes[0]
|
||||
val yBuffer = yPlane.buffer
|
||||
yBuffer.rewind()
|
||||
val yRowStride = yPlane.rowStride
|
||||
val yPixelStride = yPlane.pixelStride
|
||||
val width = imageProxy.width
|
||||
val height = imageProxy.height
|
||||
val yBytes = ByteArray(width * height)
|
||||
var outputOffset = 0
|
||||
if (yPixelStride == 1 && yRowStride == width) {
|
||||
yBuffer.get(yBytes, 0, yBytes.size.coerceAtMost(yBuffer.remaining()))
|
||||
} else {
|
||||
for (row in 0 until height) {
|
||||
var inputOffset = row * yRowStride
|
||||
for (col in 0 until width) {
|
||||
yBytes[outputOffset++] = yBuffer.get(inputOffset)
|
||||
inputOffset += yPixelStride
|
||||
}
|
||||
}
|
||||
}
|
||||
val (luminance, decodeWidth, decodeHeight) = orientLuminance(
|
||||
yBytes,
|
||||
width,
|
||||
height,
|
||||
imageProxy.imageInfo.rotationDegrees,
|
||||
)
|
||||
val source = PlanarYUVLuminanceSource(
|
||||
luminance,
|
||||
decodeWidth,
|
||||
decodeHeight,
|
||||
0,
|
||||
0,
|
||||
decodeWidth,
|
||||
decodeHeight,
|
||||
false,
|
||||
)
|
||||
return try {
|
||||
reader.decode(BinaryBitmap(HybridBinarizer(source)), hints).text
|
||||
} catch (_: NotFoundException) {
|
||||
null
|
||||
} catch (_: ChecksumException) {
|
||||
null
|
||||
} catch (_: FormatException) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun orientLuminance(
|
||||
yBytes: ByteArray,
|
||||
width: Int,
|
||||
height: Int,
|
||||
rotationDegrees: Int,
|
||||
): Triple<ByteArray, Int, Int> = when (rotationDegrees) {
|
||||
90 -> Triple(rotateY90(yBytes, width, height, clockwise = true), height, width)
|
||||
180 -> Triple(rotateY180(yBytes, width, height), width, height)
|
||||
270 -> Triple(rotateY90(yBytes, width, height, clockwise = false), height, width)
|
||||
else -> Triple(yBytes, width, height)
|
||||
}
|
||||
|
||||
private fun rotateY90(data: ByteArray, width: Int, height: Int, clockwise: Boolean): ByteArray {
|
||||
val output = ByteArray(data.size)
|
||||
for (y in 0 until height) {
|
||||
for (x in 0 until width) {
|
||||
val srcIndex = y * width + x
|
||||
val dstIndex = if (clockwise) {
|
||||
x * height + (height - 1 - y)
|
||||
} else {
|
||||
(width - 1 - x) * height + y
|
||||
}
|
||||
output[dstIndex] = data[srcIndex]
|
||||
}
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
||||
private fun rotateY180(data: ByteArray, width: Int, height: Int): ByteArray {
|
||||
val output = ByteArray(data.size)
|
||||
for (y in 0 until height) {
|
||||
for (x in 0 until width) {
|
||||
output[(height - 1 - y) * width + (width - 1 - x)] = data[y * width + x]
|
||||
}
|
||||
}
|
||||
return output
|
||||
}
|
||||
}
|
||||
@@ -31,10 +31,6 @@ import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import com.github.nullptroma.wallenc.ui.R
|
||||
import com.google.mlkit.vision.barcode.BarcodeScannerOptions
|
||||
import com.google.mlkit.vision.barcode.BarcodeScanning
|
||||
import com.google.mlkit.vision.barcode.common.Barcode
|
||||
import com.google.mlkit.vision.common.InputImage
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
@@ -48,20 +44,12 @@ fun QrScannerDialog(
|
||||
val lifecycleOwner = androidx.lifecycle.compose.LocalLifecycleOwner.current
|
||||
val cameraProviderFuture = remember { ProcessCameraProvider.getInstance(context) }
|
||||
val cameraExecutor = remember { Executors.newSingleThreadExecutor() }
|
||||
val scanner = remember {
|
||||
BarcodeScanning.getClient(
|
||||
BarcodeScannerOptions.Builder()
|
||||
.setBarcodeFormats(Barcode.FORMAT_QR_CODE)
|
||||
.build(),
|
||||
)
|
||||
}
|
||||
val consumed = remember { AtomicBoolean(false) }
|
||||
var previewViewRef by remember { mutableStateOf<PreviewView?>(null) }
|
||||
|
||||
DisposableEffect(Unit) {
|
||||
onDispose {
|
||||
runCatching { cameraProviderFuture.get().unbindAll() }
|
||||
runCatching { scanner.close() }
|
||||
cameraExecutor.shutdown()
|
||||
}
|
||||
}
|
||||
@@ -76,24 +64,17 @@ fun QrScannerDialog(
|
||||
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
|
||||
.build()
|
||||
.also { imageAnalysis ->
|
||||
imageAnalysis.targetRotation = previewView.display.rotation
|
||||
imageAnalysis.setAnalyzer(cameraExecutor) { imageProxy ->
|
||||
val mediaImage = imageProxy.image
|
||||
if (mediaImage == null) {
|
||||
try {
|
||||
if (consumed.get()) return@setAnalyzer
|
||||
val raw = QrImageDecoder.decode(imageProxy)?.trim().orEmpty()
|
||||
if (raw.isNotBlank() && consumed.compareAndSet(false, true)) {
|
||||
onScanned(raw)
|
||||
}
|
||||
} finally {
|
||||
imageProxy.close()
|
||||
return@setAnalyzer
|
||||
}
|
||||
val input = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)
|
||||
scanner.process(input)
|
||||
.addOnSuccessListener { barcodes ->
|
||||
if (consumed.get()) return@addOnSuccessListener
|
||||
val raw = barcodes.firstOrNull()?.rawValue?.trim().orEmpty()
|
||||
if (raw.isNotBlank() && consumed.compareAndSet(false, true)) {
|
||||
onScanned(raw)
|
||||
}
|
||||
}
|
||||
.addOnCompleteListener {
|
||||
imageProxy.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
cameraProvider.unbindAll()
|
||||
|
||||
Reference in New Issue
Block a user