Chapter     11

Image Recipes

Now that the majority of the global population uses smartphones with cameras, most photos are taken with smartphones instead of point–and-shoot cameras. For this reason, images are always available and play a central role in how users utilize their smartphones. Fortunately, you have several different methods by which to create, utilize, manipulate, and display images. In addition, you can add filters to images, allowing for drastic alteration of the display with very little code. By understanding these inherent functionalities and techniques in iOS, you can more easily implement stronger, more powerful, and more informative applications.

Recipe 11-1: Using Image Views

The easiest way to display an image in your application is to use the UIImageView class, which is a view-based container for displaying images and image animations. In this recipe, you create a simple app that displays an image chosen by the user. Later, you’ll build on top of it to take full advantage of the image-processing power of iOS.

To enhance the functionality of your application, you will specifically design it for the iPad and then make use of the UISplitViewController. Create a new project and select the Master-Detail Application template. On the next screen, after entering the project name as “Image Recipes,” ensure the application’s device-family is set to “iPad,” as shown in Figure 11-1.

9781430259596_Fig11-01.jpg

Figure 11-1. Configuring an iPad project

After you create your application, Xcode generates a project with a UISplitViewController set up with master and detail view controllers. If your simulator or device is in portrait mode, you will see only the view of the detail view controller; however, if you rotate to landscape mode you will get a nice mix of both views. You will see a storyboard scene in your main.storyboard file. If you simulate the app, the generic view will resemble Figure 11-2.

9781430259596_Fig11-02.jpg

Figure 11-2. An empty UISplitViewController

If you have looked at the MasterViewController files, you might have noticed there is a lot of added boilerplate functionality for adding and removing rows. For now you can just ignore this boilerplate. We won’t be touching the MasterViewController for this recipe.

Now you can configure the detail view controller to include some content. Select the detail view controller from the main.storyboard file and use Interface Builder to create the user interface. Add a label (or reuse the one created by default by the template), an image view, and two buttons. The label text should be “Select an image to be displayed,” and the text for the buttons should be “Select Image” and “Clear Image.” Arrange these objects as shown in Figure 11-3. Also, change the background color of the image view to black by selecting the image view and changing the background value from the attributes inspector.

9781430259596_Fig11-03.jpg

Figure 11-3. A simulated view of your configured user interface

Select the image view and open the attributes inspector. In the view section, change the Mode attribute from “Scale to Fill” to “Aspect Fill.” This makes the image view scale its content so that it fills the image view’s bounds while preserving the proportions of the image. This usually means that a part of the image is drawn outside the frame of the image view. To prevent this, you should also select the Drawing option “Clip Subviews.” Figure 11-4 shows these settings.

9781430259596_Fig11-04.jpg

Figure 11-4. Configuring an image view to fill with maintained proportions and clip to its bounds

Create the following outlets:

  • detailDescriptionLabel
  • imageView

Create the following actions:

  • selectImage
  • clearImage

Configure your application to display a UIPopoverController containing a UIImagePickerController to allow users to select an image from their iPads. To do this, you need your detail view controller to conform to several extra protocols: UIImagePickerControllerDelegate, UINavigationControllerDelegat e, and UIPopoverControllerDelegate. You also need to add a property to reference the UIPopoverController. To incorporate these changes, add the code in bold shown in Listing 11-1 to the DetailViewController.h file.

Listing 11-1.  The starting DetailViewController.h implementation

//
//  DetailViewController.h
//  Recipe 11-1 Using Image Views
//

#import <UIKit/UIKit.h>

@interface DetailViewController : UIViewController < UISplitViewControllerDelegate,
    UIImagePickerControllerDelegate,UINavigationControllerDelegate,
    UIPopoverControllerDelegate>

@property (strong, nonatomic) id detailItem;

@property (weak, nonatomic) IBOutlet UILabel *detailDescriptionLabel;
@property (weak, nonatomic) IBOutlet UIImageView *imageView;

@property (strong, nonatomic) UIPopoverController *pop;

- (IBAction)selectImage:(id)sender;
- (IBAction)clearImage:(id)sender;

@end

Now you can implement the selectImage: method to present an interface to select an image to display. Modify this method in the DetailViewController.m file, as shown in Listing 11-2. Take note that we changed the input type to the sender to “UIButton *” instead of “id.” This is because we use this input in the presentPopoverFromRect: call and an id type doesn’t have a “frame” property, which would throw an error.

Listing 11-2.  Configuring an image view to fill with maintained proportions and clip to its bounds

