New debug mode for extended live analysis overlay

This commit is contained in:
Pierre-Yves Nicolas
2025-07-02 08:52:59 +02:00
parent 1463ef9355
commit 9c9963845e
2 changed files with 60 additions and 20 deletions

View File

@@ -46,6 +46,7 @@ import androidx.compose.ui.graphics.BlendMode
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.viewinterop.AndroidView import androidx.compose.ui.viewinterop.AndroidView
@@ -149,17 +150,15 @@ fun bindCameraUseCases(
} }
@Composable @Composable
fun AnalysisOverlay(liveAnalysisState: LiveAnalysisState) { fun AnalysisOverlay(liveAnalysisState: LiveAnalysisState, debugMode: Boolean) {
val binaryMask = liveAnalysisState.binaryMask val binaryMask = liveAnalysisState.binaryMask
if (binaryMask == null) { if (binaryMask == null) {
return return
} }
val maskOverlay = replaceColor(binaryMask, Color.Black, Color.Transparent)
Canvas(modifier = Modifier.fillMaxSize()) { Canvas(modifier = Modifier.fillMaxSize()) {
drawImage( if (debugMode) {
maskOverlay.scale(size.width.toInt(), size.height.toInt()).asImageBitmap(), drawMask(this, binaryMask)
colorFilter = ColorFilter.tint(Color(0x8000FF00), BlendMode.SrcIn) }
)
if (liveAnalysisState.documentQuad != null) { if (liveAnalysisState.documentQuad != null) {
val scaledQuad = liveAnalysisState.documentQuad.scaledTo( val scaledQuad = liveAnalysisState.documentQuad.scaledTo(
fromWidth = binaryMask.width, fromWidth = binaryMask.width,
@@ -174,6 +173,15 @@ fun AnalysisOverlay(liveAnalysisState: LiveAnalysisState) {
} }
} }
private fun drawMask(drawScope: DrawScope, binaryMask: Bitmap) {
val maskOverlay = replaceColor(binaryMask, Color.Black, Color.Transparent)
val size = drawScope.size
drawScope.drawImage(
maskOverlay.scale(size.width.toInt(), size.height.toInt()).asImageBitmap(),
colorFilter = ColorFilter.tint(Color(0x8000FF00), BlendMode.SrcIn)
)
}
fun replaceColor(bitmap: Bitmap, toReplace: Color, replacement: Color): Bitmap { fun replaceColor(bitmap: Bitmap, toReplace: Color, replacement: Color): Bitmap {
val width = bitmap.width val width = bitmap.width
val height = bitmap.height val height = bitmap.height

View File

@@ -86,6 +86,7 @@ data class CameraUiState(
val liveAnalysisState: LiveAnalysisState, val liveAnalysisState: LiveAnalysisState,
val captureState: CaptureState, val captureState: CaptureState,
val showDetectionError: Boolean, val showDetectionError: Boolean,
val isDebugMode: Boolean
) )
const val CAPTURED_IMAGE_DISPLAY_DURATION = 1500L const val CAPTURED_IMAGE_DISPLAY_DURATION = 1500L
@@ -101,6 +102,7 @@ fun CameraScreen(
var previewView by remember { mutableStateOf<PreviewView?>(null) } var previewView by remember { mutableStateOf<PreviewView?>(null) }
val pageIds by viewModel.pageIds.collectAsStateWithLifecycle() val pageIds by viewModel.pageIds.collectAsStateWithLifecycle()
val thumbnailCoords = remember { mutableStateOf(Offset.Zero) } val thumbnailCoords = remember { mutableStateOf(Offset.Zero) }
var isDebugMode by remember { mutableStateOf(false) }
val captureController = remember { CameraCaptureController() } val captureController = remember { CameraCaptureController() }
DisposableEffect(Unit) { DisposableEffect(Unit) {
@@ -149,7 +151,12 @@ fun CameraScreen(
{ offset -> thumbnailCoords.value = offset } { offset -> thumbnailCoords.value = offset }
) )
}, },
cameraUiState = CameraUiState(pageIds.size, liveAnalysisState, captureState, showDetectionError), cameraUiState = CameraUiState(
pageIds.size,
liveAnalysisState,
captureState,
showDetectionError,
isDebugMode),
onCapture = { onCapture = {
previewView?.bitmap?.let { previewView?.bitmap?.let {
Log.i("MyScan", "Pressed <Capture>") Log.i("MyScan", "Pressed <Capture>")
@@ -160,6 +167,7 @@ fun CameraScreen(
} }
}, },
onFinalizePressed = onFinalizePressed, onFinalizePressed = onFinalizePressed,
onDebugModeSwitched = { isDebugMode = !isDebugMode },
thumbnailCoords = thumbnailCoords, thumbnailCoords = thumbnailCoords,
) )
} }
@@ -171,6 +179,7 @@ private fun CameraScreenScaffold(
cameraUiState: CameraUiState, cameraUiState: CameraUiState,
onCapture: () -> Unit, onCapture: () -> Unit,
onFinalizePressed: () -> Unit, onFinalizePressed: () -> Unit,
onDebugModeSwitched: () -> Unit,
thumbnailCoords: MutableState<Offset>, thumbnailCoords: MutableState<Offset>,
) { ) {
Box { Box {
@@ -180,6 +189,7 @@ private fun CameraScreenScaffold(
pageList = pageList, pageList = pageList,
pageCount = cameraUiState.pageCount, pageCount = cameraUiState.pageCount,
onFinalizePressed = onFinalizePressed, onFinalizePressed = onFinalizePressed,
onDebugModeSwitched = onDebugModeSwitched,
) )
} }
) { innerPadding -> ) { innerPadding ->
@@ -189,7 +199,9 @@ private fun CameraScreenScaffold(
.fillMaxSize() .fillMaxSize()
) { ) {
CameraPreviewWithOverlay(cameraPreview, cameraUiState) CameraPreviewWithOverlay(cameraPreview, cameraUiState)
if (cameraUiState.isDebugMode) {
MessageBox(cameraUiState.liveAnalysisState.inferenceTime) MessageBox(cameraUiState.liveAnalysisState.inferenceTime)
}
CaptureButton( CaptureButton(
onClick = onCapture, onClick = onCapture,
modifier = Modifier modifier = Modifier
@@ -309,7 +321,7 @@ private fun CameraPreviewWithOverlay(
.height(height.dp) .height(height.dp)
) { ) {
cameraPreview() cameraPreview()
AnalysisOverlay(cameraUiState.liveAnalysisState) AnalysisOverlay(cameraUiState.liveAnalysisState, cameraUiState.isDebugMode)
captureState.frozenImage?.let { captureState.frozenImage?.let {
Image( Image(
bitmap = it.asImageBitmap(), bitmap = it.asImageBitmap(),
@@ -358,7 +370,25 @@ fun CameraScreenFooter(
pageList: @Composable () -> Unit, pageList: @Composable () -> Unit,
pageCount: Int, pageCount: Int,
onFinalizePressed: () -> Unit, onFinalizePressed: () -> Unit,
onDebugModeSwitched: () -> Unit,
) { ) {
var tapCount by remember { mutableStateOf(0) }
var lastTapTime by remember { mutableStateOf(0L) }
val tapThreshold = 500L
val onPageCountClick = {
val currentTime = System.currentTimeMillis()
if (currentTime - lastTapTime < tapThreshold) {
tapCount++
if (tapCount >= 3) {
onDebugModeSwitched()
tapCount = 0
}
} else {
tapCount = 1
}
lastTapTime = currentTime
}
Column (modifier = Modifier.background(MaterialTheme.colorScheme.primaryContainer)) { Column (modifier = Modifier.background(MaterialTheme.colorScheme.primaryContainer)) {
pageList() pageList()
BottomAppBar( BottomAppBar(
@@ -373,7 +403,8 @@ fun CameraScreenFooter(
) { ) {
Text( Text(
text = "$pageCount pages", text = "$pageCount pages",
style = MaterialTheme.typography.bodyMedium style = MaterialTheme.typography.bodyMedium,
modifier = Modifier.clickable(onClick = onPageCountClick)
) )
Button ( Button (
@@ -432,9 +463,10 @@ private fun ScreenPreview(captureState: CaptureState) {
listState = LazyListState(), listState = LazyListState(),
) )
}, },
cameraUiState = CameraUiState(pageCount = 4, LiveAnalysisState(), captureState, false), cameraUiState = CameraUiState(pageCount = 4, LiveAnalysisState(), captureState, false, false),
onCapture = {}, onCapture = {},
onFinalizePressed = {}, onFinalizePressed = {},
onDebugModeSwitched = {},
thumbnailCoords = thumbnailCoords, thumbnailCoords = thumbnailCoords,
) )
} }