Introduction to Quartz
Quartz is at the center of all graphics in Cocoa. It provides basic graphics
data structures and drawing routines, as well Mac OS X's window server.
This beginner-level tutorial introduces basic Cocoa graphics concepts:
rectangles, points, colors, and coordinate systems.
This
tutorial is written and illustrated by
Scott Stevenson
Copyright © 2006 Scott Stevenson
Rectangles and Points
All drawing in Quartz involves rectangles. In Cocoa, you use
the NSRect struct to describe a rectangle's location
and dimensions:
(Rects don't automatically draw themselves. These
are just diagrams to go with the examples.)
typedef struct {
NSPoint origin;
NSSize size;
} NSRect;
// make a rect at (0,0) which is 20x20
NSRect myRect;
myRect.origin.x = 0;
myRect.origin.y = 0;
myRect.size.width = 20;
myRect.size.height = 20;
The origin field is the "anchor point" of the rect, where the
drawing starts. A point is described by NSPoint, which has x and y coordinates:
typedef struct {
float x;
float y;
} NSPoint;
// make three points on the canvas
NSPoint point1;
point1.x = 4;
point1.y = 11;
NSPoint point2;
point2.x = 12;
point2.y = 21;
NSPoint point3;
point3.x = 19;
point3.y = 8;
The size field of a rect is an NSSize, which holds
a width and a height. There's no way to depict an instance of NSSize, it
has to be part of a rect to be useful.
typedef struct {
float width;
float height;
} NSSize;
Much of 2D drawing in Cocoa is based on these three structs.
Remember these are not Objective-C classes. You can't
call methods on them directly, but there are functions that go with them.
All measurements in Quartz are float values,
which gives you finer control of drawing than
integer-based coordinates.
Convenience Functions
Cocoa has a number of functions for creating geometry structs. Most of
them are listed in Foundation's NSGeometry.h file.
// make a point at coordinate 20,20
NSPoint newPoint = NSMakePoint ( 20, 20 );
// make a size of 100 wide x 100 high
NSSize newSize = NSMakeSize ( 100, 100 );
// use the previous point and size to make a rect
NSRect newRect = NSMakeRect ( newPoint.x,
newPoint.y,
newSize.width,
newSize.height );
// also can just do this
NSRect newRect = NSMakeRect ( 20, 20, 100, 100 );
Using these functions instead of creating the structs manually makes the
code a bit more obvious and makes searching easier.
Coordinates in Quartz
The drawing area of a view in Cocoa is treated as a rect. Quartz
calls this drawing area the "bounds."
An NSPoint can represent any location in the view bounds.
The standard Quartz coordinate system is based on PDF model,
which means drawing in a view starts in the bottom-left. This
is what you see in geometry textbooks.
Sometimes it's easier to write drawing code if the origin
is in the top-left. This is how things work in web page design, for
example. Quartz calls this a flipped coordinate system.
You can easily convert points between standard and flipped views using
NSView's convertPoint:fromView: and convertPoint:toView: methods.
Rects as Objects
Because they're not objects, you can't store the geometry structs in an
NSArray, NSDictionary, or NSSet directly,
but you can wrap them in an NSValue object:
NSRect newRect = NSMakeRect ( 20, 20, 100, 100 );
NSValue * rectObject = [NSValue valueWithRect: newRect];
NSMutableArray * myArray = [NSMutableArray array];
[myArray addObject: rectObject];
NSRect originalRect = [[myArray objectAtIndex: 0] rectValue];
NSValue has similar methods for NSPoint and NSSize. You can also log information
about rects using the NSStringFromRect function:
NSRect newRect = NSMakeRect ( 20, 20, 100, 100 );
NSLog (@"%@", NSStringFromRect( newRect ));
Another function, NSRectFromString takes a properly-formatted rect
description and returns an NSRect. Both sets of functions also exist for
NSPoint and NSSize.
Derived Rects
Cocoa provides functions to create new rects based on
existing ones. Here's how to make a rect which has
the same dimensions as the original, but is shifted down and to the
right (offset).
// create a rect, then get the 5x5 offset
NSRect rect1;
rect1.origin.x = 0;
rect1.origin.y = 0;
rect1.size.width = 30;
rect1.size.height = 25;
NSRect rect2;
rect2 = NSOffsetRect ( rect1, 5, 5 );
You can use negative values for the offset if you want to
move in the opposite directions.
Here's how to get the intersection area of two rects:
// get the common area between two rects
NSRect rect1;
rect1.origin.x = 0;
rect1.origin.y = 0;
rect1.size.width = 30;
rect1.size.height = 25;
NSRect rect2;
rect2 = NSOffsetRect ( rect1, 5, 5 );
NSRect rect3;
rect3 = NSIntersectionRect ( rect1, rect2 );
Here's how to create a rect which encloses two
other rects (a union).
// get a combination of two rects
NSRect rect1;
rect1.origin.x = 0;
rect1.origin.y = 0;
rect1.size.width = 30;
rect1.size.height = 25;
NSRect rect2;
rect2 = NSOffsetRect ( rect1, 5, 5 );
NSRect rect3;
rect3 = NSUnionRect ( rect1, rect2 );
An inset rect is helpful if you want to create a outer boundry, then
create a rect for the content inside:
// get a contracted version of a rect
NSRect rect1;
rect1.origin.x = 0;
rect1.origin.y = 0;
rect1.size.width = 30;
rect1.size.height = 25;
NSRect rect2;
rect2 = NSInsetRect ( rect1, 5, 5 );
Comparing Rects and Points
Foundation provides a group of functions to check the equality of points and
rects, as well as functions to see if points and rects are inside
in other rects.
NSRect rect1;
rect1.origin.x = 0;
rect1.origin.y = 0;
rect1.size.width = 30;
rect1.size.height = 25;
NSPoint point1 = NSMakePoint ( 8,21 );
BOOL isInRect;
isInRect = NSPointInRect ( point1, rect1 );
Below is a table of the most useful comparison functions.
All of these functions return a YES or NO value.
NSEqualRects |
Are rects identical? |
NSEqualPoints |
Are points identical? |
NSEqualSizes |
Are sizes identical? |
NSContainsRect |
Does the first rect contain the other? |
NSIntersectsRect |
Do the rects at least partially overlap? |
NSPointInRect |
Is the point inside the rect? |
NSMouseInRect |
Is the mouse cursor in this rect? |
NSIsEmptyRect |
Is the rect empty (no area)? |
These functions are listed in Foundation's NSGeometry.h file.
Drawing
NSRects and NSPoints only describe geometry, they don't
actually do drawing. Let's look at some primitive drawing
functions in Cocoa's NSGraphics.h file.
NSColor * gray = [NSColor grayColor];
NSColor * white = [NSColor whiteColor];
// fill background
[gray set];
NSRectFill ( [self bounds] );
// fill target rect
NSRect rect1 = NSMakeRect ( 21,21,210,210 );
[white set];
NSRectFill ( rect1 );
The example above uses NSColor. When you call the -set method
on a color object, Quartz uses it for all drawing until you
set a new one.
Here's how to draw a border around a rect:
NSColor * gray = [NSColor grayColor];
NSColor * white = [NSColor whiteColor];
// fill background
[gray set];
NSRectFill ( [self bounds] );
// draw a border around target rect
NSRect rect1 = NSMakeRect ( 21,21,210,210 );
[white set];
NSFrameRectWithWidth ( rect1, 1 );
You can also call NSFrameRect if you just want to use the default
line width.
Drawing Groups
It's often faster to draw an array of rects all at once instead
of calling NSRectFill for each one individually.
Below is a more involved example which builds C-style arrays of
NSRects and NSColors, then passes both to NSRectFillListWithColors.
// setup basics
[[NSColor grayColor] set];
NSRectFill ( [self bounds] );
int count = 12;
NSRect startingRect = NSMakeRect ( 21,21,50,50 );
// create arrays of rects and colors
NSRect rectArray [count];
NSColor * colorArray[count];
rectArray [0] = startingRect;
colorArray[0] = [NSColor redColor];
// populate arrays
int i;
NSRect oneRect = rectArray[0];
for ( i = 1; i < count; i++ )
{
// move 100 pixels to the right
oneRect.origin.x += 100;
// if the right edge doesn't fit, move down 100 pixels
if ( NSMaxX (oneRect) > NSMaxX ([self bounds]) )
{
oneRect.origin.x = startingRect.origin.x;
oneRect.origin.y += 100;
}
rectArray [i] = oneRect;
// increment color
colorArray[i] = [NSColor colorWithCalibratedHue: (i*0.04)
saturation: 1
brightness: 0.9
alpha: 1];
}
// use rect and color arrays to fill
NSRectFillListWithColors ( rectArray, colorArray, count );
// draw a 2 pixel border around each rect
[[NSColor whiteColor] set];
for ( i = 0; i < count; i++) {
NSFrameRectWithWidth ( rectArray[i], 2 );
}
This example also uses the function NSMaxX to get the maximum x-axis
value of the rect (right edge). If it's outside of the view bounds, we move
down to the next row.
Wrap Up
We've covered basic Quartz concepts here. The next tutorial will dig
into intermediate topics. You can download the final example here:
IntroToQuartz Xcode 2.4 Project (52k)
Love it? Suggestions?
Send feedback on this tutorial.
For further reading, check out
Cocoa Graphics Part II
Copyright © 2004-2006 Scott Stevenson
Made with
TextMate