Chapter 15

Image Recipes

Often times a developer is faced with an all-too common problem: Too much information to display with not enough space to show it. For this, you turn to images. Pictures and graphics allow you to convey a variety of information far beyond simple text, combining emotion, information, and style. In iOS, you have multiple different methods with which to create, utilize, manipulate, and display images. New to iOS 5.0 even is the ability to apply filters to images, allowing for drastic alteration of display with very little actual code. By understanding these inherent functionalities and techniques in iOS, you are able to more easily implement stronger, more powerful, and more informative applications.

Recipe 15–1: Drawing Simple Shapes

From the youngest age, every person is taught the most basic of images, dealing with shapes, colors, and pictures. In iOS too, you can start off with the basics of drawing simple shapes in a view. Many concepts dealt with in these first implementations will end up returning in more complex image-based recipes.

Start by creating a new project called “ShapesAndSizes”. Select the Single View Application template, as in Figure 15–1, in order to build the simplest, ready-to-run pre-configured application, and make sure the device family is set to “iPhone”. The box marked “Use Automatic Reference Counting” should also be checked. Figure 15–1.

Image

Figure 15–1. Creating a single view application

Next, before building your user interface, you will start by implementing your custom drawing code in a subclass of UIView.

Start by adding QuartzCore.framework and CoreGraphics.framework to your project by navigating to your project’s Target settings. Under the Build Phases tab, in the Link Binary With Libraries section, click the + button. Find the Quartz Core and Core Graphics frameworks in the window resembling Figure 15–2, and add them both separately.

Image

Figure 15–2. Adding the Core Graphics and Quartz Core frameworks

Next, create new file called “MyView” using the “Objective-C class” template. When you enter the name, make sure the “Subclass of” field is set to “UIView”.

In the header file of this new class, add the required two import statements for your extra frameworks.

#import <QuartzCore/QuartzCore.h>
#import <CoreGraphics/CoreGraphics.h>

Now, to provide a simple view displaying a drawn rectangle and square, you will implement the -drawRect: method as such:

- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();

CGRect drawingRect = CGRectMake(0.0, 20.0f, 100.0f, 180.0f);
const CGFloat *rectColorComponents = CGColorGetComponents([[UIColor greenColor] CGColor]);
CGContextSetFillColor(context, rectColorComponents);
CGContextFillRect(context, drawingRect);

CGRect ellipseRect = CGRectMake(140.0f, 200.0f, 75.0f, 50.0f);
const CGFloat *ellipseColorComponenets = CGColorGetComponents([[UIColor blueColor] CGColor]);
CGContextSetFillColor(context, ellipseColorComponenets);
CGContextFillEllipseInRect(context, ellipseRect);
}

This method makes use of the following steps to draw basic shapes:

  1. Obtain a reference to the current “context,” represented by a CGContextRef.
  2. Define a CGRect in which to draw.
  3. Acquire color components for the desired color to fill each shape with.
  4. Set the Fill Color.
  5. Fill the specified shape using the CGContextFillRect() and CGContextFillEllipseInRect() functions.

In order to actually display this in your pre-configured view, you must add an instance of this class to your user interface. This can be done programmatically or through Interface Builder (the latter of which you will demonstrate).

In your view controller’s XIB file, drag a UIView out from the utilities pane into your view, placing it with 20-point margins on each edge, as shown in Figure 15–3.

Image

Figure 15–3. Building your XIB file with a UIView

While your UIView is selected, navigate to the third tab in the right-hand panel. Under the Custom Class section, make sure the Class field is changed from “UIView” to “MyView”, in order to specify the custom UIView subclass to be used, resembling Figure 15–4.

Image

Figure 15–4. Configuring the class of your UIView to MyView

Upon running this application, you will see the output of your drawing commands converted into a visual display, resulting in your simulated view in Figure 15–5.

Image

Figure 15–5. Your simple application drawing a rectangle and an ellipse

