Viewing, Editing, and Saving Images in an Image View
The IKImageView
class displays a single image in a frame and optionally can allow a user to drag an image to it. It is similar to the NSImageView
class, except that IKImageView
supports any image file format that Quartz 2D and the Image I/O framework support, including Quartz images (CGImageRef
), images that have metadata, and images whose location is specified as an NSURL
object. In addition, the IKImageView
class supports zoom, rotation, selection, cropping, and other image editing operations.
This chapter shows the user interface for an image view, the image edit panel (IKImageEditPanel
class), and the save options accessory view (pane) (IKSaveOptions
class). It provides instructions for creating an image editing application incrementally. You’ll see how to:
View images in an image view
Set up an image view to use the Image Edit panel
Set up a pane for saving images in various formats
Add zoom controls
Add image editing tools
Add a Save panel that uses image saving options
The Image View User Interface
The image view (IKImageView
) in the user interface looks similar to any view that contains an image. The image shown in Figure 2-1 could just as easily be in an NSView
object, an NSImageView
object, or a Carbon window.
An IKImageView
object has an important characteristic that no other image container has. When the user double-clicks an image in an image view, an Image Edit panel appears, as shown in Figure 2-2. The panel allows the user to make adjustments to the image, apply a number of effects, and view image metadata and properties. The Adjust pane provides the most commonly used image adjustments.
The Effects pane (shown in Figure 2-3) allows the user, with a single click, to preview and apply sharpening, blurring, or color filters.
The Details pane displays all the metadata for an image, as well as a comprehensive list of image properties, as shown in Figure 2-4. If the image information is more than can fit in the pane, the Image Kit supplies scroll bars automatically.
There are very few tasks you need to perform for the Image Edit panel to appear. You need to set the appropriate image view state, set up a shared instance of the Image Edit panel (IKImageEditPanel
), and set the data source. The Image Kit framework takes care of the rest—showing the panel, switching panes, responding to changes made by the user, fetching and displaying image information, and closing the Image Edit panel.
The image view supports the use of tools for moving zooming, cropping, rotating, and applying annotations to an image. You need to provide a user interface for zoom operation and tool selection. At a minimum, you need to set up a menu similar to what’s shown in Figure 2-5.
Ideally, you would also provide controls in the image view window, as shown in Figure 2-6. The figure shows two segmented controls (created in Interface Builder using NSSegmentedControl
controls), each of which uses custom icons that you provide.
The image view keeps track of the zoom factor, the rotation angle, and whether the image can be edited. You need to respond to the user’s selection by setting the tool mode. The Image Kit framework takes care of animating move, zoom, and rotation operations; showing a rotation circle (see Figure 2-7); displaying crop and selection rectangles; and displaying an annotation area. You need to implement copying data to the pasteboard, cropping the image, and handling the text for the annotation area.
No image viewing and editing application would be complete without the ability to save an edited image. The IKSaveOptions
class provides an accessory view for an NSSavePanel
object that allows the user to choose an image file format and to set options appropriate for that format. The user can choose from among a number of formats. The options appropriate for that format appear below the format. For example, for a TIFF format, the user can choose compression options (as shown in Figure 2-8).
Viewing an Image in an Image View
This section shows how to open and display an image in an image view. You’ll set the image view options so that the Image Edit panel opens when the user double-clicks the image. First you’ll set up the Xcode project, the project files, and the controller interface. Then you’ll create the user interface in Interface Builder. Finally, you’ll add the necessary routines to the implementation file.
Setting Up the Project, Project Files, and the Controller Interface
Follow these steps to set up the project:
Open Xcode, choose File > New Project.
Choose Cocoa Application and click Next.
Name the project
My Image Viewer
, and click Finish.Choose Project > Add to Project and add the Quartz and Quartz Core frameworks.
For details, see Using the Image Kit in Xcode.
Choose Project > Add to Project, navigate to an image to use as a default image, and click Add.
In the sheet that appears, click Add.
This image appears in the view whenever the application launches.
Choose File > New File.
Choose Objective-C Class and click Next.
Name the file
Controller.m
and keep the option to create the header file. Then click Finish.In the
Controller.h
file, import the Quartz framework by adding this statement just below the statement to import Cocoa:#import <Quartz/Quartz.h>
Add a directive for the
IKImageView
class:@class IKImageView;
Add instance variables to the Controller interface.
You need an image view and a window to contain the view. You’ll set these up later in Interface Builder.
IBOutlet IKImageView * mImageView;
IBOutlet NSWindow * mWindow;
You need to keep track of image properties and the uniform type identifier of the image in the view.
NSDictionary * mImageProperties;
NSString * mImageUTType;
Save and close the
Controller.h
file.In the
Controller.m
file, import the Application Kit classes by adding this statement.#import <AppKit/AppKit.h>
Close the
Controller.m
file.
Creating the User Interface
Set up the user interface in Interface Builder by following these steps:
Double-click the
MainMenu.nib
file (located in the Resources group) to open Interface Builder.Choose File > Synchronize With Xcode.
Double-click the Window icon in the nib document window.
In the Size inspector, set the width of the window to 800 and the height to 600.
Drag a Image View from the Library to the window and resize the view to fit the window.
In the Size inspector, the Autosizing springs should look as follows. If they don’t, set them so they do.
Drag an Object (
NSObject
) from the Library to the nib document window.Type Controller in the Name field of the Identity inspector and press Return.
Choose Controller from the Class pop-up menu.
Control-drag from the controller icon to the title bar of the window. Then click the
mWindow
outlet that appears in the connections panel.Control-drag from the controller icon to the
IKImageView
view. Then click themImageView
outlet that appears in the connections panel.Control-drag from the window icon to the controller icon. Then click the
delegate
outlet that appears in the connections panel.Save the nib file.
Adding Routines to the Implementation File
Now you’ll go back to Xcode to add code to implement the image viewer.
Open the
Controller.m
file.Add an
openImageURL:
method to take care of opening images.The easiest way of opening an image is to use the
setImageWithURL:
method. This method is best for RAW images.- (void)openImageURL: (NSURL*)url
{
[mImageView setImageWithURL: url];
[mWindow setTitleWithRepresentedFilename: [url path]];
}
An alternate implementation is to use the Quartz opaque data types
CGImageRef
andCGImageSourceRef
and their associated functions to create an image source, extract an image, get the image properties, and set the image to the image view. If you are using a TIFF file that contains multiple images, you need this implementation to display any image other than the first one. That’s becausesetImageWithURL:
displays only the first image of a multiple-image file.- (void)openImageURL: (NSURL*)url
{
CGImageRef image = NULL;
CGImageSourceRef isr = CGImageSourceCreateWithURL( (CFURLRef)url, NULL);
if (isr)
{
image = CGImageSourceCreateImageAtIndex(isr, 0, NULL);
if (image)
{
mImageProperties = (NSDictionary*)CGImageSourceCopyPropertiesAtIndex(
isr, 0, (CFDictionaryRef)mImageProperties);
}
CFRelease(isr);
}
if (image)
{
[mImageView setImage: image
imageProperties: mImageProperties];
[mWindow setTitleWithRepresentedFilename: [url path]];
CGImageRelease(image);
}
}
Add an
awakeFromNib
method.This method first creates a URL for the default image file by getting the path to the resource in the bundle and then converting the path to a URL. After opening the URL, the method sets up the image view so that the Image Edit panel can open, and the image zooms to fit the view.
Make sure that you substitute the appropriate string for “earring”, which should be the name of the default image file without its filename extension. Also, use the appropriate extension.
- (void)awakeFromNib
{
NSString * path = [[NSBundle mainBundle] pathForResource: @"earring"
ofType: @"jpg"];
NSURL * url = [NSURL fileURLWithPath: path];
[self openImageURL: url];
// customize the IKImageView...
[mImageView setDoubleClickOpensImageEditPanel: YES];
[mImageView setCurrentToolMode: IKToolModeMove];
[mImageView zoomImageToFit: self];
}
Open the
Controller.h
files and add the following method signature.- (void)openImageURL: (NSURL*)url;
Click Build and Go.
When the application launches, double-click the image to make sure that the Image Edit panel opens.
If the window opens, but an image does not appear, make sure that you’ve included the correct filename in your code.
Resize the image to make sure that it zooms to fit the size of the window.
If the image does not zoom to fit, check the connections between the window and the controller in Interface Builder.
The next section shows how to use the IKSaveOptions
class to add an accessory view to the Save panel.
Saving Images
The IKSaveOptions
class handles image and PDF saving options. After creating an NSSavePanel
object, you allocate and initialize a save options accessory view and then add the view to the Save dialog. Keep in mind that IKSaveOptions
handles the options, but it does not actually save the image or PDF. After you set up the save options accessory panel, you also need to implement a save method.
Follow these steps to add the image options accessory view to the My Image Viewer application:
Open the
Controller.h
file and add a save options instance variable.IKSaveOptions * mSaveOptions;
Add the following method signature:
- (IBAction)saveImage: (id)sender;
In the
Controller.m
file, add a method that presents a Save panel with the save options accessory view (pane).The method first creates an instance of the
NSSavePanel
class. Then it allocates a save options object and initializes it with the image properties and UT type of the image that will be saved. Next the code adds the save options view to the Save panel. It shows the Save panel as a sheet, providing a selector that is invoked when the Save panel terminates. (You’ll write that method next.)- (IBAction)saveImage: (id)sender
{
NSSavePanel * savePanel = [NSSavePanel savePanel];
mSaveOptions = [[IKSaveOptions alloc]
initWithImageProperties: mImageProperties
imageUTType: mImageUTType];
[mSaveOptions addSaveOptionsAccessoryViewToSavePanel: savePanel];
NSString * fileName = [[mWindow representedFilename] lastPathComponent];
[savePanel beginSheetForDirectory: NULL
file: fileName
modalForWindow: mWindow
modalDelegate: self
didEndSelector: @selector(savePanelDidEnd:returnCode:contextInfo:)
contextInfo: NULL];
}
Add a
savePanelDidEnd
method that performs the actual saving.If the user clicks the Save button, the method obtains the filename and type and retrieves the image from the view. If the image exists, the method uses the
CGImageDestinationRef
opaque type (from the Image I/O framework) to create an image destination based on a URL representation of the file path. It saves the image with its properties, finalizes the destination, and releases theCGImageDestination
object because it is no longer needed.- (void)savePanelDidEnd: (NSSavePanel *)sheet
returnCode: (int)returnCode
contextInfo: (void *)contextInfo
{
if (returnCode == NSOKButton)
{
NSString * path = [sheet filename];
NSString * newUTType = [mSaveOptions imageUTType];
CGImageRef image;
image = [mImageView image];
if (image)
{
NSURL * url = [NSURL fileURLWithPath: path];
CGImageDestinationRef dest = CGImageDestinationCreateWithURL((CFURLRef)url,
(CFStringRef)newUTType, 1, NULL);
if (dest)
{
CGImageDestinationAddImage(dest, image,
(CFDictionaryRef)[mSaveOptions imageProperties]);
CGImageDestinationFinalize(dest);
CFRelease(dest);
}
} else
{
NSLog(@"*** saveImageToPath - no image");
}
}
}
Save the
Controller.m
file.Open the
MainMenu.nib
file.Double-click the MainMenu icon in the nib document window.
Open the File menu so you can see the menu items New, Open, and so on.
Control-drag from the Save menu item to the controller.
In the connections panel for the controller, choose
saveImage:
.Save the nib file.
In Xcode, click Build and Go.
After the application launches, choose File > Save. You should see a Save panel appear with an accessory view that looks similar to the following.
Choose other items from the Format menu to see the options that are offered for each image format.
Supporting Zooming
To support zooming, you need to add menu commands or controls (or both) for zooming, and a method to respond to the commands.
To support zooming, follow these steps:
Add the following method signature to the
Controller.h
file, then save the file:- (IBAction)doZoom: (id)sender;
Open the
Controller.m
file and add constants for the zoom factor.#define ZOOM_IN_FACTOR 1.414214 // doubles the area
#define ZOOM_OUT_FACTOR 0.7071068 // halves the area
Add a method to handle commands to zoom out, zoom in, zoom to fit, and zoom to the actual size of the image.
- (IBAction)doZoom: (id)sender
{
NSInteger zoom;
CGFloat zoomFactor;
if ([sender isKindOfClass: [NSSegmentedControl class]])
zoom = [sender selectedSegment];
else
zoom = [sender tag];
switch (zoom)
{
case 0:
zoomFactor = [mImageView zoomFactor];
[mImageView setZoomFactor: zoomFactor * ZOOM_OUT_FACTOR];
break;
case 1:
zoomFactor = [mImageView zoomFactor];
[mImageView setZoomFactor: zoomFactor * ZOOM_IN_FACTOR];
break;
case 2:
[mImageView zoomImageToActualSize: self];
break;
case 3:
[mImageView zoomImageToFit: self];
break;
}
}
Open the
MainMenu.nib
file.Double-click the MainMenu icon in the nib document window.
Create a new menu by dragging a Submenu Menu item from the Library and dropping it between the Edit and Format menu items.
Name the new menu Views.
Add menu items from the library to the Views menu so that you have enough for these zoom commands. Use the inspector to add the tags and key equivalents shown in the table.
Command
Tag
Key equivalent
Zoom Out
0
Command –
Zoom In
1
Command =
Zoom to Actual Size
2
Zoom to Fit
3
Control-drag from each item to the Controller icon. Then, in the connections panel for the controller, click
doZoom:
.Save the
MainMenu.nib
file.In Xcode, click Build and Go. Then make sure that the zoom commands operate as expected.
Quit the application and go back to the
MainMenu.nib
file in Interface Builder.This time you’ll revise the window user interface by adding zoom controls.
Change the image view size so that the top of the view is 50 pixels from the top of the window.
You’ll need this space to put zoom controls.
Drag a segmented control from the Library to the top-left side of the window.
In the Attributes inspector for the control set the number of segments to 4.
Double-click each segment to change its label and to use the inspector to add a tag. Use the commands and tags shown previously for the Views menu.
If possible, you should add artwork, similar to the following, to the Xcode project so that you can use icons instead of text labels.
In the Size inspector, set the Control Size to small.
In the Attributes inspector, choose Select Any in the Mode pop-up menu.
Control-drag from the segmented zoom control to the Controller icon. Then, in the connections panel for the controller, click
doZoom:
.Click the Info button in the nib document window. When the MainMenu.nib Info window opens, set the Deployment Target pop-up menu to OS X v10.5.
Save the nib file.
In Xcode, click Build and Go. Then make sure that the zoom commands operate as expected.
Adding Image Editing Tools
The IKImageView
class has built-in support for move, select, crop, rotate, and annotate tools. To set up the image editing application to include the tools for these actions, follow these steps:
In Xcode, add image files (Project > Add to Project), similar to the following, for each of the tool modes—move, select, crop, rotate, and annotate.
You’ll need to use an application such as Adobe Illustrator to create icons. See OS X Human Interface Guidelines for information on using icons in the interface.
Add the following method signature to the
Controller.h
file:- (IBAction)switchToolMode: (id)sender;
Add a method to the
Controller.m
file to handle setting the tool mode.- (IBAction)switchToolMode: (id)sender
{
NSInteger newTool;
if ([sender isKindOfClass: [NSSegmentedControl class]])
newTool = [sender selectedSegment];
else
newTool = [sender tag];
switch (newTool)
{
case 0:
[mImageView setCurrentToolMode: IKToolModeMove];
break;
case 1:
[mImageView setCurrentToolMode: IKToolModeSelect];
break;
case 2:
[mImageView setCurrentToolMode: IKToolModeCrop];
break;
case 3:
[mImageView setCurrentToolMode: IKToolModeRotate];
break;
case 4:
[mImageView setCurrentToolMode: IKToolModeAnnotate];
break;
}
}
Save the
Controller.m
file.Open the
MainMenu.nib
file.Add a submenu to the Views menu and name it Tools.
Add the following items to the Tools submenu, using the inspector to add the tag values.
Item
Tag
Move
0
Select
1
Crop
2
Rotate
3
Annotate
4
Control-drag from the Move menu item to the Controller icon. Choose
switchToolMode:
in the connections panel for the controller.Repeat the previous step for each of the other items in the Tools menu.
Add a segmented control to the top-right side of the window so the control is aligned with the zoom controls.
In the Attributes inspector, set the number of segments to 5.
In the Size inspector, set the Size to small and the Width to 60.
Switch to the Attributes inspector.
For each segment, drag the appropriate icon from the Media pane of the Library, and set the segment to autosize.
From left to right, the icons should represent Move, Select, Crop, Annotate, and Rotate.
Choose Select One from the Mode pop-up menu.
Make sure that Selected is checked for Segment 1.
In the Size inspector set the Autosizing struts and springs so they look as follows:
Control-drag from the segmented control to the controller and connect to the
switchToolMode:
action.Save the nib file.
In Xcode, click Build and Go. Then test the Tools menu and the toolbar.
The move and rotate tools operate without any additional code on your part. The selection and crop tools copy the selected areas to the pasteboard. You need to provide code that implements pasting from the pasteboard. The annotate tool simply shows a colored circle. You need to write code that supports text entry and editing, and saves the annotation.
Supporting Opening Image Files
As it is now, your application opens a default image file. It would be greatly improved if it allowed the user to choose an image other than the default. Next you’ll write an open image method that is invoked when the user chooses File > Open. You need to provide a selector that is invoked when the Open panel closes. If the user chooses an image, your openImageURL:
method (which you already added to the application) is invoked.
Follow these steps to support letting the user open image files:
Add the following method signature to the
Controller.h
file:- (IBAction)openImage: (id)sender;
Write an open image method.
The method creates an instance of the
NSOpenPanel
class and defines the allowable filename extensions. It then calls a method that shows the Open panel.- (IBAction)openImage: (id)sender
{
NSOpenPanel * openPanel = [NSOpenPanel openPanel];
NSString * extensions = @"tiff/tif/TIFF/TIF/jpg/jpeg/JPG/JPEG/pdf/PDF";
NSArray * types = [extensions pathComponents];
[openPanel beginSheetForDirectory: NULL
file: NULL
types: types
modalForWindow: mWindow
modalDelegate: self
didEndSelector: @selector(openPanelDidEnd:returnCode:contextInfo:)
contextInfo: NULL];
}
Write a selector method that’s invoked when the Open panel terminates.
If the user chooses an image, this method calls the
openImageURL:
method, passing to it the first item in the selected path.- (void)openPanelDidEnd: (NSOpenPanel *)panel
returnCode: (int)returnCode
contextInfo: (void *)contextInfo
{
if (returnCode == NSOKButton)
{
[self openImageURL: [[panel URLs] objectAtIndex: 0]];
}
}
Save the
Controller.m
file.Double-click the MainMenu icon in the nib document window.
Click the File menu so you can see the items in it—New, Open, and so on.
Control-drag from the Open menu item to the controller and connect to the
openImage:
action in the connections panel.Save the nib file.
In Xcode, click Build and Go.
Choose File > Open and make sure that you can open an image.
Copyright © 2008 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2008-06-09