Avoid swallowing errors during export preparation
This commit is contained in:
@@ -134,8 +134,15 @@ class MainActivity : ComponentActivity() {
|
||||
val onExportClick = if (launchMode == LaunchMode.EXTERNAL_SCAN_TO_PDF) {
|
||||
{
|
||||
lifecycleScope.launch {
|
||||
val result = exportViewModel.generatePdfForExternalCall()
|
||||
sendActivityResult(result)
|
||||
try {
|
||||
val result = exportViewModel.generatePdfForExternalCall()
|
||||
sendActivityResult(result)
|
||||
} catch (e: Exception) {
|
||||
val message = "Export failed"
|
||||
showToast(message)
|
||||
appContainer.logger.e("MainActivity", message, e)
|
||||
return@launch
|
||||
}
|
||||
viewModel.startNewDocument()
|
||||
finish()
|
||||
}
|
||||
@@ -229,6 +236,10 @@ class MainActivity : ComponentActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun showToast(text: String) {
|
||||
Toast.makeText(this, text, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SettingsScreenWrapper(
|
||||
settingsViewModel: SettingsViewModel,
|
||||
@@ -246,8 +257,7 @@ class MainActivity : ComponentActivity() {
|
||||
settingsViewModel.setExportDirUri(uri.toString())
|
||||
} catch (e: Exception) {
|
||||
logger.e("Settings", "Failed to set export dir to $uri", e)
|
||||
val text = getString(R.string.error_file_picker_result)
|
||||
Toast.makeText(this, text, Toast.LENGTH_SHORT).show()
|
||||
showToast(this.getString(R.string.error_file_picker_result))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -263,7 +273,7 @@ class MainActivity : ComponentActivity() {
|
||||
} catch (e: Exception) {
|
||||
val message = getString(R.string.error_file_picker_launch)
|
||||
logger.e("Settings", message, e)
|
||||
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
|
||||
showToast(message)
|
||||
}
|
||||
},
|
||||
onResetExportDirClick = { settingsViewModel.setExportDirUri(null) },
|
||||
@@ -288,7 +298,7 @@ class MainActivity : ComponentActivity() {
|
||||
clipboard.setClipEntry(
|
||||
ClipData.newPlainText("FairScan logs", event.logs).toClipEntry()
|
||||
)
|
||||
Toast.makeText(context, msgCopiedLogs, Toast.LENGTH_SHORT).show()
|
||||
showToast(msgCopiedLogs)
|
||||
}
|
||||
is AboutEvent.PrepareEmailWithLastImage -> {
|
||||
val file = imageRepository.lastAddedSourceFile()
|
||||
@@ -312,8 +322,7 @@ class MainActivity : ComponentActivity() {
|
||||
if (isGranted) {
|
||||
exportViewModel.onSaveClicked()
|
||||
} else {
|
||||
val message = getString(R.string.storage_permission_denied)
|
||||
Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
|
||||
showToast(this.getString(R.string.storage_permission_denied))
|
||||
}
|
||||
}
|
||||
LaunchedEffect(Unit) {
|
||||
@@ -423,7 +432,7 @@ class MainActivity : ComponentActivity() {
|
||||
try {
|
||||
startActivity(chooser)
|
||||
} catch (_: ActivityNotFoundException) {
|
||||
Toast.makeText(this, getString(R.string.error_no_app), Toast.LENGTH_SHORT).show()
|
||||
showToast(getString(R.string.error_no_app))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
package org.fairscan.app.domain
|
||||
|
||||
import org.fairscan.app.data.ImageRepository
|
||||
import org.fairscan.imageprocessing.encodeJpeg
|
||||
import org.fairscan.imageprocessing.extractDocument
|
||||
import org.fairscan.imageprocessing.resizeForMaxPixels
|
||||
import org.fairscan.imageprocessing.scaledTo
|
||||
@@ -29,85 +30,84 @@ suspend fun jpegsForExport(
|
||||
|
||||
val pages = imageRepository.pages().asSequence()
|
||||
return when (exportQuality) {
|
||||
ExportQuality.BALANCED -> pages.mapNotNull { imageRepository.jpegBytes(it.key()) }
|
||||
ExportQuality.BALANCED -> pages.map { jpegBytes(it, imageRepository) }
|
||||
|
||||
ExportQuality.LOW -> pages.mapNotNull { page ->
|
||||
imageRepository.jpegBytes(page.key())?.let { jpeg ->
|
||||
resizeJpegBytesForMaxPixels(
|
||||
jpegBytes = jpeg,
|
||||
maxPixels = exportQuality.maxPixels.toDouble(),
|
||||
jpegQuality = exportQuality.jpegQuality
|
||||
)
|
||||
}
|
||||
ExportQuality.LOW -> pages.map { page ->
|
||||
resizeJpegBytesForMaxPixels(
|
||||
jpegBytes = jpegBytes(page, imageRepository),
|
||||
maxPixels = exportQuality.maxPixels.toDouble(),
|
||||
jpegQuality = exportQuality.jpegQuality
|
||||
)
|
||||
}
|
||||
|
||||
ExportQuality.HIGH -> pages.mapNotNull { page ->
|
||||
ExportQuality.HIGH -> pages.map { page ->
|
||||
val sourceJpegBytes = imageRepository.sourceJpegBytes(page.id)
|
||||
val pageMetadata = page.metadata
|
||||
val manualRotation = page.manualRotation
|
||||
if (sourceJpegBytes != null && pageMetadata != null)
|
||||
prepareJpegForHigh(sourceJpegBytes, pageMetadata, manualRotation, exportQuality)
|
||||
else
|
||||
imageRepository.jpegBytes(page.key())
|
||||
jpegBytes(page, imageRepository)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun resizeJpegBytesForMaxPixels(
|
||||
private fun jpegBytes(page: ScanPage, imageRepository: ImageRepository): ByteArray {
|
||||
val key = page.key()
|
||||
return imageRepository.jpegBytes(key)
|
||||
?: throw IllegalArgumentException("JPEG not found for $key")
|
||||
}
|
||||
|
||||
private fun resizeJpegBytesForMaxPixels(
|
||||
jpegBytes: ByteArray,
|
||||
maxPixels: Double,
|
||||
jpegQuality: Int
|
||||
): ByteArray? {
|
||||
val decoded = decodeJpeg(jpegBytes)
|
||||
if (decoded == null)
|
||||
return null
|
||||
|
||||
val resized = resizeForMaxPixels(decoded, maxPixels)
|
||||
val outJpegBytes = encodeJpeg(resized, jpegQuality)
|
||||
|
||||
decoded.release()
|
||||
resized.release()
|
||||
return outJpegBytes
|
||||
): ByteArray {
|
||||
var decoded: Mat? = null
|
||||
var resized: Mat? = null
|
||||
try {
|
||||
decoded = decodeJpeg(jpegBytes)
|
||||
resized = resizeForMaxPixels(decoded, maxPixels)
|
||||
return encodeJpeg(resized, jpegQuality)
|
||||
} finally {
|
||||
decoded?.release()
|
||||
resized?.release()
|
||||
}
|
||||
}
|
||||
|
||||
fun prepareJpegForHigh(
|
||||
private fun prepareJpegForHigh(
|
||||
sourceJpegBytes: ByteArray,
|
||||
pageMetadata: PageMetadata,
|
||||
manualRotation: Rotation,
|
||||
exportQuality: ExportQuality,
|
||||
): ByteArray? {
|
||||
): ByteArray {
|
||||
|
||||
val decoded = decodeJpeg(sourceJpegBytes)
|
||||
if (decoded == null)
|
||||
return null
|
||||
|
||||
val quad = pageMetadata.normalizedQuad.scaledTo(1,1,decoded.width(), decoded.height())
|
||||
val page = extractDocument(
|
||||
decoded,
|
||||
quad,
|
||||
pageMetadata.baseRotation.add(manualRotation).degrees,
|
||||
pageMetadata.isColored,
|
||||
exportQuality.maxPixels)
|
||||
val outJpegBytes = encodeJpeg(page, exportQuality.jpegQuality)
|
||||
|
||||
decoded.release()
|
||||
page.release()
|
||||
return outJpegBytes
|
||||
var decoded: Mat? = null
|
||||
var page: Mat? = null
|
||||
try {
|
||||
decoded = decodeJpeg(sourceJpegBytes)
|
||||
val quad = pageMetadata.normalizedQuad.scaledTo(1, 1, decoded.width(), decoded.height())
|
||||
page = extractDocument(
|
||||
decoded,
|
||||
quad,
|
||||
pageMetadata.baseRotation.add(manualRotation).degrees,
|
||||
pageMetadata.isColored,
|
||||
exportQuality.maxPixels
|
||||
)
|
||||
return encodeJpeg(page, exportQuality.jpegQuality)
|
||||
} finally {
|
||||
decoded?.release()
|
||||
page?.release()
|
||||
}
|
||||
}
|
||||
|
||||
fun decodeJpeg(jpegBytes: ByteArray): Mat? {
|
||||
private fun decodeJpeg(jpegBytes: ByteArray): Mat {
|
||||
val src = MatOfByte(*jpegBytes)
|
||||
val decoded = Imgcodecs.imdecode(src, Imgcodecs.IMREAD_COLOR)
|
||||
src.release()
|
||||
if (decoded.empty()) {
|
||||
decoded.release()
|
||||
return null
|
||||
throw IllegalStateException("Failed to decode JPEG")
|
||||
}
|
||||
return decoded
|
||||
}
|
||||
|
||||
fun encodeJpeg(mat: Mat, jpegQuality: Int): ByteArray? {
|
||||
return runCatching {
|
||||
org.fairscan.imageprocessing.encodeJpeg(mat, jpegQuality)
|
||||
}.getOrNull()
|
||||
}
|
||||
|
||||
@@ -276,7 +276,7 @@ class ExportViewModel(container: AppContainer, val imageRepository: ImageReposit
|
||||
}
|
||||
|
||||
private suspend fun save(context: Context, saveDir: SaveDir?, exportFormat: ExportFormat) {
|
||||
val result = applyRenaming() ?: return
|
||||
val result = applyRenaming()
|
||||
val savedItems = mutableListOf<SavedItem>()
|
||||
val filesForMediaScan = mutableListOf<File>()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user