Compress source JPEG during capture preview rather than later
This commit is contained in:
@@ -15,12 +15,14 @@
|
||||
package org.fairscan.app.domain
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.util.Log
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.fairscan.app.ui.screens.camera.extractDocumentFromBitmap
|
||||
import org.fairscan.imageprocessing.ImageSize
|
||||
@@ -32,7 +34,6 @@ import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.opencv.android.OpenCVLoader
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class DocumentDetectionTest {
|
||||
@@ -45,6 +46,7 @@ class DocumentDetectionTest {
|
||||
val segmentationService = ImageSegmentationService(context) { _, _, _ -> }
|
||||
segmentationService.initialize()
|
||||
OpenCVLoader.initLocal()
|
||||
val scope = CoroutineScope(SupervisorJob() + Dispatchers.Unconfined)
|
||||
|
||||
listOf("img01.jpg", "img02.jpg", "img03.jpg").forEach { imageFileName ->
|
||||
val inputStream = context.assets.open("uncropped/$imageFileName")
|
||||
@@ -60,7 +62,7 @@ class DocumentDetectionTest {
|
||||
if (quad != null) {
|
||||
val resizedQuad =
|
||||
quad.scaledTo(mask.width, mask.height, bitmap.width, bitmap.height)
|
||||
outputJpeg = extractDocumentFromBitmap(bitmap, resizedQuad, 0, mask).pageJpeg
|
||||
outputJpeg = extractDocumentFromBitmap(bitmap, resizedQuad, 0, mask, scope).pageJpeg
|
||||
val file = File(context.getExternalFilesDir(null), imageFileName)
|
||||
file.writeBytes(outputJpeg)
|
||||
Log.i("DocumentDetectionTest", "Image saved to ${file.absolutePath}")
|
||||
|
||||
@@ -20,6 +20,7 @@ import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.asCoroutineDispatcher
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
@@ -35,10 +36,6 @@ import org.fairscan.app.domain.PageViewKey
|
||||
import org.fairscan.app.ui.NavigationState
|
||||
import org.fairscan.app.ui.Screen
|
||||
import org.fairscan.app.ui.state.DocumentUiModel
|
||||
import org.fairscan.imageprocessing.encodeJpeg
|
||||
import org.opencv.android.Utils
|
||||
import org.opencv.core.Mat
|
||||
import org.opencv.imgproc.Imgproc
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
class MainViewModel(val imageRepository: ImageRepository, launchMode: LaunchMode): ViewModel() {
|
||||
@@ -125,10 +122,13 @@ class MainViewModel(val imageRepository: ImageRepository, launchMode: LaunchMode
|
||||
|
||||
fun handleImageCaptured(capturedPage: CapturedPage) {
|
||||
viewModelScope.launch {
|
||||
val sourceJpeg = withContext(Dispatchers.IO) {
|
||||
capturedPage.sourceJpeg.await()
|
||||
}
|
||||
val pages = withContext(repositoryDispatcher) {
|
||||
imageRepository.add(
|
||||
capturedPage.pageJpeg,
|
||||
compressJpeg(capturedPage.source, 90),
|
||||
sourceJpeg,
|
||||
capturedPage.metadata,
|
||||
)
|
||||
imageRepository.pages()
|
||||
@@ -136,17 +136,4 @@ class MainViewModel(val imageRepository: ImageRepository, launchMode: LaunchMode
|
||||
_pages.value = pages
|
||||
}
|
||||
}
|
||||
|
||||
private fun compressJpeg(bitmap: Bitmap, quality: Int): ByteArray {
|
||||
val rgba = Mat()
|
||||
Utils.bitmapToMat(bitmap, rgba)
|
||||
val bgr = Mat()
|
||||
Imgproc.cvtColor(rgba, bgr, Imgproc.COLOR_RGBA2BGR)
|
||||
rgba.release()
|
||||
return try {
|
||||
encodeJpeg(bgr, quality)
|
||||
} finally {
|
||||
bgr.release()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,10 +15,10 @@
|
||||
|
||||
package org.fairscan.app.domain
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import kotlinx.coroutines.Deferred
|
||||
|
||||
data class CapturedPage(
|
||||
val pageJpeg: ByteArray,
|
||||
val source: Bitmap,
|
||||
val sourceJpeg: Deferred<ByteArray>,
|
||||
val metadata: PageMetadata,
|
||||
)
|
||||
|
||||
@@ -86,6 +86,7 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import kotlinx.coroutines.delay
|
||||
import org.fairscan.app.MainViewModel
|
||||
import org.fairscan.app.R
|
||||
@@ -536,7 +537,7 @@ fun CameraScreenPreviewWithProcessedImage() {
|
||||
bitmap(debugImage("uncropped/img01.jpg")),
|
||||
CapturedPage(
|
||||
debugImage("gallica.bnf.fr-bpt6k5530456s-1.jpg"),
|
||||
bitmap(debugImage("gallica.bnf.fr-bpt6k5530456s-1.jpg")),
|
||||
CompletableDeferred(ByteArray(0)),
|
||||
PageMetadata(quad, R0, false))))
|
||||
}
|
||||
|
||||
|
||||
@@ -17,10 +17,11 @@ package org.fairscan.app.ui.screens.camera
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Matrix
|
||||
import androidx.camera.core.ImageProxy
|
||||
import androidx.core.graphics.createBitmap
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
@@ -42,7 +43,6 @@ import org.fairscan.imageprocessing.extractDocument
|
||||
import org.fairscan.imageprocessing.isColoredDocument
|
||||
import org.fairscan.imageprocessing.scaledTo
|
||||
import org.opencv.android.Utils
|
||||
import org.opencv.core.CvType
|
||||
import org.opencv.core.Mat
|
||||
import org.opencv.imgproc.Imgproc
|
||||
|
||||
@@ -165,7 +165,8 @@ class CameraViewModel(appContainer: AppContainer): ViewModel() {
|
||||
val quad = detectDocumentQuad(mask, originalSize, isLiveAnalysis = false)
|
||||
if (quad != null) {
|
||||
val resizedQuad = quad.scaledTo(mask.width, mask.height, source.width, source.height)
|
||||
result = extractDocumentFromBitmap(source, resizedQuad, rotationDegrees, mask)
|
||||
result = extractDocumentFromBitmap(
|
||||
source, resizedQuad, rotationDegrees, mask, viewModelScope)
|
||||
}
|
||||
}
|
||||
return@withContext result
|
||||
@@ -196,6 +197,19 @@ class CameraViewModel(appContainer: AppContainer): ViewModel() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun compressJpeg(bitmap: Bitmap, quality: Int): ByteArray {
|
||||
val rgba = Mat()
|
||||
Utils.bitmapToMat(bitmap, rgba)
|
||||
val bgr = Mat()
|
||||
Imgproc.cvtColor(rgba, bgr, Imgproc.COLOR_RGBA2BGR)
|
||||
rgba.release()
|
||||
return try {
|
||||
encodeJpeg(bgr, quality)
|
||||
} finally {
|
||||
bgr.release()
|
||||
}
|
||||
}
|
||||
|
||||
sealed class CaptureState {
|
||||
open val frozenImage: Bitmap? = null
|
||||
|
||||
@@ -209,7 +223,7 @@ sealed class CaptureState {
|
||||
}
|
||||
|
||||
fun extractDocumentFromBitmap(
|
||||
source: Bitmap, quad: Quad, rotationDegrees: Int, mask: Mask
|
||||
source: Bitmap, quad: Quad, rotationDegrees: Int, mask: Mask, viewModelScope: CoroutineScope
|
||||
): CapturedPage {
|
||||
val rgba = Mat()
|
||||
Utils.bitmapToMat(source, rgba)
|
||||
@@ -226,7 +240,10 @@ fun extractDocumentFromBitmap(
|
||||
val normalizedQuad = quad.scaledTo(source.width, source.height, 1, 1)
|
||||
val baseRotation = Rotation.fromDegrees(rotationDegrees)
|
||||
val metadata = PageMetadata(normalizedQuad, baseRotation, isColored)
|
||||
return CapturedPage(pageJpeg, source, metadata)
|
||||
val sourceJpegDeferred = viewModelScope.async(Dispatchers.IO) {
|
||||
compressJpeg(source, 90)
|
||||
}
|
||||
return CapturedPage(pageJpeg, sourceJpegDeferred, metadata)
|
||||
}
|
||||
|
||||
fun rotateBitmap(source: Bitmap, angle: Float): Bitmap {
|
||||
|
||||
Reference in New Issue
Block a user