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 4 of 5 of this article. Click here to view the first page.

What About Image Metadata?

Let’s talk about image metadata for a moment. Image files taken on mobile phones have a variety of data associated with them, such as GPS coordinates, image format, and orientation. Specifically orientation is something that you need to preserve. The process of loading into a CIImage, rendering to a CGImage, and converting to a UIImage strips the metadata from the image. In order to preserve orientation, you’ll need to record it and then put it back into the UIImage.

Start by adding a new private instance variable to ViewController.m:

@implementation ViewController {
    CIContext *context;
    CIFilter *filter;
    CIImage *beginImage;
    UIImageOrientation orientation; // New!
}

Next, set the value when you load the image from the photo library in the -imagePickerController: didFinishPickingMediaWithInfo: method. Add the following line before the “beginImage = [CIImage imageWithCGImage:gotImage.CGImage]” line:

orientation = gotImage.imageOrientation;

Finally, alter the line in amountSliderChanged: creates the UIImage that you set to the imageView object:

UIImage *newImage = [UIImage imageWithCGImage:cgimg scale:1.0 orientation:orientation];

Now, if you take a picture taken in something other than the default orientation, it will be preserved.

What Other Filters are Available?

The CIFilter API has 130 filters on the Mac OS plus the ability to create custom filters. In iOS 6, it has 93 or more. Currently there isn’t a way to build custom filters on the iOS platform, but it’s possible that it will come.

In order to find out what filters are available, you can use the [CIFilter filterNamesInCategory:kCICategoryBuiltIn] method. This method will return an array of filter names. In addition, each filter has an attributes method that will return a dictionary containing information about that filter. This information includes the filter’s name, the kinds of inputs the filter takes, the default and acceptable values for the inputs, and the filter’s category.

Let’s put together a method for your class that will print all the information for all the currently available filters to the log. Add this method right above viewDidLoad:


-(void)logAllFilters {
    NSArray *properties = [CIFilter filterNamesInCategory:
      kCICategoryBuiltIn];
    NSLog(@"%@", properties);
    for (NSString *filterName in properties) {
        CIFilter *fltr = [CIFilter filterWithName:filterName];
        NSLog(@"%@", [fltr attributes]);
    }
}

This method simply gets the arrary of filters from the filterNamesInCategory method. It prints the list of names first. Then, for each name in the list, it creates that filter and logs the attributes dictionary from that filter.

Then call this method at the end of viewDidLoad:

[self logAllFilters];

You will see the following in the log output:

Logging the Core Image filters available on iOS

Wow, that’s a lot of filters!

More Intricate Filter Chains

Now that we’ve looked at all the filters that are available on the iOS 6 platform, it’s time to create a more intricate filter chain. In order to do this, you’ll create a dedicated method to process the CIImage. It will take in a CIImage, filter it, and return a CIImage. Add the following method:

-(CIImage *)oldPhoto:(CIImage *)img withAmount:(float)intensity {

    // 1
    CIFilter *sepia = [CIFilter filterWithName:@"CISepiaTone"];
    [sepia setValue:img forKey:kCIInputImageKey];
    [sepia setValue:@(intensity) forKey:@"inputIntensity"];
    
    // 2
    CIFilter *random = [CIFilter filterWithName:@"CIRandomGenerator"];
    
    // 3
    CIFilter *lighten = [CIFilter filterWithName:@"CIColorControls"];
    [lighten setValue:random.outputImage forKey:kCIInputImageKey];
    [lighten setValue:@(1 - intensity) forKey:@"inputBrightness"];
    [lighten setValue:@0.0 forKey:@"inputSaturation"];
    
    // 4
    CIImage *croppedImage = [lighten.outputImage imageByCroppingToRect:[beginImage extent]];
    
    // 5
    CIFilter *composite = [CIFilter filterWithName:@"CIHardLightBlendMode"];
    [composite setValue:sepia.outputImage forKey:kCIInputImageKey];
    [composite setValue:croppedImage forKey:kCIInputBackgroundImageKey];
    
    // 6
    CIFilter *vignette = [CIFilter filterWithName:@"CIVignette"];
    [vignette setValue:composite.outputImage forKey:kCIInputImageKey];
    [vignette setValue:@(intensity * 2) forKey:@"inputIntensity"];
    [vignette setValue:@(intensity * 30) forKey:@"inputRadius"];
    
    // 7
    return vignette.outputImage;
}

Let’s go over this section by section:

It doesn’t take any parameters. You’ll use this noise pattern to add texture to your final old photo look.

  1. In section one you set up the sepia filter the same way you did in the simpler scenario. You’re passing in the float in the method to set the intensity of the sepia. This value will be provided by the slider.
  2. In the second section you set up a filter that is new to iOS 6 (though not new on the Mac). The random filter creates a random noise pattern, it looks like this:
  3. In section three, you are altering the output of the random noise generator. You want to change it to grey and lighten it up a little bit so the effect is less dramatic. You’ll notice that the input image key is set to the .outputImage property of the random filter. This is a convenient way to chain the output of one filter into the input of the next.
  4. The fourth section you make use of a convenient method on CIImage. The imageByCroppingToRect method takes an output CIImage and crops it to the provided rect. In this case, you need to crop the output of the CIRandomGenerator filter because it is infinite. As a generated CIImage, goes on infinitely. If you don’t crop it at some point, you’ll get an error saying that the filters have ‘an infinte extent’. CIImages don’t actually contain data, they describe it. It’s not until you call a method on the CIContext that data is actually processed.
  5. In section five you are combining the output of the sepia filter with the output of the alter CIRandom filter. This filter does the exact same operation as the ‘Hard Light’ setting does in a photoshop layer. Most of (if not all, I’m not sure) the options in photoshop are available in Core Image.
  6. In the sixth section, you run a vignette filter on this composited output that darkens the edges of the photo. You’re using the value from the slider to set the radius and intensity of this effect.
  7. Finally, you return the output of the last filter.

That’s all for this filter. You can get an idea of how complex these filter chains may become. By combining Core Image filters into these kinds of chains, you can achieve endless different effects.

The next thing to do is implement this method in amountSliderValueChanged:. Change these two lines:

[filter setValue:@(slideValue) forKey:@"inputIntensity"];
CIImage *outputImage = [filter outputImage];

To this one line:

CIImage *outputImage = [self oldPhoto:beginImage withAmount:slideValue];

This just replaces the previous method of setting the outputImage variable to your new method. You pass in the slider value for the intensity and you use the beginImage, which you set in the viewDidLoad method as the input CIImage. Build and run now and you should get a more refined old photo effect.

An example of chaining filters with Core Image

That noise could probably be more subtle, but I’ll leave that experiment to you, dear reader. Now you have the power of Core Image. Go crazy!

Jake Gundersen

Contributors

Jake Gundersen

Author

Over 300 content creators. Join our team.