speedup + minor fixes

This commit is contained in:
Hadrian Burkhardt
2026-02-26 03:19:53 +01:00
parent 471270a396
commit 027d2391b7
5 changed files with 54 additions and 19 deletions
@@ -1,6 +1,7 @@
package com.clean.scanner.data.scanner
import android.graphics.Rect
import android.os.SystemClock
import androidx.camera.core.ImageAnalysis
import androidx.camera.core.ImageProxy
import com.clean.scanner.domain.ScanResult
@@ -47,6 +48,8 @@ class MlKitBarcodeAnalyzer(
const val MATCH_DISTANCE_THRESHOLD = 0.18f
const val BOX_SMOOTHING_ALPHA = 0.35f
const val MAX_MISSED_FRAMES = 2
const val MIN_ANALYSIS_INTERVAL_MS = 45L
const val MIN_STATE_PUBLISH_INTERVAL_MS = 66L
}
private val scanner = BarcodeScanning.getClient(
@@ -58,6 +61,10 @@ class MlKitBarcodeAnalyzer(
@Volatile
private var processing = false
@Volatile
private var lastAnalysisStartMs = 0L
@Volatile
private var lastStatePublishMs = 0L
@Volatile
private var lastHasPotential = false
@Volatile
private var lastHasReadable = false
@@ -86,6 +93,11 @@ class MlKitBarcodeAnalyzer(
imageProxy.close()
return
}
val nowMs = SystemClock.elapsedRealtime()
if (nowMs - lastAnalysisStartMs < MIN_ANALYSIS_INTERVAL_MS) {
imageProxy.close()
return
}
if (processing) {
imageProxy.close()
return
@@ -100,6 +112,7 @@ class MlKitBarcodeAnalyzer(
val sourceHeight = if (rotationDegrees == 90 || rotationDegrees == 270) imageProxy.width else imageProxy.height
val image = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)
processing = true
lastAnalysisStartMs = nowMs
scanner.process(image)
.addOnSuccessListener { barcodes ->
@@ -210,6 +223,7 @@ class MlKitBarcodeAnalyzer(
sourceWidth: Int,
sourceHeight: Int
) {
val nowMs = SystemClock.elapsedRealtime()
if (
lastHasPotential == hasPotential &&
lastHasReadable == hasReadable &&
@@ -217,11 +231,19 @@ class MlKitBarcodeAnalyzer(
lastSourceWidth == sourceWidth &&
lastSourceHeight == sourceHeight
) return
val isStateKindUnchanged = lastHasPotential == hasPotential &&
lastHasReadable == hasReadable &&
lastSourceWidth == sourceWidth &&
lastSourceHeight == sourceHeight
if (isStateKindUnchanged && nowMs - lastStatePublishMs < MIN_STATE_PUBLISH_INTERVAL_MS) {
return
}
lastHasPotential = hasPotential
lastHasReadable = hasReadable
lastBoxes = boxes
lastSourceWidth = sourceWidth
lastSourceHeight = sourceHeight
lastStatePublishMs = nowMs
onDetectionStateChanged(hasPotential, hasReadable, boxes, sourceWidth, sourceHeight)
}
@@ -1,6 +1,7 @@
package com.clean.scanner.ui.components
import android.annotation.SuppressLint
import android.util.Size
import android.view.MotionEvent
import android.view.ScaleGestureDetector
import androidx.camera.core.CameraSelector
@@ -12,6 +13,7 @@ import androidx.camera.view.PreviewView
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
@@ -49,7 +51,7 @@ fun CameraPreview(
val mainExecutor = remember(context) { ContextCompat.getMainExecutor(context) }
val previewView = remember { PreviewView(context) }
val cameraRef = remember { mutableStateOf<androidx.camera.core.Camera?>(null) }
val zoomRatio = remember { mutableStateOf(1f) }
val zoomRatio = remember { mutableFloatStateOf(1f) }
val latestAnalysisEnabled = rememberUpdatedState(analysisEnabled)
val latestOnScan = rememberUpdatedState(onScan)
val latestOnDetectionStateChanged = rememberUpdatedState(onDetectionStateChanged)
@@ -107,6 +109,7 @@ fun CameraPreview(
val imageAnalysis = ImageAnalysis.Builder()
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.setTargetResolution(Size(1280, 720))
.build().apply {
setAnalyzer(cameraExecutor, analyzer)
}
@@ -22,7 +22,6 @@ import androidx.compose.foundation.gestures.detectTransformGestures
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
@@ -55,6 +54,8 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
@@ -62,6 +63,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.CornerRadius
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.asImageBitmap
@@ -85,6 +87,7 @@ import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import com.clean.scanner.R
import com.clean.scanner.data.scanner.DetectionBox
import com.clean.scanner.data.scanner.DetectionPoint
import com.clean.scanner.domain.ScanResult
import com.clean.scanner.ui.BatchScanRecord
import com.clean.scanner.ui.components.CameraPreview
@@ -93,6 +96,7 @@ import com.clean.scanner.util.Intents
import com.clean.scanner.util.ScanContentParsers
import com.clean.scanner.util.UrlRiskScorer
import com.google.mlkit.vision.barcode.BarcodeScanning
import com.google.mlkit.vision.barcode.BarcodeScanner
import com.google.mlkit.vision.barcode.BarcodeScannerOptions
import com.google.mlkit.vision.barcode.common.Barcode
import com.google.mlkit.vision.common.InputImage
@@ -150,8 +154,8 @@ fun ScannerScreen(
var hasPotentialInView by remember { mutableStateOf(false) }
var hasReadableInView by remember { mutableStateOf(false) }
var detectionBoxes by remember { mutableStateOf<List<DetectionBox>>(emptyList()) }
var detectionSourceWidth by remember { mutableStateOf(0) }
var detectionSourceHeight by remember { mutableStateOf(0) }
var detectionSourceWidth by remember { mutableIntStateOf(0) }
var detectionSourceHeight by remember { mutableIntStateOf(0) }
val activity = context as? Activity
val imageScanner = remember {
BarcodeScanning.getClient(
@@ -193,7 +197,7 @@ fun ScannerScreen(
val raw = barcode.rawValue?.takeIf { it.isNotBlank() } ?: return@mapNotNull null
val normalizedBox = barcode.boundingBox?.let { bounds ->
val corners = barcode.cornerPoints?.map { p ->
com.clean.scanner.data.scanner.DetectionPoint(
DetectionPoint(
x = (p.x / image.width.toFloat()).coerceIn(0f, 1f),
y = (p.y / image.height.toFloat()).coerceIn(0f, 1f)
)
@@ -254,10 +258,16 @@ fun ScannerScreen(
}
}
BoxWithConstraints(modifier = Modifier.fillMaxSize()) {
var containerSize by remember { mutableStateOf(IntSize.Zero) }
Box(
modifier = Modifier
.fillMaxSize()
.onSizeChanged { containerSize = it }
) {
val density = LocalDensity.current
val viewW = with(density) { maxWidth.toPx() }
val viewH = with(density) { maxHeight.toPx() }
val viewW = containerSize.width.toFloat()
val viewH = containerSize.height.toFloat()
val galleryOpen = imageScanPreviewUri != null
val aimW = viewW * 0.62f
val aimH = with(density) { 200.dp.toPx() }
@@ -324,7 +334,7 @@ fun ScannerScreen(
val right = offsetX + (box.right * sourceW * scale)
val bottom = offsetY + (box.bottom * sourceH * scale)
val mappedCorners = box.corners.map { p ->
androidx.compose.ui.geometry.Offset(
Offset(
x = offsetX + (p.x * sourceW * scale),
y = offsetY + (p.y * sourceH * scale)
)
@@ -343,8 +353,8 @@ fun ScannerScreen(
} else if (right > left && bottom > top) {
drawRoundRect(
color = boxColor.copy(alpha = 0.95f),
topLeft = androidx.compose.ui.geometry.Offset(left, top),
size = androidx.compose.ui.geometry.Size(right - left, bottom - top),
topLeft = Offset(left, top),
size = Size(right - left, bottom - top),
cornerRadius = CornerRadius(14f, 14f),
style = Stroke(width = 4f)
)
@@ -750,7 +760,7 @@ private fun loadBitmapFromUri(context: android.content.Context, uri: Uri): Bitma
}
private suspend fun detectBarcodes(
scanner: com.google.mlkit.vision.barcode.BarcodeScanner,
scanner: BarcodeScanner,
image: InputImage
): List<Barcode> = suspendCancellableCoroutine { cont ->
scanner.process(image)
@@ -772,10 +782,10 @@ private fun GalleryScanPreviewDialog(
val context = LocalContext.current
val bitmap = remember(imageUri) { imageUri?.let { loadBitmapFromUri(context, it) } }
var liveCandidates by remember(imageUri, candidates) { mutableStateOf(candidates) }
var zoom by remember(imageUri) { mutableStateOf(1f) }
var zoom by remember(imageUri) { mutableFloatStateOf(1f) }
var pan by remember(imageUri) { mutableStateOf(Offset.Zero) }
var viewportSize by remember { mutableStateOf(IntSize.Zero) }
var scanTick by remember { mutableStateOf(0) }
var scanTick by remember { mutableIntStateOf(0) }
val markerPaint = remember {
Paint().apply {
color = android.graphics.Color.WHITE
@@ -850,7 +860,7 @@ private fun GalleryScanPreviewDialog(
val rightN = ((bounds.right + cropLeft) / imgW).coerceIn(0f, 1f)
val bottomN = ((bounds.bottom + cropTop) / imgH).coerceIn(0f, 1f)
val corners = barcode.cornerPoints?.map { p ->
com.clean.scanner.data.scanner.DetectionPoint(
DetectionPoint(
x = ((p.x + cropLeft) / imgW).coerceIn(0f, 1f),
y = ((p.y + cropTop) / imgH).coerceIn(0f, 1f)
)
@@ -952,8 +962,8 @@ private fun GalleryScanPreviewDialog(
if (right > left && bottom > top) {
drawRoundRect(
color = color,
topLeft = androidx.compose.ui.geometry.Offset(left, top),
size = androidx.compose.ui.geometry.Size(right - left, bottom - top),
topLeft = Offset(left, top),
size = Size(right - left, bottom - top),
cornerRadius = CornerRadius(10f, 10f),
style = Stroke(width = 4f)
)
+1 -1
View File
@@ -1,5 +1,5 @@
plugins {
id("com.android.application") version "9.0.0" apply false
id("com.android.application") version "9.0.1" apply false
id("com.google.devtools.ksp") version "2.3.5" apply false
id("org.jetbrains.kotlin.plugin.compose") version "2.3.10" apply false
}