Swift：CIKernelを使ってフィルタを自作する方法

#はじめに
Swiftで画像にフィルタをかける場合は[CIFilter](https://developer.apple.com/library/archive/documentation/GraphicsImaging/Reference/CoreImageFilterReference/index.html)を使うことができますが、フィルタを自作したい場合もあります。今回は、CIKernelを使ってフィルタを自作する方法をまとめます。

#CIColorKernelでのカラーフィルタ作成
アルファがある閾値以上の場合のみ色を描画するようなフィルタを例示します。
フィルタはGLSLの形式で記述します。

```swift:
func alphaOverThreshold(input: CIImage, threshold: Float) -> CIImage {
    let kernelStr: String = """
    kernel vec4 extract (__sample s, float threshold) {
        if (s.a > threshold) {
            return s.rgba;
        } else {
            return vec4(0.0, 0.0, 0.0, 0.0);
        }
    }
    """
    let kernel = CIColorKernel(source: kernelStr)!
    return kernel.apply(extent: input.extent, arguments: [input, threshold])!
}
```

#CIWarpKernelでの描画座標変更フィルタ作成
おおまかな流れはCIColorKernelと同じです。
左右反転するフィルタを例示します。

```swift:例
func flipHorizontal(_ input: CIImage) -> CIImage {
    let kernelStr: String = """
    kernel vec2 flipHorizontal (float width) {
        vec2 current = destCoord();
        return vec2(width - current.x, current.y);
    }
    """
    let kernel = CIWarpKernel(source: kernelStr)!
    let roiCallback = { (index: Int32, rect: CGRect) -> CGRect in
        return rect
    }
    return kernel.apply(extent: input.extent, roiCallback: roiCallback, image: input, arguments: [input.extent.width])!
}
```

#CIKernelでのフィルタ作成
CIColorKernelでは特定の座標の色情報のみを扱うことができ、CIWarpKernelでは座標情報以外（色情報）を扱うことができません。ある座標の色とその隣の座標の色を比較するといったことをする場合は、CIKernelを使います。

ぼかしフィルタを例示します。

```swift:例
func blur(_ input: CIImage) -> CIImage {
    let kernelStr: String = """
    kernel vec4 blur (sampler image) {
        vec2 current = destCoord();
        vec4 lt = sample(image, samplerTransform(image, current + vec2(-1.0, -1.0)));
        vec4 t  = sample(image, samplerTransform(image, current + vec2( 0.0, -1.0)));
        vec4 rt = sample(image, samplerTransform(image, current + vec2( 1.0, -1.0)));
        vec4 l  = sample(image, samplerTransform(image, current + vec2(-1.0,  0.0)));
        vec4 c  = sample(image, samplerTransform(image, current));
        vec4 r  = sample(image, samplerTransform(image, current + vec2( 1.0,  0.0)));
        vec4 lb = sample(image, samplerTransform(image, current + vec2(-1.0,  1.0)));
        vec4 b  = sample(image, samplerTransform(image, current + vec2( 0.0,  1.0)));
        vec4 rb = sample(image, samplerTransform(image, current + vec2( 1.0,  1.0)));
            
        vec4 color = lt + t + rt + l + c + r + lb + b + rb;
        return color / 9.0;
    }
    """
    let kernel = CIKernel(source: kernelStr)!
    let roiCallback = { (index: Int32, rect: CGRect) -> CGRect in
        return rect.insetBy(dx: -1.0, dy: -1.0)
    }
    return kernel.apply(extent: input.extent.insetBy(dx: -1.0, dy: -1.0), roiCallback: roiCallback, arguments: [input])!
}
```

#おまけ
##フィルタをかけたい画像をCIImageとして取得

```swift:例
let path = Bundle.main.path(forResource: "imageName", ofType: "jpg")!
let url = URL(fileURLWithPath: path)
let ciImage = CIImage(contentsOf: url))!
```

##フィルタをかけた画像の取得（同時にNSImage/UIImageへの変換）

```swift:
let outputImage = alphaOverThreshold(input: ciImage, threshold: 0.4)
 
//NSImage
let context = CIContext(options: [kCIContextUseSoftwareRenderer : false])
let nsImage = NSImage(cgImage: context.createCGImage(outputImage, from: outputImage.extent)!, 
                      size: outputImage.extent.size)
//または
let rep = NSBitmapImageRep(ciImage: outputL)
let nsImage = NSImage(size: outputL.extent.size)
nsImage.addRepresentation(rep)

//UIImage
let uiImage = UIImage(CIImage: outputImage)
```