ImageOrientationTest.swift
117 lignes · 5326 octets
import XCTest import UIKit @testable import Card2vcf /// Port of android `scan/ImageOrientationTest.kt` (pure logic parts): /// EXIF rotation, camera fallback, and no-double-rotation guarantees. /// The real-photo fixture tests (dimo card) stay Android-only. final class ImageOrientationTest: XCTestCase { func testExif6Rotates90AndSwapsDimensions() { let image = solidImage(width: 200, height: 100, orientation: .right) // EXIF 6 let oriented = ImageOrientation.orient(image) XCTAssertTrue(oriented.fromExif) XCTAssertEqual(oriented.appliedRotationDegrees, 90) XCTAssertEqual(pixelSize(oriented.bitmap).width, 100) XCTAssertEqual(pixelSize(oriented.bitmap).height, 200) } func testExif3Rotates180WithoutChangingDimensions() { let image = solidImage(width: 160, height: 80, orientation: .down) // EXIF 3 let oriented = ImageOrientation.orient(image) XCTAssertTrue(oriented.fromExif) XCTAssertEqual(oriented.appliedRotationDegrees, 180) XCTAssertEqual(pixelSize(oriented.bitmap).width, 160) XCTAssertEqual(pixelSize(oriented.bitmap).height, 80) } func testExif8Rotates270() { let image = solidImage(width: 120, height: 60, orientation: .left) // EXIF 8 let oriented = ImageOrientation.orient(image) XCTAssertEqual(oriented.appliedRotationDegrees, 270) XCTAssertEqual(pixelSize(oriented.bitmap).width, 60) XCTAssertEqual(pixelSize(oriented.bitmap).height, 120) } func testWithoutExifUsesCameraRotation() { let image = solidImage(width: 200, height: 100, orientation: .up) let oriented = ImageOrientation.orient(image, cameraRotationDegrees: 90) XCTAssertFalse(oriented.fromExif) XCTAssertEqual(oriented.appliedRotationDegrees, 90) XCTAssertEqual(pixelSize(oriented.bitmap).width, 100) XCTAssertEqual(pixelSize(oriented.bitmap).height, 200) } func testExifWinsOverCameraNoDoubleRotation() { let image = solidImage(width: 200, height: 100, orientation: .right) // If EXIF and camera rotations were cumulated we would get 180° and the // original dimensions back. That must never happen. let oriented = ImageOrientation.orient(image, cameraRotationDegrees: 90) XCTAssertTrue(oriented.fromExif) XCTAssertEqual(oriented.appliedRotationDegrees, 90) XCTAssertEqual(pixelSize(oriented.bitmap).width, 100) XCTAssertEqual(pixelSize(oriented.bitmap).height, 200) } func testNormalizeDegreesQuantizesToQuarterTurns() { XCTAssertEqual(ImageOrientation.normalizeDegrees(10), 0) XCTAssertEqual(ImageOrientation.normalizeDegrees(95), 90) XCTAssertEqual(ImageOrientation.normalizeDegrees(190), 180) XCTAssertEqual(ImageOrientation.normalizeDegrees(-90), 270) } func testExifRotationDegreesMapping() { XCTAssertEqual(ImageOrientation.exifRotationDegrees(.right), 90) XCTAssertEqual(ImageOrientation.exifRotationDegrees(.leftMirrored), 90) XCTAssertEqual(ImageOrientation.exifRotationDegrees(.down), 180) XCTAssertEqual(ImageOrientation.exifRotationDegrees(.downMirrored), 180) XCTAssertEqual(ImageOrientation.exifRotationDegrees(.left), 270) XCTAssertEqual(ImageOrientation.exifRotationDegrees(.rightMirrored), 270) XCTAssertEqual(ImageOrientation.exifRotationDegrees(.up), 0) XCTAssertEqual(ImageOrientation.exifRotationDegrees(.upMirrored), 0) } func testJpegBytesWithoutExifUseCameraFallback() throws { let jpeg = try XCTUnwrap( solidImage(width: 200, height: 100, orientation: .up).jpegData(compressionQuality: 0.9) ) let oriented = try XCTUnwrap( ImageOrientation.fromJpegBytes(jpeg, cameraRotationDegrees: 90) ) XCTAssertFalse(oriented.fromExif) XCTAssertEqual(oriented.appliedRotationDegrees, 90) XCTAssertEqual(pixelSize(oriented.bitmap).width, 100) XCTAssertEqual(pixelSize(oriented.bitmap).height, 200) } // MARK: - Helpers /// Solid image whose *pixel* buffer is width x height, tagged with the given /// orientation (equivalent of the Kotlin JPEG + EXIF tag fixture). private func solidImage( width: Int, height: Int, orientation: UIImage.Orientation ) -> UIImage { let format = UIGraphicsImageRendererFormat.default() format.scale = 1 let size = CGSize(width: width, height: height) let base = UIGraphicsImageRenderer(size: size, format: format).image { context in UIColor.red.setFill() context.fill(CGRect(origin: .zero, size: size)) UIColor.black.setFill() context.fill(CGRect(x: 0, y: 0, width: 8, height: 8)) // corner marker } guard let cgImage = base.cgImage else { preconditionFailure("failed to build test image") } return UIImage(cgImage: cgImage, scale: 1, orientation: orientation) } private func pixelSize(_ image: UIImage) -> (width: Int, height: Int) { guard let cgImage = image.cgImage else { return (Int(image.size.width), Int(image.size.height)) } return (cgImage.width, cgImage.height) } }
GitRust