CoreImage:CIImage 写入 JPG 正在改变颜色 [macOS]

CoreImage: CIImage write JPG is shifting colors [macOS]

使用 CoreImage 过滤照片,我发现保存为 JPG 文件会导致图像具有微妙但可见的蓝色调。在这个使用黑白图像的示例中,直方图显示了颜色在已保存文件中的变化情况。

---输入

输出 []直方图显示颜色层偏移

-- MacOS 'Preview' App

演示的问题

我可以仅使用预览应用程序显示类似的结果。

  1. 这里是测试图片:https://i.stack.imgur.com/Y3f03.jpg
  2. 使用预览打开 JPG 图片。
  3. 以任何 'Quality' 导出为 JPEG,而不是默认值 (85%?)
  4. 打开导出的文件并查看直方图,可以看到与我在应用程序中遇到的相同的颜色偏移。

-- 自定义 MacOS 应用程序中演示的问题

这里的代码尽可能简单,从照片创建一个 CIImage 并立即保存它而不执行任何过滤器。在此示例中,我选择 0.61 进行压缩,因为它产生的文件大小与原始文件大小相似。如果使用更高的压缩比,失真似乎更大,但我找不到任何可以消除它的值。

if let img = CIImage(contentsOf: url) {
   let dest = procFolder.url(named: "InOut.jpg")
   img.jpgWrite(url: dest)
}

extension CIImage {
    func jpgWrite(url: URL) {

        let prop: [NSBitmapImageRep.PropertyKey: Any] = [
            .compressionFactor: 0.61
        ]

        let bitmap = NSBitmapImageRep(ciImage: self)
        let data = bitmap.representation(using: NSBitmapImageRep.FileType.jpeg, properties: prop)

        do {
            try data?.write(to: url, options: .atomic)
        } catch {
            log.error(error)
        }
    }
}

更新 1:使用@Frank Schlegel 的答案保存 JPG 文件

JPG 现在带有颜色同步配置文件,我可以(不科学地)追踪到人像图像性能提升约 10%(风景图像性能提升较少),这是不错的改进。但是,不幸的是,生成的文件仍然以上面直方图中显示的相同方式扭曲颜色。

extension CIImage {
   static let writeContext = CIContext(mtlDevice: MTLCreateSystemDefaultDevice()!, options: [
        // using an extended working color space allows you to retain wide gamut information, e.g., if the input is in DisplayP3
        .workingColorSpace: CGColorSpace(name: CGColorSpace.extendedSRGB)!,
        .workingFormat: CIFormat.RGBAh // 16 bit color depth, needed in extended space
    ])

    func jpgWrite(url: URL) {
       // write the output in the same color space as the input; fallback to sRGB if it can't be determined
        let outputColorSpace = colorSpace ?? CGColorSpace(name: CGColorSpace.sRGB)!
        do {
            try CIImage.writeContext.writeJPEGRepresentation(of: self, to: url, colorSpace: outputColorSpace, options: [:])
        } catch {
        }
    }
}

问题:

如何将黑白 JPG 作为 CIImage 打开,并重新保存 JPG 文件以避免任何颜色偏移?

这看起来像是一个颜色同步问题(正如 Leo 指出的那样)——更具体地说,输入、处理和输出之间 mismatch/misinterpretation 的颜色 spaces。

当您调用 NSBitmapImageRep(ciImage:) 时,后台实际上发生了很多事情。系统实际上需要 render 您提供的 CIImage 以获得结果的位图数据。它通过创建具有默认(特定于设备)设置的 CIContext 来实现,使用它来处理您的图像(应用所有过滤器和转换),然后为您提供结果的原始位图数据。在此过程中,会发生多种颜色 space 转换,您在使用此 API 时无法控制(并且似乎不会导致您想要的结果)。由于这个原因,我不喜欢这些 "convenience" APIs 来渲染 CIImages,我在 SO 上看到很多与它们相关的问题。

我建议您改用 CIContextCIImage 渲染成 JPEG 文件。这使您可以直接控制颜色 spaces 和更多:

let input = CIImage(contentsOf: url)

// ideally you create this context once and re-use it because it's an expensive object
let context = CIContext(mtlDevice: MTLCreateSystemDefaultDevice()!, options: [
    // using an extended working color space allows you to retain wide gamut information, e.g., if the input is in DisplayP3
    .workingColorSpace: CGColorSpace(name: CGColorSpace.extendedSRGB)!,
    .workingFormat: CIFormat.RGBAh // 16 bit color depth, needed in extended space
])

// write the output in the same color space as the input; fallback to sRGB if it can't be determined
let outputColorSpace = input.colorSpace ?? CGColorSpace(name: CGColorSpace.sRGB)!

context.writeJPEGRepresentation(of: input, to: dest, colorSpace: outputColorSpace, options: [kCGImageDestinationLossyCompressionQuality: 0.61])

如果您在使用此 API 时仍然发现差异,请告诉我。

我从未找到此问题的根本原因,因此没有找到我正在寻找的 'true' 解决方案。在与@Frank Schlegel 的讨论中,人们相信它是 Apple 的 jpeg 转换器的神器。当使用看似单色但实际上包含少量颜色信息的测试文件时,问题肯定会更加明显。

对我的应用程序最简单的修复是确保源图像中没有颜色,因此我在保存文件之前将饱和度降低到 0。

    let params = [
        "inputBrightness": brightness,  // -1...1, This filter calculates brightness by adding a bias value: color.rgb + vec3(brightness)
        "inputContrast": contrast,      // 0...2, this filter uses the following formula: (color.rgb - vec3(0.5)) * contrast + vec3(0.5)
        "inputSaturation": saturation   // 0...2
    ]
    image.applyingFilter("CIColorControls", parameters: params)