Thankfully, you are not at all limited to drawing only rectangles and ellipses! You are able to use a few other functions to draw custom objects by creating “paths.” These paths consist of a movement from point to point, connected by lines, in order to draw a custom shape. Add the following code to your -drawRect: method to draw a gray parallelogram.

CGContextBeginPatsh(context);
CGContextMoveToPoint(context, 0.0f, 0.0f);
CGContextAddLineToPoint(context, 100.0f, 0.0f);
CGContextAddLineToPoint(context, 140.0f, 100.0f);
CGContextAddLineToPoint(context, 40.0f, 100.0f);
CGContextClosePath(context);
CGContextSetGrayFillColor(context, 0.4f, 0.85f);
CGContextSetGrayStrokeColor(context, 0.0, 0.0);
CGContextFillPath(context);

It is important to note that when creating these paths, you do not have to add a final line back to your last point. By calling the CGContextClosePath() function, your shape will automatically be closed between its ending point and starting point.

When you run your application now, you will see your view with a new parallelogram created from your path, as in Figure 15–6.

Image

Figure 15–6. Your application with a shape created from a custom path

Programming Screenshots

Just as you are able to put things into a CGContext, you are also quite easily able to take them out. By making use of the UIGraphicsGetImageFromCurrentImageContext() function, you can extract an image from whatever is currently drawn.

In the MyView class, add a UIImage property, making sure to synthesize it.

@property (nonatomic, strong) UIImage *image;

Now at the end of your -drawRect: method, append the following code to draw the image if it is non-nil.

if (self.image)
    {
        CGRect imageRect = CGRectMake(200.0f, 50.0f, 100.0f, 300.0f);
        [image drawInRect:imageRect];
    }

Add the same two import statements as earlier to your view controller, as well as a third for your MyView class.

#import <QuartzCore/QuartzCore.h>
#import <CoreGraphics/CoreGraphics.h>
#import "MyView.h"

You will also need to reference your MyView object, so connect the one you already added into the user interface to your view controller’s header file with the property customView.

@property (strong, nonatomic) IBOutlet MyView *customView;

Add a UIButton labeled “Snapshot” to the bottom of your user interface. Connect it to an IBAction method called -snapShotPressed:.

- (IBAction)snapshotPressed:(id)sender;

This method will then be implemented like so:

-(IBAction)snapshotPressed:(id)sender
{
//Acquire image of current layer
UIGraphicsBeginImageContext(self.view.bounds.size);
CGContextRef context = UIGraphicsGetCurrentContext();
    [self.view.layer renderInContext:context];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

self.customView.image = image;
    [self.customView setNeedsDisplay];
}

This method makes use of the -setNeedsDisplay method in the UIView class to instruct a UIView to re-call its -drawRect method in order to incorporate any recent changes.

Now, after testing the application again, upon pressing the Snapshot button, you should see a smaller screenshot of your own screen appear on the right side of the view, as in Figure 15–7.

Image

Figure 15–7. Your application having taken a screenshot, then scaled it into the view

While most of the functionalities you’ve built into this application have been pretty basic, you will see a variety of them come back in more complex forms in the later, more complex image recipes.

Recipe 15–2: Using UIImageViews

The absolutely simplest way of displaying an image in your application is by use of the UIImageView class. You will start off by creating a simple application that can display an image chosen by the user, and then build on top of it to take full advantage of iOS’s image processing power.

In order 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 “ImageRecipes”, make sure the application’s device-family is set to “iPad”, and that the box marked “Use Automatic Reference Counting” is checked. No other boxes should be marked, so that your dialog resembles Figure 15–8.

Image

Figure 15–8. Configuring project settings

Upon creating your application, you will be given a nicely configured project with a UISplitViewController already 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, but if you rotate to landscape then you will get a nice mix of both views. You will not see both views when working in Interface Builder, but if you simulate the app, the generic view will resemble Figure 15–9.

