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.imageprocessing.ColorMode
import org.fairscan.imageprocessing.Mask
import org.fairscan.imageprocessing.Point
import org.fairscan.imageprocessing.Quad
import org.fairscan.imageprocessing.autoColorMode
import org.fairscan.imageprocessing.createQuad
import org.fairscan.imageprocessing.extractDocument
import org.fairscan.imageprocessing.resizeForMaxPixels
import org.fairscan.imageprocessing.rotate
import org.fairscan.imageprocessing.scaledTo
import org.opencv.android.Utils
import org.opencv.core.Mat
@@ -106,28 +110,43 @@ fun processedImage(
fun extractDocumentFromBitmap(
source: Bitmap,
quadInMask: Quad,
quadInMask: Quad?,
rotationDegrees: Int,
mask: Mask,
mask: Mask?,
viewModelScope: CoroutineScope,
defaultColorMode: DefaultColorMode = DefaultColorMode.AUTO
): CapturedPage {
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()
Utils.bitmapToMat(source, rgba)
val bgr = Mat()
Imgproc.cvtColor(rgba, bgr, Imgproc.COLOR_RGBA2BGR)
rgba.release()
val autoColorMode = autoColorMode(bgr, mask, quad)
val colorMode = defaultColorMode.colorMode ?: autoColorMode
val page = extractDocument(bgr, quad, rotationDegrees, colorMode, exportQuality.maxPixels)
if (mask == null || quadInMask == null) {
// 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)
bgr.release()
page.release()
val normalizedQuad = quad.scaledTo(source.width, source.height, 1, 1)
val baseRotation = Rotation.fromDegrees(rotationDegrees)
val metadata = PageMetadata(normalizedQuad, baseRotation, autoColorMode)
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.util.Log
import androidx.activity.compose.BackHandler
import androidx.camera.core.ImageProxy
import androidx.camera.view.PreviewView
import androidx.compose.animation.core.animateFloat
@@ -526,7 +525,7 @@ private fun CameraPreviewWithOverlay(
.background(Color.Black.copy(alpha = 0.6f))
)
}
if (cameraUiState.showDetectionError) {
if (cameraUiState.showCaptureError) {
Box(
modifier = Modifier
.align(Alignment.Center)
@@ -534,7 +533,7 @@ private fun CameraPreviewWithOverlay(
.padding(16.dp)
) {
Text(
text = stringResource(R.string.error_no_document),
text = stringResource(R.string.error_occurred),
color = Color.White,
fontSize = 16.sp
)

View File

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

View File

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

View File

@@ -29,7 +29,7 @@
<string name="error_context_provider">المزود: %1$s</string>
<string name="error_export_dir_permission_lost">لم يعد من الممكن الوصول إلى مجلد التصدير المحدّد. يُرجى اختيار مجلد آخر.</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="export">صدِّر</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_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_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="export">Exportovat</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_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_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="export">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_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_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="export">Exportar</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_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_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="export">Exporter</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_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_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="export">Exportar</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_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_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="export">Esporta</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_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_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="export">Exportar</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_export_dir_permission_lost">Выбранная папка экспорта больше недоступна. Пожалуйста, выберите другую папку.</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="export">Экспорт</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_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_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="export">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_export_dir_permission_lost">所選的匯出目錄已無法存取。請選擇其他目錄。</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="export">匯出</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_export_dir_permission_lost">所选的导出目录已无法访问。请选择其他目录。</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="export">导出</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_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_document">No document detected</string>
<string name="error_occurred">An error occurred</string>
<string name="error_save">Failed to save file</string>
<string name="export">Export</string>
<string name="export_as">Export as %1$s</string>