ImageRepository: make jpegBytes and getThumbnail suspend

This commit is contained in:
Pierre-Yves Nicolas
2026-03-29 10:10:25 +02:00
parent 05a8af577c
commit 7cd3dfd990
6 changed files with 43 additions and 30 deletions

View File

@@ -14,6 +14,7 @@
*/
package org.fairscan.app.data
import org.fairscan.app.domain.JpegProvider
import java.io.File
import java.io.FileOutputStream
import java.io.OutputStream
@@ -25,7 +26,7 @@ data class GeneratedPdf(
)
fun interface PdfWriter {
fun writePdfFromJpegs(jpegs: Sequence<ByteArray>, outputStream: OutputStream): Int
suspend fun writePdfFromJpegs(jpegs: List<JpegProvider>, outputStream: OutputStream): Int
}
class FileManager(
@@ -42,7 +43,7 @@ class FileManager(
}
}
fun generatePdf(jpegs: Sequence<ByteArray>): GeneratedPdf {
suspend fun generatePdf(jpegs: List<JpegProvider>): GeneratedPdf {
pdfDir.mkdirs()
require(pdfDir.exists() && pdfDir.isDirectory) { "Invalid pdfDir: $pdfDir" }
val file = File(pdfDir, "${System.currentTimeMillis()}.pdf")

View File

@@ -170,13 +170,12 @@ class ImageRepository(
saveMetadata()
}
fun jpegBytes(key: PageViewKey): ByteArray? = runBlocking(Dispatchers.IO) {
suspend fun jpegBytes(key: PageViewKey): ByteArray? =
getOrCompute(imageCache, key, ::computeProcessedImage)
}
fun getThumbnail(key: PageViewKey): ByteArray? = runBlocking(Dispatchers.IO) {
suspend fun getThumbnail(key: PageViewKey): ByteArray? =
getOrCompute(thumbnailCache, key, ::computeThumbnail)
}
// --- Cache compute functions ---

View File

@@ -22,36 +22,46 @@ import org.fairscan.imageprocessing.resizeForMaxPixels
import org.fairscan.imageprocessing.scaledTo
import org.opencv.core.Mat
fun interface JpegProvider {
suspend fun get(): ByteArray
}
suspend fun jpegsForExport(
imageRepository: ImageRepository,
exportQuality: ExportQuality
): Sequence<ByteArray> {
): List<JpegProvider> {
val pages = imageRepository.pages().asSequence()
val pages = imageRepository.pages()
return when (exportQuality) {
ExportQuality.BALANCED -> pages.map { jpegBytes(it, imageRepository) }
ExportQuality.BALANCED -> pages.map {
JpegProvider { jpegBytes(it, imageRepository) }
}
ExportQuality.LOW -> pages.map { page ->
resizeJpegBytesForMaxPixels(
jpegBytes = jpegBytes(page, imageRepository),
maxPixels = exportQuality.maxPixels.toDouble(),
jpegQuality = exportQuality.jpegQuality
)
JpegProvider {
resizeJpegBytesForMaxPixels(
jpegBytes = jpegBytes(page, imageRepository),
maxPixels = exportQuality.maxPixels.toDouble(),
jpegQuality = exportQuality.jpegQuality
)
}
}
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
jpegBytes(page, imageRepository)
JpegProvider {
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
jpegBytes(page, imageRepository)
}
}
}
}
private fun jpegBytes(page: ScanPage, imageRepository: ImageRepository): ByteArray {
private suspend fun jpegBytes(page: ScanPage, imageRepository: ImageRepository): ByteArray {
val key = page.key()
return imageRepository.jpegBytes(key)
?: throw IllegalArgumentException("JPEG not found for $key")

View File

@@ -22,17 +22,18 @@ import com.tom_roush.pdfbox.pdmodel.common.PDRectangle
import com.tom_roush.pdfbox.pdmodel.graphics.image.JPEGFactory
import org.fairscan.app.BuildConfig
import org.fairscan.app.data.PdfWriter
import org.fairscan.app.domain.JpegProvider
import java.io.OutputStream
import java.util.Calendar
class AndroidPdfWriter : PdfWriter {
override fun writePdfFromJpegs(jpegs: Sequence<ByteArray>, outputStream: OutputStream): Int {
override suspend fun writePdfFromJpegs(jpegs: List<JpegProvider>, outputStream: OutputStream): Int {
val doc = PDDocument()
doc.documentInformation.creationDate = Calendar.getInstance()
doc.documentInformation.creator = "FairScan ${BuildConfig.VERSION_NAME}"
doc.use { document ->
for (jpegBytes in jpegs) {
val image = JPEGFactory.createFromByteArray(document, jpegBytes)
val image = JPEGFactory.createFromByteArray(document, jpegBytes.get())
val page = PDPage(PDRectangle(image.width.toFloat(), image.height.toFloat()))
document.addPage(page)
val contentStream = PDPageContentStream(document, page, AppendMode.OVERWRITE, false)

View File

@@ -174,9 +174,9 @@ class ExportViewModel(container: AppContainer, val imageRepository: ImageReposit
val jpegs = jpegsForExport(imageRepository, exportQuality)
val timestamp = System.currentTimeMillis()
preparationDir.mkdirs()
val files = jpegs.mapIndexed { index, bytes ->
val files = jpegs.mapIndexed { index, jpeg ->
val file = File(preparationDir, "$timestamp-${index + 1}.jpg")
file.writeBytes(bytes)
file.writeBytes(jpeg.get())
file
}.toList()
val sizeInBytes = files.sumOf { it.length() }

View File

@@ -14,7 +14,9 @@
*/
package org.fairscan.app.data
import kotlinx.coroutines.test.runTest
import org.assertj.core.api.Assertions.assertThat
import org.fairscan.app.domain.JpegProvider
import org.junit.Test
import java.io.File
import java.io.OutputStream
@@ -68,16 +70,16 @@ class FileManagerTest {
}
@Test
fun generatePdf() {
fun generatePdf() = runTest {
val fakePdfWriter = object : PdfWriter {
override fun writePdfFromJpegs(jpegs: Sequence<ByteArray>, outputStream: OutputStream): Int {
override suspend fun writePdfFromJpegs(jpegs: List<JpegProvider>, outputStream: OutputStream): Int {
val list = jpegs.toList()
list.forEach { bytes -> outputStream.write(bytes) }
list.forEach { bytes -> outputStream.write(bytes.get()) }
return list.size
}
}
val manager = FileManager(pdfDir, externalDir, fakePdfWriter)
val jpegs = listOf(byteArrayOf(0x01, 0x02), byteArrayOf(0x11)).asSequence()
val jpegs = listOf(byteArrayOf(0x01, 0x02), byteArrayOf(0x11)).map { JpegProvider { it } }
val pdf = manager.generatePdf(jpegs)
assertThat(pdf.pageCount).isEqualTo(2)
assertThat(pdf.sizeInBytes).isEqualTo(3)