3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

[Swift] PhotoshopのACVファイルを使って画像フィルターを作る

Posted at

始めに

写真にフィルターをかけるために、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にかけるまで

以下の手順を追って反映させていきます

  1. ACVファイルからトーンカーブのポイントデータを抽出する
  2. ポイントデータからColorCubeデータを生成する
  3. 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ファイルを使用したフィルターのキャプチャをサンプルとして載せておきます。

image_filter_sample.jpg
左: original、 右: aurora

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?