画素値の配列からCGImageを作成する方法として、CGImageクラスのイニシャライザを使う方法 がありますが、ここではAccelerateフレームワークを利用した方法を紹介します。
Accelerateフレームワークを利用することで、次の効果が期待できます。
- CPUのベクトル演算機能の活用で高速・省エネな変換
- 画像の上下・左右反転やサイズ変更等の加工を高速・簡単に実現
ここでは10x4の画素値配列をCGImageに変換/加工してみます。
※iOS13以降を前提としてます。
配列からCGImageへの変換
グレースケールの場合
let width = 10
let height = 4
// グレースケール画素値
var grayScalePixels: [UInt8] = [
0, 25, 50, 75,100,125,150,175,200,225,
25, 50, 75,100,125,150,175,200,225,200,
50, 75,100,125,150,175,200,225,200,175,
75,100,125,150,175,200,225,200,175,150,
]
import UIKit
import Accelerate
let grayScaleImage: CGImage? = grayScalePixels.withUnsafeMutableBufferPointer { pixelPointer in
// 画素値配列をvImage_Bufferの形にする
let sourceBuffer = vImage_Buffer(data: pixelPointer.baseAddress!,
height: vImagePixelCount(height),
width: vImagePixelCount(width),
rowBytes: width)
// 画像のピクセルフォーマットを定義
guard let format = vImage_CGImageFormat(bitsPerComponent: 8,
bitsPerPixel: 8,
colorSpace: CGColorSpaceCreateDeviceGray(),
bitmapInfo: CGBitmapInfo(rawValue: 0)) else {
return nil
}
// CGImageに変換
return try? sourceBuffer.createCGImage(format: format)
}
-
colorSpace
には、CGColorSpaceCreateDeviceGray()を指定。 -
bitmapInfo
には、CGBitmapInfo(rawValue: 0))を指定。カラー画像を生成する場合、bitmapInfoにはアルファ値の位置やRBGのバイトオーダーを指定するが、8bitグレースケールの場合は定義がなく、0を指定しておく。 -
createCGImage(format:flags:)
はiOS13に追加されたメソッドでバッファからCGImageを生成します。
カラー画像(RGB)の場合
// RGB画素値
var rgbPixels: [UInt8] = [
0, 0, 0, 25, 0, 0, 50, 0, 0, 75, 0, 0,100, 0, 0,
125, 0, 0,150, 0, 0,175, 0, 0,200, 0, 0,225, 0, 0,
0, 0, 0, 0, 25, 0, 0, 50, 0, 0, 75, 0, 0,100, 0,
0,125, 0, 0,150, 0, 0,175, 0, 0,200, 0, 0,225, 0,
0, 0, 0, 0, 0, 25, 0, 0, 50, 0, 0, 75, 0, 0,100,
0, 0,125, 0, 0,150, 0, 0,175, 0, 0,200, 0, 0,225,
0, 0, 0, 25, 25, 25, 50, 50, 50, 75, 75, 75,100,100,100,
125,125,125,150,150,150,175,175,175,200,200,200,225,225,225,
]
let rgbImage: CGImage? = rgbPixels.withUnsafeMutableBufferPointer { pixelPointer in
let buffer = vImage_Buffer(data: pixelPointer.baseAddress!,
height: vImagePixelCount(height),
width: vImagePixelCount(width),
rowBytes: width * 3)
guard let format = vImage_CGImageFormat(bitsPerComponent: 8,
bitsPerPixel: 8 * 3,
colorSpace: CGColorSpaceCreateDeviceRGB(),
bitmapInfo: CGBitmapInfo(rawValue: CGImageAlphaInfo.none.rawValue | CGImageByteOrderInfo.orderDefault.rawValue)) else {
return nil
}
return try? buffer.createCGImage(format: format)
}
- vImage_Buffer()に与える rowBytes には、widthの3倍(R,G,B分)を指定。
- vImage_CGImageFormat()に与える bitsPerPixel は24(=8x3)を指定。
- colorSpaceには、CGColorSpaceCreateDeviceRGB()を指定。
- bitmapInfoには、
CGImageAlphaInfo.none
でアルファチャネルがないこと、CGImageByteOrderInfo.orderDefault
で1バイト単位のバイトオーダーであることを指定。これらの指定で「アルファチャネルがなくRGBの順番で並んでいてそれぞれ8bit」というフォーマットとなる1。
カラー画像(ARGB)の場合
// ARGB画素値
var argbPixels: [UInt8] = [
//--------------|----------------|----------------|----------------|----------------|
255, 0, 0, 0, 255, 25, 0, 0, 255, 50, 0, 0, 255, 75, 0, 0, 255,100, 0, 0,
128,125, 0, 0, 128,150, 0, 0, 128,175, 0, 0, 128,200, 0, 0, 128,225, 0, 0,
255, 0, 0, 0, 255, 0, 25, 0, 255, 0, 50, 0, 255, 0, 75, 0, 255, 0,100, 0,
128, 0,125, 0, 128, 0,150, 0, 128, 0,175, 0, 128, 0,200, 0, 128, 0,225, 0,
255, 0, 0, 0, 255, 0, 0, 25, 255, 0, 0, 50, 255, 0, 0, 75, 255, 0, 0,100,
128, 0, 0,125, 128, 0, 0,150, 128, 0, 0,175, 128, 0, 0,200, 128, 0, 0,225,
255, 0, 0, 0, 255, 25, 25, 25, 255, 50, 50, 50, 255, 75, 75, 75, 255,100,100,100,
128,125,125,125, 128,150,150,150, 128,175,175,175, 128,200,200,200, 128,225,225,225,
]
let argbImage: CGImage? = argbPixels.withUnsafeMutableBufferPointer { pixelPointer in
let buffer = vImage_Buffer(data: pixelPointer.baseAddress!,
height: vImagePixelCount(height),
width: vImagePixelCount(width),
rowBytes: width * 4)
guard let format = vImage_CGImageFormat(bitsPerComponent: 8,
bitsPerPixel: 8 * 4,
colorSpace: CGColorSpaceCreateDeviceRGB(),
bitmapInfo: CGBitmapInfo(rawValue: CGImageAlphaInfo.first.rawValue | CGImageByteOrderInfo.orderDefault.rawValue)) else {
return nil
}
return try? buffer.createCGImage(format: format)
}
- vImage_Buffer()に与える rowBytes には、widthの4倍(A,R,G,B)を指定。
- vImage_CGImageFormat()に与える bitsPerPixel は32(bit)を指定。
- colorSpaceには、CGColorSpaceCreateDeviceRGB()を指定。
- bitmapInfoには、
CGImageAlphaInfo.first
でアルファチャネルが最初であること(RGBAならCGImageAlphaInfo.last)、CGImageByteOrderInfo.orderDefault
で8bitのバイトオーダーであることを指定。
出力結果: ※右半分が不透明率=0.5なので、背景(Viewの背景色が黄色)が見える。
CGImageへの変換前に加工
左右反転 ※前述のARGB配列を利用
let argbImage: CGImage? = argbPixels.withUnsafeMutableBufferPointer { pixelPointer in
var inBuffer = vImage_Buffer(data: pixelPointer.baseAddress!,
height: vImagePixelCount(height),
width: vImagePixelCount(width),
rowBytes: width * 4)
guard var outBuffer = try? vImage_Buffer(width: width, height: height, bitsPerPixel: 32) else { return nil }
defer {
// 確保したバッファは明示的に解放する必要がある
outBuffer.free()
}
vImageHorizontalReflect_ARGB8888(&inBuffer,
&outBuffer,
vImage_Flags(kvImageNoFlags))
// フォーマットの定義は略
return try? outBuffer.createCGImage(format: format)
}
- 左右を反転する命令はvImageHorizontalReflect_ARGB8888()です。出力バッファ
outBuffer
を確保し、入力用のバッファを引数に与えることで、左右反転した画像データを作れます。 - outBufferのように確保したバッファは明示的に解放する必要があります。
この例でinBufferはswiftの配列をポインタを渡してインスタンス化しているので(新たなメモリは確保しないので)解放は不要です(無理矢理free()するとpointer being freed was not allocated
で強制終了)。 - 上下反転の場合はvImageVerticalReflect_ARGB8888()が用意されています。
拡大 ※前述のARGB配列を利用。縦横10倍に拡大
guard var outBuffer = try? vImage_Buffer(width: width*10, height: height*10, bitsPerPixel: 32) else { return nil }
defer {
outBuffer.free()
}
vImageScale_ARGB8888(&inBuffer,
&outBuffer,
nil,
vImage_Flags(kvImageHighQualityResampling))
- 拡大・縮小の命令はvImageScale_ARGB8888()です。出力バッファは拡大・縮小後のデータサイズを確保します。
- オプションフラグにはいくつか拡大縮小時の挙動を指定することができます2。kvImageHighQualityResamplingを指定することでLanczos補間となります。kvImageNoFlagsを指定しても何かしら平滑化する補完がなされてジャギーは出ません。
ここで紹介した画像処理の他にもvImageでは様々な画像処理が可能です。詳しくは参考URLの「vImage Operations」を参照ください。
参考URL
-
CGImage.hのCGImageAlphaInfo、CGImageByteOrderInfoを確認しましたが「RGBの順番である」というのはどうやら決まり事のようで、BGRのような順番の指定方法はわかりませんでした。 ↩