我正在尝试使用iOS上的Metal纹理保存带有P3色彩空间的16位深度PNG图像。纹理有pixelformat = .rgba16Unorm,我用这段代码提取数据
func dataProviderRef() -> CGDataProvider? {
let pixelCount = width * height
var imageBytes = [UInt8](repeating: 0, count: pixelCount * bytesPerPixel)
let region = MTLRegionMake2D(0, 0, width, height)
getBytes(&imageBytes, bytesPerRow: bytesPerRow, from: region, mipmapLevel: 0)
return CGDataProvider(data: NSData(bytes: &imageBytes, length: pixelCount * bytesPerPixel * MemoryLayout<UInt8>.size))
}
我发现在iOS上保存PNG图像的方法是首先创建一个UIImage,并初始化它,我需要创建一个CGImage。问题是我不知道要传递给CGIBitmapInfo的内容。在documentation中我可以看到你可以为32位格式指定byteOrder,但不能为64位指定。
我用来将纹理转换为UIImage的函数是这样的,
extension UIImage {
public convenience init?(texture: MTLTexture) {
guard let rgbColorSpace = texture.defaultColorSpace else {
return nil
}
let bitmapInfo:CGBitmapInfo = [CGBitmapInfo(rawValue: CGImageAlphaInfo.last.rawValue)]
guard let provider = texture.dataProviderRef() else {
return nil
}
guard let cgim = CGImage(
width: texture.width,
height: texture.height,
bitsPerComponent: texture.bitsPerComponent,
bitsPerPixel: texture.bitsPerPixel,
bytesPerRow: texture.bytesPerRow,
space: rgbColorSpace,
bitmapInfo: bitmapInfo,
provider: provider,
decode: nil,
shouldInterpolate: false,
intent: .defaultIntent
)
else {
return nil
}
self.init(cgImage: cgim)
}
}
请注意,“纹理”使用MTLTexture中不存在的一系列属性。我方便地创建了一个简单的扩展。唯一有趣的一点,我想这是色彩空间,目前只是,
public extension MTLTexture {
var defaultColorSpace: CGColorSpace? {
get {
switch pixelFormat {
case .rgba16Unorm:
return CGColorSpace(name: CGColorSpace.displayP3)
default:
return CGColorSpaceCreateDeviceRGB()
}
}
}
}
看起来我用上面的代码创建的图像是每像素4个字节,而不是8个。所以我显然最终得到了一个有趣的图像......
如何创建适当的CGBitmapInfo?它甚至可能吗?
附:如果你想看一个例子的完整代码,那就是github:https://github.com/endavid/VidEngine/tree/master/SampleColorPalette
答案是使用byteOrder16。例如,我在上面的代码中替换了bitmapInfo,
let isFloat = texture.bitsPerComponent == 16
let bitmapInfo:CGBitmapInfo = [isFloat ? .byteOrder16Little : .byteOrder32Big, CGBitmapInfo(rawValue: CGImageAlphaInfo.last.rawValue)]
(alpha也可以预乘)。
SDK documentation没有提供许多暗示为什么会这样,但是本书Programming with Quartz对这16位的含义有一个很好的解释:
值byteOrder16Little指定Quartz,您的数据提供程序提供的每个16位数据块应以小端顺序处理[...]例如,当使用byteOrder16Little值为指定RGB格式的图像时使用16位对于每个组件和每像素48位,您的数据提供程序为每个像素提供R,G,B组件的数据,但每个颜色组件值都以小端顺序[...]以便在使用byteOrder16Little时获得最佳性能,图像的像素大小或组件大小必须是16位。
因此,对于rgba16中的64位图像,像素大小为64位,但组件大小为16位。它工作得很好:)
(谢谢@warrenm!)