Core Imageで画像フィルター処理を実装してみて分かったこと

Core Imageで画像フィルター処理を実装してみて分かったこと 学習記録

Contactsフレームワークで連絡先にアクセスする実装を終えたところで、アプリ内で撮影した写真に簡単な加工を加えたいと考え、Core Imageによるフィルター処理に挑戦しました。

1. フィルターのパラメータ名が分からず調べるのに苦労した

最初、フィルターを適用するコード自体は簡単に書けるだろうと考えていましたが、各フィルターにどんなパラメータがあるのか把握するのに想像以上に時間がかかりました。

let filter = CIFilter(name: "CISepiaTone")
filter?.setValue(image, forKey: kCIInputImageKey)
filter?.setValue(0.8, forKey: kCIInputIntensityKey)

パラメータ名がkCIInputから始まる独自の命名規則になっており、フィルターごとに指定できる項目も異なるため、公式のフィルター一覧を都度確認しながら実装する必要があると分かりました。

2. フィルター適用後の画像がそのまま表示できると思っていた

フィルターを適用して得られるCIImageを、そのままUIImageViewに設定しようとしてもうまく表示されないことがありました。

let context = CIContext()
let cgImage = context.createCGImage(outputImage, from: outputImage.extent)

CIImageはあくまで画像処理の「レシピ」のようなもので、実際に画面へ描画可能な形式にするにはCIContextを使ってCGImage(またはUIImage)に変換するレンダリング処理が必要だと分かりました。この変換処理を挟むことで、正しく画面に表示できるようになりました。

3. 複数フィルターを連結すると想定より重くなった

セピア調に加えてぼかしも適用したいと考え、複数のフィルターを単純に連結する実装にしたところ、処理が予想以上に重くなってしまいました。

気づいたこと:
フィルターごとに毎回CIContextでレンダリングしていた

原因を調べると、フィルターを1つ適用するたびにCIContextでのレンダリングを挟んでいたことで、無駄な変換処理が繰り返されていたことが分かりました。複数のフィルターはCIImageのまま連結して、最後に一度だけレンダリングする形に変更したところ、処理時間を大きく改善できました。

4. フィルター処理を毎回メインスレッドで行っていた

動作確認中、フィルターを適用するたびに画面が一瞬固まる現象に気づきました。

DispatchQueue.global(qos: .userInitiated).async {
}

フィルター処理やレンダリングは決して軽い処理ではなく、メインスレッドで同期的に実行するとUIの描画がブロックされることが分かりました。バックグラウンドキューで処理を行い、完了後にメインスレッドで表示を更新する形に変更したことで、操作感がスムーズになりました。

まとめ

  • フィルターのパラメータ名はkCIInputから始まる独自の命名規則で、フィルターごとに公式ドキュメントの確認が必要
  • CIImageは処理のレシピに過ぎず、CIContextでのレンダリングを経て初めて画面に表示できる
  • 複数フィルターは都度レンダリングせず、CIImageのまま連結して最後に一度だけレンダリングする
  • 重い処理であるフィルター適用・レンダリングはバックグラウンドキューで行い、メインスレッドをブロックしない

画像処理という性質上、正しさだけでなくパフォーマンスまで意識して実装する必要があると実感した機能でした。


本記事は生成AI(Claude)を活用して執筆・編集しています。内容は実体験に基づき、執筆者本人が確認・監修しています。

コメント

タイトルとURLをコピーしました