アライドアーキテクツAdvent Calendar14日目の記事になります。
お久しぶりです、14日目担当の happy_ryo です。今回は、前回 Objective-C で書いた Core Image を利用したチルトシフトフィルターを Swift でリライトしてみました。
最初に書いておきますが、Objective-C 脳のぼくが Swift で書いても、特にスタイリッシュになったり、劇的にコードが簡略化される、といった事はおきませんでした。強いて言えば、Swift は Objective-C と違って、Core Foundation のオブジェクトもメモリ管理してくれるので、CGImage 等を自分でリリースしなくても良い、という部分において Swift の良さを享受できました。
もっと Swift にすると良い感じのお題を選べばよかったなぁ。
とはいえ、こちらの記事と見比べると Swift で Core Image を使うときに Objective-C で書いていたアレが、Swift だとこんな感じになるのかーという事が何となく伝わると思います。
前回同様、以下の手順で作業を進めます。
- 元画像をブラー処理したものを作成
- グラデーションを2種類作成する
- 2種類のグラデーションを合成する
- 元画像、ブラー処理画像、合成されたグラデーション画像をマスク処理で組み合わせる
下準備
元画像をブラー処理する
まずは CIGaussianBlur を利用して、ブラー処理された画像を作成します。 inputRadius パラメータを変更する事で、ブラーのかかり具合を調整する事が出来ます。サイズ合わせの所では、生成後の画像のサイズを元の画像にあわせて調整しています。
1 2 3 4 5 6 7 8 9 10 11 12 |
func blurred() -> CIImage { let ciImage = CIImage(image: _baseImageView.image) let gaussianBlurFilter = CIFilter(name: "CIGaussianBlur") gaussianBlurFilter.setValue(ciImage, forKey: kCIInputImageKey) gaussianBlurFilter.setValue(10, forKey: kCIInputRadiusKey) let cropFilter = CIFilter(name: "CICrop") cropFilter.setValue(gaussianBlurFilter.outputImage, forKey: kCIInputImageKey) cropFilter.setValue(CIVector(CGRect: ciImage.extent()), forKey: "inputRectangle") return cropFilter.outputImage } |
グラデーションを2種類作成する
上から下、下から上に向けてのグラデーション2種類を CILinearGradient を使用して作成します。 inputPoint の値を調整する事で、チルトシフト加工後の画像のどの部分に焦点が当たっているように表現するかを変更する事が可能です。こちらもブラー処理と同様にサイズ調整をしています。Swift の CGRect には、保持している CGSize の値を返す extension が用意されているので、ciImage.extent().size.height ではなく ciImage.extent().height の様に書けるのはちょっと嬉しいです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
func topLinearGradient() -> CIImage { let ciImage = CIImage(image: _baseImageView.image) let filter = CIFilter(name: "CILinearGradient") filter.setValue(CIVector(x: 0, y: 0.75 * ciImage.extent().height), forKey: "inputPoint0") filter.setValue(CIColor(red: 0, green: 1, blue: 0, alpha: 0.9), forKey: "inputColor0") filter.setValue(CIVector(x: 0, y: 0.5 * ciImage.extent().height), forKey: "inputPoint1") filter.setValue(CIColor(red: 0, green: 1, blue: 0, alpha: 0), forKey: "inputColor1") let cropFilter = CIFilter(name: "CICrop") cropFilter.setValue(filter.outputImage, forKey: kCIInputImageKey) cropFilter.setValue(CIVector(CGRect: ciImage.extent()), forKey: "inputRectangle") return cropFilter.outputImage } func bottomLinearGradient() -> CIImage { let ciImage = CIImage(image: _baseImageView.image) let filter = CIFilter(name: "CILinearGradient") filter.setValue(CIVector(x: 0, y: 0.25 * ciImage.extent().height), forKey: "inputPoint0") filter.setValue(CIColor(red: 0, green: 1, blue: 0, alpha: 0.9), forKey: "inputColor0") filter.setValue(CIVector(x: 0, y: 0.5 * ciImage.extent().height), forKey: "inputPoint1") filter.setValue(CIColor(red: 0, green: 1, blue: 0, alpha: 0), forKey: "inputColor1") let cropFilter = CIFilter(name: "CICrop") cropFilter.setValue(filter.outputImage, forKey: kCIInputImageKey) cropFilter.setValue(CIVector(CGRect: ciImage.extent()), forKey: "inputRectangle") return cropFilter.outputImage } |
2種類のグラデーションを合成する
先ほど作成した二つのグラデーションを CIAdditionCompositing を利用して一つに合成します。ここでは他に特別な処理はせず、 CIAdditionCompositing に任せて合成するだけです。 topLinearGradient() と bottomLinearGradient() の呼び出しで Objective-C と差をつける為に self を省略していますが self.topLinearGradient() と書いてもちゃんと動きます。
1 2 3 4 5 6 7 |
func additionCompositing() -> CIImage { let context = CIContext(options: nil) let filter = CIFilter(name: "CIAdditionCompositing") filter.setValue(topLinearGradient(), forKey: kCIInputImageKey) filter.setValue(bottomLinearGradient(), forKey: kCIInputBackgroundImageKey) return filter.outputImage } |
仕上げ
最後に元の画像と、これまで用意したブラー処理された画像とグラデーションを CIBlendWithMask を利用してマスク処理します。 Objective-C と違って CGImage を Swift 側で管理してくれるので自分でリリースする必要はありません。習慣づけられていて、滅多に忘れる事は無いですが「自分でやらなくて良い」に越した事は無いので、この点は嬉しいですね。(解放忘れで問題が起きると探すのに一苦労ですし……)
1 2 3 4 5 6 7 8 9 10 11 12 13 |
func blendWithMask() { let context = CIContext(options: nil) let ciImage = CIImage(image: _baseImageView.image) let filter = CIFilter(name: "CIBlendWithMask") filter.setValue(blurred(), forKey: kCIInputImageKey) filter.setValue(ciImage, forKey: kCIInputBackgroundImageKey) filter.setValue(additionCompositing(), forKey: kCIInputMaskImageKey) let extent = filter.outputImage.extent() let cgImage: CGImage = context.createCGImage(filter.outputImage, fromRect: extent) _resultImageView.image = UIImage(CGImage: cgImage) } |
処理の結果吐き出される画像は、Objective-C で実行したときと同じ(はず)です。
Objective-C 自体は非常に冗長な言語ですが、それを補うように IDE 側が進化して、冗長な部分をプログラマが強く意識しなくても書けるようになって居る為、IDE の Swift へのサポートがまだ成熟していない現在ではまだまだ Swift よりも Objective-C の方が書きやすいなと感じますが、これから IDE のサポートが充実してくるにつれて立場が変わってくるのかな?と感じます。
先日 AppCode 3.1 がリリースされ、大分 Swift のサポートが強化されましたが、型推論を利用すると補完が効かず let ciImage: CIImage = CIImage(image: image) の様に書かないといけなかったり(Xcode は型推論を利用しても補完してくれる)、リファクタリングの機能がまだまだ貧弱だったりと、これからまだまだ成長していく界隈なので、追いかけていくのが楽しみです。
happy_ryo は Objective-C で散々助けて貰っている AppCode を応援しています。
明日は ザック の順番です。
営業→工場で作業→Word&Excel→Java→shellscript→Java→PHP→Python→Objective-C→Swift→PHP→JavaScript→ベトナム 子会社CTO→本社CTO←イマココ