-(void)selectImage:( UIButton *)sender
{
    UIImagePickerController *picker = [[UIImagePickerController alloc] init];
    if ([UIImagePickerController
            isSourceTypeAvailable:UIImagePickerControllerSourceTypePhotoLibrary])
    {
        picker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
        picker.delegate = self;

        self.pop = [[UIPopoverController alloc] initWithContentViewController:picker];
        self.pop.delegate = self;
        [self.pop presentPopoverFromRect:sender.frame inView:self.view
            permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
    }
}

You can then implement your UIImagePickerController delegate methods to properly handle the selection of an image or cancellation. Listing 11-3 shows the first one, which handles the cancellation.

Listing 11-3.  Implementing the imagePickerControllerDidCancel: delegate method

-(void)imagePickerControllerDidCancel:(UIImagePickerController *)picker
{
    [self.pop dismissPopoverAnimated:YES];
}

Listing 11-4 shows the second UIImagePickerController delegate method for handling image selection.

Listing 11-4.  Implementing the imagePickerController:didFinishPickingMediaWIthInfo:

-(void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
    UIImage *image = [info valueForKey:@"UIImagePickerControllerOriginalImage"];
    self.imageView.image = image;
    

    [self.pop dismissPopoverAnimated:YES];
}

As you can see from Listing 11-4, you configure the image view to display the selected image by using the image property.

Finally, you can implement the clearImage: action method shown in Listing 11-5 to allow your view to be reset.

Listing 11-5.  Implementing the clearImage: method

- (IBAction)clearImage:(id)sender
{
    self.imageView.image = nil;
}

At this point, you can run your application, select an image, and display it in a UIImageView, as shown in Figure 11-5. Because you set the view mode to aspect fill and chose the “Clip Subviews” option, the image will size to the smaller dimension and clip the rest.

9781430259596_Fig11-05.jpg

Figure 11-5. Your application displaying an image in a UIImageView

Tip   If you are testing the application on the iOS simulator, you will need to have some images to display. The easiest way to save images to the simulator’s photos library is to drag and drop them onto the simulator window. This brings up Safari, where you can click and hold the mouse on the image. You then are given an option to save the image, and after this you can use it in your application.

Recipe 11-2: Scaling Images

Often the images that your applications deal with come from a variety of sources and usually do not fit your specific view’s display perfectly. To adjust for this, you can implement methods to scale and resize your images.

With an image view, scaling and resizing are easy. For example, in the preceding recipe you used Aspect Fill in combination with Clip Subviews to scale proportionally and still fill the entire image view. This results in a clipped but nice-looking image. Another option is to use the Aspect Fit mode, which also scales the image with retained aspect ratio but displays the entire image. This, of course, might cause unused space in your image view, which might resemble a widescreen movie on a non-widescreen television. If you don’t care about image aspect, you can use the default mode “Scale to Fill.” Use these and other options by simply changing the Mode attribute of the image view from the attributes inspector.

However, sometimes you might like to scale the actual image programmatically, such as if you want to save the resulting image or just optimize the display by providing an already scaled image. In this recipe, we’ll show you how to scale an image using code. You will implement two different methods corresponding to the Scale to Fill and the Aspect Fit modes of an image view.

You will build on the previous recipe, but for now use the master view’s table view, which contains three functions: Select Image, Resize Image, and Scale Image. Because these functions operate directly on a UIImage, you need to turn off the inherent scaling function of the image view. Do this by changing its Mode attribute to “Center” instead of Aspect Fill from the attributes inspector.

Before moving on, remove some of the boilerplate code that was provided with the Master-Detail Application template mentioned in the last recipe. This boilerplate code allows for creation and deletion of rows, which is functionality we won’t be using. In the MasterViewController.m file you can remove the following items from the viewDidLoad method.

self.navigationItem.leftBarButtonItem = self.editButtonItem;

    UIBarButtonItem *addButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(insertNewObject:)];
    self.navigationItem.rightBarButtonItem = addButton;

You also can remove the following methods from the MasterViewController:

insertNewObject:
tableView: canEditRowAtIndexPath:
tableView: commitEditingStyle:forRowAtIndexPath:
tableView: canMoveRowAtIndexPath:

Now that you’ve cleaned up your code, add a couple of outlets to the two buttons in the DetailViewController class. The outlets should be named “selectImageButton” and “clearImageButton,” respectively.

Next, create a method to configure the user interface of your detail view controller. Listing 11-6 shows the method declaration in the DetailViewController.h file.

Listing 11-6.  Adding the method declaration for handling user interface configuration

//
//  DetailViewController.h
//  Recipe 11-2 Scaling Images
//

#import <UIKit/UIKit.h>

@interface DetailViewController : UIViewController <UISplitViewControllerDelegate,
                                                   UIImagePickerControllerDelegate,
                                                    UINavigationControllerDelegate,
                                                    UIPopoverControllerDelegate>

@property (strong, nonatomic) id detailItem;

@property (weak, nonatomic) IBOutlet UILabel *detailDescriptionLabel;
@property (weak, nonatomic) IBOutlet UIImageView *imageView;
@property (weak, nonatomic) IBOutlet UIButton *selectImageButton;
@property (weak, nonatomic) IBOutlet UIButton *clearImageButton;

@property (strong, nonatomic) UIPopoverController *pop;

- (IBAction)selectImage:(id)sender;
- (IBAction)clearImage:(id)sender;

- (void)configureDetailsWithImage:(UIImage *)image label:(NSString *)label showsButtons:(BOOL)showButton;

@end

The method declared in Listing 11-6 should be added to the DetailViewController.h file. The implementation is shown in Listing 11-7.

Listing 11-7.  Implementing the configureDetailsWithImage:label:showbuttons: method

-(void)configureDetailsWithImage:(UIImage *)image label:(NSString *)label showsButtons:(BOOL)showsButton
{
    self.imageView.image = image;
    self.detailDescriptionLabel.text = label;
    if (showsButton == NO)
    {
        self.selectImageButton.hidden = YES;
        self.clearImageButton.hidden = YES;
    }
    else if (showsButton == YES)
    {
        self.selectImageButton.hidden = NO;
        self.clearImageButton.hidden = NO;
    }
}