Image

Figure 15–9. An empty UISplitViewController

Now, you will configure the detail view controller to include a bit more content. Add a UIImageView, as well as two UIButtons to your XIB’s interface so that your simulated application will look like Figure 15–10.

Image

Figure 15–10. A simulated view of your configured user interface

In order to make sure that both your buttons and image view will be correctly centered in your final display, first set them to the center of your XIB’s view as you normally would. Then, you will set the “autosizing” options for each element. Open up the utilities pane on the right side of your screen, and navigate to the fifth tab where you can set an element’s frame, shown in Figure 15–11.

Image

Figure 15–11. Configuring autosizing to customize resizing behavior

Make sure that for each element, the Autosizing box, with its various red lines and arrows, is configured exactly as shown in Figure 15–11. This will specify that each element will, if the view changes, maintain its relative position to the top of the view and stretch horizontally to maintain its general position.

Connect each element to your header file using the property names selectImageButton, clearImageButton, and imageViewContent. Each UIButton will have its respective action, -selectImagePressed: and -clearImagePressed:.

You will be configuring your application to display a UIPopoverController containing a UIImagePickerController in order to allow users to select an image from their phone. To do this, you will need your detail view controller to conform to several extra protocols: UIImagePickerControllerDelegate, UINavigationControllerDelegate and UIPopoverControllerDelegate.

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

You will also create two extra properties in order to store your selected image, as well as a reference to your UIPopoverController that you will use. Make sure both of these are properly synthesized, as well as properly nullified in the -viewDidUnload method.

@property (strong, nonatomic) UIPopoverController *pop;
@property (strong, nonatomic) UIImage *selectedImage;

At this point, with your basic user interface configured, your overall detail view controller’s header file should resemble the following.

#import <UIKit/UIKit.h>

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

@property (strong, nonatomic) id detailItem;

@property (strong, nonatomic) IBOutlet UILabel *detailDescriptionLabel;
@property (strong, nonatomic) IBOutlet UIButton *selectImageButton;
@property (strong, nonatomic) IBOutlet UIButton *clearImageButton;
@property (strong, nonatomic) IBOutlet UIImageView *imageViewContent;
-(IBAction)selectImagePressed:(id)sender;
-(IBAction)clearImagePressed:(id)sender;

@property (strong, nonatomic) UIPopoverController *pop;
@property (strong, nonatomic) UIImage *selectedImage;

@end

Now you can implement your -selectImagePressed: method to present an interface to select a saved image to display.

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

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

You can then implement your UIImagePickerControllerDelegate protocol methods to properly handle the selection of an image or cancellation.

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

    [self.pop dismissPopoverAnimated:YES];
}

As you can see, you configure your selected image to be displayed in your UIImageView by using the image property. You also set the contentMode property to UIViewContentModeScaleAspectFill, in order to ensure that the bounds of your UIImageView are always filled by at least most of the image.

Finally, you can implement a simple method for -clearImagePressed: to allow your view to be reset:

- (IBAction)clearImagePressed:(id)sender
{
self.selectedImage = nil;
self.imageViewContent.image = nil;
}

At this point, you can run your application, select an image, and display it in a UIImageView, as in Figure 15–12!

Image

Figure 15–12. Your application displaying an image in a UIImageView

If you are testing this application on the iOS simulator, you will need to actually have some images saved to display. The easiest way to save images to the simulator’s photo library is to use the Safari app on the simulator, navigate to your desired image, and then click and hold the mouse on the image. You will be given an option to save the image, and after this you can use it in your application.

Recipe 15–3: Scaling Images

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

You will configure your application to overall have a single image selected in your first detail view controller. Upon selecting different rows in your master view controller’s table, you will change the display in your detail view controller to a variety of images. For now, you will configure your views to display your images as they are resized differently.

You will start off by creating a method to fully adjust the content of your detail view controller. Add the following handler to your detail view controller’s header file.

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

