云端 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