Because we will be communicating between the master view controller and the detail view controller, it’s best to set up a delegate so the detail view controller can notify the master view controller when an image change has occurred. Then the master view controller can set that image directly. To start with, implement the protocol in the DetailViewController.h file, as shown in Listing 11-8. In this modification, you first declare that you’re going to use a protocol. Next, you create a property for the delegate. Then you declare the protocol methods.

Listing 11-8.  Setting up a protocol for communication between the master and detail view controllers

//
//  DetailViewController.h
//  Recipe 11-2 Scaling Images
//

#import <UIKit/UIKit.h>

@protocol DetailViewControllerDelegateProtocol;

@interface DetailViewController : UIViewController <UISplitViewControllerDelegate,
                                                    UIImagePickerControllerDelegate,
                                                    UINavigationControllerDelegate,
                                                    UIPopoverControllerDelegate>
@property (strong, nonatomic) id detailItem;

@property (nonatomic, weak) id <DetailViewControllerDelegateProtocol> delegate;

@property (weak, nonatomic) IBOutlet UILabel *detailDescriptionLabel;
@property (weak, nonatomic) IBOutlet UIImageView *imageView;

@property (nonatomic, strong) IBOutlet UIButton *selectImageButton;
@property (nonatomic, strong) IBOutlet UIButton *clearImageButton;

@property (strong, nonatomic) UIPopoverController *pop;

- (IBAction)selectImage:(id)sender;
- (IBAction)clearImage:(id)sender;

- (void)configureDetailsWithImage:(UIImage *)image label:(NSString *)label showsButtons:(BOOL)showsButton;

@end

@protocol DetailViewControllerDelegateProtocol <NSObject>

- (void)detailViewController:(DetailViewController *)controller didSelectImage:(UIImage *)image;
- (void)detailViewControllerDidClearImage:(DetailViewController *)controller;

@end

Now, add a property to your master view controller class to store the chosen image, as shown in Listing 11-9.

Listing 11-9.  Adding a UIImage property to the MasterViewController.h file

//
//  MasterViewController.h
//  Recipe 11-2 Scaling Images
//

#import <UIKit/UIKit.h>

@interface MasterViewController : UITableViewController

@property (strong, nonatomic) DetailViewController *detailViewController;
@property (strong, nonatomic) UIImage *mainImage;

@end

Back in your detail view controller, you will need to update the imagePickerController:didFinishPickingMediaWithInfo: delegate method to update the image of the master view controller. Do this by calling the protocol method, as shown in Listing 11-10.

Listing 11-10.  Modifying the imagePickerController: to call the new protocol

-(void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
    UIImage *image = [info valueForKey:@"UIImagePickerControllerOriginalImage"];
          self.imageView.image = image;
    [self.pop dismissPopoverAnimated:YES];

    [self.delegate detailViewController:self didSelectImage:image];
}

You also need to adjust the implementation of the clearImage: action method. Again, we are calling a protocol method, as you can see from Listing 11-11.

Listing 11-11.  Modifying the clearImage: method to take advantage of the detailViewControllerDidClearImage: protocol method

- (IBAction)clearImage:(id)sender
{
    self.imageView.image = nil;
    [self.delegate detailViewControllerDidClearImage:self];
}

In your master view controller, you should declare that you are conforming to DetailViewControllerDelegateProtocol. So modify the MasterViewController.h file to accommodate this change, as shown in Listing 11-12.

Listing 11-12.  Declaring the DetailViewControllerDelegateProtocol

//
//  MasterViewController.h
//  Recipe 11-2 Scaling Images
//

#import <UIKit/UIKit.h>
#import "DetailViewController.h"

@interface MasterViewController : UITableViewController <DetailViewControllerDelegateProtocol>

@property (strong, nonatomic) DetailViewController *detailViewController;
@property (strong, nonatomic) UIImage *mainImage;

@end

You also should set the delegate for the master view controller in the ViewDidLoad method, as shown in Listing 11-13.

Listing 11-13.  Setting the delegate in the viewDidLoad method of the MasterViewController.m file

//
//  MasterViewController.m
//  Recipe 11-2 Scaling Images
//
//...
- (void)viewDidLoad
{
    [super viewDidLoad];
    self.detailViewController = (DetailViewController *)[[self.splitViewController.viewControllers lastObject] topViewController];
    self.detailViewController.delegate = self;
}

Next, create two different methods to resize an image. Add the two class methods shown in Listing 11-14 to your MasterViewController.m file. You do not need to declare these in the MasterViewController.h file because the only class that needs to use them is the MasterViewController class.

Listing 11-14.  Implementing the scaleImage and aspectScaleImage: toSize methods

//
//  MasterViewController.m
//  Recipe 11-2 Scaling Images
//

#import "MasterViewController.h"

@interface MasterViewController ()
//...
-(UIImage *)scaleImage:(UIImage *)image toSize:(CGSize)size
{
    UIGraphicsBeginImageContext(size);
    [image drawInRect:CGRectMake(0, 0, size.width, size.height)];
    UIImage *scaledImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return scaledImage;
}

