As we discussed earlier, the Application
Kit defines a number of data types and structures that are useful for
drawing with Quartz. These data types are defined in the file
NSGeometry.h
, and we’ll
describe some of them in this section.
Any point on the computer’s screen is defined by an NSPoint structure:
typedef struct _NSPoint { float x; float y; } NSPoint;
An extent in space is defined by an NSSize structure:
typedef struct _NSSize { float width; // should never be negative float height; // should never be negative } NSSize;
Conveniently, a rectangle, NSRect, is defined by an extent from a point:
typedef struct _NSRect { NSPoint origin; NSSize size; } NSRect;
Cocoa defines a number of inline functions for creating and managing these structures. The code for an inline function is integrated directly into the code for its callers, so there is no function call overhead associated with using them. These functions allow you to create an unnamed structure that is used as an argument in a function call or method invocation and then destroyed. The functions are:
NSPoint NSMakePoint(float x, float y) NSSize NSMakeSize(float w, float h) NSRect NSMakeRect(float x, float y, float w, float h)
There are also a number of convenience functions in Cocoa, which are summarized in Table 14-1.
Table 14-1. Convenience graphics functions
Graphics function |
Purpose |
---|---|
float NSMaxX(aRect) |
Returns the maximum X coordinate (the right side) of the rectangle |
float NSMaxY(aRect) |
Returns the maximum Y coordinate (usually the top, unless the coordinate system is flipped) of the rectangle |
float NSMidX(aRect) |
Returns the horizontal median of the rectangle |
float NSMidY(aRect) |
Returns the vertical median of the rectangle |
float NSMinX(aRect) |
Returns the minimum X coordinate (the left side) of the rectangle |
float NSMinY(aRect) |
Returns the minimum Y coordinate (usually the bottom) of the rectangle |
float NSWidth(aRect) |
Returns the width of the rectangle |
float NSHeight(aRect) |
Returns the height of the rectangle |
BOOL NSEqualPoints(point1,point2) |
Returns YES if point1==point2 |
BOOL NSEqualSizes(size1,size2) |
Returns YES if size1==size2 |
BOOL NSEqualRects(rect1,rect2) |
Returns YES if rect1==rect2 |
BOOL NSIsEmptyRect(aRect) |
Returns YES if aRect has a zero size |
NSRect NSUnionRect(rect1,rect2) |
Returns the union of two rectangles |
NSRect NSIntersectionRect(rect1,rect2) |
Returns the intersection of two rectangles |
BOOL NSPointInRect(aPoint,aRect) |
Returns YES if aPoint is inside aRect |
BOOL NSContainsRect(aRect,bRect) |
Returns YES if bRect is inside aRect |
BOOL NSIntersectsRect(aRect,bRect) |
Returns YES if the two rectangles intersect |
NSString *NSStringFromPoint(aPoint) |
Returns a standard string coding of a point |
NSString *NSStringFromSize(aSize) |
Returns a standard string coding of a size |
NSString *NSStringFromRect(aRect) |
Returns a standard string coding of a rectangle |
NSPoint NSPointFromString(aString) |
Maps the string back to a point |
NSSize NSSizeFromString(aString) |
Maps the string back to a size |
NSRect NSRectFromString(aString) |
Maps the string back to a rectangle |
There are other functions as well; you should review the file
NSGeometry.h
or the
“Functions” section of the
Foundation framework documentation to learn about them.
The NSColor class is used both to specify a particular color and to set it to be the current color in the current drawing context.
The NSColor class predefines 15 colors as class (or factory) methods. They are shown in Table 14-2 (the + means they are class methods).
You can also create a color by specifying its components using a variety of color models, including RGB, CYMK, HSB, and Pantone colors. These NSColor class methods are listed in Table 14-3.
Table 14-3. Factory methods for creating colors
+ colorWithCalibratedHue:saturation:brightness:alpha: |
+ colorWithCalibratedRed:green:blue:alpha: |
+ colorWithCalibratedWhite:alpha: |
+ colorWithCatalogName:colorName: |
+ colorWithDeviceCyan:magenta:yellow:black:alpha: |
+ colorWithDeviceHue:saturation:brightness:alpha: |
+ colorWithDeviceRed:green:blue:alpha: |
+ colorWithDeviceWhite:alpha: |
After you have created an NSColor object, you can make it the current drawing color by sending it the set method. Remember that before you call this method, a view must be established as the current drawing context with the lockFocus method (and have its drawing method called with drawRect:).
After you set a color, you can draw in that color. In the
NSGraphics.h
file, you’ll find
an extensive list of functions for drawing a variety of rectangles,
bitmaps, bezeled rectangles, and so on. More complicated objects can
be drawn using the NSBezierPath class. One way to think of this class
is as a variable-length storage object that allows you to create a
path with a particular combination of lines and curves. This object
can have a specific line width, fill, end caps, etc. When
you’re done, you send the instance the stroke method to perform all of the drawing.
For example, we could use the following two statements to draw a white rectangle in the currently selected drawing view:
[ [NSColor whiteColor] set]; [NSBezierPath fillRect:rect];
We could draw four horizontal black lines, each two points thick, using the following sequence:
[ [NSColor blackColor] set]; [NSBezierPath setDefaultLineWidth:2.0]; [NSBezierPath strokeLineFromPoint:NSMakePoint(20,20) toPoint:NSMakePoint(300,20)]; [NSBezierPath strokeLineFromPoint:NSMakePoint(20,30) toPoint:NSMakePoint(300,30)]; [NSBezierPath strokeLineFromPoint:NSMakePoint(20,40) toPoint:NSMakePoint(300,40)]; [NSBezierPath strokeLineFromPoint:NSMakePoint(20,50) toPoint:NSMakePoint(300,50)];
To create a simple four-pointed star, we could use four Bezier curves that have their endpoints at the corners of the star and their control points in the star’s center (see the documentation for more on Bezier curves), as shown in this code:
NSBezierPath *path = [NSBezierPath bezierPath]; NSPoint center = NSMakePoint(50,50); [ [NSColor blackColor] set]; [path setLineWidth:1.0]; [path moveToPoint:NSMakePoint(50,0)]; [path curveToPoint:NSMakePoint(100,50) controlPoint1:center controlPoint2:center]; [path curveToPoint:NSMakePoint(50,100) controlPoint1:center controlPoint2:center]; [path curveToPoint:NSMakePoint(0,50) controlPoint1:center controlPoint2:center]; [path curveToPoint:NSMakePoint(50,0) controlPoint1:center controlPoint2:center]; [path stroke];
To draw text with Quartz, you need the following:
A view in which to draw
The actual text that you want to draw
The font, font size, and other attribute information for the text
A location in the selected view where you want the text to be drawn
You can draw text in Cocoa with either the NSString class or the
NSAttributedString class. The classes themselves
don’t do the actual drawings; instead, the AppKit
adds a category to each called NSStringDrawing. Details of this
category can be found in the include file
NSStringDrawing.h
.
The view in which you want to draw is selected with the lockFocus method. The text is specified by the contents of the NSString or NSAttributedString class with which you are drawing.
The AppKit allows you to specify a wide variety of attributes when
you draw text. These attributes can be stored in an NSDictionary. If
you are drawing text with the NSString class, the NSDictionary must
be provided to the NSString object when the drawing begins. If you
are drawing with an NSAttributedString object, the attributes can be
applied to a range of characters within the NSAttributedString class
before you actually draw. Table 14-4 lists the
available attributes. These attributes are defined in the file
NSAttributedString.h
, and they are all
NSString
*
values.
Table 14-4. Drawing attributes for the NSAttributedString and NSString classes
Attribute identifier |
Value class |
Default value |
---|---|---|
NSFontAttributeName |
NSFont |
12-point Helvetica |
NSForegroundColorAttributeName |
NSColor |
Black |
NSBackgroundColorAttributeName |
NSColor |
None (no background drawn) |
NSUnderlineStyleAttributeName |
NSNumber, as an int |
None (no underline) |
NSSuperscriptAttributeName |
NSNumber, as an int |
0 |
NSBaselineOffsetAttributeName |
NSNumber, as a float |
0.0 |
NSKernAttributeName |
NSNumber, as a float |
0.0 |
NSLigatureAttributeName |
NSNumber, as an int |
1 (standard ligatures) |
NSParagraphStyleAttributeName |
NSParagraphStyle |
Value returned by NSParagraphStyle’s defaultParagraphStyle method |
NSAttachmentAttributeName |
NSTextAttachment |
None (no attachment) |
For example, let’s say that we want to draw some text in green, 36-point Helvetica. First we need to create the font. We can do this using the fontWithName:size: class method, as follows:
NSFont *font = [NSFont fontWithName:@"Helvetica" size:36.0];
Next we need to create an NSMutableDictionary of key/value pairs that contains this font as the value for the key NSFontAttributeName and the green NSColor object as the value for the NSForegroundColorAttributeName key.
The NSMutableDictionary should look like this:
NSMutableDictionary *attrs = [NSMutableDictionary dictionary]; [attrs setObject:font forKey:NSFontAttributeName]; [attrs setObject:[NSColor greenColor]forKey:NSForegroundColorAttributeName];
With the NSMutableDictionary set up, we can now create an NSAttributedString that contains the text that we want to draw, modified by the new attributes:
str = [ [NSMutableAttributedString alloc] initWithString:@"MathPaper" attributes:attrs];
To draw with this string, we need to lock focus on a particular view and use the NSAttributeString’s drawAtPoint: method:
[aView lockFocus]; [str drawAtPoint:NSMakePoint(20,50)]; [aView unlockFocus];
Even better would be implementing a drawRect: method to draw the text:
- (void)drawRect:(NSRect)rect { [str drawAtPoint:NSMakePoint(20,50)]; }
As an alternative to drawing with an NSAttributedString class, we can
simply use an NSString object. We can use any NSString object, even
one created with the @""
operator:
- (void)drawRect:(NSRect)rect { [@"Text" drawAtPoint:NSMakePoint(20,20) withAttributes:attrs]; }
If we know that the text should fit within a particular size box, we can use the drawInRect:withAttributes: method:
- (void)drawRect:(NSRect)rect { [@"Text" drawAInRect:NSMakeRect(20,20,100,100) withAttributes:attrs]; }
The drawInRect:withAttributes: method is also useful for displaying text that is wrapped to a particular width.
It’s just as easy to draw pictures with Quartz as it is to draw text. In fact, in some ways it’s even easier. Practically anything that you’ll ever need to do with an image in Cocoa can be done with the NSImage class for manipulating images.
Using the NSImage class, you can do all of the following:
Read an image from a file.
Scale an image to a particular size.
Convert an image from one representation to another.
Draw an image in a view, or combine the contents of the image with the contents already present in the view.
NSImage accomplishes this magic by using objects of another class, called NSImageRep, to perform the actual work of storing the image. A single NSImage instance can have several NSImageRep representations of an image. For example, it might have both a bitmap representation for quick redisplay on the screen and a PDF representation for detailed display on a printer. (At this point, we recommend that you read the Cocoa documentation for the NSImage class.)
The NSImage class transfers images to the screen through a process called compositing . Compositing is a way of combining two images, a source image and a destination image (the image already in place on the screen). The combining is done with a special function called the compositing operator , which combines the two images on a pixel-by-pixel basis and displays the result.
When you composite, you can specify the following:
The source image for the compositing.
The destination image.
The compositing operation.
The fraction of the compositing operation that should be used for calculating the final result. A fraction of 1.0 means that the source pixels should be set entirely depending on the results of the compositing. A fraction of 0.5 means that half of the pixel’s final value should come from the result of the compositing operation, and half of the pixel’s final value should be the same as the original value.
Cocoa supports 14 different compositing operations. These operations
are defined in the file NSGraphics.h
. The two
most common compositing operations are NSCompositeCopy and
NSCompositeSourceOver. NSCompositeCopy copies the rectangle bounded
by the source image into the destination image; everything in the
destination image is lost. NSCompositeSourceOver is similar, but the
source image is placed on top of the destination image, so that you
may be able to see parts of the destination image through any pixels
in the source image that are transparent or partially transparent.
Because of the way Aqua handles transparency, you should generally
use NSCompositeSourceOver and not
NSCompositeCopy. If you have a few transparent pixels in your source
image, NSCompositeCopy will copy these transparent pixels to the
destination, making it transparent as well. This is not usually what
you want.
The most common compositing operations are listed in Table 14-5. In each case, the source is defined as the image stored inside the NSImage object, while the destination is the region in which the NSImage is being composited. The destination can be any locked focus, including an NSView, another NSImage, or even a Quartz graphics state.
Table 14-5. Common compositing operations
Compositing operation |
Meaning |
---|---|
NSCompositeSourceOver |
“Source over destination” composites with attention to transparency in NSImage. This is the operation that you should normally use to “copy” an image into a window. |
NSCompositeCopy |
Copies the image to the NSView (destination). You generally should not use this operation, as it can cause your windows to be “promoted” to windows that contain alpha (transparency) if the source image has alpha. |
NSCompositeClear |
Clears the area where the image is to be copied. This isn’t used much. |
NSCompositeXOR |
Performs an exclusive-OR between the NSImage and the NSView destination. |
NSCompositePlusDarker |
Performs mathematical addition between the source and the NSView. Whites get brighter and blacks get darker. |
NSCompositeHighlight |
Highlights the source image. |
The key method for compositing is compositeToPoint:operation . Because this method is a Quartz drawing operation, it should be used only inside a drawRect: method or between invocations of the lockFocus and unlockFocus methods sent to the NSView object in which the compositing is to occur.
For example, to display an NSImage in a view is to lock focus on the view and then to composite the image to a point. To do this, you might use code that looks like this:
- drawRect:(NSRect)aRect { image = [NSImage imageNamed:@"PaperIcon"]; [image compositeToPoint:NSMakePoint(100,100) operation:NSCompositeSourceOver]; }
18.119.28.108