swift - 如何将 16 位图像加载到 Metal 纹理中?

标签 swift cocoa cocoa-touch metal

使用 MTKTextureLoader.newTexture 的推荐方法不适用于 16 位图像。

  1. named: 版本默默地将图像转换为 8 位像素格式
  2. cgImage: 版本以Image decoding failed结束

UIImage 和 NSImage 都支持加载 16 位图像,并且有它们方便的 .cgimage 方法,可以在一行中转换为 CGImage,因此在两个平台上获取 CGImage 都得到了解决。

我如何编写一个转换 CGImage 并返回 16 位 Metal 纹理的函数?

最佳答案

下面的 loadEXRTexture 函数加载扩展范围的图像并将其像素转换为半精度 float ,将生成的像素数据存储到 MTLTexture.rgba16Float 格式。它试图通过使用 CGImageSource 选项来保留浮点图像的原始范围,这些选项改变了它的行为,而不是 Quartz 默认值(它应用了一个“解码”函数,将源数据压缩到一个适合的范围绘制到位图上下文中)。它假设图像源创建的图像具有三个按 RGB 顺序打包的浮点分量。

func convertRGBF32ToRGBAF16(_ src: UnsafePointer<Float>, _ dst: UnsafeMutablePointer<UInt16>, pixelCount: Int) {
    for i in 0..<pixelCount {
        storeAsF16(src[i * 3 + 0], dst + (i * 4) + 0)
        storeAsF16(src[i * 3 + 1], dst + (i * 4) + 1)
        storeAsF16(src[i * 3 + 2], dst + (i * 4) + 2)
        storeAsF16(1.0, dst + (i * 4) + 3)
    }
}

func loadEXRTexture(_ url: URL, device: MTLDevice) -> MTLTexture? {
    guard let imageSource = CGImageSourceCreateWithURL(url as CFURL, nil) else { return nil }

    let options = [ kCGImageSourceShouldCache : true, kCGImageSourceShouldAllowFloat : true ] as CFDictionary
    guard let image = CGImageSourceCreateImageAtIndex(imageSource, 0, options) else { return nil }

    let descriptor = MTLTextureDescriptor.texture2DDescriptor(pixelFormat: .rgba16Float,
                                                              width: image.width,
                                                              height: image.height,
                                                              mipmapped: false)
    descriptor.usage = .shaderRead
    guard let texture = device.makeTexture(descriptor: descriptor) else { return nil }

    if image.bitsPerComponent == 32 && image.bitsPerPixel == 96 {
        let srcData: CFData! = image.dataProvider?.data
        CFDataGetBytePtr(srcData).withMemoryRebound(to: Float.self, capacity: image.width * image.height * 3) { srcPixels in
            let dstPixels = UnsafeMutablePointer<UInt16>.allocate(capacity: 4 * image.width * image.height)
            convertRGBF32ToRGBAF16(srcPixels, dstPixels, pixelCount: image.width * image.height)
            texture.replace(region: MTLRegionMake2D(0, 0, image.width, image.height),
                            mipmapLevel: 0,
                            withBytes: dstPixels,
                            bytesPerRow: MemoryLayout<UInt16>.size * 4 * image.width)
            dstPixels.deallocate()
        }
    }

    return texture
}

您需要将此实用程序包含在桥接 header 中,因为据我所知,Swift 没有 __fp16 类型或任何等效类型:

#include <stdint.h>
static inline void storeAsF16(float value, uint16_t *pointer) { *(__fp16 *)pointer = value; }

关于swift - 如何将 16 位图像加载到 Metal 纹理中?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48872043/

相关文章:

ios - 导入应用程序后保存文件

objective-c - 如何将 C 风格的 char* 数组转换为 NSArray?

iphone - 无法使用 UITextView 退出 FirstResponder

iphone - 如何为iPhone解析文件?我应该使用NSScanner吗?

ios - 在更改 View (Segue) 时显示事件指示器并为下一个 View (API) 加载数据

iphone - 我的开发人员没有使用 Interface Builder,这是一件坏事吗?

swift - MacO 上状态的保存和恢复

ios - 达到 URLCache 限制时会发生什么

arrays - 如何在 Swift 中从数组中删除元素

objective-c - cocoa 沙盒应用程序 : Spawn FFMPEG