LoginSignup
2
3

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-07-29

はじめに

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)
2
3
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
2
3