Core Image Tutorial: Getting Started

Learn the basics of cool image filtering effects with Core Image and Swift. By Ron Kliffer.

Leave a rating/review
Download materials
Save for later
Share
Update note: Ron Kliffer updated this tutorial for Xcode 13, Swift 5.5 and iOS 15. Jake Gunderson wrote the original.

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.

Starter app state

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:

  1. Create a CIImage object. A CIImage has several initialization methods. In this tutorial, you’ll use CIImage(image:) to create a CIImage from a UIImage. Explore the documentation to learn more ways you can create a CIImage.
  2. Create a CIContext. A CIContext can be CPU- or GPU-based. A CIContext is expensive to initialize, so you reuse it rather than create it over and over. You’ll always need one when outputting the CIImage object.
  3. Create a CIFilter. When you create the filter, you configure some properties on it that depend on the filter you’re using.
  4. Get the filter output. The filter gives you an output image as a CIImage. You can convert this to a UIImage using the CIContext, 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:

  1. Creates a UIImage and uses it to create a CIImage.
  2. Creates a CIFilter of type CISepiaTone. It’s the type of sepia-tone.
  3. The CISepiaTone filter takes two values. First, an input image: kCIInputImageKey, which is a CIImage instance. Second, an intensity: kCIInputIntensityKey, a float value between 0 and 1. Most of the filters use their default values if there isn’t any value. One exception is the CIImage. This must provide a value because there’s no default.
  4. Gets a CIImage back out of the filter, using the outputImage property.
  5. Turns the CIImage back to a UIImage 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:

Image with sepia filter

Congratulations, you have used CIImage and CIFilters 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.

Image with sepia filter

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.

Intensity slider in action