雲端 Mac 驗證 iOS 圖片的 EXIF 方向與像素正規化

雲端 Mac 驗證 iOS 圖片的 EXIF 方向與像素正規化

照片選取畫面顯示的是直向圖片,上傳後的縮圖卻變成橫向。接手這類問題時,先別調整裁切比例:JPEG 可能儲存了橫向排列的像素,再透過 EXIF 方向標記要求檢視器旋轉顯示。iOS 的 UIImage 能正確顯示,不代表另一段匯出程式碼或接收端也會用相同方式處理。以下在雲端 Mac 的 iOS 測試目標中建立固定樣本,把「顯示正確」與「檔案正確」分開驗證。

先區分方向標記與像素

記錄三項數值:檔案的 EXIF 方向、解碼後的 UIImage.imageOrientation,以及底層 CGImage 的像素寬高。EXIF 方向值 6 表示顯示時需要順時針旋轉,不代表像素已經轉過。如果底層像素寬 120、高 80,正確顯示時就應該是寬 80、高 120。

不要只憑預覽截圖判定修正完成。預覽元件可能替檔案套用了方向標記,但後續處理流程仍在傳遞原始像素。

先釐清要保留哪些檔案:如果需要保留原始檔,就將它與正規化後的檔案分開儲存;如果只需要方向一致的上傳圖片,就在匯出前重新繪製,並再次解碼匯出的檔案確認結果。不要直接把原始檔的方向值改成 1,這不會移動任何像素。

在 iOS 測試中產生固定樣本

將以下測試加入已啟用 UIKit 的 iOS 單元測試目標。測試會在執行時產生一張 120×80 像素的 JPEG,並寫入方向值 6;不依賴開發者電腦上的照片,也不必將真實素材或使用者資訊提交到儲存庫。程式碼會同時檢查 ImageIO 讀出的標記,以及 UIKit 解碼後的方向。

import XCTest
import UIKit
import ImageIO
import UniformTypeIdentifiers

final class ImageOrientationTests: XCTestCase {
    func testOrientationSurvivesExport() throws {
        let format = UIGraphicsImageRendererFormat()
        format.scale = 1
        format.opaque = true

        let source = UIGraphicsImageRenderer(
            size: CGSize(width: 120, height: 80),
            format: format
        ).image { _ in
            UIColor.red.setFill()
            UIRectFill(CGRect(x: 0, y: 0, width: 60, height: 80))
            UIColor.blue.setFill()
            UIRectFill(CGRect(x: 60, y: 0, width: 60, height: 80))
        }

        let jpeg = NSMutableData()
        let destination = try XCTUnwrap(
            CGImageDestinationCreateWithData(
                jpeg, UTType.jpeg.identifier as CFString, 1, nil
            )
        )
        CGImageDestinationAddImage(
            destination,
            try XCTUnwrap(source.cgImage),
            [kCGImagePropertyOrientation: 6] as CFDictionary
        )
        XCTAssertTrue(CGImageDestinationFinalize(destination))

        let input = jpeg as Data
        let imageSource = try XCTUnwrap(
            CGImageSourceCreateWithData(input as CFData, nil)
        )
        let properties = try XCTUnwrap(
            CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil)
                as? [CFString: Any]
        )
        XCTAssertEqual(
            (properties[kCGImagePropertyOrientation] as? NSNumber)?.intValue,
            6
        )

        let decoded = try XCTUnwrap(UIImage(data: input))
        XCTAssertEqual(decoded.imageOrientation, .right)
        XCTAssertEqual(decoded.cgImage?.width, 120)
        XCTAssertEqual(decoded.cgImage?.height, 80)

        let upright = UIGraphicsImageRenderer(
            size: CGSize(width: 80, height: 120),
            format: format
        ).image { _ in
            decoded.draw(in: CGRect(x: 0, y: 0, width: 80, height: 120))
        }
        XCTAssertEqual(upright.imageOrientation, .up)

