Transitions are typically used between images in a slide show or to switch from one scene to another in video. These effects are rendered over time and require that you set up a timer. This section shows how to set up and apply the copy machine transition filter—CICopyMachine—to two still images. The copy machine transition creates a light bar similar to what you see in a copy machine. The light bar sweeps across the initial image to reveal the target image. Figure 2-5 shows what this filter looks like before, partway through, and after the transition from an image of ski boots to an image of a skier.
Transition filters require the following tasks:
Create Core Image images (CIImage objects) to use for the transition.
Set up and schedule a timer.
Create a CIContext object.
Create a CIFilter object for the filter to apply to the image.
Set the default values for the filter.
Set the filter parameters.
Set the source and the target images to process.
Calculate the time.
Apply the filter.
Draw the result.
Repeat steps 8–10 until the transition is complete.
You’ll notice that many of these tasks are the same as those required to process an image using a filter other than a transition filter. What’s different is the need to set up a timer and to repeatedly draw the effect at various time intervals throughout the transition.
The awakeFromNib method, shown in Listing 2-8, gets two images (boots.jpg and skier.jpg) and sets them as the source and target images. Using the NSTimer class, a timer is set to repeat every 1/30 second. Note the variables thumbnailWidth and thumbnailHeight. These are used to constrain the rendered images to the view set up in Interface Builder.
Note: The NSAnimation class, introduced in Mac OS X v10.4, implements timing for animation in Cocoa. If you use NSAnimation instead of NSTimer, you can set up more than one slide show to play transitions at the same time, using only one timing device. For more information see the documents NSAnimation Class Reference and Animation Programming Guide for Cocoa. See also the CIAnnotationCIAnnotation sample application.
Listing 2-8 Getting images and setting up a timer
- (void)awakeFromNib |
{ |
NSTimer *timer; |
NSURL *url; |
thumbnailWidth = 340.0; |
thumbnailHeight = 240.0; |
url = [NSURL fileURLWithPath: [[NSBundle mainBundle] |
pathForResource: @"boots" ofType: @"jpg"]]; |
[self setSourceImage: [CIImage imageWithContentsOfURL: url]]; |
url = [NSURL fileURLWithPath: [[NSBundle mainBundle] |
pathForResource: @"skier" ofType: @"jpg"]]; |
[self setTargetImage: [CIImage imageWithContentsOfURL: url]]; |
timer = [NSTimer scheduledTimerWithTimeInterval: 1.0/30.0 |
target: self |
selector: @selector(timerFired:) |
userInfo: nil |
repeats: YES]; |
base = [NSDate timeIntervalSinceReferenceDate]; |
[[NSRunLoop currentRunLoop] addTimer: timer |
forMode: NSDefaultRunLoopMode]; |
[[NSRunLoop currentRunLoop] addTimer: timer |
forMode: NSEventTrackingRunLoopMode]; |
} |
You set up a transition filter just as you’d set up any other filter. Listing 2-9 uses the method filterWithName: to create the filter. It then calls setDefaults to initialize all input parameters. The code sets the extent to correspond with the thumbnail width and height that is declared in the awakeFromNib: method shown in Listing 2-8.
The routine uses the thumbnail variables to specify the center of the effect. For this example, the center of the effect is the center of the image, but it doesn’t have to be.
Listing 2-9 Setting up the transition filter
- (void)setupTransition |
{ |
CIVector *extent; |
float w,h; |
w = thumbnailWidth; |
h = thumbnailHeight; |
extent = [CIVector vectorWithX: 0 Y: 0 Z: w W: h]; |
transition = [CIFilter filterWithName: @"CICopyMachineTransition"]; |
[transition setDefaults]; |
[transition setValue: extent |
forKey: @"inputExtent"]; |
[transition retain]; |
} |
The drawRect: routine for the copy machine transition effect is shown in Listing 2-10. This routine sets up a rectangle that’s the same size as the view and then sets up a floating-point value for the rendering time. If the CIContext object hasn’t already been created, the routine creates one. If the transition is not yet set up, the routine calls the setupTransition method (see Listing 2-9). Finally, the routine calls the drawImage:atPoint:fromRect: method, passing the image that should be shown for the rendering time. The imageForTransition: method, shown in Listing 2-11, applies the filter and returns the appropriate image for the rendering time.
Listing 2-10 The drawRect: method for the copy machine transition effect
- (void)drawRect: (NSRect)rectangle |
{ |
float t; |
CGRect cg = CGRectMake(NSMinX(rectangle), NSMinY(rectangle), |
NSWidth(rectangle), NSHeight(rectangle)); |
t = 0.4*([NSDate timeIntervalSinceReferenceDate] - base); |
if(context == nil) |
{ |
context = [CIContext contextWithCGContext: |
[[NSGraphicsContext currentContext] graphicsPort] |
options: nil]; |
[context retain]; |
} |
if(transition == nil) |
[self setupTransition]; |
[context drawImage: [self imageForTransition: t + 0.1] |
atPoint: cg.origin |
fromRect: cg]; |
} |
The imageForTransition: method figures out, based on the rendering time, which image is the source image and which one is the target image. It’s set up to allow a transition to repeatedly loop. If your application applies a transition that doesn’t loop, it would not need the if-else construction shown in Listing 2-11.
The routine sets the inputTime value based on the rendering time passed to the imageForTransition: method. It applies the transition, passing the output image from the transition to the crop filter (CICrop). Cropping ensures the output image fits in the view rectangle. The routine returns the cropped transition image to the drawRect: method, which then draws the image.
Listing 2-11 Applying the transition filter
- (CIImage *)imageForTransition: (float)t |
{ |
CIFilter *crop; |
if(fmodf(t, 2.0) < 1.0f) |
{ |
[transition setValue: sourceImage forKey: @"inputImage"]; |
[transition setValue: targetImage forKey: @"inputTargetImage"]; |
} |
else |
{ |
[transition setValue: targetImage forKey: @"inputImage"]; |
[transition setValue: sourceImage forKey: @"inputTargetImage"]; |
} |
[transition setValue: [NSNumber numberWithFloat: |
0.5*(1-cos(fmodf(t, 1.0f) * M_PI))] |
forKey: @"inputTime"]; |
crop = [CIFilter filterWithName: @"CICrop" |
keysAndValues: @"inputImage", |
[transition valueForKey: @"outputImage"], |
@"inputRectangle", [CIVector vectorWithX: 0 Y: 0 |
Z: thumbnailWidth |
W: thumbnailHeight], |
nil]; |
return [crop valueForKey: @"outputImage"]; |
} |
Each time the timer that you set up fires, the display must be updated. Listing 2-12 shows a timerFired: routine that does just that.
Listing 2-12 Using the timer to update the display
- (void)timerFired: (id)sender |
{ |
[self setNeedsDisplay: YES]; |
} |
Finally, Listing 2-13 shows the housekeeping that needs to be performed if your application switches the source and target images, as the example does.
Listing 2-13 Setting source and target images
- (void)setSourceImage: (CIImage *)source |
{ |
[source retain]; |
[sourceImage release]; |
sourceImage = source; |
} |
- (void)setTargetImage: (CIImage *)target |
{ |
[target retain]; |
[targetImage release]; |
targetImage = target; |
} |
Last updated: 2008-06-09