ImageRepository: make jpegBytes and getThumbnail suspend
This commit is contained in:
@@ -14,6 +14,7 @@
|
|||||||
*/
|
*/
|
||||||
package org.fairscan.app.data
|
package org.fairscan.app.data
|
||||||
|
|
||||||
|
import org.fairscan.app.domain.JpegProvider
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileOutputStream
|
import java.io.FileOutputStream
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
@@ -25,7 +26,7 @@ data class GeneratedPdf(
|
|||||||
)
|
)
|
||||||
|
|
||||||
fun interface PdfWriter {
|
fun interface PdfWriter {
|
||||||
fun writePdfFromJpegs(jpegs: Sequence<ByteArray>, outputStream: OutputStream): Int
|
suspend fun writePdfFromJpegs(jpegs: List<JpegProvider>, outputStream: OutputStream): Int
|
||||||
}
|
}
|
||||||
|
|
||||||
class FileManager(
|
class FileManager(
|
||||||
@@ -42,7 +43,7 @@ class FileManager(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun generatePdf(jpegs: Sequence<ByteArray>): GeneratedPdf {
|
suspend fun generatePdf(jpegs: List<JpegProvider>): GeneratedPdf {
|
||||||
pdfDir.mkdirs()
|
pdfDir.mkdirs()
|
||||||
require(pdfDir.exists() && pdfDir.isDirectory) { "Invalid pdfDir: $pdfDir" }
|
require(pdfDir.exists() && pdfDir.isDirectory) { "Invalid pdfDir: $pdfDir" }
|
||||||
val file = File(pdfDir, "${System.currentTimeMillis()}.pdf")
|
val file = File(pdfDir, "${System.currentTimeMillis()}.pdf")
|
||||||
|
|||||||
@@ -170,13 +170,12 @@ class ImageRepository(
|
|||||||
saveMetadata()
|
saveMetadata()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun jpegBytes(key: PageViewKey): ByteArray? = runBlocking(Dispatchers.IO) {
|
suspend fun jpegBytes(key: PageViewKey): ByteArray? =
|
||||||
getOrCompute(imageCache, key, ::computeProcessedImage)
|
getOrCompute(imageCache, key, ::computeProcessedImage)
|
||||||
}
|
|
||||||
|
|
||||||
fun getThumbnail(key: PageViewKey): ByteArray? = runBlocking(Dispatchers.IO) {
|
|
||||||
|
suspend fun getThumbnail(key: PageViewKey): ByteArray? =
|
||||||
getOrCompute(thumbnailCache, key, ::computeThumbnail)
|
getOrCompute(thumbnailCache, key, ::computeThumbnail)
|
||||||
}
|
|
||||||
|
|
||||||
// --- Cache compute functions ---
|
// --- Cache compute functions ---
|
||||||
|
|
||||||
|
|||||||
@@ -22,36 +22,46 @@ import org.fairscan.imageprocessing.resizeForMaxPixels
|
|||||||
import org.fairscan.imageprocessing.scaledTo
|
import org.fairscan.imageprocessing.scaledTo
|
||||||
import org.opencv.core.Mat
|
import org.opencv.core.Mat
|
||||||
|
|
||||||
|
fun interface JpegProvider {
|
||||||
|
suspend fun get(): ByteArray
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun jpegsForExport(
|
suspend fun jpegsForExport(
|
||||||
imageRepository: ImageRepository,
|
imageRepository: ImageRepository,
|
||||||
exportQuality: ExportQuality
|
exportQuality: ExportQuality
|
||||||
): Sequence<ByteArray> {
|
): List<JpegProvider> {
|
||||||
|
|
||||||
val pages = imageRepository.pages().asSequence()
|
val pages = imageRepository.pages()
|
||||||
return when (exportQuality) {
|
return when (exportQuality) {
|
||||||
ExportQuality.BALANCED -> pages.map { jpegBytes(it, imageRepository) }
|
ExportQuality.BALANCED -> pages.map {
|
||||||
|
JpegProvider { jpegBytes(it, imageRepository) }
|
||||||
|
}
|
||||||
|
|
||||||
ExportQuality.LOW -> pages.map { page ->
|
ExportQuality.LOW -> pages.map { page ->
|
||||||
resizeJpegBytesForMaxPixels(
|
JpegProvider {
|
||||||
jpegBytes = jpegBytes(page, imageRepository),
|
resizeJpegBytesForMaxPixels(
|
||||||
maxPixels = exportQuality.maxPixels.toDouble(),
|
jpegBytes = jpegBytes(page, imageRepository),
|
||||||
jpegQuality = exportQuality.jpegQuality
|
maxPixels = exportQuality.maxPixels.toDouble(),
|
||||||
)
|
jpegQuality = exportQuality.jpegQuality
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ExportQuality.HIGH -> pages.map { page ->
|
ExportQuality.HIGH -> pages.map { page ->
|
||||||
val sourceJpegBytes = imageRepository.sourceJpegBytes(page.id)
|
JpegProvider {
|
||||||
val pageMetadata = page.metadata
|
val sourceJpegBytes = imageRepository.sourceJpegBytes(page.id)
|
||||||
val manualRotation = page.manualRotation
|
val pageMetadata = page.metadata
|
||||||
if (sourceJpegBytes != null && pageMetadata != null)
|
val manualRotation = page.manualRotation
|
||||||
prepareJpegForHigh(sourceJpegBytes, pageMetadata, manualRotation, exportQuality)
|
if (sourceJpegBytes != null && pageMetadata != null)
|
||||||
else
|
prepareJpegForHigh(sourceJpegBytes, pageMetadata, manualRotation, exportQuality)
|
||||||
jpegBytes(page, imageRepository)
|
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()
|
val key = page.key()
|
||||||
return imageRepository.jpegBytes(key)
|
return imageRepository.jpegBytes(key)
|
||||||
?: throw IllegalArgumentException("JPEG not found for $key")
|
?: throw IllegalArgumentException("JPEG not found for $key")
|
||||||
|
|||||||
@@ -22,17 +22,18 @@ import com.tom_roush.pdfbox.pdmodel.common.PDRectangle
|
|||||||
import com.tom_roush.pdfbox.pdmodel.graphics.image.JPEGFactory
|
import com.tom_roush.pdfbox.pdmodel.graphics.image.JPEGFactory
|
||||||
import org.fairscan.app.BuildConfig
|
import org.fairscan.app.BuildConfig
|
||||||
import org.fairscan.app.data.PdfWriter
|
import org.fairscan.app.data.PdfWriter
|
||||||
|
import org.fairscan.app.domain.JpegProvider
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
import java.util.Calendar
|
import java.util.Calendar
|
||||||
|
|
||||||
class AndroidPdfWriter : PdfWriter {
|
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()
|
val doc = PDDocument()
|
||||||
doc.documentInformation.creationDate = Calendar.getInstance()
|
doc.documentInformation.creationDate = Calendar.getInstance()
|
||||||
doc.documentInformation.creator = "FairScan ${BuildConfig.VERSION_NAME}"
|
doc.documentInformation.creator = "FairScan ${BuildConfig.VERSION_NAME}"
|
||||||
doc.use { document ->
|
doc.use { document ->
|
||||||
for (jpegBytes in jpegs) {
|
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()))
|
val page = PDPage(PDRectangle(image.width.toFloat(), image.height.toFloat()))
|
||||||
document.addPage(page)
|
document.addPage(page)
|
||||||
val contentStream = PDPageContentStream(document, page, AppendMode.OVERWRITE, false)
|
val contentStream = PDPageContentStream(document, page, AppendMode.OVERWRITE, false)
|
||||||
|
|||||||
@@ -174,9 +174,9 @@ class ExportViewModel(container: AppContainer, val imageRepository: ImageReposit
|
|||||||
val jpegs = jpegsForExport(imageRepository, exportQuality)
|
val jpegs = jpegsForExport(imageRepository, exportQuality)
|
||||||
val timestamp = System.currentTimeMillis()
|
val timestamp = System.currentTimeMillis()
|
||||||
preparationDir.mkdirs()
|
preparationDir.mkdirs()
|
||||||
val files = jpegs.mapIndexed { index, bytes ->
|
val files = jpegs.mapIndexed { index, jpeg ->
|
||||||
val file = File(preparationDir, "$timestamp-${index + 1}.jpg")
|
val file = File(preparationDir, "$timestamp-${index + 1}.jpg")
|
||||||
file.writeBytes(bytes)
|
file.writeBytes(jpeg.get())
|
||||||
file
|
file
|
||||||
}.toList()
|
}.toList()
|
||||||
val sizeInBytes = files.sumOf { it.length() }
|
val sizeInBytes = files.sumOf { it.length() }
|
||||||
|
|||||||
@@ -14,7 +14,9 @@
|
|||||||
*/
|
*/
|
||||||
package org.fairscan.app.data
|
package org.fairscan.app.data
|
||||||
|
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
|
import org.fairscan.app.domain.JpegProvider
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
@@ -68,16 +70,16 @@ class FileManagerTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun generatePdf() {
|
fun generatePdf() = runTest {
|
||||||
val fakePdfWriter = object : PdfWriter {
|
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()
|
val list = jpegs.toList()
|
||||||
list.forEach { bytes -> outputStream.write(bytes) }
|
list.forEach { bytes -> outputStream.write(bytes.get()) }
|
||||||
return list.size
|
return list.size
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val manager = FileManager(pdfDir, externalDir, fakePdfWriter)
|
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)
|
val pdf = manager.generatePdf(jpegs)
|
||||||
assertThat(pdf.pageCount).isEqualTo(2)
|
assertThat(pdf.pageCount).isEqualTo(2)
|
||||||
assertThat(pdf.sizeInBytes).isEqualTo(3)
|
assertThat(pdf.sizeInBytes).isEqualTo(3)
|
||||||
|
|||||||
Reference in New Issue
Block a user