        let exported = try XCTUnwrap(upright.pngData())
        let reopened = try XCTUnwrap(UIImage(data: exported))
        XCTAssertEqual(reopened.imageOrientation, .up)
        XCTAssertEqual(reopened.cgImage?.width, 80)
        XCTAssertEqual(reopened.cgImage?.height, 120)
    }
}

執行前,先確認測試目標已包含這份 Swift 檔案,並選擇專案支援的 iOS 模擬器。從命令列執行時,請為 -workspace、-scheme 和 -destination 填入專案中的實際名稱;不要以為範例裝置名稱在每部機器上都可用。

將驗證接到實際匯出流程

上述測試證明正規化方法可行,還無法證明業務程式碼確實呼叫了它。下一步,把「解碼、依目標尺寸繪製、編碼」放進專案現有的圖片匯出函式,讓測試呼叫同一個函式。測試輸入仍可在執行時產生,但斷言應針對重新開啟的匯出檔案,而不是函式回傳前的 UIImage。

明確定義業務函式的尺寸契約

先約定函式接收原始檔案資料,輸出顯示方向已確定的資料。對這張方向值為 6 的樣本,輸出像素應從 120×80 變成 80×120。如果業務流程還會縮放,就改為檢查輸出的寬高比與縮放後的精確目標尺寸,並另外測試縮放規則;不要把旋轉、壓縮與裁切的失敗混在同一個斷言裡。

樣本使用左右兩種顏色,是為了在人工檢查方向時也能發現內容是否被鏡像。若自動驗證還要檢查顏色位置,應採用允許誤差的像素比較:JPEG 是有損編碼,會改變邊界附近的顏色,不能要求每個色彩通道的數值逐位相等。

追查預覽正確、匯出卻失敗的流程

先找出圖片在哪個步驟轉換了表示形式:檔案資料、UIImage、CGImage、繪圖環境,還是重新編碼後的檔案。逐一記錄每次轉換的方向與像素尺寸,通常就能定位「取出 cgImage 後直接編碼」這類分歧:cgImage 提供的是像素,不能把 UIImage 繪製時的方向處理視為已經寫入像素。

也要檢查匯出結果。重新繪製會產生新的像素與檔案資料,編碼格式、中繼資料和檔案大小也可能改變。如果工作流程需要保留原始檔的其他中繼資料,應逐項列出必須保留的欄位並撰寫測試,而不是假設重新繪製會原樣複製。上傳流程最好在傳送前再次解碼最終檔案;在檢視器中顯示正確的中間物件,並不是最後交付的結果。

合併前檢查清單

至少涵蓋方向值為 1 的一般圖片、方向值為 6 的旋轉圖片,以及業務流程實際允許的鏡像方向。對每種樣本,分別斷言輸入標記、正規化後的像素寬高,以及再次解碼後的顯示方向。如果正式程式碼允許裁切,再補一張非正方形樣本:正方形即使旋轉錯誤,也無法靠寬高斷言看出來。

最後,在與交付流程相同的 iOS 測試目標中執行測試。測試失敗時,記錄輸入方向、輸出尺寸和處理階段即可;不要把使用者原圖寫入公開日誌。如此一來,回歸測試失敗時就能指向具體的檔案處理步驟,而不只是「某張照片看起來不對」。

常見問題

UIImage 顯示正確,為何匯出的照片仍可能轉向?

UIImage 繪製時會參考方向中繼資料,但接收端未必以相同方式處理。請重新解碼匯出的檔案,確認實際像素方向。

只把方向標記改成 1,就算完成正規化嗎?

不算。若像素排列沒有改變,只修改標記反而會改變顯示結果。必須依原方向重新繪製,再檢查輸出尺寸與方向。

把下一次建置交給專屬雲端 Mac。

確認 ViewMini 的機型、地區與租期,再設定符合工作流程的獨享實體節點。

設定雲端 Mac

再確認一次設備與租期。

三款在售機型提供日租、週租、月租與季租價格;下單前請查看完整方案。

查看機型與價格 設定雲端 Mac