You will implement this method like so:

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

You will add a reference to the master view controller to your detail view controller to allow your chosen image to be passed back. Add the following import statement (or your own class name for the master view controller):

#import "MainMasterViewController.h"

Add the master view controller property, and make sure to synthesize it.

@property (strong, nonatomic) MainMasterViewController *masterViewController;

Add the following line to your -viewDidUnload method.

[self setMasterViewController:nil];

Next, you will add a property to your master view controller class to store the chosen image. Make sure to properly synthesize and handle it as usual.

@property (strong, nonatomic) UIImage *mainImage;

Back in your detail view controller, you will update your -imagePickerController:didFinishPickingMediaWithInfo: method to also send the chosen image back to the master view controller.

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

//New Line
self.masterViewController.mainImage = image;

self.imageViewContent.image = image;
self.imageViewContent.contentMode = UIViewContentModeScaleAspectFill;   

    [self.pop dismissPopoverAnimated:YES];
}

You will also adjust the implementation of your -clearImagePressed: method accordingly.

- (IBAction)clearImagePressed:(id)sender
{
self.selectedImage = nil;
self.imageViewContent.image = nil;
self.masterViewController.mainImage = nil;
}

In your master view controller, you will later implement code to utilize your images in the actual table, so you will implement a custom setter method for the mainImage property to reload the UITableView’s data.

-(void)setMainImage:(UIImage *)image
{
mainImage = image;
NSIndexPath *currentIndexPath = self.tableView.indexPathForSelectedRow;
    [self.tableView reloadData];
    [self.tableView selectRowAtIndexPath:currentIndexPath animated:YES scrollPosition:UITableViewScrollPositionTop];
}

Next, you will create two different methods to resize an image. Add the following two handlers to your detail view controller’s header file.

+ (UIImage *)scaleImage:(UIImage *)image toSize:(CGSize)size;
+ (UIImage *)aspectScaleImage:(UIImage *)image toSize:(CGSize)size;

The first method will simply recreate the image within a specified size, completely ignoring the aspect ratio of the image.

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

The second will, with a little calculation, determine the best way to resize the image in order to both preserve the aspect ratio and fit inside the given size.

+ (UIImage *)aspectScaleImage:(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;
}

To make sure your view controllers are properly interacting, add the following two lines to your application delegate’s -application:didFinishLaunchingWithOptions: after both view controllers have been created.

detailViewController.masterViewController = masterViewController;
masterViewController.detailViewController = detailViewController;

Now, to finish configuring the behavior of the master view controller, modify the following delegate methods:

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if (self.mainImage == nil)
    {
return 1;
    }
else
    {
return 3;
    }
}
- (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");
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath
*)indexPath
{
if (self.mainImage != nil)
    {
        UIImage *image;
        NSString *label;
BOOL showsButtons;
if (indexPath.row == 0)
        {
            image = self.mainImage;
            label = @"Select an Image to Display";
            showsButtons = YES;
        }
else if (indexPath.row == 1)
        {
            image = [MainDetailViewController scaleImage:self.mainImage toSize:self.detailViewController.imageViewContent.frame.size];
            label = @"Chosen Image Resized";
            showsButtons = NO;
        }
else if (indexPath.row == 2)
        {
            image = [MainDetailViewController aspectScaleImage:self.mainImage toSize:self.detailViewController.imageViewContent.frame.size];
            label = @"Chosen Image Scaled";
            showsButtons = NO;
        }
        [self.detailViewController configureDetailsWithImage:image label:label showsButtons:showsButtons];
    }
}

Your original image, as shown previously, ended up expanding beyond the frame of the UIImageView in order to maintain its aspect ratio. Since this can cause some issues in blocking other elements, you would most likely want to use one of your resized images. Upon running this application, you can see the quite vast differences in your options in presenting differently sized images.

Figure 15–13 displays an example of the same image used previously, but resized simply to fit within your image view’s frame.

