Core Image Tutorial: Getting Started
Learn the basics of cool image filtering effects with Core Image and Swift. By Nick Lockwood.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Core Image Tutorial: Getting Started
35 mins
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.
- 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. - Calling
createCGImage(outputImage:fromRect:)
on the context with the supplied CIImage will return a new CGImage instance. - 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!