-(UIImage *)aspectScaleImage:(UIImage *)image toSize:(CGSize)size
{
    if (image.size.height < image.size.width)
    {
        float ratio = size.height / image.size.height;
        CGSize newSize = CGSizeMake(image.size.width * ratio, size.height);
        UIGraphicsBeginImageContext(newSize);
        [image drawInRect:CGRectMake(0, 0, newSize.width, newSize.height)];
    }
    else {
        float ratio = size.width / image.size.width;
        CGSize newSize = CGSizeMake(size.width, image.size.height * ratio);
        UIGraphicsBeginImageContext(newSize);
        [image drawInRect:CGRectMake(0, 0, newSize.width, newSize.height)];
    }
    UIImage *aspectScaledImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return aspectScaledImage;
}

//...

In Listing 11-14 the first method simply recreates the image within a specified size, ignoring the aspect ratio of the image. The second method, with a little calculation, determines the best way to resize the image to both preserve the aspect ratio and fit inside the given size.

To make sure your view controllers are properly interacting, add the DetailViewControllerDelegateProtocol methods shown in Listing 11-15, which set the image or clear the image and reload the table.

Listing 11-15.  Implementing the DetailViewControllerDelegateProtocol methods

- (void)detailViewController:(DetailViewController *)controller didSelectImage:(UIImage *)image
{
    self.mainImage = image;
    [self.tableView reloadData];
}

- (void)detailViewControllerDidClearImage:(DetailViewController *)controller
{
    self.mainImage = nil;
    [self.tableView reloadData];
}

To finish configuring the behavior of the master view controller, you’ll need to fill in the data source data for the table view.

First, set the number of sections to one. You will need to replace the boilerplate code that was provided in this method as part of the Master-Detail View Controller template. The new implementation is shown in Listing 11-16.

Listing 11-16.  Modifying the numberOfSectionsInTableView: delegate method

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return 1;
}

Next, update the number of rows depending on whether or not you have an image to work with. If there is no image, then you should display only the text “Selected Image,” which will require only one row. Again, you will replace the boilerplate code with the new code shown in Listing 11-17.

Listing 11-17.  Implementing the tableView:numberOfRowsInSection: delegate method

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    if (self.mainImage == nil)
        return 1;
    else
        return 3;
}

Next, set the cell labels depending on whether the current cell is 0, 1, or 2. This implementation is shown in Listing 11-18.

Listing 11-18.  Implementing the tableView: cellForRowAtIndexPath: method

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell" forIndexPath:indexPath];

    if (indexPath.row == 0)
        cell.textLabel.text = NSLocalizedString(@"Selected Image", @"Detail");
    else if (indexPath.row == 1)
        cell.textLabel.text = NSLocalizedString(@"Resized Image", @"Detail");
    else if (indexPath.row == 2)
        cell.textLabel.text = NSLocalizedString(@"Scaled Image", @"Detail");
    return cell;
}

Lastly, if a cell is selected, you should determine which cell it was and then set the image and title for the DetailViewController using the configureDetailsWithImage:showsButtons: protocol method. Listing 11-19 shows the new method with the boilerplate code replaced.

Listing 11-19.  The new implementation of the tableView:didSelectRowAtIndextPath: delegate method

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    if (self.mainImage != nil)
    {
        UIImage *image;
        NSString *label;
        BOOL showsButtons = NO;
        if (indexPath.row == 0)
        {
            image = self.mainImage;
            label = @"Select an Image to Display";
            showsButtons = YES;
        }
        else if (indexPath.row == 1)
        {
            image = [self scaleImage:self.mainImage
                                            toSize:self.detailViewController.imageView.frame.size];
            label = @"Chosen Image Resized";
        }
        else if (indexPath.row == 2)
        {
            image = [self aspectScaleImage:self.mainImage
                                                toSize:self.detailViewController.imageView.frame.size];
            label = @"Chosen Image Scaled";
        }
        [self.detailViewController configureDetailsWithImage:image label:label
                                                showsButtons:showsButtons];
    }
}

You are done and can now build and run the application. This time, when you select an image you’ll see that it doesn’t scale and (assuming the image is larger than the image view) will be clipped, such as the one in Figure 11-6.

9781430259596_Fig11-06.jpg

Figure 11-6. An image of a geyser in Yellowstone National Park

Now if you select the Resized Image cell in the master view you will see the same picture, this time run through the scaleImage:toSize: method you created. The image has been scaled, without considering its original proportions, to the same size as the image view. Figure 11-7 shows an example of this.

9781430259596_Fig11-07.jpg

Figure 11-7. An image scaled without keeping its proportions

The issue with this option, however, is that the picture has become slightly deformed. This might not be quite obvious with this particular image, but when dealing with images of people the distortion of physical features is quite obvious and unsightly. To solve this, use the Aspect-Scaled image option.

When you select the Scaled Image cell you’ll see the effect of your aspectScaleImage:toSize: method. This method has created a UIImage of a size that fits within the image view but with maintained original proportions. This results in an image without any size distortions, but will scale the image to either the width or height. Depending on the image, the background of the image view might be showing. The image we chose scaled to the width and cut off the top and bottom, as shown in Figure 11-8.

9781430259596_Fig11-08.jpg

Figure 11-8. An alternative method of scaling to remove distortion

