ImageOrientationTest.kt
130 lignes · 5464 octets
package fr.ebii.card2vcf.scan import android.graphics.Bitmap import android.graphics.Canvas import android.graphics.Color import android.graphics.Paint import androidx.exifinterface.media.ExifInterface import org.junit.runner.RunWith import org.robolectric.RobolectricTestRunner import org.robolectric.annotation.Config import java.io.ByteArrayOutputStream import java.io.File import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertNotNull import kotlin.test.assertTrue /** * Tests génériques d'orientation : EXIF, fallback CameraX, anti double-rotation, * et fixture réelle (carte dimo photographiée en conditions utilisateur). */ @RunWith(RobolectricTestRunner::class) @Config(sdk = [31]) class ImageOrientationTest { @Test fun exif6FaitTourner90EtInverseDimensions() { val src = solidBitmap(200, 100, Color.RED) val jpeg = encodeJpeg(src) val file = writeJpegWithExif(jpeg, ExifInterface.ORIENTATION_ROTATE_90) val oriented = ImageOrientation.fromFile(file)!! assertTrue(oriented.fromExif) assertEquals(90, oriented.appliedRotationDegrees) assertEquals(100, oriented.bitmap.width) assertEquals(200, oriented.bitmap.height) } @Test fun exif3FaitTourner180SansChangerDimensions() { val src = solidBitmap(160, 80, Color.BLUE) val file = writeJpegWithExif(encodeJpeg(src), ExifInterface.ORIENTATION_ROTATE_180) val oriented = ImageOrientation.fromFile(file)!! assertEquals(180, oriented.appliedRotationDegrees) assertEquals(160, oriented.bitmap.width) assertEquals(80, oriented.bitmap.height) } @Test fun exif8FaitTourner270() { val src = solidBitmap(120, 60, Color.GREEN) val file = writeJpegWithExif(encodeJpeg(src), ExifInterface.ORIENTATION_ROTATE_270) val oriented = ImageOrientation.fromFile(file)!! assertEquals(270, oriented.appliedRotationDegrees) assertEquals(60, oriented.bitmap.width) assertEquals(120, oriented.bitmap.height) } @Test fun sansExifUtiliseRotationCameraX() { val src = solidBitmap(200, 100, Color.YELLOW) val jpeg = encodeJpeg(src) // EXIF normal / absent val oriented = ImageOrientation.fromJpegBytes(jpeg, cameraRotationDegrees = 90)!! assertFalse(oriented.fromExif) assertEquals(90, oriented.appliedRotationDegrees) assertEquals(100, oriented.bitmap.width) assertEquals(200, oriented.bitmap.height) } @Test fun exifPrioritaireSurCameraXPasDeDoubleRotation() { val src = solidBitmap(200, 100, Color.MAGENTA) val file = writeJpegWithExif(encodeJpeg(src), ExifInterface.ORIENTATION_ROTATE_90) // CameraX dirait aussi 90° : si on cumule → 180° et dimensions d'origine. On refuse ça. val oriented = ImageOrientation.fromFile(file, cameraRotationDegrees = 90)!! assertTrue(oriented.fromExif) assertEquals(90, oriented.appliedRotationDegrees) assertEquals(100, oriented.bitmap.width) assertEquals(200, oriented.bitmap.height) } @Test fun normalizeDegreesQuantifieAuxQuarts() { assertEquals(0, ImageOrientation.normalizeDegrees(10)) assertEquals(90, ImageOrientation.normalizeDegrees(95)) assertEquals(180, ImageOrientation.normalizeDegrees(190)) assertEquals(270, ImageOrientation.normalizeDegrees(-90)) } @Test fun carteDimoReelleOrientationExif6() { val bytes = javaClass.classLoader!! .getResourceAsStream("cards/dimo_genoux.jpg")!! .readBytes() val oriented = ImageOrientation.fromJpegBytes(bytes)!! assertNotNull(oriented.bitmap) // Photo OnePlus : EXIF orientation 6 → 90° → portrait 3008x4000 assertTrue(oriented.fromExif, "la carte dimo doit être corrigée via EXIF") assertEquals(90, oriented.appliedRotationDegrees) assertEquals(3008, oriented.bitmap.width) assertEquals(4000, oriented.bitmap.height) assertTrue(oriented.bitmap.height > oriented.bitmap.width, "attendu portrait après EXIF") } @Test fun carteDimoNeDoitPasEtreDoubleTourneeSiCameraDitAussi90() { val bytes = javaClass.classLoader!! .getResourceAsStream("cards/dimo_genoux.jpg")!! .readBytes() val oriented = ImageOrientation.fromJpegBytes(bytes, cameraRotationDegrees = 90)!! assertEquals(3008, oriented.bitmap.width) assertEquals(4000, oriented.bitmap.height) } private fun solidBitmap(w: Int, h: Int, color: Int): Bitmap { val bmp = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888) Canvas(bmp).drawColor(color) // marqueur coin pour debug visuel Canvas(bmp).drawRect(0f, 0f, 8f, 8f, Paint().apply { this.color = Color.BLACK }) return bmp } private fun encodeJpeg(bitmap: Bitmap): ByteArray { val out = ByteArrayOutputStream() bitmap.compress(Bitmap.CompressFormat.JPEG, 95, out) return out.toByteArray() } private fun writeJpegWithExif(jpeg: ByteArray, orientation: Int): File { val file = File.createTempFile("orient", ".jpg") file.writeBytes(jpeg) ExifInterface(file.absolutePath).apply { setAttribute(ExifInterface.TAG_ORIENTATION, orientation.toString()) saveAttributes() } return file } }
GitRust