Rely on PDFBox-Android to reduce the size of generated PDFs

This commit is contained in:
Pierre-Yves Nicolas
2025-06-04 18:22:57 +02:00
parent ebd5453b65
commit 6a90723fb3
5 changed files with 31 additions and 26 deletions

View File

@@ -60,6 +60,7 @@ dependencies {
implementation(libs.litert.support)
implementation(libs.litert.metadata)
implementation(libs.opencv)
implementation(libs.pdfbox)
testImplementation(libs.junit)
testImplementation(libs.assertj)

View File

@@ -2,7 +2,6 @@ package org.mydomain.myscan
import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.media.MediaScannerConnection
import android.os.Bundle
import android.os.Environment
@@ -32,7 +31,7 @@ class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
initOpenCV()
initLibraries()
val viewModel: MainViewModel by viewModels { MainViewModel.getFactory(this) }
enableEdgeToEdge()
setContent {
@@ -68,19 +67,15 @@ class MainActivity : ComponentActivity() {
viewModel: MainViewModel,
context: Context
): () -> Unit = {
val document = viewModel.createPdf()
val outputDir = File(cacheDir, "pdfs").apply { mkdirs() }
val outputFile = File(outputDir, "scan_${System.currentTimeMillis()}.pdf")
var success = true
try {
FileOutputStream(outputFile).use { outputStream ->
document.writeTo(outputStream)
}
val fileOutputStream = FileOutputStream(outputFile)
viewModel.createPdf(fileOutputStream)
} catch (_: IOException) {
Toast.makeText(context, "Failed to share PDF", Toast.LENGTH_SHORT).show()
success = false
} finally {
document.close()
}
if (success) {
val uri = FileProvider.getUriForFile(
@@ -101,13 +96,12 @@ class MainActivity : ComponentActivity() {
viewModel: MainViewModel,
context: Context
): () -> Unit = {
val document = viewModel.createPdf()
try {
val downloadsDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
if (!downloadsDir.exists()) downloadsDir.mkdirs()
val file = File(downloadsDir, "scan_${System.currentTimeMillis()}.pdf")
val outputStream = FileOutputStream(file)
document.writeTo(outputStream)
viewModel.createPdf(outputStream)
outputStream.flush()
outputStream.close()
@@ -119,12 +113,12 @@ class MainActivity : ComponentActivity() {
} catch (e: Exception) {
Log.e("MyScan", "Failed to save PDF", e)
Toast.makeText(context, "Failed to save PDF", Toast.LENGTH_SHORT).show()
} finally {
document.close()
}
}
private fun initOpenCV() {
private fun initLibraries() {
com.tom_roush.pdfbox.android.PDFBoxResourceLoader.init(applicationContext)
if (!OpenCVLoader.initLocal()) {
Log.e("OpenCV", "Initialization failed")
} else {

View File

@@ -19,6 +19,7 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.ByteArrayOutputStream
import java.io.OutputStream
class MainViewModel(
private val imageSegmentationService: ImageSegmentationService,
@@ -128,9 +129,9 @@ class MainViewModel(
return BitmapFactory.decodeByteArray(bytes, 0, bytes.size)
}
fun createPdf(): PdfDocument {
fun createPdf(outputStream: OutputStream) {
val jpegs = imageRepository.imageIds().asSequence()
.map { id -> imageRepository.getContent(id) }
return createPdfFromJpegs(jpegs)
writePdfFromJpegs(jpegs, outputStream)
}
}

View File

@@ -1,21 +1,28 @@
package org.mydomain.myscan
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.pdf.PdfDocument
import androidx.core.graphics.scale
import com.tom_roush.pdfbox.pdmodel.PDDocument
import com.tom_roush.pdfbox.pdmodel.PDPage
import com.tom_roush.pdfbox.pdmodel.PDPageContentStream
import com.tom_roush.pdfbox.pdmodel.PDPageContentStream.AppendMode
import com.tom_roush.pdfbox.pdmodel.common.PDRectangle
import com.tom_roush.pdfbox.pdmodel.graphics.image.JPEGFactory
import java.io.OutputStream
import kotlin.math.max
fun createPdfFromJpegs (jpegs: Sequence<ByteArray>): PdfDocument {
val document = PdfDocument()
for ((index, jpegBytes) in jpegs.withIndex()) {
val bitmap = BitmapFactory.decodeByteArray(jpegBytes, 0, jpegBytes.size)
val pageInfo = PdfDocument.PageInfo.Builder(bitmap.width, bitmap.height, index + 1).create()
val page = document.startPage(pageInfo)
page.canvas.drawBitmap(bitmap, 0f, 0f, null)
document.finishPage(page)
fun writePdfFromJpegs(jpegs: Sequence<ByteArray>, outputStream: OutputStream) {
PDDocument().use { document ->
for (jpegBytes in jpegs) {
val image = JPEGFactory.createFromByteArray(document, jpegBytes)
val page = PDPage(PDRectangle(image.width.toFloat(), image.height.toFloat()))
document.addPage(page)
val contentStream = PDPageContentStream(document, page, AppendMode.OVERWRITE, false)
contentStream.drawImage(image, 0f, 0f)
contentStream.close()
}
document.save(outputStream)
}
return document
}
fun resizeImage(original: Bitmap): Bitmap {