Capture/import image even when no document is detected

This commit is contained in:
Pierre-Yves Nicolas
2026-05-12 20:38:33 +02:00
parent 17aa9e1a4c
commit 0f0045fade
17 changed files with 49 additions and 36 deletions

View File

@@ -27,9 +27,13 @@ import org.fairscan.app.domain.Rotation
import org.fairscan.app.ui.screens.settings.DefaultColorMode import org.fairscan.app.ui.screens.settings.DefaultColorMode
import org.fairscan.imageprocessing.ColorMode import org.fairscan.imageprocessing.ColorMode
import org.fairscan.imageprocessing.Mask import org.fairscan.imageprocessing.Mask
import org.fairscan.imageprocessing.Point
import org.fairscan.imageprocessing.Quad import org.fairscan.imageprocessing.Quad
import org.fairscan.imageprocessing.autoColorMode import org.fairscan.imageprocessing.autoColorMode
import org.fairscan.imageprocessing.createQuad
import org.fairscan.imageprocessing.extractDocument import org.fairscan.imageprocessing.extractDocument
import org.fairscan.imageprocessing.resizeForMaxPixels
import org.fairscan.imageprocessing.rotate
import org.fairscan.imageprocessing.scaledTo import org.fairscan.imageprocessing.scaledTo
import org.opencv.android.Utils import org.opencv.android.Utils
import org.opencv.core.Mat import org.opencv.core.Mat
@@ -106,28 +110,43 @@ fun processedImage(
fun extractDocumentFromBitmap( fun extractDocumentFromBitmap(
source: Bitmap, source: Bitmap,
quadInMask: Quad, quadInMask: Quad?,
rotationDegrees: Int, rotationDegrees: Int,
mask: Mask, mask: Mask?,
viewModelScope: CoroutineScope, viewModelScope: CoroutineScope,
defaultColorMode: DefaultColorMode = DefaultColorMode.AUTO defaultColorMode: DefaultColorMode = DefaultColorMode.AUTO
): CapturedPage { ): CapturedPage {
val exportQuality = ExportQuality.BALANCED val exportQuality = ExportQuality.BALANCED
val quad = quadInMask.scaledTo(mask.width, mask.height, source.width, source.height) var colorMode = ColorMode.COLOR
var autoColorMode = colorMode
var normalizedQuad = createQuad(listOf(
Point(0.0, 0.0), Point(0.0, 1.0), Point(1.0, 1.0), Point(1.0, 0.0))
)
var page: Mat
val rgba = Mat() val rgba = Mat()
Utils.bitmapToMat(source, rgba) Utils.bitmapToMat(source, rgba)
val bgr = Mat() val bgr = Mat()
Imgproc.cvtColor(rgba, bgr, Imgproc.COLOR_RGBA2BGR) Imgproc.cvtColor(rgba, bgr, Imgproc.COLOR_RGBA2BGR)
rgba.release() rgba.release()
val autoColorMode = autoColorMode(bgr, mask, quad)
val colorMode = defaultColorMode.colorMode ?: autoColorMode if (mask == null || quadInMask == null) {
val page = extractDocument(bgr, quad, rotationDegrees, colorMode, exportQuality.maxPixels) // No document detected
val resized = resizeForMaxPixels(bgr, exportQuality.maxPixels.toDouble())
page = rotate(resized, rotationDegrees)
resized.release()
} else {
val quad = quadInMask.scaledTo(mask.width, mask.height, source.width, source.height)
normalizedQuad = quad.scaledTo(source.width, source.height, 1, 1)
autoColorMode = autoColorMode(bgr, mask, quad)
colorMode = defaultColorMode.colorMode ?: autoColorMode
page = extractDocument(bgr, quad, rotationDegrees, colorMode, exportQuality.maxPixels)
}
val pageJpeg = Jpeg.fromMat(page, exportQuality.jpegQuality) val pageJpeg = Jpeg.fromMat(page, exportQuality.jpegQuality)
bgr.release() bgr.release()
page.release() page.release()
val normalizedQuad = quad.scaledTo(source.width, source.height, 1, 1)
val baseRotation = Rotation.fromDegrees(rotationDegrees) val baseRotation = Rotation.fromDegrees(rotationDegrees)
val metadata = PageMetadata(normalizedQuad, baseRotation, autoColorMode) val metadata = PageMetadata(normalizedQuad, baseRotation, autoColorMode)
val sourceJpegDeferred = viewModelScope.async(Dispatchers.IO) { val sourceJpegDeferred = viewModelScope.async(Dispatchers.IO) {

View File

@@ -16,7 +16,6 @@ package org.fairscan.app.ui.screens.camera
import android.content.res.Configuration import android.content.res.Configuration
import android.util.Log import android.util.Log
import androidx.activity.compose.BackHandler
import androidx.camera.core.ImageProxy import androidx.camera.core.ImageProxy
import androidx.camera.view.PreviewView import androidx.camera.view.PreviewView
import androidx.compose.animation.core.animateFloat import androidx.compose.animation.core.animateFloat
@@ -526,7 +525,7 @@ private fun CameraPreviewWithOverlay(
.background(Color.Black.copy(alpha = 0.6f)) .background(Color.Black.copy(alpha = 0.6f))
) )
} }
if (cameraUiState.showDetectionError) { if (cameraUiState.showCaptureError) {
Box( Box(
modifier = Modifier modifier = Modifier
.align(Alignment.Center) .align(Alignment.Center)
@@ -534,7 +533,7 @@ private fun CameraPreviewWithOverlay(
.padding(16.dp) .padding(16.dp)
) { ) {
Text( Text(
text = stringResource(R.string.error_no_document), text = stringResource(R.string.error_occurred),
color = Color.White, color = Color.White,
fontSize = 16.sp fontSize = 16.sp
) )

View File

@@ -38,7 +38,7 @@ data class CameraUiState(
val liveAnalysisState: LiveAnalysisState, val liveAnalysisState: LiveAnalysisState,
val captureState: CaptureState, val captureState: CaptureState,
val importState: ImportState, val importState: ImportState,
val showDetectionError: Boolean, val showCaptureError: Boolean,
val isLandscape: Boolean, val isLandscape: Boolean,
val isDebugMode: Boolean, val isDebugMode: Boolean,
val isTorchEnabled: Boolean, val isTorchEnabled: Boolean,

View File

@@ -154,19 +154,14 @@ class CameraViewModel(appContainer: AppContainer): ViewModel() {
private suspend fun processCapturedImage( private suspend fun processCapturedImage(
source: Bitmap, source: Bitmap,
rotationDegrees: Int, rotationDegrees: Int,
): CapturedPage? = withContext(Dispatchers.IO) { ): CapturedPage = withContext(Dispatchers.IO) {
var result: CapturedPage? = null
val segmentation = imageSegmentationService.runSegmentationAndReturn(source) val segmentation = imageSegmentationService.runSegmentationAndReturn(source)
if (segmentation != null) { val mask = segmentation?.segmentation
val mask = segmentation.segmentation
val originalSize = ImageSize(source.width, source.height) val originalSize = ImageSize(source.width, source.height)
val quad = detectDocumentQuad(mask, originalSize, isLiveAnalysis = false) val quad = mask?.let { detectDocumentQuad(mask, originalSize, isLiveAnalysis = false) }
if (quad != null) {
val defaultColorMode = settingsRepository.defaultColorMode.first() val defaultColorMode = settingsRepository.defaultColorMode.first()
result = extractDocumentFromBitmap( val result = extractDocumentFromBitmap(
source, quad, rotationDegrees, mask, viewModelScope, defaultColorMode) source, quad, rotationDegrees, mask, viewModelScope, defaultColorMode)
}
}
return@withContext result return@withContext result
} }

View File

@@ -29,7 +29,7 @@
<string name="error_context_provider">المزود: %1$s</string> <string name="error_context_provider">المزود: %1$s</string>
<string name="error_export_dir_permission_lost">لم يعد من الممكن الوصول إلى مجلد التصدير المحدّد. يُرجى اختيار مجلد آخر.</string> <string name="error_export_dir_permission_lost">لم يعد من الممكن الوصول إلى مجلد التصدير المحدّد. يُرجى اختيار مجلد آخر.</string>
<string name="error_no_app">لم يُعثر على تطبيق لفتح هذا الملف</string> <string name="error_no_app">لم يُعثر على تطبيق لفتح هذا الملف</string>
<string name="error_no_document">لم يكتشف أي مستند</string> <string name="error_occurred">حدث خطأ</string>
<string name="error_save">فشل حفظ الملف</string> <string name="error_save">فشل حفظ الملف</string>
<string name="export">صدِّر</string> <string name="export">صدِّر</string>
<string name="export_as">صدِّر ك %1$s</string> <string name="export_as">صدِّر ك %1$s</string>

View File

@@ -29,7 +29,7 @@
<string name="error_context_provider">Poskytovatel: %1$s</string> <string name="error_context_provider">Poskytovatel: %1$s</string>
<string name="error_export_dir_permission_lost">Vybraná složka pro export již není dostupná. Vyberte prosím jinou složku.</string> <string name="error_export_dir_permission_lost">Vybraná složka pro export již není dostupná. Vyberte prosím jinou složku.</string>
<string name="error_no_app">Nebyla nalezena žádná aplikace pro otevření tohoto souboru</string> <string name="error_no_app">Nebyla nalezena žádná aplikace pro otevření tohoto souboru</string>
<string name="error_no_document">Nebyl rozpoznán žádná dokument</string> <string name="error_occurred">Došlo k chybě</string>
<string name="error_save">Soubor se nepodařilo uložit</string> <string name="error_save">Soubor se nepodařilo uložit</string>
<string name="export">Exportovat</string> <string name="export">Exportovat</string>
<string name="export_as">Exportovat jako %1$s</string> <string name="export_as">Exportovat jako %1$s</string>

View File

@@ -29,7 +29,7 @@
<string name="error_context_provider">Anbieter: %1$s</string> <string name="error_context_provider">Anbieter: %1$s</string>
<string name="error_export_dir_permission_lost">Der ausgewählte Exportordner ist nicht mehr zugänglich. Bitte wählen Sie einen anderen Ordner.</string> <string name="error_export_dir_permission_lost">Der ausgewählte Exportordner ist nicht mehr zugänglich. Bitte wählen Sie einen anderen Ordner.</string>
<string name="error_no_app">Keine App zum Öffnen dieser Datei gefunden</string> <string name="error_no_app">Keine App zum Öffnen dieser Datei gefunden</string>
<string name="error_no_document">Kein Dokument erkannt</string> <string name="error_occurred">Ein Fehler ist aufgetreten</string>
<string name="error_save">Datei konnte nicht gespeichert werden</string> <string name="error_save">Datei konnte nicht gespeichert werden</string>
<string name="export">Exportieren</string> <string name="export">Exportieren</string>
<string name="export_as">Als %1$s exportieren</string> <string name="export_as">Als %1$s exportieren</string>

View File

@@ -29,7 +29,7 @@
<string name="error_context_provider">Proveedor: %1$s</string> <string name="error_context_provider">Proveedor: %1$s</string>
<string name="error_export_dir_permission_lost">La carpeta de exportación seleccionada ya no es accesible. Por favor, elija otra carpeta.</string> <string name="error_export_dir_permission_lost">La carpeta de exportación seleccionada ya no es accesible. Por favor, elija otra carpeta.</string>
<string name="error_no_app">No se encontró ninguna aplicación para abrir este archivo</string> <string name="error_no_app">No se encontró ninguna aplicación para abrir este archivo</string>
<string name="error_no_document">No se detectó ningún documento</string> <string name="error_occurred">Se produjo un error</string>
<string name="error_save">No se pudo guardar el archivo</string> <string name="error_save">No se pudo guardar el archivo</string>
<string name="export">Exportar</string> <string name="export">Exportar</string>
<string name="export_as">Exportar como %1$s</string> <string name="export_as">Exportar como %1$s</string>

View File

@@ -29,7 +29,7 @@
<string name="error_context_provider">Fournisseur : %1$s</string> <string name="error_context_provider">Fournisseur : %1$s</string>
<string name="error_export_dir_permission_lost">Le dossier dexport sélectionné nest plus accessible. Veuillez choisir un autre dossier.</string> <string name="error_export_dir_permission_lost">Le dossier dexport sélectionné nest plus accessible. Veuillez choisir un autre dossier.</string>
<string name="error_no_app">Aucune application trouvée pour ouvrir ce fichier</string> <string name="error_no_app">Aucune application trouvée pour ouvrir ce fichier</string>
<string name="error_no_document">Aucun document détecté</string> <string name="error_occurred">Une erreur sest produite</string>
<string name="error_save">Échec de l\'enregistrement du fichier</string> <string name="error_save">Échec de l\'enregistrement du fichier</string>
<string name="export">Exporter</string> <string name="export">Exporter</string>
<string name="export_as">Exporter en %1$s</string> <string name="export_as">Exporter en %1$s</string>

View File

@@ -29,7 +29,7 @@
<string name="error_context_provider">Provedor: %1$s</string> <string name="error_context_provider">Provedor: %1$s</string>
<string name="error_export_dir_permission_lost">O cartafol de exportación seleccionado xa non é accesible. Escolle outro cartafol.</string> <string name="error_export_dir_permission_lost">O cartafol de exportación seleccionado xa non é accesible. Escolle outro cartafol.</string>
<string name="error_no_app">Non se atopou ningunha aplicación para abrir este ficheiro</string> <string name="error_no_app">Non se atopou ningunha aplicación para abrir este ficheiro</string>
<string name="error_no_document">Non se detectou ningún documento</string> <string name="error_occurred">Produciuse un erro</string>
<string name="error_save">Produciuse un erro ao gardar o ficheiro</string> <string name="error_save">Produciuse un erro ao gardar o ficheiro</string>
<string name="export">Exportar</string> <string name="export">Exportar</string>
<string name="export_as">Exportar como %1$s</string> <string name="export_as">Exportar como %1$s</string>

View File

@@ -29,7 +29,7 @@
<string name="error_context_provider">Provider: %1$s</string> <string name="error_context_provider">Provider: %1$s</string>
<string name="error_export_dir_permission_lost">La cartella di esportazione selezionata non è più accessibile. Scegli unaltra cartella.</string> <string name="error_export_dir_permission_lost">La cartella di esportazione selezionata non è più accessibile. Scegli unaltra cartella.</string>
<string name="error_no_app">Nessuna app trovata per aprire questo file</string> <string name="error_no_app">Nessuna app trovata per aprire questo file</string>
<string name="error_no_document">Nessun documento rilevato</string> <string name="error_occurred">Si è verificato un errore</string>
<string name="error_save">Impossibile salvare il file</string> <string name="error_save">Impossibile salvare il file</string>
<string name="export">Esporta</string> <string name="export">Esporta</string>
<string name="export_as">Esporta come %1$s</string> <string name="export_as">Esporta come %1$s</string>

View File

@@ -29,7 +29,7 @@
<string name="error_context_provider">Provedor: %1$s</string> <string name="error_context_provider">Provedor: %1$s</string>
<string name="error_export_dir_permission_lost">A pasta de exportação selecionada não está mais acessível. Escolha outra pasta.</string> <string name="error_export_dir_permission_lost">A pasta de exportação selecionada não está mais acessível. Escolha outra pasta.</string>
<string name="error_no_app">Nenhum app encontrado para abrir este arquivo</string> <string name="error_no_app">Nenhum app encontrado para abrir este arquivo</string>
<string name="error_no_document">Nenhum documento detectado</string> <string name="error_occurred">Ocorreu um erro</string>
<string name="error_save">Falha ao salvar o arquivo</string> <string name="error_save">Falha ao salvar o arquivo</string>
<string name="export">Exportar</string> <string name="export">Exportar</string>
<string name="export_as">Exportar como %1$s</string> <string name="export_as">Exportar como %1$s</string>

View File

@@ -29,7 +29,7 @@
<string name="error_context_provider">Провайдер: %1$s</string> <string name="error_context_provider">Провайдер: %1$s</string>
<string name="error_export_dir_permission_lost">Выбранная папка экспорта больше недоступна. Пожалуйста, выберите другую папку.</string> <string name="error_export_dir_permission_lost">Выбранная папка экспорта больше недоступна. Пожалуйста, выберите другую папку.</string>
<string name="error_no_app">Не найдено приложение для открытия этого файла</string> <string name="error_no_app">Не найдено приложение для открытия этого файла</string>
<string name="error_no_document">Документ не обнаружен</string> <string name="error_occurred">Произошла ошибка</string>
<string name="error_save">Не удалось сохранить файл</string> <string name="error_save">Не удалось сохранить файл</string>
<string name="export">Экспорт</string> <string name="export">Экспорт</string>
<string name="export_as">Экспортировать как %1$s</string> <string name="export_as">Экспортировать как %1$s</string>

View File

@@ -29,7 +29,7 @@
<string name="error_context_provider">Sağlayıcı: %1$s</string> <string name="error_context_provider">Sağlayıcı: %1$s</string>
<string name="error_export_dir_permission_lost">Seçilen dışa aktarma dizini artık erişilebilir değil. Lütfen başka bir dizin seçin.</string> <string name="error_export_dir_permission_lost">Seçilen dışa aktarma dizini artık erişilebilir değil. Lütfen başka bir dizin seçin.</string>
<string name="error_no_app">No app found to open this file</string> <string name="error_no_app">No app found to open this file</string>
<string name="error_no_document">No document detected</string> <string name="error_occurred">Bir hata oluştu</string>
<string name="error_save">Failed to save file</string> <string name="error_save">Failed to save file</string>
<string name="export">Dışa aktar</string> <string name="export">Dışa aktar</string>
<string name="export_as">%1$s olarak dışa aktar</string> <string name="export_as">%1$s olarak dışa aktar</string>

View File

@@ -29,7 +29,7 @@
<string name="error_context_provider">提供者:%1$s</string> <string name="error_context_provider">提供者:%1$s</string>
<string name="error_export_dir_permission_lost">所選的匯出目錄已無法存取。請選擇其他目錄。</string> <string name="error_export_dir_permission_lost">所選的匯出目錄已無法存取。請選擇其他目錄。</string>
<string name="error_no_app">找不到可開啟此檔案的應用程式</string> <string name="error_no_app">找不到可開啟此檔案的應用程式</string>
<string name="error_no_document">未偵測到文件</string> <string name="error_occurred">發生錯誤</string>
<string name="error_save">儲存檔案失敗</string> <string name="error_save">儲存檔案失敗</string>
<string name="export">匯出</string> <string name="export">匯出</string>
<string name="export_as">匯出為 %1$s</string> <string name="export_as">匯出為 %1$s</string>

View File

@@ -29,7 +29,7 @@
<string name="error_context_provider">提供方:%1$s</string> <string name="error_context_provider">提供方:%1$s</string>
<string name="error_export_dir_permission_lost">所选的导出目录已无法访问。请选择其他目录。</string> <string name="error_export_dir_permission_lost">所选的导出目录已无法访问。请选择其他目录。</string>
<string name="error_no_app">未找到可打开此文件的应用</string> <string name="error_no_app">未找到可打开此文件的应用</string>
<string name="error_no_document">未检测到任何文档</string> <string name="error_occurred">发生错误</string>
<string name="error_save">无法保存文件</string> <string name="error_save">无法保存文件</string>
<string name="export">导出</string> <string name="export">导出</string>
<string name="export_as">导出为 %1$s</string> <string name="export_as">导出为 %1$s</string>

View File

@@ -33,7 +33,7 @@
<string name="error_file_picker_launch" translatable="false">Failed to launch system file picker on this device</string> <string name="error_file_picker_launch" translatable="false">Failed to launch system file picker on this device</string>
<string name="error_file_picker_result" translatable="false">Failed to set export directory</string> <string name="error_file_picker_result" translatable="false">Failed to set export directory</string>
<string name="error_no_app">No app found to open this file</string> <string name="error_no_app">No app found to open this file</string>
<string name="error_no_document">No document detected</string> <string name="error_occurred">An error occurred</string>
<string name="error_save">Failed to save file</string> <string name="error_save">Failed to save file</string>
<string name="export">Export</string> <string name="export">Export</string>
<string name="export_as">Export as %1$s</string> <string name="export_as">Export as %1$s</string>