Advertisement
iOS SDK

Build a Photo App with GPUImage

by

This tutorial will teach you how to apply Instagram-like filters and special effects to images with the incredibly powerful GPUImage project. Along the way, you'll learn how to build a simple camera application capable of either taking new photos or accessing existing images from the photo album.


Project Demo

Final Effect

The above is a collage of image filters applied with the app this tutorial will teach you how to build. The source image is from ep.Sos.de on Flickr.


Step 1: Start a New Xcode Project

Launch Xcode and create a new application using the Single View template.

Figure 1: Choosing a Project Template

For this tutorial, we'll use both Storyboards and Automatic Reference counting, so be sure to select both boxes. Name the project "PhotoFX" and supply a unique company identifier for device testing.

Figure 2: Project Setup Screen

Step 2: Create the Application Interface

The application interface will consist of a UINavigationBar for the app title on the UIView and save button, a UIToolbar for the album, camera, and filter utility buttons, and a UIImageView set to aspect fill for viewing and modifying selected images.

Figure 3: App Interface Images

Open the MainStoryboard.storyboard file. Select the UIView on screen and then select Editor > Embed In > Navigation Controller.

Figure 4: App Interface Images

Select the UINavigationController and then go to the Attributes Inspector in the Utility pane. Set the "Top Bar" drop down to "Black Navigation Bar" and the "Status Bar" to "None". To finalize the status bar removal, go to the PhotoFX-Info.plist file and add a new key with the text "Status bar is initially hidden". Set the value to "YES".

Because we've just transitioned our app into a UINavigationController template, you should now go to ViewController.h and add the following delegate declaration:

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController <UINavigationControllerDelegate>

@end

We'll need that later.

Now go back to the Storyboard file and double-click the title in the center of the UINavigationItem and replace the default text with "PhotoFX".

Drag a UIBarButtonItem from the Object Library onto the UINavigationItem. With the new button item selected, go to the Attributes Inspector tab of the Utilities panel and set the button's Identifier property to "Save". Next, with the "Save" button still selected, uncheck the "Enabled" checkbox for this button. This will prevent the user from trying to save an image before either loading one from the photo album or taking a picture with the camera (we'll enable it with code again later).

Figure 5: App Interface Images

Return to the object library and drag a UIToolbar onto the main UIView. Next add a total of three UIBarButtonItem objects on the toolbar. Change the title text for the first button to "Album", set the Identifier property for the second to "Camera", and set the third button's title text to "Filter". The "Filter" button should be disabled by default, just like the "Save" button from above.

To polish the toolbar layout, we need to right-align the filter button on the toolbar. You can achieve this effect by using a Flexible Space Bar Button Item, which you can simply drag onto the toolbar from the Object library.

Figure 6: App Interface Images

Notice how glaring the white UIView is in contrast to the black, shiny navigation bar and tool bar? Let's do something about that. Select the UIView and set the background color to "tungsten".

The only subview left to add is the main UIImageView used to show the user's image. Drag a UIImageView from the Object Library and center it between the UINavigationItem and the UIToolbar. Pull up the Attributes Inspector and select "Aspect Fit" as the UIImageView mode under the "View" Inspector subsection.

All the major components of the interface are now in place! The next step is to wire these elements from the Storyboard to the ViewController class.

Open the ViewController.m file. Add the following IBOutlet properties and IBAction methods into the ViewController class extension:

@interface ViewController ()

@property(nonatomic, weak) IBOutlet UIImageView *selectedImageView;
@property(nonatomic, weak) IBOutlet UIBarButtonItem *filterButton;
@property(nonatomic, weak) IBOutlet UIBarButtonItem *saveButton;

- (IBAction)photoFromAlbum;
- (IBAction)photoFromCamera;
- (IBAction)applyImageFilter:(id)sender;
- (IBAction)saveImageToAlbum;

@end

So, why create IBOutlet properties for the image view, filter button, and save button, but not the other Interface Builder components? The answer is that these are the only objects that we'll need to access programmatically. The image view will be accessed to set images selected by the user while the filter and save buttons will be accessed to switch the state from disabled to enabled after the user selects an image or takes a photo.

