Posted at

Core Imageを使った画像エフェクト(Swift)


何をやるのか?

指定された画像に対して、Core Imageというフレームワークを用いて複数のエフェクトをかけ簡易的な画像加工を行うこと。


前提

xcodeにてstoryboardにImageViewとButtonを設置済みとして解説していく。


Core Imageとは?

Core Imageは、Apple社が提供している画像処理、解析を行うためのフレームワークである。


Core Imageの特徴としては、

・170種類以上のフィルタが標準で組み込まれている。

・フィルタを自らで製作することにより、ユーザーオリジナルのフィルタを使用することができる。

・静止画や動画に対して視覚効果を与えることができる。

・QRコードの解析や、顔検出といった画像解析も行える。

大きく分けて以上のような特徴があり、今回はフィルタを使い画像加工を行っていく。


今回使用するクラス

Core Imageには多くのクラスが用意されているが、今回は下記のクラスをしようしていく。

クラス
説明

CIContext
画像処理の結果から画像を生成したり、解析を行ったりするためのコンテキスト。この記事では処理の大まかな流れを指定するときに使う。また、createCGImageメソッドで、Core Image用の画像情報(CIImage)から表示用の画像(CGImage)を生成することもできる。

CIFilter
一つ以上の画像を受け取り、フィルタを適用して新しい画像を生成するクラス。新しく生成される画像データは、CIImageオブジェクトになる。

CIImage
画像関連のデータを管理するためのクラス。Core Imageの各クラス(CIFilter、CIContextなど)では、CIImageクラスで生成されたCIImageオブジェクトを使用する。


実装コード

import UIKit

class ViewController: UIViewController {

override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}

  //ImageViewを宣言
@IBOutlet weak var mainImage: UIImageView!

//フィルタの種類を配列化
let filterArray = ["CIPhotoEffectMono",
"CIPhotoEffectChrome",
"CIPhotoEffectFade",
"CIPhotoEffectInstant",
"CIPhotoEffectNoir",
"CIPhotoEffectProcess",
"CIPhotoEffectTonal",
"CIPhotoEffectTransfer",
"CISepiaTone"
]
//配列番号の初期値を設定
var filterSelectNumber = 0

@IBAction func effectButton(_ sender: Any) {
//UIImageViewのimageをオプショナルバインディングでアンラップし、imageに代入
if let image = mainImage.image {
//フィルター名を指定
let filterName = filterArray[filterSelectNumber]
//ボタンを押すたびにフィルター内容が変わるように設定
filterSelectNumber += 1
       //配列内の要素数とSelectNumberの値が等しければ、0を代入して初期化
if filterSelectNumber == filterArray.count {
filterSelectNumber = 0
}
       //画像の回転角度をを取得
let rotate = image.imageOrientation
//UIImage形式の画像をCIImage形式に変更し、加工可能な状態にする。
let inputImage = CIImage(image: image)
//フィルターの種類を引数で指定された種類を指定してCIFilterのインスタンスを取得
guard let effectFilter = CIFilter(name: filterName) else{
return
}
       //エフェクトのパラメータを初期化
effectFilter.setDefaults()
//インスタンスにエフェクトする画像を指定
effectFilter.setValue(inputImage, forKey: kCIInputImageKey)
      //エフェクト後のCIImage形式の画像を取り出す
guard let outputImage = effectFilter.outputImage else{
return
}
// CIContextのインスタンスを取得
let ciContext = CIContext(options: nil)
// エフェクト後の画像をCIContext上に描画し、結果をcgImageとしてCGImage形式の画像を取得
guard let cgImage = ciContext.createCGImage(outputImage, from: outputImage.extent) else {
return
}
      //エフェクト後の画像をCGImage形式からUIImage形式に変更、回転角度を指定、ImageViewに表示
mainImage.image = UIImage(cgImage: cgImage, scale: 1.0, orientation: rotate)

}
}
}

