Help us understand the problem. What is going on with this article?

Swift:CIKernelを使ってフィルタを自作する方法

More than 1 year has passed since last update.

はじめに

Swiftで画像にフィルタをかける場合はCIFilterを使うことができますが、フィルタを自作したい場合もあります。今回は、CIKernelを使ってフィルタを自作する方法をまとめます。

CIColorKernelでのカラーフィルタ作成

アルファがある閾値以上の場合のみ色を描画するようなフィルタを例示します。
フィルタはGLSLの形式で記述します。

func alphaOverThreshold(input: CIImage, threshold: Float) -> CIImage {
    let kernelStr: String = """
    kernel vec4 extract (__sample s, float threshold) {
        if (s.a > threshold) {
            return s.rgba;
        } else {
            return vec4(0.0, 0.0, 0.0, 0.0);
        }
    }
    """
    let kernel = CIColorKernel(source: kernelStr)!
    return kernel.apply(extent: input.extent, arguments: [input, threshold])!
}

CIWarpKernelでの描画座標変更フィルタ作成

おおまかな流れはCIColorKernelと同じです。
左右反転するフィルタを例示します。

func flipHorizontal(_ input: CIImage) -> CIImage {
    let kernelStr: String = """
    kernel vec2 flipHorizontal (float width) {
        vec2 current = destCoord();
        return vec2(width - current.x, current.y);
    }
    """
    let kernel = CIWarpKernel(source: kernelStr)!
    let roiCallback = { (index: Int32, rect: CGRect) -> CGRect in
        return rect
    }
    return kernel.apply(extent: input.extent, roiCallback: roiCallback, image: input, arguments: [input.extent.width])!
}

CIKernelでのフィルタ作成

CIColorKernelでは特定の座標の色情報のみを扱うことができ、CIWarpKernelでは座標情報以外(色情報)を扱うことができません。ある座標の色とその隣の座標の色を比較するといったことをする場合は、CIKernelを使います。

ぼかしフィルタを例示します。

func blur(_ input: CIImage) -> CIImage {
    let kernelStr: String = """
    kernel vec4 blur (sampler image) {
        vec2 current = destCoord();
        vec4 lt = sample(image, samplerTransform(image, current + vec2(-1.0, -1.0)));
        vec4 t  = sample(image, samplerTransform(image, current + vec2( 0.0, -1.0)));
        vec4 rt = sample(image, samplerTransform(image, current + vec2( 1.0, -1.0)));
        vec4 l  = sample(image, samplerTransform(image, current + vec2(-1.0,  0.0)));
        vec4 c  = sample(image, samplerTransform(image, current));
        vec4 r  = sample(image, samplerTransform(image, current + vec2( 1.0,  0.0)));
        vec4 lb = sample(image, samplerTransform(image, current + vec2(-1.0,  1.0)));
        vec4 b  = sample(image, samplerTransform(image, current + vec2( 0.0,  1.0)));
        vec4 rb = sample(image, samplerTransform(image, current + vec2( 1.0,  1.0)));

        vec4 color = lt + t + rt + l + c + r + lb + b + rb;
        return color / 9.0;
    }
    """
    let kernel = CIKernel(source: kernelStr)!
    let roiCallback = { (index: Int32, rect: CGRect) -> CGRect in
        return rect.insetBy(dx: -1.0, dy: -1.0)
    }
    return kernel.apply(extent: input.extent.insetBy(dx: -1.0, dy: -1.0), roiCallback: roiCallback, arguments: [input])!
}

おまけ

フィルタをかけたい画像をCIImageとして取得

let path = Bundle.main.path(forResource: "imageName", ofType: "jpg")!
let url = URL(fileURLWithPath: path)
let ciImage = CIImage(contentsOf: url))!

フィルタをかけた画像の取得(同時にNSImage/UIImageへの変換)

let outputImage = alphaOverThreshold(input: ciImage, threshold: 0.4)

//NSImage
let context = CIContext(options: [kCIContextUseSoftwareRenderer : false])
let nsImage = NSImage(cgImage: context.createCGImage(outputImage, from: outputImage.extent)!, 
                      size: outputImage.extent.size)
//または
let rep = NSBitmapImageRep(ciImage: outputL)
let nsImage = NSImage(size: outputL.extent.size)
nsImage.addRepresentation(rep)

//UIImage
let uiImage = UIImage(CIImage: outputImage)
Kyome
火星に住む犬です。 趣味でmacOS アプリを開発しています。
https://kyome.io
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away