8
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

画素値の配列からCGImageを作る

Posted at

 画素値の配列から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を生成します。

出力結果: ※拡大したもの
gray_1.png

カラー画像(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

出力結果: ※拡大したもの
rgb_2.jpg

カラー画像(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の背景色が黄色)が見える。
argb_1.png

CGImageへの変換前に加工

左右反転 ※前述のARGB配列を利用

reflect_1.png

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倍に拡大

scale_1.png

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

  1. CGImage.hのCGImageAlphaInfo、CGImageByteOrderInfoを確認しましたが「RGBの順番である」というのはどうやら決まり事のようで、BGRのような順番の指定方法はわかりませんでした。

  2. vImageScale_ARGB8888(::::)のAPIドキュメント

8
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
8
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?