まず最初にCoreImageで指定可能なフィルター名を配列内に指定します。この配列を後ほど指定することで、ボタンを押すたびにエフェクトが変わるようになります。

そして変数としてfilterSelectNumberに初期値として0を代入します。この変数を用いることで、配列内の要素を順番に使うことを可能にします。

//フィルタの種類を配列化

let filterArray = ["CIPhotoEffectMono",
"CIPhotoEffectChrome",
"CIPhotoEffectFade",
"CIPhotoEffectInstant",
"CIPhotoEffectNoir",
"CIPhotoEffectProcess",
"CIPhotoEffectTonal",
"CIPhotoEffectTransfer",
"CISepiaTone"
]
//配列番号の初期値を設定
var filterSelectNumber = 0

UIImageViewのmainImageのimageを定数imageに代入することで、アンラップが行われ、オプショナル型(nilの可能性がある型)から外されます。

 @IBAction func effectButton(_ sender: Any) {

//UIImageViewのimageをオプショナルバインディングでアンラップし、imageに代入
if let image = mainImage.image {

定数filteNameを定義し、先ほどの配列と配列番号を代入します。

そして配列番号に1を足すことで、次のサイクルの際に、次のフィルターに設定されるようになります。

最後にif文を用いて配列番号が配列の要素数を超えた場合に0に初期化して、存在しない要素をしていないようにします。

       //フィルター名を指定

let filterName = filterArray[filterSelectNumber]
//ボタンを押すたびにフィルター内容が変わるように設定
filterSelectNumber += 1
       //配列内の要素数とSelectNumberの値が等しければ、0を代入して初期化
if filterSelectNumber == filterArray.count {
filterSelectNumber = 0
}

定数rotateを定義し、imageの角度を代入します。

画像加工の際に、角度の情報も必要なため現在の角度をこのように保存しておきます。

次に現在のimageはUIImage形式であるため、このままでは加工できません。そのため、加工可能にするためにCIImage形式に変更します。

最後に定数effectFilterを定義し、指定されたフィルターを代入します。ここで記述されているguard let文はオプショナル変数(nilの可能性がある変数)を安全に値を取り出す方法のアンラップの方法です。if let文のオプショナルバインディングは「アンラップできたら」という条件判断での分岐処理ですが、guard let文はその逆で「アンラップできなかったら」という条件判断で分岐処理を行います。そのため、オプショナル変数に値がないときに実行される箇所には、「return」などのプログラムが終了する命令のみを記載できます。

           //画像の回転角度をを取得

let rotate = image.imageOrientation
//UIImage形式の画像をCIImage形式に変更し、加工可能な状態にする。
let inputImage = CIImage(image: image)
//フィルターの種類を引数で指定された種類を指定してCIFilterのインスタンスを取得
guard let effectFilter = CIFilter(name: filterName) else{
return
}

まずエフェクトのパラメータを初期化します。

そしてeffectFilterに加工する画像を指定します。

その後加工された画像をCIImage形成で取り出します。

           //エフェクトのパラメータを初期化

effectFilter.setDefaults()
//インスタンスにエフェクトする画像を指定
effectFilter.setValue(inputImage, forKey: kCIInputImageKey)
      //エフェクト後のCIImage形式の画像を取り出す
guard let outputImage = effectFilter.outputImage else{
return
}

ciContextのインスタンスを取得し、先ほど取り出したCIImage形式の画像をCGImage形式として取得します。

最後にその画像をUIImage形式に変更し、UIImageViewのimageに代入したら画像が表示され完了です。

          // CIContextのインスタンスを取得

let ciContext = CIContext(options: nil)
// エフェクト後の画像をCIContext上に描画し、結果をcgImageとしてCGImage形式の画像を取得
guard let cgImage = ciContext.createCGImage(outputImage, from: outputImage.extent) else {
return
}
      //エフェクト後の画像をCGImage形式からUIImage形式に変更、回転角度を指定、ImageViewに表示
mainImage.image = UIImage(cgImage: cgImage, scale: 1.0, orientation: rotate)