始めに
写真にフィルターをかけるために、iOSでは標準のSDKで用意されているCIFilterを使うことで、色々加工することができます。
しかし、iOSだけではなくAndroidでも同じ実装をする必要があるのはよくあることです。各OSで共通して同じ効果を得るために、画像編集ソフトなどで作成できるトーンカーブを使うと、色々な効果を再現することができます。
トーンカーブを扱うにはLUTテクスチャ画像を使用する方法や、Adobe Photoshop のACVファイルを使う方法があります。今回はSwiftでACVファイルを使って実装したので、その内容をまとめておきたいと思います。
ちなみにiOSにはGPUImageという有名なライブラリがあり、その中でもACVファイルはサポートされていて、GPUImage 3 で OpenGL -> Metal への移植が進められています。(この記事を書き始めて調べ直している間に 3 の存在を知りました…)
https://github.com/BradLarson/GPUImage3/
ACVからCIFilterにかけるまで
以下の手順を追って反映させていきます
- ACVファイルからトーンカーブのポイントデータを抽出する
- ポイントデータからColorCubeデータを生成する
- ColorCubeデータを使ってCIFilterで画像を変換する
1. ACVファイルからトーンカーブのポイントデータを抽出する
ACVファイルには red, green, blue, composit の4要素があり、それぞれ赤、緑、青、輝度を設定できます
ImageFilter/ACVParser.swift at master · Matzo/ImageFilter · GitHub
ポイントデータは2要素ずつ x,y のペアが [x1,y1,x2,y2,...xn,yn]として以下のようになっています。
composit: [0.0, 0.12549019607843137, 0.16470588235294117, 0.14901960784313725, 0.34901960784313724, 0.2784313725490196, 0.5725490196078431, 0.5176470588235293, 1.0, 1.0]
red: [0.0, 0.0, 0.27058823529411763, 0.2901960784313725, 0.5490196078431373, 0.596078431372549, 1.0, 1.0]
green: [0.06274509803921569, 0.0, 0.2784313725490196, 0.21568627450980393, 0.5725490196078431, 0.5294117647058824, 0.8, 0.7843137254901961, 1.0, 1.0]
blue: [0.0, 0.0, 0.2549019607843137, 0.17254901960784313, 0.596078431372549, 0.5058823529411764, 0.8117647058823529, 0.788235294117647, 1.0, 1.0]
2. ポイントデータからColorCubeデータを生成する
こちらを参考にさせていただきました
GitHub - churabou/ToneCurve
その際、出力時の画像が暗くなっていたので、 Does Your App Need Color Management? こちらを参考に補正を適用しました。
ドキュメント内のコードが何やらバグってますが、正しくは以下のようです。
# Conversions from and to sRGB add to the filter complexity
rgb = mix(rgb*0.0774, pow(rgb*0.9479 + 0.05213, 2.4), step(0.04045,rgb))
rgb = mix(rgb*12.92, pow(rgb,0.4167) * 1.055 - 0.055, step(0.00313,rgb))
参考) http://docs.huihoo.com/apple/wwdc/2012/session_511__core_image_techniques.pdf の 46ページ
また、composit が反映されていなかったので、反映されるように修正しています。
public var cubeData: Data {
let compositeCurve = self.getPreparedSplineCurve(point: rgbCompositeCurvePoints)
let redCurve = self.getPreparedSplineCurve(point: redCurvePoints)
let greenCurve = self.getPreparedSplineCurve(point: greenCurvePoints)
let blueCurve = self.getPreparedSplineCurve(point: blueCurvePoints)
let indexs: [Int] = [ 0, 8, 16, 25, 33, 41, 49, 58,
66, 74, 82, 90, 99, 107, 115, 123,
132, 140, 148, 156, 165, 173, 181, 189,
197, 206, 214, 222, 230, 239, 247, 255]
let size = cubeDimension
var colorCubeData: [Float] = []//[Float](repeatElement(0, count: size*size*size*4))
for b in 0..<size {
for g in 0..<size {
for r in 0..<size {
// bgra for upload to texture
var currentCurveIndex = indexs[b]
let bb = fmin(fmax(Float(currentCurveIndex) + blueCurve![currentCurveIndex] + compositeCurve![currentCurveIndex], 0), 255)
currentCurveIndex = indexs[g]
let gg = fmin(fmax(Float(currentCurveIndex) + greenCurve![currentCurveIndex] + compositeCurve![currentCurveIndex], 0), 255)
currentCurveIndex = indexs[r]
let rr = fmin(fmax(Float(currentCurveIndex) + redCurve![currentCurveIndex] + compositeCurve![currentCurveIndex], 0), 255)
let div: Float = 1.0 / 255.0
colorCubeData.append(sRGB_to_lightLiner(rr*div))
colorCubeData.append(sRGB_to_lightLiner(gg*div))
colorCubeData.append(sRGB_to_lightLiner(bb*div))
colorCubeData.append(1)
}
}
}
return NSData(bytes: colorCubeData, length: size*size*size*4*MemoryLayout<Float>.size) as Data
}
サンプルプロジェクト全体がこちら
https://github.com/Matzo/ImageFilter/tree/master/ImageFilter
3. ColorCubeデータを使ってCIFilterで画像を変換する
最後に、生成した ColorCubeデータを使って CIImage を変換するのがこちらです
func applyingFilter(to sourceImage: CIImage) -> CIImage {
guard let parser = ACVParser(with: "foo.acv") else { return sourceImage }
return sourceImage
.applyingFilter("CIColorCubeWithColorSpace", parameters: [
"inputCubeDimension": parser.cubeDimension,
"inputCubeData": parser.cubeData,
"inputColorSpace": parser.colorSpace
])
}
おわりに
ACVファイルの中身が思ったよりもシンプルで、ポイントデータからスプラインカーブを生成してColorCubeに変換するのが意外と力技なんだなと知りました。内部の計算で何をしているのか雰囲気しか分かってません。
LUTテクスチャを使用する方法もまたいずれまとめたいと思います。
最後に、 ACVファイルを使用したフィルターのキャプチャをサンプルとして載せておきます。