That’s it for this recipe! To review, you have covered two simple methods for resizing a UIImage, each with their own advantages and issues.

  1. Your first method simply resized the image to a given size, regardless of aspect ratio. While this kept your image from obstructing any other elements, it resulted in a fair bit of distortion.
  2. By using a little math, you scaled down your image to a size while manually maintaining the aspect ratio. Because this might leave a blank space around the image, you can apply a black background. This is a useful technique to use when displaying large images in an application that has no control over the original image size. It allows for any image to comfortably fit in a given space, yet it maintains a visually appealing black background no matter the case.

Recipe 11-3: Manipulating Images with Filters

The Core Image framework, a group of classes that was introduced in iOS 5.0, allows you to creatively apply a great variety of “filters” to images. Although we won’t be using any here, iOS 7 comes packed with many more available filters that can be implemented similarly, as we’ll show here.

In this recipe, you’ll apply two kinds of filters to an image, the Hue filter and the Straightening filter. The former changes the hue of the image, while the latter rotates an image to straighten it out.

You will build on the project you created in Recipes 11-1 and 11-2, adding functions to apply the filters.

Start by linking the CoreImage.framework library to your project (see Chapter 1 for a description of how to do this), and then import its API in the MasterViewController.h file. You also need a mutable array property to hold the filtered images you will display. Listing 11-20 shows the MasterViewController.h file with these changes marked in boldface.

Listing 11-20. Importing the Core Image framework and creating a property in the MasterViewController.h file

//
//  MasterViewController.h
//  Recipe 11-3 Manipulating Images with Filters
//

#import <UIKit/UIKit.h>
#import <CoreImage/CoreImage.h>
#import "DetailViewController.h"

@interface MasterViewController : UITableViewController <DetailViewControllerDelegateProtocol>
@property (strong, nonatomic) DetailViewController *detailViewController;
@property (strong, nonatomic) UIImage *mainImage;
@property (strong, nonatomic) NSMutableArray *filteredImages;

@end

Implement lazy initialization of the filteredImages property by adding the custom getter to MasterViewController.m, as shown in Listing 11-21.

Listing 11-21.  Implementing the filteredImages property initializer

-(NSMutableArray *)filteredImages
{
    if (!_filteredImages)
    {
        _filteredImages = [[NSMutableArray alloc] initWithCapacity:3];
    }
    return _filteredImages;
}

Now, modify your detailViewController delegate methods again to include handling of this array. These changes are shown in Listing 11-22.

Listing 11-22.  Modifying the detailViewController delegate methods to handle filtered images

- (void)detailViewController:(DetailViewController *)controller didSelectImage:(UIImage *)image
{
    self.mainImage = image;
    [self populateImageViewWithImage:image];
    [self.tableView reloadData];
}

- (void)detailViewControllerDidClearImage:(DetailViewController *)controller
{
    self.mainImage = nil;
    [self.filteredImages removeAllObjects];
    [self.tableView reloadData];
}

The populateFilteredImagesWithImage: method, which contains most of your Core Image framework code, is implemented as shown in Listing 11-23. This method, which creates a CIImage, requires the following steps:

  1. Obtain a CIImage of the intended input image.
  2. Create a filter using a specific name key. The name defines which filter will be applied as well as its various parameters that can be used.
  3. Reset all parameters of the filter to defaults for good measure.
  4. Set the input image to the filter using the inputImage key.
  5. Set any additional values related to the filter to customize output.
  6. Retrieve the output CIImage using the outputImage key.
  7. Create a UIImage from the CIImage by use of a CIContext. Because the CIContext returns a CGImage, which memory is not managed by ARC, you also need to release it using CGImageRelease().

Listing 11-23.  Implementing the populateImageViewWithImage: method

-(void)populateImageViewWithImage:(UIImage *)image
{
    CIImage *main = [[CIImage alloc] initWithImage:image];
    
    CIFilter *hueAdjust = [CIFilter filterWithName:@"CIHueAdjust"];
    [hueAdjust setDefaults];
    [hueAdjust setValue:main forKey:@"inputImage"];
    [hueAdjust setValue:[NSNumber numberWithFloat: 3.14/2.0f]
                 forKey:@"inputAngle"];
    CIImage *outputHueAdjust = [hueAdjust valueForKey:@"outputImage"];
    CIContext *context = [CIContext contextWithOptions:nil];
    CGImageRef cgImage1 = [context createCGImage:outputHueAdjust
        fromRect:outputHueAdjust.extent];
    UIImage *outputImage1 = [UIImage imageWithCGImage:cgImage1];
    CGImageRelease(cgImage1);
    [self.filteredImages addObject:outputImage1];
    
    CIFilter *strFilter = [CIFilter filterWithName:@"CIStraightenFilter"];
    [strFilter setDefaults];
    [strFilter setValue:main forKey:@"inputImage"];
    [strFilter setValue:[NSNumber numberWithFloat:3.14f] forKey:@"inputAngle"];
    CIImage *outputStr = [strFilter valueForKey:@"outputImage"];
    CGImageRef cgImage2 = [context createCGImage:outputStr fromRect:outputStr.extent];
    UIImage *outputImage2 = [UIImage imageWithCGImage:cgImage2];
    CGImageRelease(cgImage2);
    [self.filteredImages addObject:outputImage2];
}