Image

Figure 15–13. Your application displaying a resized image without scaling

As you can see, you have managed to fit the entire image into a smaller space, ensuring that no other view elements are obstructed by your image. The issue with this option, however, is that your image’s dimensions have been changed, resulting in a slightly deformed picture. This may not be quite obvious with this particular image, but when dealing with images of people, the distortion of physical features will become quite obvious and unsightly. To solve this, you make use of the “aspect-scaled” image, as displayed in Figure 15–14.

Image

Figure 15–14. An alternative method of scaling to remove distortion, resulting in clipping

Compared to the previous and original images, you can see that this image is clearly of higher quality, as it lacks any distortion of size. Unfortunately, since you have chosen to have your image “fill” the UIImageView, you end up with an image cropped from your original. This kind of resizing works incredibly well if you need to create small thumbnails from larger images, as the issue of cropping the image becomes fairly negligible with more miniscule sizes.

Obviously, if you are not dealing with thumbnails, but instead with presenting large pictures, then the above cropping is significantly less than ideal. You can hone your image scaling method to allow your image to “fit” the image view, and simply give the rest of the view a black background. With this change, your previous “thumbnail creation method” will no longer be functional, so be sure to save a new copy of your project if you want to keep a copy of the previous setup.

First, adjust your configuration method to the following code in order to change the contentMode property of your UIImageView.

-(void)configureDetailsWithImage:(UIImage *)image label:(NSString *)label
showsButtons:(BOOL)showsButton
{
self.selectedImage = image;
self.imageViewContent.image = image;
self.detailDescriptionLabel.text = label;
/////BEGIN NEW CODE
if ([label isEqualToString:@"Chosen Image Scaled"])
    {
self.imageViewContent.contentMode = UIViewContentModeScaleAspectFit;
self.imageViewContent.backgroundColor = [UIColor blackColor];
    }
else
    {
self.imageViewContent.contentMode = UIViewContentModeScaleAspectFill;
    }
/////END NEW CODE
if (showsButton == NO)
    {
self.selectImageButton.enabled = NO;
self.selectImageButton.hidden = YES;
self.clearImageButton.enabled = NO;
self.clearImageButton.hidden = YES;
    }
else if (showsButton == YES)
    {
self.selectImageButton.enabled = YES;
self.selectImageButton.hidden = NO;
self.clearImageButton.enabled = YES;
self.clearImageButton.hidden = NO;
    }
}

Your image scaling method will require a slightly different method of calculation to correctly scale your images this way. The following code shows the new implementation. Pay careful attention to the fact that you have changed the CGSize with which you are creating your image context.

+ (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;
}

With the newest changes, your images can now appear scaled down in size without being clipped! By adding the black background to your UIImageView, you provide a simple backdrop for your images to go on, allowing a very general, all-encompassing functionality for displaying resized images, as in Figure 15–15.

Image

Figure 15–15. Fitting your scaled image helps avoid clipping, but introduces background visibility of the UIImageView.

In Review

You have covered three simple yet different 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 ended up giving you a fair bit of distortion.
  2. By using a little math, you were able to scale down your image to a size while manually maintaining the aspect ratio. The issue with this approach was that it tended to crop out parts of your image in order to fill its given space. If you need to create small thumbnails of images to be displayed together, this is a decent way to implement it.
  3. After reconfiguring your aspect resizing method, you were able to display an aspect-locked, smaller image to fit entirely within your UIImageView. Since this would, of course, leave blank space around the image, you applied a black background. This is an especially 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 be comfortably fit in a given space, yet maintains a visually appealing black background no matter the case.

Recipe 15–4: Manipulating Images with Filters

The Core Image framework, a group of classes entirely new in iOS 5.0, allows you to creatively apply a great variety of different types of “filters” to images.

Start by importing the CoreImage.framework library into your project. Navigate to your application’s Build Phases tab, and then click the + button under the Link Binary With Libraries area. In the dialog resembling Figure 15–16, find the Core Image framework, and add it.

