Core Image Tutorial: Getting Started

Learn the basics of cool image filtering effects with Core Image and Swift. By Nick Lockwood.

Leave a rating/review
Save for later
Share
You are currently viewing page 2 of 5 of this article. Click here to view the first page.

Putting It Into Context

Before you move forward, there’s an optimization that you should know about.

I mentioned earlier that you need a CIContext in order to apply a CIFilter, yet there’s no mention of this object in the above example. It turns out that the the UIImage(CIImage:) constructor does all the work for you. It creates a CIContext and uses it to perform the work of filtering the image. This makes using the Core Image API very easy.

There is one major drawback – it creates a new CIContext every time it’s used. CIContext instances are meant to be reusable to increase performance. If you want to use a slider to update the filter value, as you’ll be doing in this tutorial, creating a new CIContext each time you change the filter would be way too slow.

Let’s do this properly. Delete step 4 from the code you added to viewDidLoad(), and replace it with the following:

// 1
let context = CIContext(options:nil)

// 2
let cgimg = context.createCGImage(filter.outputImage, fromRect: filter.outputImage.extent())

// 3
let newImage = UIImage(CGImage: cgimg)
self.imageView.image = newImage

Again, let’s go over this section by section.

  1. Here you set up the CIContext object and use it to draw a CGImage. The CIContext(options:) constructor takes an NSDictionary that specifies options such as the color format, or whether the context should run on the CPU or GPU. For this app, the default values are fine and so you pass in nil for that argument.
  2. Calling createCGImage(outputImage:fromRect:) on the context with the supplied CIImage will return a new CGImage instance.
  3. Next, you use the UIImage(CGImage:) constructor to create a UIImage from the newly created CGImage instead of directly from the CIImage as before. Note that there is no need to explicitly release the CGImage after we are finished with it, as there would have been in Objective-C. In Swift, ARC can automatically release Core Foundation objects.

Build and run, and make sure it works just as before.

In this example, handling the CIContext creation yourself doesn’t make much difference. But in the next section, you’ll see why this is important for performance, as you implement the ability to modify the filter dynamically!

Changing Filter Values

This is great, but it’s just the beginning of what you can do with Core Image filters. Lets add a slider and set it up so you can adjust the filter settings in real time.

Open Main.storyboard, and drag in a slider, and drop it in below the image view, centered horizontally. With the view selected, navigate to Editor \ Resolve Auto Layout Issues \ Selected Views \ Reset to Suggested Constraints, increasing the width constraint if desired.

Make sure the Assistant Editor is visible and displaying ViewController.swift, then control-drag from the slider down below the previously added @IBOutlet, set the name to amountSlider, and click Connect.

While you’re at it let’s connect the slider to an action method as well. Again control-drag from the slider, this time to just above the closing } of the ViewController class. Set the Connection to Action, the name to amountSliderValueChanged, make sure that the Event is set to Value Changed, and click Connect.

Every time the slider changes, you need to redo the image filter with a different value. However, you don’t want to redo the whole process, that would be very inefficient and would take too long. You’ll need to change a few things in your class so that you hold on to some of the objects you create in your viewDidLoad method.

The biggest thing you want to do is reuse the CIContext whenever you need to use it. If you recreate it each time, your program will run very slowly. The other things you can hold onto are the CIFilter and the CIImage that holds your original image. You’ll need a new CIImage for every output, but the image you start with will stay constant.

You need to add some instance variables to accomplish this task. Add the following three properties to your ViewController class:

var context: CIContext!
var filter: CIFilter!
var beginImage: CIImage!

Note that you have declared these values as implicitly-unwrapped optionals using the ! syntax, because you aren’t going to initialize them until viewDidLoad. You could have used ? instead, but you know that the way the code is designed will prevent the optionals from being nil by the time you use them. The implicitly-unwrapped syntax makes it much easier to read, without all the exclamation marks everywhere.

Change the code in viewDidLoad so it uses these properties instead of declaring new local variables, as follows:

beginImage = CIImage(contentsOfURL: fileURL)
        
filter = CIFilter(name: "CISepiaTone")
filter.setValue(beginImage, forKey: kCIInputImageKey)
filter.setValue(0.5, forKey: kCIInputIntensityKey)

let outputImage = filter.outputImage
        
context = CIContext(options:nil)
let cgimg = context.createCGImage(outputImage, fromRect: outputImage.extent())

Now you’ll implement the changeValue method. What you’ll be doing in this method is altering the value of the inputIntensity key in your CIFilter dictionary.

Once you’ve altered this value you’ll need to repeat a few steps:

  • Get the output CIImage from the CIFilter.
  • Convert the CIImage to a CGImage.
  • Convert the CGImage to a UIImage, and display it in the image view.

Replace amountSliderValueChanged(sender:) with the following:

@IBAction func amountSliderValueChanged(sender: UISlider) {
    
    let sliderValue = sender.value
    
    filter.setValue(sliderValue, forKey: kCIInputIntensityKey)
    let outputImage = filter.outputImage
    
    let cgimg = context.createCGImage(outputImage, fromRect: outputImage.extent())
    
    let newImage = UIImage(CGImage: cgimg)
    self.imageView.image = newImage
}

You’ll notice that you’ve changed the argument type from AnyObject to UISlider in the method definition. You know you’ll only be using this method to retrieve values from your UISlider, so you can go ahead and make this change. If you’d left it as AnyObject, you’d need to cast it to a UISlider or the next line would throw an error.

You retrieve the value from the slider (which returns a Float). Your slider is set to the default values – min 0, max 0, default 0.5. How convenient, these happen to be the right values for this CIFilter!

The CIFilter has methods that will allow you to set the values for the different keys in its dictionary. Here, you’re just setting the inputIntensity to whatever you get from your slider. Swift automatically converts the primitive CFloat value into an NSNumber object suitable for use with setValue(value:forKey:).

The rest of the code should look familiar, as it follows the same logic as viewDidLoad. You’re going to be using this code over and over again. From now on, you’ll use amountSliderValueChanged(sender:) to render the output of a CIFilter to your UIImageView.

Build and run, and you should have a functioning live slider that will alter the sepia value for your image in real time!

Nick Lockwood

Contributors

Nick Lockwood

Author

Over 300 content creators. Join our team.