The IBAction methods should be mostly self-explanatory and will connect directly to the UIBarButtonItem selector implied by each name.

After creating the IBOutlet properties, you should synthesize them by adding the following line of code to the class @implementation:

@implementation ViewController

@synthesize selectedImageView, filterButton, saveButton;

To complete the Interface Builder setup, map each of the above IBOutlet objects and IBAction methods declared to the proper Interface Builder components (Need help doing this? Leave a question in the comments section below). Be sure to save your changes before moving on.

With the application's interface created, we're ready to begin coding the functionality!


Step 3: Selecting Photos From the Album

This tutorial will use the UIImagePickerController class for directly accessing the images within the user's photo album. Using this class will overlay a modal view gallery browser on top of our existing interface. When a user selects the image they want, the picker will use delegation to notify our ViewController class that a selection has been made. If you're new to iOS development, don't worry, this is a lot easier than it might sound.

In the ViewController.m file, add the following implementation for the photoFromAlbum method:

@synthesize selectedImageView, filterButton, saveButton;

- (IBAction)photoFromAlbum
{
    UIImagePickerController *photoPicker = [[UIImagePickerController alloc] init];
    photoPicker.delegate = self;
    photoPicker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
    
    [self presentViewController:photoPicker animated:YES completion:NULL];

}

Simple, right? Now we just need to implement the UIImagePickerController delegate protocol in order to respond to image selections. You will do this in Step 5 of this tutorial.


Step 4: Taking Photos with the Camera

There are two primary approaches to taking photos with the device camera. You can either use the UIImagePickerController to access Apple's default camera implementation, or you can create a completely customized experience with the AVFoundation framework. GPUImage actually builds upon the functionality provided by AVFoundation to provide a class specifically with this purpose in mind. However, for this tutorial, we'll be using UIImagePickerController for photo selection exclusively. In a future tutorial on GPUImage (likely to be published in the next 1-3 weeks), I'll show you how to use the more advanced GPUImage classes to achieve this.

The code for taking photos in this tutorial is as follows:

- (IBAction)photoFromCamera
{
    UIImagePickerController *photoPicker = [[UIImagePickerController alloc] init];

    photoPicker.delegate = self;
    photoPicker.sourceType = UIImagePickerControllerSourceTypeCamera;
    
    [self presentViewController:photoPicker animated:YES completion:NULL];

}

If you compare the method above with the photoFromAlbum method from step 3, you'll see that the only difference is whether sourceType is set to UIImagePickerControllerSourceTypePhotoLibrary or UIImagePickerControllerSourceTypeCamera. Because of this, you could easily combine these two methods into one. However, I've decided to leave photoFromCamera as a separate method as I'll be refactoring it to use AVFoundation in a future tutorial and the logic will need to be separated.


Step 5: Code the Photo Picker Delegate

The user can now browser the device library or use the device camera to select an image with UIImagePickerController. Regardless of how the user selects an image, the next step is to implement the delegate method that will be responsible for placing that image on the screen.

First, go to ViewController.h and declare that this class will conform to UIImagePickerControllerDelegate:

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController <UINavigationControllerDelegate, UIImagePickerControllerDelegate>

@end

Now flip to ViewController.m and implement the imagePickerController:didFinishPickingMediaWithInfo: delegate method called by the photo picker upon selection:

- (void)imagePickerController:(UIImagePickerController *)photoPicker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
    self.saveButton.enabled = YES;
    self.filterButton.enabled = YES;
    
    UIImage *selectedImage = [info valueForKey:UIImagePickerControllerOriginalImage];
    
    [self.selectedImageView setImage:selectedImage];
    
    [photoPicker dismissModalViewControllerAnimated:YES];
}

On lines 3-4 above, the save and filter buttons are enabled because we now have an image upon which those actions can be taken.

Line 6 creates a UIImage object with the photo selected by the user, and line 8 sets the image property of the UIImageViewController to the chosen image which will display it on the screen.

Finally, line 10 dismisses the modal view used to select the photo.

The above code should work well, but there's one enhancement needed. Rather than simply storing the image selected in the selectedImageView, we should also retain a copy in an internal UIImage data member. This will allow the application to apply each filter selected directly to the original image rather than iteratively layering the effects. It will also allow the user to easily revert to the original image from a filtered perspective. To do this, first add a UIImage object to the class extension at the top of ViewController.m:

