MTLTexture の内容を確認する為に、MTLTexture から CGImage を作りコードとづっと格闘していました。良さげなコードが出来上がったので公開いたします。
そもそも、MTLTexture から バイト列取り出して、CGImage を生成すると、色が何かおかしい CGImage が出来上がりました。Photoshop でチャネルを交換して調査していたら B(青)と R(赤)のチャネルが入れ替わっている事がわかりました。
(注)上記は Photoshop で加工
コード書いて 1ピクセル毎変換するのも効率が悪いので、いい方法を探していました。CGDataProvider を使って CGImage を作るといいなんて話も Stack Overflow かどこがで見つけましたダメでした。
そもそも、MTKView の段階から、RGBA8Unorm になっていればいいかと思いきや、mtkView.colorPixelFormat = .rgba8Unorm
や 他の renderPiplelineDescriptor とか全てに「.rgba8Unorm」を設定しても画面は黒以外表示されなくなります。
さらに調べていると「MTLTexture」に「makeTextureView()」なんてメソッドがあって、見かけ上別のピクセルフォーマットで扱えるなんて書いてあり、試してみると、サポートしてないなんて感じのエラーがログに表示されて(ログのメモ忘れた)、使えそうなのに使えなくてがっかりしていました。
で、また検索しながら、そういえば昔誰かが、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)