MTKTextureLoader 使图像饱和
MTKTextureLoader saturates image
我正在尝试使用 MTKTextureLoader 将 CGImage 作为纹理加载。这是原图
然而,在我将 CGImage 转换为 MTLTexture 并将该纹理转换回 CGImage 之后,它看起来很糟糕,如下所示:
代码大致是这样的。
图像作为 CGImage 加载(我已经检查过,该图像确实具有完整的视觉质量)
我有一个函数 view() 允许我通过在 CALayer 中使用它来查看 NSImage,如下所示:
func view() {
.....
imageView!.layer = CALayer()
imageView!.layer!.contentsGravity = kCAGravityResizeAspectFill
imageView!.layer!.contents = img
imageView!.wantsLayer = true
所以我做了以下事情
let cg = CoolImage()
let ns = NSImage(cgImage: cg, size: Size(width: cg.width, height: cg.height))
view(image: ns)
并确认它具有完整的视觉保真度。
然后我像这样将 cg 图像加载到 MTLTexture 中
let textureLoader = MTKTextureLoader(device: metalState.sharedDevice!)
let options = [
MTKTextureLoader.Option.textureUsage: NSNumber(value: MTLTextureUsage.shaderRead.rawValue | MTLTextureUsage.shaderWrite.rawValue | MTLTextureUsage.renderTarget.rawValue),
MTKTextureLoader.Option.SRGB: false
]
return ensure(try textureLoader.newTexture(cgImage: cg, options: options))
然后我将 MTLTexture 转换回 UIImage,如下所示:
let texture = self
let width = texture.width
let height = texture.height
let bytesPerRow = width * 4
let data = UnsafeMutableRawPointer.allocate(bytes: bytesPerRow * height, alignedTo: 4)
defer {
data.deallocate(bytes: bytesPerRow * height, alignedTo: 4)
}
let region = MTLRegionMake2D(0, 0, width, height)
texture.getBytes(data, bytesPerRow: bytesPerRow, from: region, mipmapLevel: 0)
var buffer = vImage_Buffer(data: data, height: UInt(height), width: UInt(width), rowBytes: bytesPerRow)
var map: [UInt8] = [0, 1, 2, 3]
if (pixelFormat == .bgra8Unorm) {
map = [2, 1, 0, 3]
}
vImagePermuteChannels_ARGB8888(&buffer, &buffer, map, 0)
guard let colorSpace = CGColorSpace(name: CGColorSpace.genericRGBLinear) else { return nil }
guard let context = CGContext(data: data, width: width, height: height, bitsPerComponent: 8, bytesPerRow: bytesPerRow, space: colorSpace, bitmapInfo: CGImageAlphaInfo.noneSkipLast.rawValue) else { return nil }
guard let cgImage = context.makeImage() else { return nil }
return NSImage(cgImage: cgImage, size: Size(width: width, height: height))
并查看了它。
生成的图像非常饱和,我相信这是因为 CGImage 到 MTLTexture 的转换,我在过去相当成功。
请注意,此纹理从未渲染过,只是经过转换。
您可能想知道为什么我要使用所有这些转换,这是一个很好的观点。我的实际管道不会像这样工作,但是它确实需要这些转换组件中的每一个都能顺利工作。这不是我的实际用例,只是为了说明问题。
这里的问题不是从 CGImage
到 MTLTexture
的转换。问题是您假设源图像的颜色 space 是线性的。更有可能的是,图像数据实际上是 sRGB-encoded,因此通过创建具有通用线性颜色 space 的位图上下文,您错误地告诉 CG 它应该 gamma-encode 图像数据在显示之前,这会导致您看到的饱和度降低。
您可以通过使用原始 CGImage
的原生颜色 space 或通过以其他方式说明您的图像数据是 sRGB-encoded.[=13= 的事实来解决此问题]
我正在尝试使用 MTKTextureLoader 将 CGImage 作为纹理加载。这是原图
然而,在我将 CGImage 转换为 MTLTexture 并将该纹理转换回 CGImage 之后,它看起来很糟糕,如下所示:
代码大致是这样的。
图像作为 CGImage 加载(我已经检查过,该图像确实具有完整的视觉质量)
我有一个函数 view() 允许我通过在 CALayer 中使用它来查看 NSImage,如下所示:
func view() {
.....
imageView!.layer = CALayer()
imageView!.layer!.contentsGravity = kCAGravityResizeAspectFill
imageView!.layer!.contents = img
imageView!.wantsLayer = true
所以我做了以下事情
let cg = CoolImage()
let ns = NSImage(cgImage: cg, size: Size(width: cg.width, height: cg.height))
view(image: ns)
并确认它具有完整的视觉保真度。
然后我像这样将 cg 图像加载到 MTLTexture 中
let textureLoader = MTKTextureLoader(device: metalState.sharedDevice!)
let options = [
MTKTextureLoader.Option.textureUsage: NSNumber(value: MTLTextureUsage.shaderRead.rawValue | MTLTextureUsage.shaderWrite.rawValue | MTLTextureUsage.renderTarget.rawValue),
MTKTextureLoader.Option.SRGB: false
]
return ensure(try textureLoader.newTexture(cgImage: cg, options: options))
然后我将 MTLTexture 转换回 UIImage,如下所示:
let texture = self
let width = texture.width
let height = texture.height
let bytesPerRow = width * 4
let data = UnsafeMutableRawPointer.allocate(bytes: bytesPerRow * height, alignedTo: 4)
defer {
data.deallocate(bytes: bytesPerRow * height, alignedTo: 4)
}
let region = MTLRegionMake2D(0, 0, width, height)
texture.getBytes(data, bytesPerRow: bytesPerRow, from: region, mipmapLevel: 0)
var buffer = vImage_Buffer(data: data, height: UInt(height), width: UInt(width), rowBytes: bytesPerRow)
var map: [UInt8] = [0, 1, 2, 3]
if (pixelFormat == .bgra8Unorm) {
map = [2, 1, 0, 3]
}
vImagePermuteChannels_ARGB8888(&buffer, &buffer, map, 0)
guard let colorSpace = CGColorSpace(name: CGColorSpace.genericRGBLinear) else { return nil }
guard let context = CGContext(data: data, width: width, height: height, bitsPerComponent: 8, bytesPerRow: bytesPerRow, space: colorSpace, bitmapInfo: CGImageAlphaInfo.noneSkipLast.rawValue) else { return nil }
guard let cgImage = context.makeImage() else { return nil }
return NSImage(cgImage: cgImage, size: Size(width: width, height: height))
并查看了它。
生成的图像非常饱和,我相信这是因为 CGImage 到 MTLTexture 的转换,我在过去相当成功。
请注意,此纹理从未渲染过,只是经过转换。
您可能想知道为什么我要使用所有这些转换,这是一个很好的观点。我的实际管道不会像这样工作,但是它确实需要这些转换组件中的每一个都能顺利工作。这不是我的实际用例,只是为了说明问题。
这里的问题不是从 CGImage
到 MTLTexture
的转换。问题是您假设源图像的颜色 space 是线性的。更有可能的是,图像数据实际上是 sRGB-encoded,因此通过创建具有通用线性颜色 space 的位图上下文,您错误地告诉 CG 它应该 gamma-encode 图像数据在显示之前,这会导致您看到的饱和度降低。
您可以通过使用原始 CGImage
的原生颜色 space 或通过以其他方式说明您的图像数据是 sRGB-encoded.[=13= 的事实来解决此问题]