Image

Figure 15–16. Adding the Core Image framework to your project

Add an import statement for this framework to your main view controller’s header file.

#import <CoreImage/CoreImage.h>

Next add an NSMutableArray property to the same controller in order to store the filtered images you will display. Make sure to properly synthesize and handle it as usual.

@property (strong, nonatomic) NSMutableArray *images;

This property will also need a custom getter to ensure it is properly initialized.

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

Now, you will modify your -setMainImage: method again to include proper handling of this array.

-(void)setMainImage:(UIImage *)image
{
    [self.images removeAllObjects];
if (image != nil)
    {
        [self.images addObject:image];
        [self populateImagesWithImage:image];
    }

mainImage = image;
NSIndexPath *currentIndexPath = self.tableView.indexPathForSelectedRow;
    [self.tableView reloadData];
    [self.tableView selectRowAtIndexPath:currentIndexPath animated:YES scrollPosition:UITableViewScrollPositionTop];
}

This method, -populateImagesWithImage:, which will contain most of your Core Image code, will be implemented as follows. Remember to place the method declaration in your header file as well.

-(void)populateImagesWithImage:(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];
UIImage *outputImage1 = [UIImage imageWithCGImage:[context createCGImage:outputHueAdjust
fromRect:outputHueAdjust.extent]];
    [self.images 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"];
UIImage *outputImage2 = [UIImage imageWithCGImage:[context createCGImage:outputStr fromRect:outputStr.extent]];
    [self.images addObject:outputImage2];
}

As you can see from this method, creating 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.

Here, you have chosen to apply two different filters: a “Hue Adjustment”, and a “Straighten Filter”. The former will change the hue of your image, while the latter is used to rotate an image to straighten it out.

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

Now, you can specify these newly created filtered images to your view controller by modifying your -tableView:didSelectRowAtIndexPath: method again.

(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath
*)indexPath
{
if (self.mainImage != nil)
    {
        UIImage *image;
        NSString *label;
BOOL showsButtons;
if (indexPath.row == 0)
        {
            image = self.mainImage;
CGSize contentSize = self.detailViewController.imageViewContent.frame.size;
            image = [MainDetailViewController aspectScaleImage:image toSize:contentSize];
            label = @"Select an Image to Display";
            showsButtons = YES;
        }
else
        {
            image = [self.images objectAtIndex:indexPath.row];
CGSize contentSize = self.detailViewController.imageViewContent.frame.size;
            image = [MainDetailViewController aspectScaleImage:image toSize:contentSize];
            showsButtons = NO;

if (indexPath.row == 1)
            {
                label = @"Hue Adjustment";
            }
else if (indexPath.row == 2)
            {
                label = @"Straightening Filter";
            }
        }
        [self.detailViewController configureDetailsWithImage:image label:label showsButtons:showsButtons];
    }
}

As you can see, you have updated all of your displays to, instead of showing differently resized examples of the same image, show the original image with its different filters. You have adopted your third method of resizing images for all these to be displayed.

You will also update your -tableView:cellForRowAtIndexPath: to include your newest implementation.

-(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(@"Hue Adjust", @"Detail");
else if (indexPath.row == 2)
        cell.textLabel.text = NSLocalizedString(@"Straighten Filter", @"Detail");
return cell;
}

Back in your detail view controller, you must make a few extra changes to fully configure your new functionalities.

You will adjust your configuration method to a more general case, now that you are formatting all of your display images similarly.

