MTKView显示广色域P3颜色空间

9
我正在基于CIFilters和MetalKit构建实时照片编辑器。但是在MTKView中显示宽色域图像时遇到了问题。
标准的sRGB图像可以正常显示,但Display P3图像会变得模糊。
我尝试将CIContext.render颜色空间设置为图像颜色空间,但仍然存在问题。
以下是代码片段:
 guard let inputImage = CIImage(mtlTexture: sourceTexture!) else { return }
                let outputImage = imageEditor.processImage(inputImage)
                print(colorSpace)
                context.render(outputImage,
                               to: currentDrawable.texture,
                               commandBuffer: commandBuffer,
                               bounds: inputImage.extent,
                               colorSpace: colorSpace)
                commandBuffer?.present(currentDrawable)

let pickedImage = info[UIImagePickerControllerOriginalImage] as! UIImage
        print(pickedImage.cgImage?.colorSpace)
        if let cspace = pickedImage.cgImage?.colorSpace {
            colorSpace = cspace
        }

我在苹果开发者论坛上找到了一个类似的问题,但没有任何答案:https://forums.developer.apple.com/thread/66166


我不确定在iOS中是否可能... 在macOS中,MTKView有一个可以设置的colorSpace属性,但显然在iOS中不支持 :( https://developer.apple.com/documentation/metalkit/mtkview/2177056-colorspace - endavid
1个回答

13
为了支持广色域,您需要将您的MTKView的colorPixelFormat设置为BGRA10_XRbgra10_XR_sRGB。我怀疑macOS MTKViews的colorSpace属性在iOS上不会被支持,因为iOS中的颜色管理是非活动的,而是有目标的(请阅读颜色管理的最佳实践)。
没有看到您的图像及其实际值,很难诊断,但我会解释我的发现和实验。建议您像我一样从调试单个颜色开始。
例如,P3颜色空间中最红的点是什么?可以通过像这样的UIColor定义它:
UIColor(displayP3Red: 1, green: 0, blue: 0, alpha: 1)

为了调试目的,将背景设置为该颜色,并将UIButton添加到您的视图中。您可以在代码中获取组件,以查看这些值在sRGB中变为什么。

    var fRed : CGFloat = 0
    var fGreen : CGFloat = 0
    var fBlue : CGFloat = 0
    var fAlpha : CGFloat = 0
    let c = UIColor(displayP3Red: 1, green: 0, blue: 0, alpha: 1)
    c.getRed(&fRed, green: &fGreen, blue: &fBlue, alpha: &fAlpha)
或者您可以使用macOS Color Sync实用程序中的计算器, Color Sync Utility 请确保选择“扩展范围”,否则值将被夹在0和1之间。
因此,正如您所看到的,您的P3(1, 0, 0)对应于扩展sRGB中的(1.0930,-0.2267,-0.1501)。
现在,回到您的MTKView
- 如果将MTKViewcolorPixelFormat设置为.BGRA10_XR,则如果您的着色器输出为(1.0930,-0.2267,-0.1501),则可以获得最亮的红色。 - 如果将MTKViewcolorPixelFormat设置为.bgra10_XR_sRGB,则如果您的着色器输出为(1.22486,-0.0420312,-0.0196301),则可以获得最亮的红色,因为您必须写入线性RGB值,由于这种纹理格式会为您应用伽马校正。在应用反伽马时要小心,因为存在负值。我使用这个函数,
let f = {(c: Float) -> Float in
    if fabs(c) <= 0.04045 {
        return c / 12.92
    }
    return sign(c) * powf((fabs(c) + 0.055) / 1.055, 2.4)
}
最后要完成的是创建一个宽色域的UIImage。将颜色空间设置为CGColorSpace.displayP3并复制数据。但是什么数据呢?这个图像中最亮的红色将是什么?
(1, 0, 0)

或者在16位整数中使用(65535, 0, 0)。

我的代码使用.rgba16Unorm纹理来操作displayP3颜色空间中的图像,其中(1, 0, 0)将是P3中最亮的红色。这样,我可以直接复制其内容到UIImage中。然后,为了显示,我将颜色转换传递给着色器以从P3转换为扩展sRGB(因此,不会饱和颜色)然后再进行显示。我使用线性颜色,因此我的转换只是一个3x3矩阵。我将视图设置为.bgra10_XR_sRGB,因此Gamma值将自动应用。

该(列优先)矩阵为

 1.2249  -0.2247  0
-0.0420   1.0419  0
-0.0197  -0.0786  1.0979
你可以在这里阅读我如何生成它的内容:探索display-P3色彩空间 以下是我使用UIButtons和MTKView构建的示例,通过iPhone X进行屏幕捕获: 红色MTKView 左侧按钮是sRGB上最亮的红色,而右侧按钮使用了displayP3颜色。在中心,我放置了一个MTKView,输出如上所述的变换线性色彩。
绿色同样的实验: 绿色MTKView 如果你在最近的iPhone或iPad上看到这个,你应该看到中心的正方形和右边的按钮有相同明亮的颜色。如果你在不能显示它们的Mac上看到这个,左侧的按钮将显示相同的颜色。如果你在没有正确颜色管理的Windows机器或浏览器上看到这个,左侧的按钮也可能显示不同的颜色,但那只是因为整个图像被解释为sRGB,显然这些像素有不同的值...但外观不正确。
如果你想要更多的参考资料,请查看我在这里添加的testP3UIColor单元测试:ColorTests.swift,以及初始化UIImage的函数:Image.swift,和一个尝试转换的示例应用程序:SampleColorPalette
我还没有尝试过CIImages,但我想相同的原则适用。
希望这些信息对你有所帮助。由于我在Metal SDK文档中找不到任何关于displayP3支持的明确引用,因此我也花了很长时间才弄清楚如何正确显示颜色。

1
实际上,对于 bgra10_xr_srgb 像素格式,1.22486, -0.0420312, -0.0196301 并不是最亮的红色,而是 1.358, -0.074, -0.012。Justin Stoyles 在 Working with Wide Color 会议上提到了这一点,您可以通过在两个 MTKView 的渲染之间放置一个具有 1.0, 0.0, 0.0, 1.0 Display P3 背景颜色的 UIView 来进行检查。 - Vahan Babayan
似乎 bgra10_xr_srgb 像素格式使用的颜色空间与 CGColorSpace.extendedSRGBCGColorSpace.extendedLinearSRGB 不同。我希望能得到从 Display P3 到该颜色空间的转换矩阵,非常感谢您的帮助。 - Vahan Babayan
谢谢提供链接。不确定1.358来自何处。如果您创建了一个displayP3 UIColor,let c = UIColor(displayP3Red: 1, green: 0, blue: 0, alpha: 1); c.getRed(&fRed, green: &fGreen, blue: &fBlue, alpha: &fAlpha),红色值为1.0930339097976685。要使其在转换为线性空间时变为1.358,您需要使用gamma=log(1.358)/log(1.093)=3.44的伽马值,这是非常大的。我敢打赌他们正在使用这个常见的伽马计算:linear=((c+0.055)/1.055)^2.4,并且他们忘记除以1.055或其他一些事情。猜测而已... - endavid

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接