Swift：CIAffineClampを使って縁まで綺麗なぼかし画像を取得する

# `CIAffineClamp`ってなぁに
<img width="687" alt="CIAffineClamp.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67153/ea04a034-2853-760e-2e3e-a4e54934a6ec.png">

`CIAffineClamp`によるフィルターを画像に適用すると，画像の縁（上下左右1px）をそれぞれの方向に無限大に引き伸ばした仮想上の画像データを生成することができます．これにより，ガウシアン・フィルタや一部のカラーフィルタのように，あるピクセルの周囲のピクセルをサンプリングしてそのピクセルの色を決定するようなフィルタ（下図）を扱う場合に画像縁の欠けている部分を補うことができます．
↓こういうやつ
<img width="777" alt="ガウシアン.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67153/a670aacf-df77-ab32-dec5-c6391f2cddcd.png">
<img width="361" alt="参照できない.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67153/ccbaaa2b-9883-d5ea-966f-e5d955fa3ef2.png">
↑デフォルトでは，はみ出した参照できない部分は透明色で補完されます．

# 直接`CIGaussianBlur`を使った場合
<img width="618" alt="before.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67153/283621e6-2d27-963e-c5f6-97e1548838e9.png">
左）オリジナル，右）ぼかし加工結果
画像の縁の色が薄くなってぼかしが弱いことがわかります．

## ソース

```swift
// オリジナル画像
let nsImage = NSImage(imageLiteralResourceName: "Test")        
guard let imageData = nsImage.tiffRepresentation, let ciImage = CIImage(data: imageData) else { return }
        
// ガウシアン・フィルタ
guard let blurFilter = CIFilter(name: "CIGaussianBlur") else { return }
blurFilter.setValue(ciImage, forKey: kCIInputImageKey)
blurFilter.setValue(5, forKey: kCIInputRadiusKey)
guard let blurImage = blurFilter.outputImage else { return }
let context = CIContext(options: nil)
guard let resultImage = context.createCGImage(blurImage, from: CGRect(origin: .zero, size: nsImage.size)) else { return }

resultView.image = NSImage(cgImage: resultImage, size: nsImage.size)
```

# `CIAffineClamp`を介して間接的に`CIGaussianBlur`を使った場合
<img width="618" alt="after.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67153/7226b076-e15f-2be8-4b7d-b75e72c9adb3.png">
左）オリジナル，中央）CIAffineClamp時点での結果，右）ぼかし加工結果
画像の縁までしっかりぼかしがかかっていることがわかります．

## ソース
```swift
// オリジナル画像
let nsImage = NSImage(imageLiteralResourceName: "Test")        
guard let imageData = nsImage.tiffRepresentation, let ciImage = CIImage(data: imageData) else { return }
        
// CIAffineClamp
guard let clampFilter = CIFilter(name: "CIAffineClamp") else { return }
clampFilter.setValue(ciImage, forKey: kCIInputImageKey)
clampFilter.setValue(CGAffineTransform(scaleX: 1.0, y: 1.0), forKey: kCIInputTransformKey)
guard let clampImage = clampFilter.outputImage else { return }
let context = CIContext(options: nil)
let rect = CGRect(origin: CGPoint(x: -25, y: -25), size: CGSize(width:  nsImage.size.width + 50, height: nsImage.size.height + 50))
guard let cgImage = context.createCGImage(clampImage, from: rect) else { return }

imageView.image = NSImage(cgImage: cgImage, size: rect.size)

// ガウシアン・フィルタ
guard let blurFilter = CIFilter(name: "CIGaussianBlur") else { return }
blurFilter.setValue(clampImage, forKey: kCIInputImageKey)
blurFilter.setValue(5, forKey: kCIInputRadiusKey)
guard let blurImage = blurFilter.outputImage else { return }
guard let resultImage = context.createCGImage(blurImage, from: CGRect(origin: .zero, size: nsImage.size)) else { return }

resultView.image = NSImage(cgImage: resultImage, size: nsImage.size)
```

# 所感
CIFilterはうまく組み合わせることで，効率的に高速な画像処理が行えるため，ガンガン使っていきましょう．