-(void)configureDetailsWithImage:(UIImage *)image label:(NSString *)label showsButtons:(BOOL)showsButton
{
self.selectedImage = image;
self.imageViewContent.image = image;
self.detailDescriptionLabel.text = label;
self.imageViewContent.contentMode = UIViewContentModeScaleAspectFit;
self.imageViewContent.backgroundColor = [UIColor blackColor];
if (showsButton == NO)
    {
self.selectImageButton.enabled = NO;
self.selectImageButton.hidden = YES;
self.clearImageButton.enabled = NO;
self.clearImageButton.hidden = YES;
    }
else if (showsButton == YES)
    {
self.selectImageButton.enabled = YES;
self.selectImageButton.hidden = NO;
self.clearImageButton.enabled = YES;
self.clearImageButton.hidden = NO;
    }
}

Finally, your UIImagePickerControllerDelegate protocol method will also require some adjustment. The new code will resemble the following:

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

self.masterViewController.mainImage = image;
CGSize contentSize = self.imageViewContent.frame.size;
self.imageViewContent.image = [MainDetailViewController aspectScaleImage:image toSize:contentSize];
self.imageViewContent.contentMode = UIViewContentModeScaleAspectFit;   
self.imageViewContent.backgroundColor = [UIColor blackColor];

    [self.pop dismissPopoverAnimated:YES];
}

Upon running your application now, you will be able to see the outputs of your two types of filters used.

Shown in Figure 15–17 is the example of your hue adjustment. Your chosen input angle of 3.14/2.0 will drastically change the hues of your image.

Image

Figure 15–17. Your new application applying a hue adjustment filter

In the same application, your second filter, with its specified input angle of 3.14, will rotate your given image by 180 degrees, as is done in Figure 15–18.

Image

Figure 15–18. Applying a straightening filter to rotate an image

You are also quite easily able to combine multiple filters in series by simply specifying the output image of one filter as the input image of another. Add the following code to the -populateImagesWithImage: method to create a combination filter.

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"];
UIImage *outputImage3 = [UIImage imageWithCGImage:[context createCGImage:outputSeries
fromRect:outputSeries.extent]];
[self.images addObject:outputImage3];

Update your -tableView:numberOfRowsInSection: method to show a fourth cell.

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

Add a fourth case to your -tableView:cellForRowAtIndexPath: method to display the name of this fourth cell.

else if (indexPath.row == 3)
    cell.textLabel.text = NSLocalizedString(@"Series Filter", @"Detail");

Finally, add another case, directly after the others used to set label for rows 1 and 2, to your -tableView:didSelectRowAtIndexPath: to correctly set the UILabel.

else if (indexPath.row == 3)
{
    label = @"Series Filter";
}

Now, upon testing the application, your new double-filter will combine the effects of your previous two, resulting in a hue-adjusted and rotated image, as in Figure 15–19.

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 your application, you have chosen to create all of your filtered images at once in order to allow for quick navigation between each display. This is why, upon selecting an image, your simulator may take a couple seconds to actually display your images 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.

Image

Figure 15–19. A series combination of hue adjustment and straightening filters

Now, you will make use of your earlier resizing functionality to create a thumbnail for each filtered image, so that it can be displayed in your master’s UITableView.

Since you ended up taking your thumbnail-resizing method out of your previous recipe, here is the method again that you will use.

+ (UIImage *)scaleImageThumbnail:(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;
}

Make sure also to place this method’s handler in the detail view controller’s header file, so that your master view controller is able to call it.

+ (UIImage *)scaleImageThumbnail:(UIImage *)image toSize:(CGSize)size;

Now, you just need to modify your -tableView:cellForRowAtIndexPath: again to include the selection of an image for the cell’s imageView. In entirety, the method should resemble the following code.

- (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];
    }

/////NEW THUMBNAIL CODE
if ([self.images count] >0)
    {
CGSize thumbnailSize = CGSizeMake(120, 75);
UIImage *displayImage = [self.images objectAtIndex:indexPath.row];
if (displayImage)
        {
UIImage *thumbnailImage = [MainDetailViewController scaleImageThumbnail:displayImage toSize:thumbnailSize];
            cell.imageView.image = thumbnailImage;
        }
    }
/////END OF THUMBNAIL CODE

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

