Beginning Core Image in iOS 6

Note from Ray: This is the eighth iOS 6 tutorial in the iOS 6 Feast! In this tutorial, you’re updating one of our older tutorials to iOS 6 so it’s fully up-to-date with the latest features like the new Core Image filters in iOS 6. Parts of this tutorial come from Jake Gundersen‘s three Core […] By Jake Gundersen.

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 perform a CIFilter, yet there’s no mention of this object in the above example. It turns out that the the UIImage method you called (imageWithCIImage) 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. CIContexts are meant to be reusable to increase performance. If you want to use a slider to update the filter value, like you’ll be doing in this tutorial, creating new CIContexts each time you change the filter would be way too slow.

Let’s do this properly. Delete the code you added to viewDidLoad and replace it with the following:

CIImage *beginImage =
  [CIImage imageWithContentsOfURL:fileNameAndPath];

// 1
CIContext *context = [CIContext contextWithOptions:nil];

CIFilter *filter = [CIFilter filterWithName:@"CISepiaTone"
                              keysAndValues: kCIInputImageKey, beginImage,
                    @"inputIntensity", @0.8, nil];
CIImage *outputImage = [filter outputImage];

// 2
CGImageRef cgimg =
  [context createCGImage:outputImage fromRect:[outputImage extent]];

// 3
UIImage *newImage = [UIImage imageWithCGImage:cgimg];
self.imageView.image = newImage;

// 4
CGImageRelease(cgimg);

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

  1. Here you set up the CIContext object. The CIContext constructor takes an NSDictionary that specifies options including the color format and 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. Here you use a method on the context object to draw a CGImage. Calling the createCGImage:fromRect: on the context with the supplied CIImage will produce a CGImageRef.
  3. Next, you use UIImage +imageWithCGImage to create a UIImage from the CGImage.
  4. Finally, release the CGImageRef. CGImage is a C API that requires that you do your own memory management, even with ARC.

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

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

Changing Filter Values

This is great, but this is 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 image settings in real time.

Open MainStoryboard.storyboard and drag a slider in below the image view like so:

Adding a slider in the Storyboard editor

Make sure the Assistant Editor is visible and displaying ViewController.h, then control-drag from the slider down below the @interface. Set the Connection to Action, the name to amountSliderValueChanged, make sure that the Event is set to Value Changed, and click Connect.

While you’re at it let’s connect the slider to an outlet as well. Again control-drag from the slider down below the @interface, but this time set the Connection to Outlet, the name to amountSlider, 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 slow. The other things you can hold onto are the CIFilter and the CIImage that holds your beginning 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 instance variables to your private @implementation in ViewController.m:

@implementation ViewController {
    CIContext *context;
    CIFilter *filter;
    CIImage *beginImage;
}

Also, change the variables in your viewDidLoad method so they use the instance variables instead of declaring new local variables:

beginImage = [CIImage imageWithContentsOfURL:fileNameAndPath];
context = [CIContext contextWithOptions:nil];

filter = [CIFilter filterWithName:@"CISepiaTone" 
  keysAndValues:kCIInputImageKey, beginImage, @"inputIntensity", 
  @0.8, nil];

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 we’ve altered this value you’ll need to repeat a few steps:

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

So replace the amountSliderValueChanged: method with the following:

- (IBAction)amountSliderValueChanged:(UISlider *)slider {
    float slideValue = slider.value;
    
    [filter setValue:@(slideValue)
              forKey:@"inputIntensity"];
    CIImage *outputImage = [filter outputImage];
    
    CGImageRef cgimg = [context createCGImage:outputImage
                                     fromRect:[outputImage extent]];
    
    UIImage *newImage = [UIImage imageWithCGImage:cgimg];
    self.imageView.image = newImage;
    
    CGImageRelease(cgimg);
}

You’ll notice that you’ve changed the variable type from (id)sender to (UISlider *)sender 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 we’d left it as (id), we’d need to cast it to a UISlider or the next line would throw an error. Make sure that the method declaration in the header file matches the changes we’ve made here.

You retrieve the float value from the slider. Your slider is set to the default values – min 0, max 0, default 0.5. These happen to be the right values for this CIFilter, how convenient!

The CIFilter has methods that will allow us to set the values for the different keys in its dictionary. Here, you’re just setting the @”inputIntensity” to an NSNumber object with a float value of whatever you get from your slider.

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

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

Dynamically filtering images with Core Image

Jake Gundersen

Contributors

Jake Gundersen

Author

Over 300 content creators. Join our team.