Fix rotation for pages migrated from previous versions of the app
This commit is contained in:
@@ -30,7 +30,6 @@ import kotlinx.coroutines.launch
|
|||||||
import org.fairscan.app.data.ImageRepository
|
import org.fairscan.app.data.ImageRepository
|
||||||
import org.fairscan.app.domain.CapturedPage
|
import org.fairscan.app.domain.CapturedPage
|
||||||
import org.fairscan.app.domain.PageViewKey
|
import org.fairscan.app.domain.PageViewKey
|
||||||
import org.fairscan.app.domain.Rotation
|
|
||||||
import org.fairscan.app.ui.NavigationState
|
import org.fairscan.app.ui.NavigationState
|
||||||
import org.fairscan.app.ui.Screen
|
import org.fairscan.app.ui.Screen
|
||||||
import org.fairscan.app.ui.state.DocumentUiModel
|
import org.fairscan.app.ui.state.DocumentUiModel
|
||||||
@@ -47,7 +46,7 @@ class MainViewModel(val imageRepository: ImageRepository, launchMode: LaunchMode
|
|||||||
_pages.map { pages ->
|
_pages.map { pages ->
|
||||||
DocumentUiModel(
|
DocumentUiModel(
|
||||||
pageKeys = pages.map { p ->
|
pageKeys = pages.map { p ->
|
||||||
PageViewKey(p.id, p.metadata?.manualRotation?: Rotation.R0)
|
PageViewKey(p.id, p.manualRotation)
|
||||||
}.toImmutableList(),
|
}.toImmutableList(),
|
||||||
imageLoader = ::getBitmap,
|
imageLoader = ::getBitmap,
|
||||||
thumbnailLoader = ::getThumbnail,
|
thumbnailLoader = ::getThumbnail,
|
||||||
|
|||||||
@@ -122,8 +122,11 @@ class ImageRepository(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun pages(): List<ScanPage> =
|
fun pages(): List<ScanPage> =
|
||||||
pages.pages().map {
|
pages.pages().mapNotNull {
|
||||||
ScanPage(it.id, it.toMetadata())
|
runCatching {
|
||||||
|
val manualRotation = Rotation.fromDegrees(it.manualRotationDegrees)
|
||||||
|
ScanPage(it.id, manualRotation, it.toMetadata())
|
||||||
|
}.getOrNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun page(id: String): PageV2? = pages.get(id)
|
private fun page(id: String): PageV2? = pages.get(id)
|
||||||
@@ -140,7 +143,7 @@ class ImageRepository(
|
|||||||
id = id,
|
id = id,
|
||||||
quad = metadata.normalizedQuad.toSerializable(),
|
quad = metadata.normalizedQuad.toSerializable(),
|
||||||
baseRotationDegrees = metadata.baseRotation.degrees,
|
baseRotationDegrees = metadata.baseRotation.degrees,
|
||||||
manualRotationDegrees = metadata.manualRotation.degrees,
|
manualRotationDegrees = Rotation.R0.degrees,
|
||||||
isColored = metadata.isColored
|
isColored = metadata.isColored
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -321,13 +324,10 @@ fun NormalizedQuad.toQuad(): Quad =
|
|||||||
)
|
)
|
||||||
|
|
||||||
fun PageV2.toMetadata(): PageMetadata? {
|
fun PageV2.toMetadata(): PageMetadata? {
|
||||||
return runCatching {
|
|
||||||
if (quad == null || isColored == null) return null
|
if (quad == null || isColored == null) return null
|
||||||
PageMetadata(
|
return PageMetadata(
|
||||||
quad.toQuad(),
|
quad.toQuad(),
|
||||||
Rotation.fromDegrees(baseRotationDegrees),
|
Rotation.fromDegrees(baseRotationDegrees),
|
||||||
Rotation.fromDegrees(manualRotationDegrees),
|
|
||||||
isColored
|
isColored
|
||||||
)
|
)
|
||||||
}.getOrNull()
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,8 +45,9 @@ fun jpegsForExport(
|
|||||||
ExportQuality.HIGH -> pages.mapNotNull { page ->
|
ExportQuality.HIGH -> pages.mapNotNull { page ->
|
||||||
val sourceJpegBytes = imageRepository.sourceJpegBytes(page.id)
|
val sourceJpegBytes = imageRepository.sourceJpegBytes(page.id)
|
||||||
val pageMetadata = page.metadata
|
val pageMetadata = page.metadata
|
||||||
|
val manualRotation = page.manualRotation
|
||||||
if (sourceJpegBytes != null && pageMetadata != null)
|
if (sourceJpegBytes != null && pageMetadata != null)
|
||||||
prepareJpegForHigh(sourceJpegBytes, pageMetadata, exportQuality)
|
prepareJpegForHigh(sourceJpegBytes, pageMetadata, manualRotation, exportQuality)
|
||||||
else
|
else
|
||||||
imageRepository.jpegBytes(page.id)
|
imageRepository.jpegBytes(page.id)
|
||||||
}
|
}
|
||||||
@@ -73,6 +74,7 @@ fun resizeJpegBytesForMaxPixels(
|
|||||||
fun prepareJpegForHigh(
|
fun prepareJpegForHigh(
|
||||||
sourceJpegBytes: ByteArray,
|
sourceJpegBytes: ByteArray,
|
||||||
pageMetadata: PageMetadata,
|
pageMetadata: PageMetadata,
|
||||||
|
manualRotation: Rotation,
|
||||||
exportQuality: ExportQuality,
|
exportQuality: ExportQuality,
|
||||||
): ByteArray? {
|
): ByteArray? {
|
||||||
|
|
||||||
@@ -84,7 +86,7 @@ fun prepareJpegForHigh(
|
|||||||
val page = extractDocument(
|
val page = extractDocument(
|
||||||
decoded,
|
decoded,
|
||||||
quad,
|
quad,
|
||||||
pageMetadata.baseRotation.add(pageMetadata.manualRotation).degrees,
|
pageMetadata.baseRotation.add(manualRotation).degrees,
|
||||||
pageMetadata.isColored,
|
pageMetadata.isColored,
|
||||||
exportQuality.maxPixels)
|
exportQuality.maxPixels)
|
||||||
val outJpegBytes = encodeJpeg(page, exportQuality.jpegQuality)
|
val outJpegBytes = encodeJpeg(page, exportQuality.jpegQuality)
|
||||||
|
|||||||
@@ -19,12 +19,12 @@ import org.fairscan.imageprocessing.Quad
|
|||||||
data class PageMetadata(
|
data class PageMetadata(
|
||||||
val normalizedQuad: Quad,
|
val normalizedQuad: Quad,
|
||||||
val baseRotation: Rotation,
|
val baseRotation: Rotation,
|
||||||
val manualRotation: Rotation,
|
|
||||||
val isColored: Boolean,
|
val isColored: Boolean,
|
||||||
)
|
)
|
||||||
|
|
||||||
data class ScanPage(
|
data class ScanPage(
|
||||||
val id: String,
|
val id: String,
|
||||||
|
val manualRotation: Rotation,
|
||||||
val metadata: PageMetadata?,
|
val metadata: PageMetadata?,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -473,7 +473,7 @@ fun CameraScreenPreviewWithProcessedImage() {
|
|||||||
CapturedPage(
|
CapturedPage(
|
||||||
debugImage("gallica.bnf.fr-bpt6k5530456s-1.jpg"),
|
debugImage("gallica.bnf.fr-bpt6k5530456s-1.jpg"),
|
||||||
debugImage("gallica.bnf.fr-bpt6k5530456s-1.jpg"),
|
debugImage("gallica.bnf.fr-bpt6k5530456s-1.jpg"),
|
||||||
PageMetadata(quad, R0, R0, false))))
|
PageMetadata(quad, R0, false))))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Preview(showBackground = true, widthDp = 640, heightDp = 320)
|
@Preview(showBackground = true, widthDp = 640, heightDp = 320)
|
||||||
|
|||||||
@@ -198,7 +198,7 @@ fun extractDocumentFromBitmap(
|
|||||||
outBgr.release()
|
outBgr.release()
|
||||||
val normalizedQuad = quad.scaledTo(source.width, source.height, 1, 1)
|
val normalizedQuad = quad.scaledTo(source.width, source.height, 1, 1)
|
||||||
val baseRotation = Rotation.fromDegrees(rotationDegrees)
|
val baseRotation = Rotation.fromDegrees(rotationDegrees)
|
||||||
val metadata = PageMetadata(normalizedQuad, baseRotation, Rotation.R0, isColored)
|
val metadata = PageMetadata(normalizedQuad, baseRotation, isColored)
|
||||||
return CapturedPage(outBitmap, source, metadata)
|
return CapturedPage(outBitmap, source, metadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ class ImageRepositoryTest {
|
|||||||
private var _filesDir: File? = null
|
private var _filesDir: File? = null
|
||||||
|
|
||||||
val quad1 = Quad(Point(.01, .02), Point(.1, .03), Point(.11, .12), Point(.03, .09))
|
val quad1 = Quad(Point(.01, .02), Point(.1, .03), Point(.11, .12), Point(.03, .09))
|
||||||
val metadata1 = PageMetadata(quad1, R90, R0, true)
|
val metadata1 = PageMetadata(quad1, R90, true)
|
||||||
|
|
||||||
fun getFilesDir(): File {
|
fun getFilesDir(): File {
|
||||||
if (_filesDir == null) {
|
if (_filesDir == null) {
|
||||||
@@ -73,11 +73,11 @@ class ImageRepositoryTest {
|
|||||||
|
|
||||||
val page = repo.pages().first()
|
val page = repo.pages().first()
|
||||||
assertThat(page.id).isEqualTo(id)
|
assertThat(page.id).isEqualTo(id)
|
||||||
|
assertThat(page.manualRotation).isEqualTo(R0)
|
||||||
val metadata = page.metadata
|
val metadata = page.metadata
|
||||||
assertThat(metadata).isNotNull()
|
assertThat(metadata).isNotNull()
|
||||||
assertThat(metadata!!.normalizedQuad).isEqualTo(quad1)
|
assertThat(metadata!!.normalizedQuad).isEqualTo(quad1)
|
||||||
assertThat(metadata.baseRotation).isEqualTo(metadata1.baseRotation)
|
assertThat(metadata.baseRotation).isEqualTo(metadata1.baseRotation)
|
||||||
assertThat(metadata.manualRotation).isEqualTo(metadata1.manualRotation)
|
|
||||||
assertThat(metadata.isColored).isEqualTo(metadata1.isColored)
|
assertThat(metadata.isColored).isEqualTo(metadata1.isColored)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,19 +192,18 @@ class ImageRepositoryTest {
|
|||||||
fun rotate() {
|
fun rotate() {
|
||||||
val repo = repo()
|
val repo = repo()
|
||||||
repo.add(byteArrayOf(101, 102, 103), byteArrayOf(51), metadata1)
|
repo.add(byteArrayOf(101, 102, 103), byteArrayOf(51), metadata1)
|
||||||
assertThat(metadata1.manualRotation).isEqualTo(R0)
|
|
||||||
assertThat(repo.pages().last().metadata).isEqualTo(metadata1)
|
assertThat(repo.pages().last().metadata).isEqualTo(metadata1)
|
||||||
val id = repo.pages().last().id
|
val id = repo.pages().last().id
|
||||||
repo.rotate(id, true)
|
repo.rotate(id, true)
|
||||||
assertThat(repo.pages().last().metadata).isEqualTo(metadata1.copy(manualRotation = R90))
|
assertThat(repo.pages().last().manualRotation).isEqualTo(R90)
|
||||||
repo.rotate(id, true)
|
repo.rotate(id, true)
|
||||||
assertThat(repo.pages().last().metadata).isEqualTo(metadata1.copy(manualRotation = R180))
|
assertThat(repo.pages().last().manualRotation).isEqualTo(R180)
|
||||||
repo.rotate(id, true)
|
repo.rotate(id, true)
|
||||||
assertThat(repo.pages().last().metadata).isEqualTo(metadata1.copy(manualRotation = R270))
|
assertThat(repo.pages().last().manualRotation).isEqualTo(R270)
|
||||||
repo.rotate(id, true)
|
repo.rotate(id, true)
|
||||||
assertThat(repo.pages().last().metadata).isEqualTo(metadata1.copy(manualRotation = R0))
|
assertThat(repo.pages().last().manualRotation).isEqualTo(R0)
|
||||||
repo.rotate(id, false)
|
repo.rotate(id, false)
|
||||||
assertThat(repo.pages().last().metadata).isEqualTo(metadata1.copy(manualRotation = R270))
|
assertThat(repo.pages().last().manualRotation).isEqualTo(R270)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -248,8 +247,21 @@ class ImageRepositoryTest {
|
|||||||
assertThat(metadata).isNotNull()
|
assertThat(metadata).isNotNull()
|
||||||
assertThat(metadata!!.isColored).isEqualTo(isColored)
|
assertThat(metadata!!.isColored).isEqualTo(isColored)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
assertThat(PageV2("1", 42, 0, quad, true).toMetadata()).isNull()
|
@Test
|
||||||
|
fun `pages with invalid metadata should be skipped`() {
|
||||||
|
val bytes = byteArrayOf(105, 106, 107)
|
||||||
|
|
||||||
|
writeDocumentDotJson("""{"version":2, "pages":[{"id":"1", "manualRotationDegrees":90}]}""")
|
||||||
|
File(scanDir(), "1.jpg").writeBytes(byteArrayOf(101))
|
||||||
|
File(scanDir(), "1-90.jpg").writeBytes(bytes)
|
||||||
|
assertThat(repo().imageIds()).containsExactly("1")
|
||||||
|
|
||||||
|
writeDocumentDotJson("""{"version":2, "pages":[{"id":"1", "manualRotationDegrees":42}]}""")
|
||||||
|
File(scanDir(), "1.jpg").writeBytes(byteArrayOf(101))
|
||||||
|
File(scanDir(), "1-42.jpg").writeBytes(bytes)
|
||||||
|
assertThat(repo().imageIds()).isEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
Reference in New Issue
Block a user