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

　画素値の配列からCGImageを作成する方法として、[CGImageクラスのイニシャライザを使う方法](https://developer.apple.com/documentation/coregraphics/cgimage/1455149-init) がありますが、ここではAccelerateフレームワークを利用した方法を紹介します。
　Accelerateフレームワークを利用することで、次の効果が期待できます。

- CPUのベクトル演算機能の活用で高速・省エネな変換
- 画像の上下・左右反転やサイズ変更等の加工を高速・簡単に実現

ここでは10x4の画素値配列をCGImageに変換/加工してみます。
**※iOS13以降を前提としてます。**

## 配列からCGImageへの変換

### グレースケールの場合

```swift
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,
]
```

```swift
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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/71605/077577cf-ce69-be5f-0884-0b9fa0a62ce9.png)

### カラー画像(RGB)の場合

```swift
// 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,
]
```

```swift
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`で１バイト単位のバイトオーダーであることを指定。これらの指定で「アルファチャネルがなくRGBの順番で並んでいてそれぞれ8bit」というフォーマットとなる[^2]。

出力結果： ※拡大したもの
![rgb_2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/71605/9f1ca651-d129-2624-0b6b-cef2a18c8064.jpeg)

### カラー画像(ARGB)の場合

```swift
// 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,
]
```

```swift
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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/71605/8765d570-2a22-84e9-a615-cbe6c43c27d5.png)

## CGImageへの変換前に加工

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

![reflect_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/71605/699f7b29-3038-ed46-4770-c70e7da2dde8.png)

```swift
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のように確保したバッファは明示的に解放する必要があります。<br>この例でinBufferはswiftの配列をポインタを渡してインスタンス化しているので（新たなメモリは確保しないので）解放は不要です（無理矢理free()すると`pointer being freed was not allocated`で強制終了）。
- 上下反転の場合はvImageVerticalReflect_ARGB8888()が用意されています。

### 拡大 ※前述のARGB配列を利用。縦横10倍に拡大

![scale_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/71605/5df972d4-9c39-a51a-6072-833a436e225d.png)

```swift
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()です。出力バッファは拡大・縮小後のデータサイズを確保します。
- オプションフラグにはいくつか拡大縮小時の挙動を指定することができます[^3]。kvImageHighQualityResamplingを指定することでLanczos補間となります。kvImageNoFlagsを指定しても何かしら平滑化する補完がなされてジャギーは出ません。

ここで紹介した画像処理の他にもvImageでは様々な画像処理が可能です。詳しくは参考URLの「vImage Operations」を参照ください。

### 参考URL
- [Appleドキュメント：Building a Basic Image-Processing Workflow](https://developer.apple.com/documentation/accelerate/building_a_basic_image-processing_workflow)
- [Appleドキュメント：vImage Operations](https://developer.apple.com/documentation/accelerate/vimage/vimage_operations)


[^2]: CGImage.hのCGImageAlphaInfo、CGImageByteOrderInfoを確認しましたが「RGBの順番である」というのはどうやら決まり事のようで、BGRのような順番の指定方法はわかりませんでした。
[^3]: [vImageScale_ARGB8888(_:_:_:_:)のAPIドキュメント](https://developer.apple.com/documentation/accelerate/1509266-vimagescale_argb8888)