#import "ViewController.h"
#import "GPUImage.h"

@interface ViewController ()
{
    UIImage *originalImage;
}

@property(nonatomic, weak) IBOutlet UIImageView *selectedImageView;
@property(nonatomic, weak) IBOutlet UIBarButtonItem *filterButton;

Next modify the imagePickerController:didFinishPickingMediaWithInfo: method as follows:

- (void)imagePickerController:(UIImagePickerController *)photoPicker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
    self.saveButton.enabled = YES;
    self.filterButton.enabled = YES;
    
    originalImage = [info valueForKey:UIImagePickerControllerOriginalImage];
    
    [self.selectedImageView setImage:originalImage];
    
    [photoPicker dismissModalViewControllerAnimated:YES];
}

If you build and run the project now, you should be able to select photos directly from the device album!


Step 6: Saving the Chosen Image

The last thing we need to do before tackling GPUImage is allow users to save the photos they take with the device camera. You can do this with a single line of code within the saveImageToAlbum method:

- (IBAction)saveImageToAlbum
{
    UIImageWriteToSavedPhotosAlbum(self.selectedImageView.image, self, @selector(image:didFinishSavingWithError:contextInfo:), nil);
}

The line of code above will attempt to save the image to the photo album, but you'll need to implement the selector specified in order to respond upon success or failure:

- (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo
{
    NSString *alertTitle;
    NSString *alertMessage;
    
    if(!error)
    {
        alertTitle   = @"Image Saved";
        alertMessage = @"Image saved to photo album successfully.";
    }
    else
    {
        alertTitle   = @"Error";
        alertMessage = @"Unable to save to photo album.";
    }

    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:alertTitle
                                                    message:alertMessage
                                                   delegate:self
                                          cancelButtonTitle:@"Okay"
                                          otherButtonTitles:nil];
    [alert show];
}

The above lines of code are rather straightforward and simply display a UIAlertView message notifying the user of whether or not the image was saved successfully.


Step 7: Add GPUImage to Your Project

Adding GPUImage to your project is a bit trickier than you might expect, but by following this step it should only take a few minutes for you to be up and running.