When you test your application now, your master view controller’s cells will each have a scaled thumbnail version of the larger image they refer to, as in Figure 15–20.

Image

Figure 15–20. Your application with scaled (clipping) thumbnails

Recipe 15–5: Detecting Features

Along with the incredibly flexible use of filters, the Core Image framework has also brought to iOS 5.0 the possibility of feature detection, allowing you to effectively “search” images for key components, such as faces.

We will develop a new, smaller project to implement your new facial detection application, rather than continuing on with your previous recipe. Create a new project for the iPhone device family, making use of the Single View Application template, shown in Figure 15–21.

Image

Figure 15–21. Creating a single view application

Once your project is created, add the Core Image framework to your project, just as in the previous recipe.

Add two instances of UIImageView and a UIButton to your view, so as to resemble Figure 15–22.

Image

Figure 15–22. Your view controller’s XIB setup

Connect each of the elements to your view controller. Your UIButton should have the property name findFaceButton, and perform the action -findFacePressed:. Make your upper UIImageView imageViewMain, and your lower one imageViewAlt.

#import <UIKit/UIKit.h>

@interface MainViewController : UIViewController

@property (strong, nonatomic) IBOutlet UIImageView *imageViewMain;
@property (strong, nonatomic) IBOutlet UIImageView *imageViewAlt;
@property (strong, nonatomic) IBOutlet UIButton *findFaceButton;
- (IBAction)findFacePressed:(id)sender;

@end

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 your project’s navigation pane. When the dialog for adding files appears, make sure the box marked “Copy items into destination group’s folder (if needed)” is checked, as it is in Figure 15–23. In order to properly test this application, try to find an image with an easily visible face.

Image

Figure 15–23. Pop-up dialog for adding files into your project

Now, you can build your -viewDidLoad method to configure your UIImageView elements, as well as set the initial image to your main image view. Make sure to change the name of the image (“testImage.JPG” in the following code) to your own file name.

- (void)viewDidLoad
{
    [super viewDidLoad];

self.imageViewMain.backgroundColor = [UIColor blackColor];
self.imageViewMain.contentMode = UIViewContentModeScaleAspectFit;
self.imageViewAlt.backgroundColor = [UIColor blackColor];
self.imageViewAlt.contentMode = UIViewContentModeScaleAspectFit;

UIImage *image = [UIImage imageNamed:@"testImage.JPG"];
if (image != nil)
    {
self.imageViewMain.image = image;
    }
else
    {
        [self.findFaceButton setTitle:@"No Image" forState:UIControlStateNormal];
self.findFaceButton.enabled = NO;
self.findFaceButton.alpha = 0.6;
    }
}

Finally you can implement your -findFacePressed: method to do your feature detection. You will have this method determine the location of any faces in your given image, create a UIImage from the last face found, and then display it in your alternate image view.

-(IBAction)findFacePressed:(id)sender
{
UIImage *image = self.imageViewMain.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.imageViewAlt.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;
    }
}

This method contains the following steps:

  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.
    1. 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.
    2. 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. Since 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.

Upon running your application, you will be able to detect any faces inside your images, the latter of which will be displayed in your lower UIImageView, as in Figure 15–24.

Image

Figure 15–24. Your 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 through Twitter, e-mail, Facebook, Tumblr, and every other web service, pictures and images have certainly become one of the key foundations of modern culture. As such, by learning to create, handle, manipulate, and display images in your applications, you are able to acquire a significantly greater connection with your users, imparting a more powerful emotional response and interacting with more flexibility and control than ever. From building colored shapes to displaying photographs, to even the newest iOS 5.0 additions of manipulating images, you are able to create more interesting and useful applications. The Core Image framework even furthers this ability with the addition of image filters, to create wildly different images, and facial detection software, to provide more in-depth information from your application. At this point of technological development, it seems quite fair to say that a picture being worth a thousand words is a vast understatement.

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

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