MTKView 显示广色域 P3 色彩空间

MTKView Displaying Wide Gamut P3 Colorspace

我正在构建一个基于 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
        }

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

为了支持广色域,您需要将MTKView的colorPixelFormat设置为BGRA10_XR or bgra10_XR_sRGB. I suspect the colorSpace property of macOS MTKViews won't be supported on iOS because color management in iOS is not active but targeted (read Best practices for color management).

没有看到你的图像和它们的实际值,很难诊断,但我会解释我的发现和实验。我建议你像我一样从调试单一颜色开始。

比如P3颜色中最红的点是什么space?它可以通过这样的 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 颜色同步实用程序中的计算器,

确保 select 扩展范围,否则值将被限制为 0 和 1。

因此,如您所见,您的 P3(1, 0, 0) 对应于扩展 sRGB 中的 (1.0930, -0.2267, -0.1501)。

现在,回到你的MTKView

  • 如果您将 MTKView 的 colorPixelFormat 设置为 .BGRA10_XR,那么如果您的着色器输出为

    ,您将获得最亮的红色

    (1.0930, -0.2267, -0.1501)

  • 如果您将 MTKView 的 colorPixelFormat 设置为 .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。将颜色 space 设置为 CGColorSpace.displayP3 并将数据复制过来。但是什么数据,对吧?这张图片中最亮的红色是

(1, 0, 0)

或 16 位整数中的 (65535, 0, 0)。

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

那个(column-major)矩阵是,

 1.2249  -0.2247  0
-0.0420   1.0419  0
-0.0197  -0.0786  1.0979

你可以在这里阅读我是如何生成它的:Exploring the display-P3 color space

这是我在 iPhoneX,

上使用 UIButtons 和 MTKView screen-captured 构建的示例

左边的按钮是sRGB上最亮的红色,而右边的按钮使用的是displayP3颜色。在中心,我放置了一个 MTKView,它输出如上所述的转换后的线性颜色。

同样的绿色实验,

现在,如果您在最近的 iPhone 或 iPad 上看到这个,您应该会看到中间的正方形和右边的按钮具有相同的明亮颜色。如果您在无法显示它们的 Mac 上看到它,左按钮将显示相同的颜色。如果您在 Windows 机器或没有适当颜色管理的浏览器中看到它,左按钮也可能显示为不同的颜色,但这只是因为整个图像被解释为 sRGB 并且显然这些像素具有不同的颜色值...但外观不正确。

如果您需要更多参考,请查看我在此处添加的 testP3UIColor 单元测试:ColorTests.swift

我的函数初始化 UIImage: Image.swift,

以及用于尝试转换的示例应用程序:SampleColorPalette

我还没有尝试过 CIImages,但我想同样的原则也适用。

希望这些信息对您有所帮助。我也花了很长时间才弄清楚如何正确显示颜色,因为我在 Metal SDK 文档中找不到任何对 displayP3 支持的明确引用。