A dynamical system is one whose state changes over time using a calculation that is based on the current state of the system. Complex phenomena—fluid dynamics, stellar formation, saxophone multiphonics, self-organizing systems, and so forth—are typically modeled using iterative functions whose output is presented in graphical format. Imaging dynamical systems requires a way to feed the output of the system back to the input. Imaging these types of systems is not quite as simple as chaining a lot of filters together, as shown in “Processing an Image.” Rather, there needs to be a way to accumulate image output so that it can affect the next iteration. Core Image provides the CIImageAccumulator class for just this purpose. An image accumulator object enables feedback-based image processing for such things as the iterative painting operations required by fluid dynamics simulations.
The code in this section shows how to use a CIImageAccumulator object, but not for anything as complex as modeling dynamical systems. Instead, you’ll see how to use an image accumulator to implement a simple painting application called MicroPaint. A user drags the mouse on a canvas to apply paint. A simple button press sprays a dab of paint. A color well lets the user change color. The user can create output similar to that shown in Figure 2-6.
The “image” starts as a blank canvas. MicroPaint uses an image accumulator to collect the paint applied by the user. When the user clicks Clear, MicroPaint resets the image accumulator to a white canvas. The three essential tasks for using an image accumulator for the MicroPaint application are:
The interface file for the MicroPaint application is shown in Listing 2-14. The routines for obtaining mouse location and updating the user interface aren’t discussed here. The tasks necessary to set up and use an image accumulator are discussed in the sections that follow. (The CIMicroPaint sample application is somewhat similar to the MicroPaint application discussed here. You might also want to look at that application, which is available after you install the developer tools on your hard disk, in Developer > Examples > Quartz > Core Image.)
Listing 2-14 The interface for the MicroPaintView
@interface MicroPaintView : NSView |
{ |
BOOL initialized; |
NSBundle *bundle; |
CIImageAccumulator *_canvas; |
// User interface |
NSColor *color; |
IBOutlet NSColorWell *colorWell; |
IBOutlet NSButton *clearButton; |
// For tracking the brush and making an evenly-spaced set of paint dabs |
NSPoint lastPt; |
float lastPressure; |
float distance; |
} |
- (void)awakeFromNib; |
- (void)drawRect:(NSRect)r; |
- (void)deposit:(NSPoint)pt pressure:(float)pressure; |
- (IBAction)colorWellAction:(id)sender; |
- (IBAction)clearButtonAction:(id)sender; |
@end |
The canvas routine shown in Listing 2-15 creates and initializes an image accumulator object. The bounds of the image accumulator object are set to the bounds of the view, using a 32 bit-per-pixel, fixed-point pixel format (kCIFormatARGB8). The routine also sets up and initializes a constant color generator filter (CIConstantColorGenerator) with the color white. Then it uses the output of the constant color filter to initialize the image accumulator image. The canvas routine sets a blank (white) canvas the first time the application launches and anytime the user clicks the clear button. Otherwise, the routine returns the current image accumulator.
Listing 2-15 Creating and initializing an image accumulator
- (CIImageAccumulator *)canvas |
{ |
CGRect r; |
CIFilter *f; |
NSRect bounds; |
if (_canvas == nil) |
{ |
bounds = [self bounds]; |
r = CGRectMake(bounds.origin.x, bounds.origin.y, |
bounds.size.width, bounds.size.height); |
_canvas = [[CIImageAccumulator alloc] initWithExtent:r |
format:kCIFormatARGB8]; |
f = [CIFilter filterWithName:@"CIConstantColorGenerator" |
keysAndValues:@"inputColor", |
[CIColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:1.0], |
nil]; |
[_canvas setImage:[f valueForKey:@"outputImage"]]; |
} |
return _canvas; |
} |
MicroPaint provides its own filter—a dab filter—that applies paint to the canvas. The filter calculates where and how much paint to apply based upon the location of the mouse (or pen), the brush size and brush spacing (constants defined by the application), and the pressure, which can vary if the user paints with a pressure-sensitive device.
The dab filter is part of the MicroPaint application bundle. Its implementation isn’t discussed here. If you want to create and use your own filters, see “Creating Custom Filters.”
The deposit:pressure: method shown in Listing 2-16 is called whenever there is a mouse-down or mouse-dragged event. A detailed explanation for each numbered line of code appears following the listing.
Listing 2-16 Setting up and applying the dab filter to the accumulated image
- (void)deposit:(NSPoint)pt pressure:(float)pressure |
{ |
CIFilter *myFilter; |
CGRect r; |
myFilter = [CIFilter filterWithName:@"DabFilter"];// 1 |
[myFilter setValue:[CIVector vectorWithX:pt.x Y:pt.y] // 2 |
forKey:@"inputCenter"]; |
[myFilter setValue:[CIColor colorWithRed:[color redComponent] |
green:[color greenComponent] |
blue:[color blueComponent] alpha:1.0] |
forKey:@"inputColor"]; |
[myFilter setValue:[NSNumber numberWithFloat:brushsize * 0.5] |
forKey:@"inputRadius"]; |
[myFilter setValue:[NSNumber numberWithFloat:pressure] |
forKey:@"inputOpacity"]; |
[myFilter setValue:[[self canvas] image] // 3 |
forKey:@"inputImage"]; |
r.origin = CGPointMake(pt.x - brushsize * 0.5, |
pt.y - brushsize * 0.5);// 4 |
r.size = CGSizeMake(brushsize, brushsize); |
[[self canvas] setImage:[myFilter valueForKey:@"outputImage"] dirtyRect:r];// 5 |
[self setNeedsDisplay:YES];// 6 |
} |
Here’s what the code does:
Creates a filter for the dab filter.
Note: The dab filter is a custom filter created by the application. The process for using custom filters is the same as for using Core Image filters. You create a CIFilter object using the name assigned to the filter, set the input values, and obtain the output image. If you package your filter as an image unit, you must first load it. See “Loading Image Units” for details.
Sets the input values for the dab filter.
Sets the image accumulator image as the input image to the dab filter.
Calculates the dirty rectangle, which is based on the location of the mouse and the brush size set by the application.
Sets the image accumulator image to the output of the dab filter, but only in the area specified by the dirty rectangle.
Sets the display to be updated, which calls the drawRect: routine for the view.
The drawRect: method shown in Listing 2-17 is called when the deposit:pressure: method sets the display for updating. A detailed explanation for each numbered line of code appears following the listing.
Listing 2-17 The drawRect routine for the Mouse Paint application
- (void)drawRect:(NSRect)rect |
{ |
CGRect cg; |
CIContext *context = [[NSGraphicsContext currentContext] CIContext];// 1 |
cg = CGRectMake(NSMinX(rect), NSMinY(rect), |
NSWidth(rect), NSHeight(rect)); |
[context drawImage:[[self canvas] image] // 2 |
atPoint:cg.origin |
fromRect:cg]; |
} |
Here’s what the code does:
Creates a Core Image context by calling the NSGraphicsContext method CIContext. You need to create the context only once; always reuse the CIContext object when you can.
Draws the image returned by the CIImageAccumulator object at the origin (0,0), using the full size of the view.
Tip: If you repeatedly call Core Image without returning to your application run loop, it’s best to surround each batch of Core Image invocations with their own autorelease pool. This practice prevents your application from using more memory than necessary—which is important when you manipulate images.
Last updated: 2008-06-09