Rely on PDFBox-Android to reduce the size of generated PDFs
This commit is contained in:
@@ -60,6 +60,7 @@ dependencies {
|
|||||||
implementation(libs.litert.support)
|
implementation(libs.litert.support)
|
||||||
implementation(libs.litert.metadata)
|
implementation(libs.litert.metadata)
|
||||||
implementation(libs.opencv)
|
implementation(libs.opencv)
|
||||||
|
implementation(libs.pdfbox)
|
||||||
|
|
||||||
testImplementation(libs.junit)
|
testImplementation(libs.junit)
|
||||||
testImplementation(libs.assertj)
|
testImplementation(libs.assertj)
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package org.mydomain.myscan
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.graphics.Bitmap
|
|
||||||
import android.media.MediaScannerConnection
|
import android.media.MediaScannerConnection
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
@@ -32,7 +31,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
initOpenCV()
|
initLibraries()
|
||||||
val viewModel: MainViewModel by viewModels { MainViewModel.getFactory(this) }
|
val viewModel: MainViewModel by viewModels { MainViewModel.getFactory(this) }
|
||||||
enableEdgeToEdge()
|
enableEdgeToEdge()
|
||||||
setContent {
|
setContent {
|
||||||
@@ -68,19 +67,15 @@ class MainActivity : ComponentActivity() {
|
|||||||
viewModel: MainViewModel,
|
viewModel: MainViewModel,
|
||||||
context: Context
|
context: Context
|
||||||
): () -> Unit = {
|
): () -> Unit = {
|
||||||
val document = viewModel.createPdf()
|
|
||||||
val outputDir = File(cacheDir, "pdfs").apply { mkdirs() }
|
val outputDir = File(cacheDir, "pdfs").apply { mkdirs() }
|
||||||
val outputFile = File(outputDir, "scan_${System.currentTimeMillis()}.pdf")
|
val outputFile = File(outputDir, "scan_${System.currentTimeMillis()}.pdf")
|
||||||
var success = true
|
var success = true
|
||||||
try {
|
try {
|
||||||
FileOutputStream(outputFile).use { outputStream ->
|
val fileOutputStream = FileOutputStream(outputFile)
|
||||||
document.writeTo(outputStream)
|
viewModel.createPdf(fileOutputStream)
|
||||||
}
|
|
||||||
} catch (_: IOException) {
|
} catch (_: IOException) {
|
||||||
Toast.makeText(context, "Failed to share PDF", Toast.LENGTH_SHORT).show()
|
Toast.makeText(context, "Failed to share PDF", Toast.LENGTH_SHORT).show()
|
||||||
success = false
|
success = false
|
||||||
} finally {
|
|
||||||
document.close()
|
|
||||||
}
|
}
|
||||||
if (success) {
|
if (success) {
|
||||||
val uri = FileProvider.getUriForFile(
|
val uri = FileProvider.getUriForFile(
|
||||||
@@ -101,13 +96,12 @@ class MainActivity : ComponentActivity() {
|
|||||||
viewModel: MainViewModel,
|
viewModel: MainViewModel,
|
||||||
context: Context
|
context: Context
|
||||||
): () -> Unit = {
|
): () -> Unit = {
|
||||||
val document = viewModel.createPdf()
|
|
||||||
try {
|
try {
|
||||||
val downloadsDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
|
val downloadsDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
|
||||||
if (!downloadsDir.exists()) downloadsDir.mkdirs()
|
if (!downloadsDir.exists()) downloadsDir.mkdirs()
|
||||||
val file = File(downloadsDir, "scan_${System.currentTimeMillis()}.pdf")
|
val file = File(downloadsDir, "scan_${System.currentTimeMillis()}.pdf")
|
||||||
val outputStream = FileOutputStream(file)
|
val outputStream = FileOutputStream(file)
|
||||||
document.writeTo(outputStream)
|
viewModel.createPdf(outputStream)
|
||||||
outputStream.flush()
|
outputStream.flush()
|
||||||
outputStream.close()
|
outputStream.close()
|
||||||
|
|
||||||
@@ -119,12 +113,12 @@ class MainActivity : ComponentActivity() {
|
|||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e("MyScan", "Failed to save PDF", e)
|
Log.e("MyScan", "Failed to save PDF", e)
|
||||||
Toast.makeText(context, "Failed to save PDF", Toast.LENGTH_SHORT).show()
|
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()) {
|
if (!OpenCVLoader.initLocal()) {
|
||||||
Log.e("OpenCV", "Initialization failed")
|
Log.e("OpenCV", "Initialization failed")
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import kotlinx.coroutines.flow.map
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
|
import java.io.OutputStream
|
||||||
|
|
||||||
class MainViewModel(
|
class MainViewModel(
|
||||||
private val imageSegmentationService: ImageSegmentationService,
|
private val imageSegmentationService: ImageSegmentationService,
|
||||||
@@ -128,9 +129,9 @@ class MainViewModel(
|
|||||||
return BitmapFactory.decodeByteArray(bytes, 0, bytes.size)
|
return BitmapFactory.decodeByteArray(bytes, 0, bytes.size)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createPdf(): PdfDocument {
|
fun createPdf(outputStream: OutputStream) {
|
||||||
val jpegs = imageRepository.imageIds().asSequence()
|
val jpegs = imageRepository.imageIds().asSequence()
|
||||||
.map { id -> imageRepository.getContent(id) }
|
.map { id -> imageRepository.getContent(id) }
|
||||||
return createPdfFromJpegs(jpegs)
|
writePdfFromJpegs(jpegs, outputStream)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,28 @@
|
|||||||
package org.mydomain.myscan
|
package org.mydomain.myscan
|
||||||
|
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.graphics.BitmapFactory
|
|
||||||
import android.graphics.pdf.PdfDocument
|
|
||||||
import androidx.core.graphics.scale
|
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
|
import kotlin.math.max
|
||||||
|
|
||||||
fun createPdfFromJpegs (jpegs: Sequence<ByteArray>): PdfDocument {
|
fun writePdfFromJpegs(jpegs: Sequence<ByteArray>, outputStream: OutputStream) {
|
||||||
val document = PdfDocument()
|
PDDocument().use { document ->
|
||||||
for ((index, jpegBytes) in jpegs.withIndex()) {
|
for (jpegBytes in jpegs) {
|
||||||
val bitmap = BitmapFactory.decodeByteArray(jpegBytes, 0, jpegBytes.size)
|
val image = JPEGFactory.createFromByteArray(document, jpegBytes)
|
||||||
val pageInfo = PdfDocument.PageInfo.Builder(bitmap.width, bitmap.height, index + 1).create()
|
val page = PDPage(PDRectangle(image.width.toFloat(), image.height.toFloat()))
|
||||||
val page = document.startPage(pageInfo)
|
document.addPage(page)
|
||||||
page.canvas.drawBitmap(bitmap, 0f, 0f, null)
|
val contentStream = PDPageContentStream(document, page, AppendMode.OVERWRITE, false)
|
||||||
document.finishPage(page)
|
contentStream.drawImage(image, 0f, 0f)
|
||||||
|
contentStream.close()
|
||||||
|
}
|
||||||
|
document.save(outputStream)
|
||||||
}
|
}
|
||||||
return document
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun resizeImage(original: Bitmap): Bitmap {
|
fun resizeImage(original: Bitmap): Bitmap {
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ litert = "1.2.0"
|
|||||||
opencv = "4.11.0"
|
opencv = "4.11.0"
|
||||||
flowlayout = "0.36.0"
|
flowlayout = "0.36.0"
|
||||||
assertj = "3.27.3"
|
assertj = "3.27.3"
|
||||||
|
pdfbox = "2.0.27.0"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
||||||
@@ -41,6 +42,7 @@ litert = { group = "com.google.ai.edge.litert", name = "litert", version.ref = "
|
|||||||
litert-support = { group = "com.google.ai.edge.litert", name = "litert-support", version.ref = "litert" }
|
litert-support = { group = "com.google.ai.edge.litert", name = "litert-support", version.ref = "litert" }
|
||||||
litert-metadata = { group = "com.google.ai.edge.litert", name = "litert-metadata", version.ref = "litert" }
|
litert-metadata = { group = "com.google.ai.edge.litert", name = "litert-metadata", version.ref = "litert" }
|
||||||
opencv = { group="org.opencv", name="opencv", version.ref = "opencv" }
|
opencv = { group="org.opencv", name="opencv", version.ref = "opencv" }
|
||||||
|
pdfbox = { group = "com.tom-roush", name = "pdfbox-android", version.ref = "pdfbox" }
|
||||||
|
|
||||||
assertj = { group="org.assertj", name="assertj-core", version.ref = "assertj" }
|
assertj = { group="org.assertj", name="assertj-core", version.ref = "assertj" }
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user