Note   There are a large number of filters that can be applied to images, all with their own specific parameters and keys. To find details for a specific filter, see the Apple documentation at http://developer.apple.com/library/ios/#DOCUMENTATION/GraphicsImaging/Reference/CoreImageFilterReference/Reference/reference.html.

Next, add the filter functions to the table view. Start by making a small change to the tableView:numberOfRowsInSection: delegate method, as shown in Listing 11-24.

Listing 11-24.  Modifying the tableView:numberOfRowsInSection: method to account for new filters

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    if (self.mainImage == nil)
        return 1;
    else
        return 5;
}

Also, update tableView:cellForRowAtIndexPath : to configure the cells for the new rows, as shown in Listing 11-25.

Listing 11-25.  Modifying the detailViewController delegate methods to handle filtered images

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Cell";
    
    UITableViewCell *cell =
        [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil)
    {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
            reuseIdentifier:CellIdentifier];
    }

    if (indexPath.row == 0)
        cell.textLabel.text = NSLocalizedString(@"Selected Image", @"Detail");
    else if (indexPath.row == 1)
        cell.textLabel.text = NSLocalizedString(@"Resized Image", @"Detail");
    else if (indexPath.row == 2)
        cell.textLabel.text = NSLocalizedString(@"Scaled Image", @"Detail");
    else if (indexPath.row == 3)
        cell.textLabel.text = NSLocalizedString(@"Hue Adjust", @"Detail");
    else if (indexPath.row == 4)
        cell.textLabel.text = NSLocalizedString(@"Straighten Filter", @"Detail");
    return cell;
}

Modify the tableView:didSelectRowAtIndexPath: method, as shown in Listing 11-26, to add the new filters.

Listing 11-26.  Modifying the tableView:didSelectRowAtIndexPath: to add the new filters

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    if (self.mainImage != nil)
    {
        UIImage *image;
        NSString *label;
        BOOL showsButtons = NO;
        if (indexPath.row == 0)
        {
            image = self.mainImage;
            label = @"Select an Image to Display";
            showsButtons = YES;
        }
        else if (indexPath.row == 1)
        {
            image = [self scaleImage:self.mainImage
                toSize:self.detailViewController.imageView.frame.size];
            label = @"Chosen Image Resized";
        }
        else if (indexPath.row == 2)
        {
            image = [self aspectScaleImage:self.mainImage
                toSize:self.detailViewController.imageView.frame.size];
            label = @"Chosen Image Scaled";
        }
        else if (indexPath.row == 3)
        {
            image = [self.filteredImages objectAtIndex:0];
            image = [self aspectScaleImage:image toSize:self.detailViewController.imageView.frame.size];
            label = @"Hue Adjustment";
        }
        else if (indexPath.row == 4)
        {
            image = [self.filteredImages objectAtIndex:1];
            image = [self aspectScaleImage:image toSize:self.detailViewController.imageView.frame.size];
            label = @"Straightening Filter";
        }                   [self.detailViewController configureDetailsWithImage:image label:label
            showsButtons:showsButtons];
    }
}

As you can see in Listing 11-26, you’re reusing the aspectScaleImage:toSize: method you created in the previous recipe to scale the filtered images so that they will fit nicely within the image view.

When running your application now, you can see the outputs of the two types of filters. Shown in Figure 11-9 is an example of the straightening filter. As you might remember from the code, it specified an angle of pi (3.14), which means a 180-degree rotation and an upside-down image.

9781430259596_Fig11-09.jpg

Figure 11-9. The straightening filter has rotated an image 180 degrees

Combining Filters

It’s easy to apply multiple filters to an image. You just combine them in a series by specifying the output image of one filter as the input image of another. As an example, you’ll add a function that applies both the hue filter and the straightening filter to the selected image.

Add the code in 11-27 to the populateImagesWithImage: method to create a combination filter.

Listing 11-27.  Modifying the populateImageViewWithImage: method to create a combination filter

-(void)populateImageViewWithImage:(UIImage *)image
{
    // ...

    CIFilter *seriesFilter = [CIFilter filterWithName:@"CIStraightenFilter"];
    [seriesFilter setDefaults];
    [seriesFilter setValue:outputHueAdjust forKey:@"inputImage"];
    [seriesFilter setValue:[NSNumber numberWithFloat:3.14/2.0f] forKey:@"inputAngle"];
    CIImage *outputSeries = [seriesFilter valueForKey:@"outputImage"];
    CGImageRef cgImage3 = [context createCGImage:outputSeries
        fromRect:outputSeries.extent];
    UIImage *outputImage3 = [UIImage imageWithCGImage:cgImage3];
    [self.filteredImages addObject:outputImage3];
}

Update the tableView:numberOfRowsInSection: method to show a sixth cell, as shown in Listing 11-28.

Listing 11-28.  Updating the tableView:numberOfRowsInSection: method to create six table rows

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    if (self.mainImage == nil)
        return 1;
    else
        return 6;
}

Likewise, add a sixth case to your tableView:cellForRowAtIndexPath: method to display the name of this sixth cell, as shown in Listing 11-29.

