Function Composition with CI Filters

Episode #148 | 12 minutes | published on December 11, 2014
Subscribers Only
We continue our example of CoreImage CIFilters, this time showing how function composition can facilitate working with filters a bit more flexible and chainable. Starting with the imperative, method-based approach, then gradually building towards a pattern that allows us to easily build filters functionally, swapping out the order, and changing around input parameters.

Episode Links

  • Source Code
  • Functional Swift Presentation by Chris Eidhof - I saw Chris give a version of this talk in Milan, and I enjoyed it, particularly the example about CIFilters. It was the inspiration fort his episode. Thanks Chris!
  • Functional Programming in Swift - Speaking of Chris Eidhof, he has written an excellent book on functional programming Swift with Florian Kugler and Wouter Swierstra. As someone fairly naïve with functional programming concepts, this book has helped relate these concepts to Swift. Highly recommended.

Defining our Function Type

Creating a name for our functions can do a lot to help with clarity.

typealias Filter = CIImage -> CIImage

Using this we can build functions that use this as a parameter without dealing with -> explosion everywhere, which can be hard to read. In addition, giving something a name means you can reason about it more abstractly.

Defining our Filter Functions

func sepia() -> Filter {
    return {
        image in
        let filter = CIFilter(name: 'CISepiaTone', withInputParameters: [
            kCIInputImageKey: image
        ])
        return filter.outputImage
    }
}

func blur(radius: Double) -> Filter {
    return {
        image in
        let filter = CIFilter(name: 'CIGaussianBlur', withInputParameters: [
            kCIInputImageKey: image,
            kCIInputRadiusKey: radius
            ])
        return filter.outputImage
    }
}

Using these is a little awkward at first:

let output = blur(4.0)(sepia()(cimg))

Here it's hard to change the order of these, reason about the parameters, etc. We can introduction function composition to make this a bit easier to read.

Function Composition

func composeFilter(f1: Filter, f2: Filter) -> Filter {
    return {
        image in
        f2(f1(image))
    }
}

With this in place, we can compose our 2 filters into a new filter:

let blurSepia = composeFilter(sepia(), blur(4.0))
let output = blurSepia(cimg)

This is a lot nicer to work with, because it reads in the order of the filters. It gets harder when you want to compose another filter with it:

let blurSepia = composeFilter(tint(UIColor.redColor), composeFilter(sepia(), blur(4.0))
let output = blurSepia(cimg)

It felt nice for 2 filters, but 3 got ugly. Imagine a 10 filter step... ouch. If you'll permit me, we can even go a tad further with a custom operator. (Insert standard warning of caution around inventing operators here).

For no apparent reason, I chose the symbol. This character can be typed on a Mac by pressing ⌥ 8.

infix operator  { associativity left }

func (f1: Filter, f2: Filter) -> Filter {
    return {
        image in
        f2(f1(image))
    }
}

Now any number of filters can be composed easily:

let output = tint(UIColor.redColor)  sepia()  blur(4.0)
blog comments powered by Disqus