LoginSignup
13
7

More than 5 years have passed since last update.

MTLTexture から CGImage を生成

Last updated at Posted at 2016-12-28

MTLTexture の内容を確認する為に、MTLTexture から CGImage を作りコードとづっと格闘していました。良さげなコードが出来上がったので公開いたします。

そもそも、MTLTexture から バイト列取り出して、CGImage を生成すると、色が何かおかしい CGImage が出来上がりました。Photoshop でチャネルを交換して調査していたら B(青)と R(赤)のチャネルが入れ替わっている事がわかりました。

rgba-bgra.png

(注)上記は Photoshop で加工

コード書いて 1ピクセル毎変換するのも効率が悪いので、いい方法を探していました。CGDataProvider を使って CGImage を作るといいなんて話も Stack Overflow かどこがで見つけましたダメでした。

そもそも、MTKView の段階から、RGBA8Unorm になっていればいいかと思いきや、mtkView.colorPixelFormat = .rgba8Unorm や 他の renderPiplelineDescriptor とか全てに「.rgba8Unorm」を設定しても画面は黒以外表示されなくなります。

さらに調べていると「MTLTexture」に「makeTextureView()」なんてメソッドがあって、見かけ上別のピクセルフォーマットで扱えるなんて書いてあり、試してみると、サポートしてないなんて感じのエラーがログに表示されて(ログのメモ忘れた)、使えそうなのに使えなくてがっかりしていました。

Screen Shot 2016-12-29 at 0.30.09.png

で、また検索しながら、そういえば昔誰かが、vImage で変換できると言っていたような気がする事を思い出して、vImage をキーワードに追加して検索。でここに到着。

Converting BGRA to ARGB

ちゃんと、macOS でも iOS でも動作を確認しました。そしてついでに上下逆にするのも、vImage で API を見つけたので、それも実装してやっと出来上がりました。

コードは gist からも入手できます。
https://gist.github.com/codelynx/4e56758fb89e94d0d1a58b40ddaade45


import Foundation
import CoreGraphics
import MetalKit
import GLKit
import Accelerate

extension MTLTexture {

    #if os(iOS)
    typealias XImage = UIImage
    #elseif os(macOS)
    typealias XImage = NSImage
    #endif

    var cgImage: CGImage? {

        assert(self.pixelFormat == .bgra8Unorm)

        // read texture as byte array
        let rowBytes = self.width * 4
        let length = rowBytes * self.height
        let bgraBytes = [UInt8](repeating: 0, count: length)
        let region = MTLRegionMake2D(0, 0, self.width, self.height)
        self.getBytes(UnsafeMutableRawPointer(mutating: bgraBytes), bytesPerRow: rowBytes, from: region, mipmapLevel: 0)

        // use Accelerate framework to convert from BGRA to RGBA
        var bgraBuffer = vImage_Buffer(data: UnsafeMutableRawPointer(mutating: bgraBytes),
                    height: vImagePixelCount(self.height), width: vImagePixelCount(self.width), rowBytes: rowBytes)
        let rgbaBytes = [UInt8](repeating: 0, count: length)
        var rgbaBuffer = vImage_Buffer(data: UnsafeMutableRawPointer(mutating: rgbaBytes),
                    height: vImagePixelCount(self.height), width: vImagePixelCount(self.width), rowBytes: rowBytes)
        let map: [UInt8] = [2, 1, 0, 3]
        vImagePermuteChannels_ARGB8888(&bgraBuffer, &rgbaBuffer, map, 0)

        // flipping image vertically
        let flippedBytes = bgraBytes // share the buffer
        var flippedBuffer = vImage_Buffer(data: UnsafeMutableRawPointer(mutating: flippedBytes),
                    height: vImagePixelCount(self.height), width: vImagePixelCount(self.width), rowBytes: rowBytes)
        vImageVerticalReflect_ARGB8888(&rgbaBuffer, &flippedBuffer, 0)

        // create CGImage with RGBA Flipped Bytes
        let colorScape = CGColorSpaceCreateDeviceRGB()
        let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue)
        guard let data = CFDataCreate(nil, flippedBytes, length) else { return nil }
        guard let dataProvider = CGDataProvider(data: data) else { return nil }
        let cgImage = CGImage(width: self.width, height: self.height, bitsPerComponent: 8, bitsPerPixel: 32, bytesPerRow: rowBytes,
                    space: colorScape, bitmapInfo: bitmapInfo, provider: dataProvider,
                    decode: nil, shouldInterpolate: true, intent: .defaultIntent)
        return cgImage
    }

    var image: XImage? {
        guard let cgImage = self.cgImage else { return nil }
        #if os(iOS)
        return UIImage(cgImage: cgImage)
        #elseif os(macOS)
        return NSImage(cgImage: cgImage, size: CGSize(width: cgImage.width, height: cgImage.height))
        #endif
    }

}

[環境に関する表記]

Xcode Version 8.2.1 (8C1002)
Apple Swift version 3.0.2 (swiftlang-800.0.63 clang-800.0.42.1)
13
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
7