Listing 11-29.  Modifying the tableView:cellForRowAtIndexPath: to set the sixth label title

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    // ...

    if (indexPath.row == 0)
        cell.textLabel.text = NSLocalizedString(@"Selected Image", @"Detail");
    else if (indexPath.row == 1)
        cell.textLabel.text = NSLocalizedString(@"Resized Image", @"Detail");
    else if (indexPath.row == 2)
        cell.textLabel.text = NSLocalizedString(@"Scaled Image", @"Detail");
    else if (indexPath.row == 3)
        cell.textLabel.text = NSLocalizedString(@"Hue Adjust", @"Detail");
    else if (indexPath.row == 4)
        cell.textLabel.text = NSLocalizedString(@"Straighten Filter", @"Detail");
    else if (indexPath.row == 5)
        cell.textLabel.text = NSLocalizedString(@"Series Filter", @"Detail");
    return cell;
}

Finally, add another case to the tableView:didSelectRowAtIndexPath: to initialize the detail view controller with the combined filter image, as shown in Listing 11-30.

Listing 11-30.  Adding a new case to the tableView:didSelectRowAtIndexPath: method

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    if (self.mainImage != nil)
    {
        UIImage *image;
        NSString *label;
        BOOL showsButtons = NO;
        if (indexPath.row == 0)
        {
            image = self.mainImage;
            label = @"Select an Image to Display";
            showsButtons = YES;
        }

// ...

     else if (indexPath.row == 5)
        {
            image = [self.filteredImages objectAtIndex:2];
            image = [self aspectScaleImage:image toSize:self.detailViewController.imageView.frame.size];
            label = @"Series Filter";
        }
[self.detailViewController configureDetailsWithImage:image label:label showsButtons:showsButtons];
    }
}

When you test the application, your new double filter combines the effects of your previous two, resulting in a hue-adjusted and rotated image, this time with a 90-degree rotation, as shown in Figure 11-10.

9781430259596_Fig11-10.jpg

Figure 11-10. An image with both a hue filter and a 90-degree straightening filter applied

Note   The majority of the processing work, when dealing with the Core Image framework, comes from when the UIImage is created from the CIImage using the CIContext. The creation of a CIImage itself is a very fast operation. In this application, we have chosen to create all the filtered images at once to allow for quick navigation between each display. This is why, on selecting an image, your simulator may take a couple seconds to actually display the image and refresh. If you were building this application for release, you would want to convey in some way to the user that work is being done through a UIActivityIndicatorView or UIProgressView.

Creating Thumbnail Images for the Table View

As a final touch, we’re going to return to the resizing topic of the previous recipe. You’ll implement another aspect-scaling method that does what the Aspect Fill mode of an image view does; that is, it scales with maintained proportions but ensures that the entire area is being covered. This method is more suitable for the creation of thumbnail images, which you’re now going to implement for the filter functions in the table view.

Start by adding the new scaling method shown in Listing 11-31 to the master view controller.

Listing 11-31.  Implementing the aspectFillImage:toSize: method

-(UIImage *)aspectFillImage:(UIImage *)image toSize:(CGSize)size
{
    UIGraphicsBeginImageContext(size);
    if (image.size.height< image.size.width)
    {
        float ratio = size.height/image.size.height;
        [image drawInRect:CGRectMake(0, 0, image.size.width*ratio, size.height)];
    }
    else
    {
        float ratio = size.width/image.size.width;
        [image drawInRect:CGRectMake(0, 0, size.width, image.size.height*ratio)];
    }
    UIImage *aspectScaledImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return aspectScaledImage;
}

Now you just need to modify the tableView:cellForRowAtIndexPath: again to include the selection of an image for the cell’s imageView, as shown in Listing 11-32.

Listing 11-32.  Adding a thumbnail to the cells in the tableView:cellForRowAtIndexPath: method

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Cell";
    
    UITableViewCell *cell =
        [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
            reuseIdentifier:CellIdentifier];
    }

    if (indexPath.row == 0)
        cell.textLabel.text = NSLocalizedString(@"Selected Image", @"Detail");
    else if (indexPath.row == 1)
        cell.textLabel.text = NSLocalizedString(@"Resized Image", @"Detail");
    else if (indexPath.row == 2)
        cell.textLabel.text = NSLocalizedString(@"Scaled Image", @"Detail");
    else if (indexPath.row == 3)
    {
        CGSize thumbnailSize = CGSizeMake(120, 75);
        UIImage *displayImage = [self.filteredImages objectAtIndex:0];
        UIImage *thumbnailImage = [self aspectFillImage:displayImage
            toSize:thumbnailSize];
        cell.imageView.image = thumbnailImage;
        cell.textLabel.text = NSLocalizedString(@"Hue Adjust", @"Detail");
    }
    else if (indexPath.row == 4)
    {
        CGSize thumbnailSize = CGSizeMake(120, 75);
        UIImage *displayImage = [self.filteredImages objectAtIndex:1];
        UIImage *thumbnailImage = [self aspectFillImage:displayImage
            toSize:thumbnailSize];
        cell.imageView.image = thumbnailImage;
        cell.textLabel.text = NSLocalizedString(@"Straighten Filter", @"Detail");
    }
    else if (indexPath.row == 5)
    {
        CGSize thumbnailSize = CGSizeMake(120, 75);
        UIImage *displayImage = [self.filteredImages objectAtIndex:2];
        UIImage *thumbnailImage = [self aspectFillImage:displayImage
            toSize:thumbnailSize];
        cell.imageView.image = thumbnailImage;
        cell.textLabel.text = NSLocalizedString(@"Series Filter", @"Detail");
    }
    return cell;
}

