Help us understand the problem. What is going on with this article?

MTLTexture から CGImage を生成

More than 1 year has passed since last update.

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

http://stackoverflow.com/questions/10654909/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)
Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away