CoreImage:CIImage 写入 JPG 正在改变颜色 [macOS]
CoreImage: CIImage write JPG is shifting colors [macOS]
使用 CoreImage 过滤照片,我发现保存为 JPG 文件会导致图像具有微妙但可见的蓝色调。在这个使用黑白图像的示例中,直方图显示了颜色在已保存文件中的变化情况。
---输入
输出 []直方图显示颜色层偏移
-- MacOS 'Preview' App
演示的问题
我可以仅使用预览应用程序显示类似的结果。
- 这里是测试图片:https://i.stack.imgur.com/Y3f03.jpg
- 使用预览打开 JPG 图片。
- 以任何 'Quality' 导出为 JPEG,而不是默认值 (85%?)
- 打开导出的文件并查看直方图,可以看到与我在应用程序中遇到的相同的颜色偏移。
-- 自定义 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 来渲染 CIImage
s,我在 SO 上看到很多与它们相关的问题。
我建议您改用 CIContext
将 CIImage
渲染成 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)
使用 CoreImage 过滤照片,我发现保存为 JPG 文件会导致图像具有微妙但可见的蓝色调。在这个使用黑白图像的示例中,直方图显示了颜色在已保存文件中的变化情况。
---输入
输出 [
-- MacOS 'Preview' App
演示的问题我可以仅使用预览应用程序显示类似的结果。
- 这里是测试图片:https://i.stack.imgur.com/Y3f03.jpg
- 使用预览打开 JPG 图片。
- 以任何 'Quality' 导出为 JPEG,而不是默认值 (85%?)
- 打开导出的文件并查看直方图,可以看到与我在应用程序中遇到的相同的颜色偏移。
-- 自定义 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 来渲染 CIImage
s,我在 SO 上看到很多与它们相关的问题。
我建议您改用 CIContext
将 CIImage
渲染成 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)