First, you need to download a copy of GPUImage from the official project GitHub. Unarchive the downloaded file and open the "framework" folder. These are the essential files needed to import GPUImage in your project. Rather than copying all of these into your project directly, use Finder to go to the location you saved your Xcode project in Step 1 (for me that's ~/Desktop/PhotoFX).Create a new folder called "Submodules" with a child folder called "GPUImage". Now copy the "framework" folder downloaded from GitHub and paste it into the "GPUImage" folder. Next, open the "framework" folder and select the GPUImage.xcodeproj file. Your screen should look something like this:

Figure 7: Adding GPUImage to the Project

Now drag the GPUImage.xcodeproj file into the Xcode Project Navigator. If you did this successfully, you should see something like the following:

Figure 8: GPUImage in Xcode

With the project added successfully, you'll need to add GPUImage as a dependency in your app's build settings. Select "PhotoFX" from the project navigator, select the "PhotoFX" target, and then go to the "Build Phases" tab. Expand the "Target Dependencies" drop down and then click the "+" icon. Select "GPUImage" from the list that appears. Take a look at the following image to get a feel for how this is done:

Figure 9: GPUImage Dependency

Now you need to drag the libGPUImage.a file (found within Xcode's Project Navigator at GPUImage.xcodeproj > Products) to the "Link Binary with Libraries" drop down. With this completed, you should see something like the following:

Figure 10: GPUImage Framework

While you're focused on the "Link Binary With Libraries" dropdown, go ahead and add the following required frameworks by clicking the "+" button in the bottom left corner:

  • CoreMedia
  • CoreVideo
  • OpenGLES
  • AVFoundation
  • QuartzCore

Almost done! The next step is to select the PhotoFX project and go to "Build Settings". Search for "Header Search Paths" (you may need to select the "All" button instead of "Basic" in order for this option to appear), and then double-click to add Submodules/GPUImage/framework in the popup dialog that will appear. Click the checkbox next to the entry in order to indicate that this path should be searched recursively. If you did this correctly, you should be looking at something like the following:

Figure 11: Adding GPUImage Framework

The final step is to return to ViewController.m and add the following line at the top:

#import "ViewController.h"
#import "GPUImage.h"

You should now be able to compile and run the project without issue. Having trouble? Leave a comment below.


Step 8: Display a List of Filters

GPUImage comes with an impressive number of filters for use within your applications. For this tutorial, I've selected the following sample to get our feet wet:

  • GPUImageGrayscaleFilter
  • GPUImageSepiaFilter
  • GPUImageSketchFilter
  • GPUImagePixellateFilter
  • GPUImageColorInvertFilter
  • GPUImageToonFilter
  • GPUImagePinchDistortionFilter

To create your own list or simply see all the filters GPUImage has to offer, check out the official documentation on GitHub.

In order to present the above list of filters to the user, we'll be using a simple UIActionSheet. Implement the applyImageFilter: method as follows:

- (IBAction)applyImageFilter:(id)sender
{
    UIActionSheet *filterActionSheet = [[UIActionSheet alloc] initWithTitle:@"Select Filter"
                                                                   delegate:self
                                                          cancelButtonTitle:@"Cancel"
                                                     destructiveButtonTitle:nil
                                                          otherButtonTitles:@"Grayscale", @"Sepia", @"Sketch", @"Pixellate", @"Color Invert", @"Toon", @"Pinch Distort", @"None", nil];

    [filterActionSheet showFromBarButtonItem:sender animated:YES];
}

Step 9: Implement Filter Selection

In order to respond to the filter selection, we'll have to implement the UIActionSheetDelegate. Go to ViewController.h and declare that the class will conform to this delegate as follows:

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController <UINavigationControllerDelegate, UIImagePickerControllerDelegate, UIActionSheetDelegate>

@end

Now jump back to ViewController.m and add the following method:

- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex
{
    GPUImageFilter *selectedFilter;
    
    switch (buttonIndex) {
        case 0:
            selectedFilter = [[GPUImageGrayscaleFilter alloc] init];
            break;
        case 1:
            selectedFilter = [[GPUImageSepiaFilter alloc] init];
            break;
        case 2:
            selectedFilter = [[GPUImageSketchFilter alloc] init];
            break;
        case 3:
            selectedFilter = [[GPUImagePixellateFilter alloc] init];
            break;
        case 4:
            selectedFilter = [[GPUImageColorInvertFilter alloc] init];
            break;
        case 5:
            selectedFilter = [[GPUImageToonFilter alloc] init];
            break;
        case 6:
            selectedFilter = [[GPUImagePinchDistortionFilter alloc] init];
            break;
        case 7:
            selectedFilter = [[GPUImageFilter alloc] init];
            break;
        default:
            break;
    }
    
    UIImage *filteredImage = [selectedFilter imageByFilteringImage:originalImage];
    [self.selectedImageView setImage:filteredImage];
}

Bam! Your app should now work as desired. As you can see from the above, applying filters to an existing image with GPUImage couldn't be simpler. You simply need to instantiate a GPUImageFilter and then call the imageByFilteringImage:originalImage method.


Step 10: Add an App Icon

This app just needs one last thing: a good dock icon. Thanks to our sister-site Psdtuts+, I was able to find just what I was looking for:


The above is simply a 57x57 (non-retina) and 114x114 (retina) pixel crop from the final effect taught in How to Draw a Leica Camera in Photoshop by Mohammad Jeprie.

In order to get these into your app, you just have to drag them into the Xcode Project Navigator.


Wrap Up

This tutorial has just barely scratched the surface of what is possible with GPUImage. If you've enjoyed this tutorial or think you'll benefit from the power of GPUImage in the future, find @bradlarson and thank him for creating such an awesome open source project.


More GPUImage Content?

Do you want to see more content on GPUImage and image processing? If so, let me know! You can either leave your feedback in the comments section below (preferred) or just send me a message on Twitter (@markhammonds).

UPDATE: I've now posted a second tutorial that details how to use the GPUImage camera and display photos in a gallery.
Enhancing a Photo App with GPUImage and iCarousel.

Related Posts