Fix rotation for pages migrated from previous versions of the app

This commit is contained in:
Pierre-Yves Nicolas
2026-01-17 20:29:33 +01:00
parent 7d74c1cd46
commit 8951218cb4
7 changed files with 41 additions and 28 deletions

View File

@@ -30,7 +30,6 @@ import kotlinx.coroutines.launch
import org.fairscan.app.data.ImageRepository
import org.fairscan.app.domain.CapturedPage
import org.fairscan.app.domain.PageViewKey
import org.fairscan.app.domain.Rotation
import org.fairscan.app.ui.NavigationState
import org.fairscan.app.ui.Screen
import org.fairscan.app.ui.state.DocumentUiModel
@@ -47,7 +46,7 @@ class MainViewModel(val imageRepository: ImageRepository, launchMode: LaunchMode
_pages.map { pages ->
DocumentUiModel(
pageKeys = pages.map { p ->
PageViewKey(p.id, p.metadata?.manualRotation?: Rotation.R0)
PageViewKey(p.id, p.manualRotation)
}.toImmutableList(),
imageLoader = ::getBitmap,
thumbnailLoader = ::getThumbnail,

View File

@@ -122,8 +122,11 @@ class ImageRepository(
}
fun pages(): List<ScanPage> =
pages.pages().map {
ScanPage(it.id, it.toMetadata())
pages.pages().mapNotNull {
runCatching {
val manualRotation = Rotation.fromDegrees(it.manualRotationDegrees)
ScanPage(it.id, manualRotation, it.toMetadata())
}.getOrNull()
}
private fun page(id: String): PageV2? = pages.get(id)
@@ -140,7 +143,7 @@ class ImageRepository(
id = id,
quad = metadata.normalizedQuad.toSerializable(),
baseRotationDegrees = metadata.baseRotation.degrees,
manualRotationDegrees = metadata.manualRotation.degrees,
manualRotationDegrees = Rotation.R0.degrees,
isColored = metadata.isColored
)
)
@@ -321,13 +324,10 @@ fun NormalizedQuad.toQuad(): Quad =
)
fun PageV2.toMetadata(): PageMetadata? {
return runCatching {
if (quad == null || isColored == null) return null
PageMetadata(
quad.toQuad(),
Rotation.fromDegrees(baseRotationDegrees),
Rotation.fromDegrees(manualRotationDegrees),
isColored
)
}.getOrNull()
if (quad == null || isColored == null) return null
return PageMetadata(
quad.toQuad(),
Rotation.fromDegrees(baseRotationDegrees),
isColored
)
}

View File

@@ -45,8 +45,9 @@ fun jpegsForExport(
ExportQuality.HIGH -> pages.mapNotNull { page ->
val sourceJpegBytes = imageRepository.sourceJpegBytes(page.id)
val pageMetadata = page.metadata
val manualRotation = page.manualRotation
if (sourceJpegBytes != null && pageMetadata != null)
prepareJpegForHigh(sourceJpegBytes, pageMetadata, exportQuality)
prepareJpegForHigh(sourceJpegBytes, pageMetadata, manualRotation, exportQuality)
else
imageRepository.jpegBytes(page.id)
}
@@ -73,6 +74,7 @@ fun resizeJpegBytesForMaxPixels(
fun prepareJpegForHigh(
sourceJpegBytes: ByteArray,
pageMetadata: PageMetadata,
manualRotation: Rotation,
exportQuality: ExportQuality,
): ByteArray? {
@@ -84,7 +86,7 @@ fun prepareJpegForHigh(
val page = extractDocument(
decoded,
quad,
pageMetadata.baseRotation.add(pageMetadata.manualRotation).degrees,
pageMetadata.baseRotation.add(manualRotation).degrees,
pageMetadata.isColored,
exportQuality.maxPixels)
val outJpegBytes = encodeJpeg(page, exportQuality.jpegQuality)

View File

@@ -19,12 +19,12 @@ import org.fairscan.imageprocessing.Quad
data class PageMetadata(
val normalizedQuad: Quad,
val baseRotation: Rotation,
val manualRotation: Rotation,
val isColored: Boolean,
)
data class ScanPage(
val id: String,
val manualRotation: Rotation,
val metadata: PageMetadata?,
)

View File

@@ -473,7 +473,7 @@ fun CameraScreenPreviewWithProcessedImage() {
CapturedPage(
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)

View File

@@ -198,7 +198,7 @@ fun extractDocumentFromBitmap(
outBgr.release()
val normalizedQuad = quad.scaledTo(source.width, source.height, 1, 1)
val baseRotation = Rotation.fromDegrees(rotationDegrees)
val metadata = PageMetadata(normalizedQuad, baseRotation, Rotation.R0, isColored)
val metadata = PageMetadata(normalizedQuad, baseRotation, isColored)
return CapturedPage(outBitmap, source, metadata)
}

View File

@@ -38,7 +38,7 @@ class ImageRepositoryTest {
private var _filesDir: File? = null
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 {
if (_filesDir == null) {
@@ -73,11 +73,11 @@ class ImageRepositoryTest {
val page = repo.pages().first()
assertThat(page.id).isEqualTo(id)
assertThat(page.manualRotation).isEqualTo(R0)
val metadata = page.metadata
assertThat(metadata).isNotNull()
assertThat(metadata!!.normalizedQuad).isEqualTo(quad1)
assertThat(metadata.baseRotation).isEqualTo(metadata1.baseRotation)
assertThat(metadata.manualRotation).isEqualTo(metadata1.manualRotation)
assertThat(metadata.isColored).isEqualTo(metadata1.isColored)
}
@@ -192,19 +192,18 @@ class ImageRepositoryTest {
fun rotate() {
val repo = repo()
repo.add(byteArrayOf(101, 102, 103), byteArrayOf(51), metadata1)
assertThat(metadata1.manualRotation).isEqualTo(R0)
assertThat(repo.pages().last().metadata).isEqualTo(metadata1)
val id = repo.pages().last().id
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)
assertThat(repo.pages().last().metadata).isEqualTo(metadata1.copy(manualRotation = R180))
assertThat(repo.pages().last().manualRotation).isEqualTo(R180)
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)
assertThat(repo.pages().last().metadata).isEqualTo(metadata1.copy(manualRotation = R0))
assertThat(repo.pages().last().manualRotation).isEqualTo(R0)
repo.rotate(id, false)
assertThat(repo.pages().last().metadata).isEqualTo(metadata1.copy(manualRotation = R270))
assertThat(repo.pages().last().manualRotation).isEqualTo(R270)
}
@Test
@@ -248,8 +247,21 @@ class ImageRepositoryTest {
assertThat(metadata).isNotNull()
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