在我的应用程序中,我尝试从存储在Apple照片库中的HDRBT.2100JPEG XL(即16bpc)导出SDR sRGB JPEG(即8bpc)(但这在这里并不重要) ).
我需要正确地将 HDR 输入色彩空间映射到 SDR(SRGB 或 P3 色彩空间,适合 JPG 的低位深度)。这应该是可能的,因为苹果的色彩管理已经支持它。例如,当尝试在 macOS 中的任何位置显示在 SDR 显示器上正确进行颜色管理的原始 JXL 图像时,将产生正确的输出。
由于我想支持非常大(61MP+)10bpc 或 16bpc 图像,因此我尝试使用 ImageIO(如果可能的话,直接使用CGImageSource
到
CGImageDestination
),因为它是最高效的内存效率和性能方法。在此之前,我使用 Core Image,它也不会生成正确的色调映射图像,但会增加内存使用量(转换时内存使用量会增加 500-800 MB),并且需要相当长的时间才能完成。欢迎任何建议。
请注意,这些文件是实际的高动态范围,而不是过去的“HDR 摄影”,后者会将多次曝光合并到传统的 SDR 图像中。
这是我到目前为止所拥有的:
guard let data = sourceImageData,
let source = CGImageSourceCreateWithData(data as CFData, [kCGImageSourceShouldCache: false, kCGImageSourceTypeIdentifierHint: asset.uniformType.identifier as CFString] as CFDictionary),
let cfOutputData = CFDataCreateMutable(nil, 0),
let imageDestination = CGImageDestinationCreateWithData(cfOutputData, outputUTType.identifier as CFString, 1, nil) else {
break
}
var outputOptions: [AnyHashable: Any] = [kCGImageDestinationMergeMetadata: true,
kCGImageDestinationLossyCompressionQuality: options.outputLossyCompressionQuality,
kCGImageSourceCreateThumbnailFromImageAlways: true,
kCGImageSourceCreateThumbnailWithTransform: true]
if case let .scaled(scale) = options.outputResize {
outputOptions[kCGImageDestinationImageMaxPixelSize] = Double(max(asset.pixelWidth, asset.pixelHeight)) * scale
} else if case let .aspectResized(maxPixelSize) = options.outputResize {
outputOptions[kCGImageDestinationImageMaxPixelSize] = maxPixelSize
}
if [.jpeg /* and other 8bpc formats */].contains(options.outputDataFormat) {
outputOptions[kCGImageSourceDecodeRequest] = kCGImageSourceDecodeToSDR
outputOptions[kCGImageDestinationPreserveGainMap] = false
outputOptions[kCGImagePropertyNamedColorSpace] = CGColorSpace.sRGB
} else {
outputOptions[kCGImageSourceDecodeRequest] = kCGImageSourceDecodeToHDR
outputOptions[kCGImageSourceDecodeRequestOptions] = [
"kCGFallbackHDRGain": 10000,
"kCGTargetColorSpace": CGColorSpace.itur_2100_PQ,
"kCGTargetHeadroom": 1000000
]
}
CGImageDestinationAddImageFromSource(imageDestination, source, 0, outputOptions as CFDictionary)
if CGImageDestinationFinalize(imageDestination) {
imageData = cfOutputData as Data
}
我希望 kCGImageSourceDecodeRequest = kCGImageSourceDecodeToSDR
能够完成我需要的操作,将 HDR 图像色调映射到 SDR,但可惜,事实并非如此。有趣的是,用 iPhone 拍摄的 HDR 图像并非采用 BT.2100(或其他 HDR)色彩空间,而是由 P3 基础图像与增益图元数据层组成,我可以使用
kCGImageDestinationPreserveGainMap = false
正确处理。以下是 HDR JXL 文件的示例:
https://www.dropbox.com/scl/fi/p9bpuuykjgtwaeax7cfyc/orig.jxl?rlkey=w48cx1av2p2uzbt8m39luq2bb&st=bnrcpkg3&dl=0
如果您在现代 MacBook Pro 上的 macOS 15.0+ 预览版中打开此文件,或者使用合适的 HDR 显示器(例如 Pro Display XDR),您应该能够看到整个动态范围。在 Sequoia 之前的 macOS 上,预览未配置为显示 HDR 图像;在这种情况下,您可以将图像导入到“照片”中,然后“照片”应该正确显示 HDR 范围。 JXL 在 macOS 14 及更高版本、iOS 17 及更高版本中受支持。一些(不正确的)输出示例:
由于它根本没有改变色彩空间,因此该 JPG 的色彩空间是 BT.2100,并且您可以看到源自低位深度的色调分离(色带)。
此处,SRGB 色彩空间已正确分配给图像,但未执行色调映射,因此颜色不正确。
照片应用程序
毫不奇怪,如果使用“照片”应用程序复制 (CMD+C) JXL 图像,它会生成具有平滑渐变的正确色调映射的 P3 色彩空间 JPG。任何有关如何实现我需要的建议都将受到赞赏。谢谢
kCGImageSourceDecodeRequest: kCGImageSourceDecodeToHDR
选项放在了错误的地方,目的地,而不是源。这是一个有效的示例代码:
guard var imageData,
let source = CGImageSourceCreateWithData(imageData as CFData, [
kCGImageSourceShouldCache: false,
kCGImageSourceTypeIdentifierHint: asset.uniformType.identifier,
kCGImageSourceDecodeRequest: isRequestSDR ? kCGImageSourceDecodeToSDR : kCGImageSourceDecodeToHDR
] as CFDictionary),
let imageDestination = CGImageDestinationCreateWithData(cfOutputData, outputUTType.identifier as CFString, 1, nil) else {
return imageData as Any
}
CGImageDestinationAddImageFromSource(_imageDestination, source, CGImageSourceGetPrimaryImageIndex(source), outputOptions as CFDictionary)
if CGImageDestinationFinalize(imageDestination) {
imageData = cfOutputData as Data
}
return imageData
这解决了我原来的问题,但我确实觉得这是一种“魔法”,幕后发生了一些事情,但我不清楚到底发生了什么。如果我想从一种 HDR 格式转换为另一种格式,例如 PQ 转换为 HLG,或者将一种 HDR 色彩空间转换为另一种,我仍然不清楚如何实现。