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)を活用して執筆・編集しています。内容は実体験に基づき、執筆者本人が確認・監修しています。

コメント