Core Image Tutorial: Getting Started
Learn the basics of cool image filtering effects with Core Image and Swift. By Ron Kliffer.
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
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
20 mins
Core Image is a robust framework that lets you apply filters to images. It provides all kinds of effects, such as modifying the vibrancy, hue or exposure. It can use either the CPU or GPU to process the image data quickly – fast enough to do real-time processing of video frames!
You can chain Core Image filters together to apply many effects to an image or video frame at once. The many filters combine into a single filter and apply to the image. This makes it quite efficient compared to processing the image through each filter, one at a time.
In this tutorial, you’ll get hands-on experience playing around with Core Image. You’ll apply a few different filters and see how easy it is to apply cool effects to images in real-time.
Getting Started
Before you begin, look at some of the most important classes in the Core Image framework:
-
CIContext.
CIContext
does all the processing of a core image. This is somewhat like a Core Graphics or OpenGL context. -
CIImage. This class holds the image data. A
UIImage
, an image file or pixel data can create it. -
CIFilter. The
CIFilter
class has a dictionary. This defines the attributes of the particular filter it represents. Examples of filters include vibrancy, color inversion, cropping and many more.
You’ll use each of these classes in this project.
CoreImageFun
Click the Download Materials button at the top or bottom of this tutorial to download the starter project. Open CoreImageFun.xcodeproj and run it. This is a simple app, a single screen with an image and a slider. The slider doesn’t do anything yet, but we’ll use it to show CIFilter
‘s powers. You’ll also notice a camera button at the top right of the screen. You’ll use this later in the tutorial to bring up the image picker.
Image-Filtering Basics
You’re going to start by running your image through a CIFilter
and displaying it on the screen. Every time you want to apply a CIFilter
to an image, you need to do four things:
-
Create a CIImage object. A
CIImage
has several initialization methods. In this tutorial, you’ll useCIImage(image:)
to create aCIImage
from aUIImage
. Explore the documentation to learn more ways you can create aCIImage
. -
Create a CIContext. A
CIContext
can be CPU- or GPU-based. ACIContext
is expensive to initialize, so you reuse it rather than create it over and over. You’ll always need one when outputting theCIImage
object. - Create a CIFilter. When you create the filter, you configure some properties on it that depend on the filter you’re using.
-
Get the filter output. The filter gives you an output image as a
CIImage
. You can convert this to aUIImage
using theCIContext
, as you’ll see below.
Applying Filter
After the theoretical information, it’s time to see how this works. Add the following code to ViewController.swift:
func applySepiaFilter(intensity: Float) {
// 1
guard let uiImage = UIImage(named: "image") else { return }
let ciImage = CIImage(image: uiImage)
// 2
guard let filter = CIFilter(name: "CISepiaTone") else { return }
// 3
filter.setValue(ciImage, forKey: kCIInputImageKey)
filter.setValue(intensity, forKey: kCIInputIntensityKey)
// 4
guard let outputImage = filter.outputImage else { return }
// 5
let newImage = UIImage(ciImage: outputImage)
imageView.image = newImage
}
Here’s what the code does. It:
- Creates a
UIImage
and uses it to create aCIImage
. - Creates a
CIFilter
of typeCISepiaTone
. It’s the type of sepia-tone. - The
CISepiaTone
filter takes two values. First, an input image:kCIInputImageKey
, which is aCIImage
instance. Second, an intensity:kCIInputIntensityKey
, afloat
value between 0 and 1. Most of the filters use their default values if there isn’t any value. One exception is theCIImage
. This must provide a value because there’s no default. - Gets a
CIImage
back out of the filter, using theoutputImage
property. - Turns the
CIImage
back to aUIImage
and displays it in the image view.
Next, call the added new method by adding the following to viewDidLoad()
:
applySepiaFilter(intensity: 0.5)
This triggers the image filtering with an intensity value of 0.5. Later in the tutorial, you’ll use a slider to try various intensity values.
Build and run the project. You’ll see your image filtered by the sepia tone filter:
Congratulations, you have used CIImage
and CIFilter
s well! :]
Putting it Into Context
Before you proceed, there’s an optimization you should know about.
As noted above, you need a CIContext
to apply a CIFilter
. But there’s no mention of this object in the example above. It turns out UIImage(CIImage:)
does all the work for you. It creates a CIContext
and uses it to filter the image. This makes the Core Image API quite easy to use.
There’s one major drawback: It creates a new CIContext
every time it’s used. CIContext
instances should be reusable to increase performance. If you want to use a slider to update the filter value, you must create a new CIContext
every time you change the filter. This method would be quite slow.
First, add the following property to ViewController
:
let context = CIContext(options: nil)
CIContext
accepts an options dictionary. It 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, so you pass in nil
for that argument.
Next, delete Step 5 from applySepiaFilter(intensity:)
and replace it with the following:
guard let cgImage = context.createCGImage(outputImage, from: outputImage.extent) else { return }
imageView.image = UIImage(cgImage: cgImage)
Here, you use CIContext
to draw a CGImage
and use that to create a UIImage
to display in the image view.
Build and run. Make sure it works as before.
In this example, handling the CIContext
creation yourself doesn’t make much difference. You’ll see why doing so is important for performance as you put in place the ability to change the filter in the next section.
Changing Filter Values
This is great, but it’s just the beginning of what you can do with Core Image filters. It’s time to use that nice slider below the image to alter the filter effect.
You already added a property for the CIContext
instance. Now, you’ll add a property to hold the filter.
There’s already an IBAction
connected to the slider’s Value Changed action. It’s called sliderValueChanged(_:)
. In this method, you’ll redo the image filter whenever the slider value changes. But you don’t want to redo the whole process. That would be quite inefficient and would take too long. You’ll need to change a few things in your class, so you hold onto some of the objects you create in applySepiaFilter(intensity:)
.
Add the following properties right below the context
declaration:
let filter = CIFilter(name: "CISepiaTone")!
Next, add the following to viewDidLoad()
before calling applySepiaFilter(intensity:)
:
guard let uiImage = UIImage(named: "image") else { return }
let ciImage = CIImage(image: uiImage)
filter.setValue(ciImage, forKey: kCIInputImageKey)
Here, you set the image to filter. You did this before in applySepiaFilter(intensity:)
. But it’s better to move it to viewDidLoad()
to prevent calls on every slider value change.
You moved some code to viewDidLoad()
, so replace applySepiaFilter(intensity:)
with the following:
func applySepiaFilter(intensity: Float) {
filter.setValue(intensity, forKey: kCIInputIntensityKey)
guard let outputImage = filter.outputImage else { return }
guard let cgImage = context.createCGImage(outputImage, from: outputImage.extent) else { return }
imageView.image = UIImage(cgImage: cgImage)
}
Finally, add the following to sliderValueChanged(_:)
:
applySepiaFilter(intensity: slider.value)
When the slider value changes, applySepiaFilter(intensity:)
will run with new intensity value.
Your slider is set to the default values: min 0, max 1, default 0.5. How convenient! These happen to be the right values for this CIFilter
.
Build and run. You should have a functioning live slider that will alter the sepia value for your image in real-time.