Learn more about Russian war crimes in Ukraine.

How to pass multiple inputs to a CIKernel

I previously described how to create a custom CIFilter which swapped the red and green channels of an input image. That filter had a single input parameter: the input image. But kernels are C functions, and they can have many parameters of many types.

Here’s an example with multiple inputs. It alpha-composes two images with a configurable alpha value. (It ignores the alpha components of the input images and returns an opaque image.) Its parameters are therefore the two images (samplers) and the alpha value for the top image (a float).

We don’t actually need to create CIFilter objects at all! A CIFilter just wraps a CIKernel object, and the CIKernel is more interesting.

import Foundation
import CoreImage
import AppKit
let kernels = CIKernel.kernels(with:
    "kernel vec4 alpha_compose(sampler image1, sampler image2, float image2_opacity) { " +
    "  vec4 i1_px = sample(image1, samplerCoord(image1)); " +
    "  vec4 i2_px = sample(image2, samplerCoord(image2)); " +
    "  vec4 out_px = (i1_px * (1.0 - image2_opacity)) + (i2_px * image2_opacity); " +
    "  out_px.a = 1.0; " +
    "  return out_px;" +
let myKernel = kernels[0]
func ciImageFromPath(path: String) -> CIImage {
    let nsImage = NSImage(contentsOfFile: path)!
    let cgImage = nsImage.cgImage(forProposedRect: nil, context: nil, hints: [:])!
    return CIImage(cgImage: cgImage)
let inputImage1 = ciImageFromPath(path: "/Users/jim/shapes.png")
let inputImage2 = ciImageFromPath(path: "/Users/jim/Lenna.png")
let outputRect = CGRect(x: 0, y: 0, width: inputImage1.cgImage!.width, height: inputImage2.cgImage!.width)
let outputImage = myKernel.apply(
    withExtent: outputRect,
    roiCallback: { (Int32, CGRect) -> CGRect in return outputRect },
    arguments: [inputImage1, inputImage2, 0.2]
let rep = NSBitmapImageRep(ciImage: outputImage.cropping(to: outputRect))
let imageData = rep.representation(using: NSBitmapImageFileType.PNG, properties: [:])!
try! imageData.write(to: URL.init(string: "file:/Users/jim/output.png")!, options: NSData.WritingOptions.atomic)

Notice that our kernel has normal typed parameters, but since we compile it at runtime in Swift, we don’t have access to that type-safety. Instead, we pass arguments to our filter using .apply, with an array of arguments which are checked for compatibility at call-time.

What can computers do? What are the limits of mathematics? And just how busy can a busy beaver be? This year, I’m writing Busy Beavers, a unique interactive book on computability theory. You and I will take a practical and modern approach to answering these questions — or at least learning why some questions are unanswerable!

It’s only $19, and you can get 50% off if you find the discount code ... Not quite. Hackers use the console!

After months of secret toil, I and Andrew Carr released Everyday Data Science, a unique interactive online course! You’ll make the perfect glass of lemonade using Thompson sampling. You’ll lose weight with differential equations. And you might just qualify for the Olympics with a bit of statistics!

It’s $29, but you can get 50% off if you find the discount code ... Not quite. Hackers use the console!

More by Jim

Tagged . All content copyright James Fisher 2017. This post is not associated with my employer. Found an error? Edit this page.