When you test your application now, the cells for the hue, straightening, and series filters have a scaled thumbnail version of the larger image they refer to. Figure 11-11 shows an example of this.

9781430259596_Fig11-11.jpg

Figure 11-11. An application with thumbnails in its table view

Recipe 11-4: Detecting Features

Along with the flexible use of filters, the Core Image framework has also brought the possibility of feature detection. With it, you can search images for key components such as faces.

In this recipe, you implement a facial detection application. Create a new single-view project for the iPhone device family. Once your project is created, add the Core Image framework to your project, just as in the preceding recipe.

Set the background color of the main view to black and add two image views and a button so that the user interface resembles Figure 11-12.

9781430259596_Fig11-12.jpg

Figure 11-12. A simple user interface for face recognition

Create outlets for each of the three elements:

  • mainImageView
  • findFaceButton
  • faceImageView

Also, create an action with the name “findFace” for the button.

Next, find an image to be displayed in your application and add it to your project. You can do this by dragging the file from the finder into the resources file of the project navigator. To properly test this application, try to find an image with an easily visible face.

Now you can build your viewDidLoad method to configure the image views as well as set the initial image to your main image view. Be sure to change the name of the image (testimage.jpg in the following code) to your own filename. These changes are shown in Listing 11-33.

Listing 11-33.  Filling out the viewDidLoad method

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    self.mainImageView.contentMode = UIViewContentModeScaleAspectFit;
    self.faceImageView.contentMode = UIViewContentModeScaleAspectFit;
    
    UIImage *image = [UIImage imageNamed:@"testimage.jpg"];
    if (image != nil)
    {
        self.mainImageView.image = image;
    }
    else
    {
        [self.findFaceButton setTitle:@"No Image" forState:UIControlStateNormal];
        self.findFaceButton.enabled = NO;
        self.findFaceButton.alpha = 0.6;
    }
}

Now you can implement the findFace: action method to do the feature detection. You can use this method to determine the location of any faces in the given image, create a UIImage from the last face found, and then display it in the face image view.

Listing 11-34  uses the following steps to build the findFace implementation:

  1. Acquire a CIImage object from your initial UIImage.
  2. Create a CIContext with which to analyze images.
  3. Create an instance of CIDetector with type and options parameters.

    The type parameter specifies the specific feature to identify. Currently, the only possible value for this is CIDetectorTypeFace, which allows you to specifically look for faces.

    The options parameter allows you to specify the accuracy with which you want to look for features. Low accuracy will be faster, but high accuracy will be more precise.

  4. Create an array of all the features found in your image. Because you specified the CIDetectorTypeFace type, these objects will all be instances of the CIFaceFeature class.
  5. Create a CIImage using the imageByCroppingToRect: method with the original image as well as the bounds specified by the last CIFaceFeature found in the image. These bounds specify the CGRect in which the face exists.
  6. Create a UIImage out of your CIImage (done exactly as in the previous recipe) and then display it in your UIImageView.

Listing 11-34.  Full implementation of the findFace: action method

- (IBAction)findFace:(id)sender
{
    UIImage *image = self.mainImageView.image;
    CIImage *coreImage = [[CIImage alloc] initWithImage:image];
    CIContext *context = [CIContext contextWithOptions:nil];
    CIDetector *detector =
        [CIDetector detectorOfType:@"CIDetectorTypeFace"context:context
            options:[NSDictionary dictionaryWithObjectsAndKeys:
                @"CIDetectorAccuracyHigh", @"CIDetectorAccuracy", nil]];
    NSArray *features = [detector featuresInImage:coreImage];
    
    if ([features count] >0)
    {
        CIImage *faceImage =
            [coreImage imageByCroppingToRect:[[features lastObject] bounds]];
        UIImage *face = [UIImage imageWithCGImage:[context createCGImage:faceImage
            fromRect:faceImage.extent]];
        self.faceImageView.image = face;
        
        [self.findFaceButton setTitle:[NSString stringWithFormat:@"%i Face(s) Found",
            [features count]] forState:UIControlStateNormal];
        self.findFaceButton.enabled = NO;
        self.findFaceButton.alpha = 0.6;
    }
    else
    {
        [self.findFaceButton setTitle:@"No Faces Found"forState:UIControlStateNormal];
        self.findFaceButton.enabled = NO;
        self.findFaceButton.alpha = 0.6;
    }
}

When running your application, you can detect any faces inside your image, which will be displayed in your lower UIImageView, as in Figure 11-13.

9781430259596_Fig11-13.jpg

Figure 11-13. An application detecting and cropping a face from an image

Summary

Images create our world. From the simplest of picture books that children love to read to the massive amounts of visual data transmitted around the Internet, pictures and images have become one of the key foundations of modern culture. iOS offers great tools to create, handle, manipulate, and display images in your applications. With these simple APIs, you can create more interesting and useful apps in less time.

In this chapter, you have learned how to draw use image views, resize images with maintained proportions, use filters to manipulate images, and detect faces in a photo. We hope this has given you the headstart you need to take full advantage of the powerful graphics features of iOS.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset