The Palm OS® Cobalt rendering model includes support for complex path and shape construction, arbitrary two-dimensional transformations, a state stack, color gradients, alpha blending, and anti-aliasing. You interact with this new drawing model using the Graphics Context functions.
Previous versions of Palm OS had a more simplistic drawing model with a substantially different idea of the drawing state. The functions for this old drawing model are supported in Palm OS Cobalt and work as before. This chapter focuses first on the new drawing model and how to work with it. Later, it describes some compatibility issues that you might find when porting an older application to Palm OS Cobalt.
Conceptual Overview
Basic Drawing Steps
Where and When to Draw
Compatibility with the Old Drawing System
Drawing Tips
Drawing bitmaps to the screen and creating bitmaps programmatically are closely related topics that are not covered in this chapter. See Chapter 9, "Working with Bitmaps."
Conceptual Overview
This section introduces you to the features of the new drawing model. It covers:
Graphics Context
Path-Based Drawing
Alpha Blending
Striking Rule
Graphics Context
The new drawing API does not draw directly to a window on the screen. Instead, it draws to a graphics context, which stores the current rendering state.
The Palm OS Cobalt rendering system has more control over the size and placement of windows and thus when to draw into them. When the rendering system needs to refresh the screen, it creates graphics contexts, tells all of the windows on the screen to update themselves, and then destroys the graphics contexts when all drawing is finished. Your application sees this request to draw as a frmUpdateEvent
. Thus, to guarantee that your window has a graphics context that is ready to accept drawing, your application should only draw in response to an frmUpdateEvent
.
For performance reasons, none of the Graphics Context functions perform any error checking on the parameters you pass. (Error checking is performed by the rendering system.) You must verify that you have a graphics context when you try to acquire one.
The rendering system runs in a different process than the application. To avoid IPC overhead, drawing operations are buffered and sent to the rendering system in batches. The rendering system sends its buffered commands at the end of each event loop. Because of this, drawing is likely not to have finished when a drawing function returns. In the rare cases where you need to wait for the IPC to complete before your application continues, there are two functions, GcFlush()
and GcCommit()
, that allow you to do so; however, they are slow and should be used only for debugging.
Path-Based Drawing
The new Graphics Context drawing functions behave very much like the Postscript language. Instead of drawing simple shapes directly to a window, you define a complex path and then paint it onto the screen. A path consists of shapes made out of lines, arcs, and curves. A single path can have numerous disjointed shapes in it. Each shape is called a subpath.
Think of drawing with these functions as painting a portrait. First you sketch lightly in pencil what you want to paint. Then you fill the sketch with paint.
The path has a notion of a current point. All drawing functions draw from the current point to the points you specify in the function.
To begin a path, you generally use GcMoveTo()
to define the starting point. All drawing functions that you call from then on connect to the end point of the previous call until you call GcMoveTo()
again. The GcMoveTo()
function is like lifting the pencil and moving it to the location you specify.
When you have defined the path and are ready to paint it onto the screen, you call GcPaint()
. GcPaint()
fills the shapes contained in the path onto the screen and then clears the path from the rendering state. It does not clear any other aspect of the rendering state.
By default, GcPaint()
expects to fill the current path. If you want GcPaint()
to trace the path, you should call GcStroke()
first. GcStroke()
turns the path definition into the outline of the shape rather than the shape itself. (This is called a stroked path.)
Alpha Blending
When you set the color in the rendering state, you may specify an alpha transparency level. An alpha level of 0 is fully transparent, and an alpha level of 255 is fully opaque.
If you specify an alpha level somewhere between fully transparent and fully opaque, the rendering system composites pixels in the path with the current value of those pixels on the screen using standard alpha blending. This is a change from earlier Palm OS releases, where any current drawing simply overwrote any previous drawing.
Striking Rule
The striking rule controls how the system determines which pixels get painted. The coordinate system and antialiasing both affect the striking rule.
You define a path in terms of coordinates. The coordinates you specify are floating-point values, allowing you to specify sub-coordinate boundaries. The border of a path is always interpreted to have 0 width. The rendering system draws the path using the coordinates you've specified, and then it looks at which pixels are covered by the path. For stroked paths, it takes the pen width into consideration. The pen width is drawn such that 50% of the width is on one side of the path and 50% is on the other side of the path.
As described in Chapter 1, "The Display," the coordinate system specifies how the values that you pass to the path definition functions are interpreted. The rendering model uses the current draw window's coordinate system by default, so you can set it to use standard coordinates, native coordinates, or a specific screen density.
Figure 8.1 shows the result of executing the code in Listing 8.1 for both standard coordinates and native coordinates on a single-density and a double-density display.
Listing 8.1 Drawing a triangle
GcMoveTo(gc, 1.0, 1.0); GcLineTo(gc, 1.0, 5.0); GcLineTo(gc, 6.0, 5.0); GcClosePath(gc); GcPaint(gc);
Figure 8.1 Standard and native coordinate systems

TIP: It is acceptable to specify coordinates that lie outside of screen boundaries. Any drawing outside of the current draw window's boundaries is simply clipped.
Antialiasing specifies whether the rendering system tries to smooth out rough lines. When antialiasing is off, the system decides whether to paint a pixel as follows: If the center point of the pixel falls within the line or shape being drawn, that pixel is painted with the current color. When antialiasing is on, each pixel that contains part of the shape gets a color, whether that pixel's center point is in the shape or not. The alpha is determined by how much of the shape is in the pixel. If only 10% of the shape is in the pixel, then the pixel gets a value equal to 10% of the color, and so on. This has the effect of making curved lines appear smoother (see Figure 8.2).

Basic Drawing Steps
This section introduces you to the basic things you need to know to work with the new Graphics Context API. Read it to get started.
Listing 8.2 shows a simple drawing example.
Listing 8.2 Drawing a button frame
GcHandle gc; case frmUpdateEvent: gc = GcGetCurrentContext(); if (gc) { GcSetColor(gc, 0, 0, 0, 255); GcRoundRect(gc, 0, 0, 50, 15, 3, 3); GcPaint(gc); GcReleaseContext(gc); } break;
The basic steps for drawing with the new functions are:
- Call
GcGetCurrentContext()
.This function obtains a handle to the graphics context that you pass to the rest of the functions.
- Set the rendering state.
- Define the path.
- Paint the path onto the screen.
- Call
GcReleaseContext()
.This function signals that you are done drawing and the graphics context memory can be freed. If you do not release the graphics context, your user interface will eventually freeze.
IMPORTANT: Do not change the draw window in between calls to
GcGetCurrentContext()
and GcReleaseContext()
.
The next several sections discuss these steps in more detail.
Setting the Rendering State
The rendering state contains the attributes listed in Table 8.1.
The color |
||
Determines how the coordinates you pass to the path definition functions are converted to pixels. The default is the current draw window's coordinate system. |
||
For stroked paths only, the width of each line drawn. The default is 1 coordinate. |
||
For stroked paths only, specifies what the ends of lines look like. The default is |
||
For stroked paths only, specifies what the connection between two lines looks like. The default is a bevel join ( |
||
For text paths only, the font in which the text is drawn. The default is the Palm OS plain scalable font. |
||
|
Specify changes to the coordinate system that affect subsequent path definitions. The default is no transformation. |
|
Specifies whether antialiasing is on or off. The default is off. |
||
Specifies whether antialiasing is on or off for text. The default is on. |
||
If enabled, characters are always drawn to exact pixel boundaries. The default is on. |
||
Specify changes to the text definition. The default is no transformation. |
||
Before changing the state, it is a good idea to preserve the current state with GcPushState()
. Remember that the path is part of the current state, so when you call GcPopState()
to restore the previous state, you'll clear any path definition you've just performed.
For performance reasons, you should specify the rendering state first, then define the path. Don't change the state in the middle of the path unless you are applying one of the transformations. The transformation is the only part of the state that should apply to individual points and not the path as a whole. It is best to leave most state settings alone unless you know that your application absolutely must have a specific value.
However, you should also be aware that some of the functions that draw standard user interface elements to the screen change the coordinate system and other aspects of the draw state. Therefore, it is a good idea to preserve the state before drawing standard user interface elements and pop it afterwards.
Defining a Path
As stated previously, a path is a series of connected or disconnected lines or shapes.
The following functions are path definition primitives:
-
GcMoveTo()
— Move the current point to the location. -
GcLineTo()
— Draw a straight line from the current point. -
GcClosePath()
— Draw a straight line from the current point to the start of the most recent subpath. This function is most often used to close the path so that it is a shape that can be filled. -
GcBezierTo()
— Draw a Bezier curve from the current point.
In addition, the graphics context defines the following convenience functions built from the primitives. Always use the convenience functions where possible. They are optimized for performance.
-
GcRect()
— Draws a rectangle. -
GcRoundRect()
— Draws a rectangle with rounded corners. -
GcRectI()
— Draws a rectangle using integer coordinates. -
GcArcTo()
— Draws an arc connected to the current point. -
GcArc()
— Draws an arc connected to the current point.
Painting the Path
When you have defined the path and are ready to paint it onto the screen, you call GcPaint()
.
By default, GcPaint()
expects to fill the current path with the current color. If you want to draw the outline of the path, you should call GcStroke()
first. GcStroke()
turns the path definition into the outline of the shape rather than the shape itself.
You must only paint closed paths. If you've defined an open path such as two lines connected to each other, use GcStroke()
to specify that these two lines are what you want filled. The results are undefined if the graphics context tries to fill an open path.
There are two special cases of painting paths to the screen: text and bitmaps.
If you want to paint text to the screen, you specify the path and paint it to the screen in a single function: GcDrawTextAt()
. This function clears any other path that you had defined previously, so it is best to call this outside of any other path definition.
"Displaying a Bitmap on the Screen" describes how to paint a bitmap to the screen.
Specifying Clipping Regions
The rendering system allows you to create a clipping region. When a clipping region is defined, GcPaint()
is constrained to painting only into that region. Any portions of the path outside of that region are discarded.
To specify a clipping region, do the following:
- Call
GcGetCurrentContext()
. - Call
GcBeginClip()
. - Call one or more of the path definition functions to specify the clipping region.
- Call
GcPaint()
, which fills the specified region with white. - Call
GcEndClip()
. - Call the path definition functions to create the path you want to draw.
- Call
GcPaint()
.This call toGcPaint()
only paints the areas of the path that are contained in the region you defined in Step 3. - Call
GcReleaseContext()
.
Figure 8.3 shows an example of specifying a diamond shaped clipping region and then drawing a pattern that is constrained to the screen.

The clipping region remains in effect until you call GcPopState()
. If you define a new clipping region before calling GcPopState()
, all painting is constrained to the intersection of those clipping regions. If you want to paint to two disjointed areas of the screen, include them both in the path before calling GcEndClip()
.
Clipping regions are usually rectangular, but they do not have to be. Although you can specify any complex shape as the clipping region, you should use a simple rectangle whenever possible. Doing so allows the system to perform some optimizations when rendering. Note that clipping regions are never antialiased.
Where and When to Draw
The draw window is the window that receives the painted pixels. Drawing may be performed into on-screen windows, off-screen windows, or bitmaps.
On-Screen Windows
As described in "Window Type", an on-screen window may be either an update-based window, a transitional window, or a legacy window. If you are drawing to an update-based window, you may only draw in response to a frmUpdateEvent
because that is the only time you are guaranteed to have a graphics context. For transitional windows, drawing only in response to update events is not a requirement, but it is a good idea to still try to follow this rule.
Off-Screen Windows
To perform double-buffering, you can create an off-screen window and draw to it. You may draw to off-screen windows at any time without producing an error.
Size can be an issue with off-screen windows. They allocate a region of memory proportional to the size of the window. You should ensure that the window's dimensions are only as large as necessary. For example, if all you want to do is draw a 10-pixel long horizontal black line, you can create a window with the width of 10, the height of 1, and the bit depth of 1.
After you create the off-screen window, set the draw window to be the off-screen window, and then obtain the graphics context and perform the drawing operations. See Listing 8.3.
Listing 8.3 Creating an off-screen window
WinHandle win; GcHandle gc; uint32_t depth = 16; WinPushDrawState(); //Set the bit depth. WinScreenMode(winScreenModeSet, NULL, NULL, &depth, NULL); // Create off-screen window using native format. WinSetCoordinateSystem(kCoordinatesNative); win = WinCreateOffscreenWindow(width, height, nativeFormat, &error); //Set the draw window, get the graphics context, and draw. WinSetDrawWindow(win); gc = GcGetCurrentContext(); if (gc) { //draw GcReleaseContext(gc); } WinPopDrawState();
Bitmaps
You can programmatically create a bitmap, draw into it, and then later paint it onto the screen or store it in a database for future use. See Chapter 9, "Working with Bitmaps," for more information.
Compatibility with the Old Drawing System
If you are porting a 68K-based application that does drawing, it probably uses the WinDraw.
.. or WinPaint
... functions (such as WinDrawLine()
or WinPaintLine()
).
The old window drawing functions are still supported for backward compatibility and work as you would expect.
The Graphics Context functions and the Window Manager drawing functions live in two separate worlds. They have separate notions of the drawing state. Do not mix the two. Do not use the functions described in "Window Drawing Functions and Macros" in between calls to GcGetCurrentContext()
and GcReleaseContext()
. Do not expect the Window Manager functions that set the draw state to affect the Graphics Context (see Listing 8.4).
Listing 8.4 Do not mix Window drawing and Graphics Context drawing
GcHandle gc; RGBColorType *textColor, *oldTextColor; textColor->r = 255; textColor->g = textColor->b = 0; // CAUTION! WinSetTextColoRGB() does not affect // GcDrawTextAt(). WinSetTextColorRGB(textColor, oldTextColor); gc = GetCurrentContext(); if (gc) { GcDrawTextAt(gc, 200, 200, "I am not red.", 15); }
In particular, be careful not to confuse WinSetCoordinateSystem()
and GcSetCoordinateSystem()
. If you call GcSetCoordinateSystem()
, it changes only the coordinate system used by the graphics context, not by the Window Manager. It is a good idea to always make both calls to be sure that the window's coordinate system is the same coordinate system used by the graphics context.
Note that it is perfectly acceptable to call Window Manager functions that do not set or depend on the draw state; for example, Window Manager functions that convert a set of coordinates from one system to another, such as WinConvertRectangle()
, are acceptable.
Making Library Calls
Sometimes, you may unintentionally mix the two drawing systems. For example, if you make a call to a library, you might not know how that library draws to the screen or even if it does. If you need to make a library call while you have the graphics context, preserve your state before you do so, and pop the state upon return. See Listing 8.5.
Listing 8.5 Making a library call
GcHandle gc = GetCurrentContext(); if (gc) { GcSetColor(gc, 50, 50, 50, 255); GcSetCoordinates(gc, kCoordinatesStandard); GcRect(gc, 10, 10, 10, 10); GcPushState(gc); // preserves path and state. // Call to library goes here. GcPopState(gc); // more drawing GcReleaseContext(gc); }
If you are calling Palm OS user interface functions, it is also a good idea to preserve the state before the call and pop it after the call. Some Palm OS functions change the graphics context state, in particular the coordinate system.
Color Table Compatibility
In earlier releases of Palm OS, the system supported screens that had color tables or palettes. Each entry in the color table specified a color that the screen could display.
All Palm OS Cobalt displays are direct color displays. Direct color displays do not rely on a color lookup table because the value stored into each pixel location specifies the amount of red, green, and blue components directly. For example, a 16-bit direct color display could have 5 bits of each pixel assigned as the red component, 6 bits as the green component, and 5 bits as the blue component. With this type of display, the application is no longer limited to drawing with a color that is in the color lookup table.
When the screen is a direct color display, the color lookup table for the screen is present only for compatibility with the old indexed mode color calls. The lookup table has no effect on the display hardware, since the hardware derives the color from the red, green, and blue bits stored in each pixel location of the frame buffer.
You can still change the color table used by the current draw windows with the WinPalette()
function. However, WinPalette()
no longer affects what is already drawn onto the screen. It only affects future drawing.
Drawing Tips
This section provides the following tips for efficient use of the Graphics Context API:
Draw to Exact Pixel Boundaries
Reuse the Current Path
Take Advantage of Winding Rule
Avoid Antialiasing
Avoid Alpha Blending
Efficient Cap and Join Modes
Avoid Transformations
Avoid Flushing the Buffer
Draw to Exact Pixel Boundaries
If you are drawing user interface items, your drawing will look better if it takes up entire pixels rather than partial pixels. Generally, using whole integer coordinates helps limit the number of partial pixels for filled paths. For stroked paths, you must take the pen width into account and make sure that the majority of the pen is on whole pixel boundaries. Remember that 50% of the pen width lies on one side of your path and 50% on the other side. If the pen width is odd (for example 1 pixel), then you should specify coordinates that end in 0.5. If the pen width is even, you can use whole integer coordinates.
Suppose you have the code shown in Listing 8.6 to draw a rectangle frame. This may result in a frame that is one pixel off from where you expect, as shown in the top two rectangles in Figure 8.4. The rendering system paints whichever pixels have their center points covered. The hardware determines which exact pixels are chosen, the ones on the left side of your path or the ones on the right. If antialiasing is turned on, the effect is even worse. None of the pixels are wholly part of the path, so they all get only a percentage of the color.
Listing 8.6 Drawing to exact pixel boundaries (incorrect)
GcHandle gc = GetCurrentContext(); if (gc) { GcSetColor(gc, 255, 255, 255, 255); GcSetPenSize(gc, 1.0); GcSetJoin(gc, kMiterJoin, 1); //BAD CODE! This may result in a rect one pixel off // from where you expect GcRect(gc, 4.0, 2.0, 10.0, 7.0); GcStroke(gc); GcPaint(gc); GcReleaseContext(gc); }
Figure 8.4 Stroked paths with odd pen width

If you change your rectangle coordinates so that they all end in 0.5 as shown in Listing 8.7, the pen draws on exact pixel boundaries, and you'll get the desired rectangle regardless of the antialiasing setting, as shown in the bottom two rectangles of Figure 8.4.
Listing 8.7 Drawing to exact pixel boundaries (correct)
GcHandle gc = GetCurrentContext(); if (gc) { GcSetColor(gc, 255, 255, 255, 255); GcSetPenSize(gc, 1.0); GcSetJoin(gc, kMiterJoin, 1); GcRect(gc, 4.5, 8.5, 10.5, 13.5); GcStroke(gc); GcPaint(gc); GcReleaseContext(gc); }
If the pen width is even, however, it is better to specify whole integer coordinates. Then the system paints all of the pixels on the left side of your path and all of the pixels on the right side of your path as shown in the top two rectangles of Figure 8.5. When the pen width is even, the path looks bad when you specify sub-pixel coordinates.
Figure 8.5 Stroked paths with even pen width

Reuse the Current Path
The current path is stored as part of your drawing state and is cleared by GcPaint()
. You can take advantage of this to perform multiple operations on this path without having to redefine it each time. For example, Listing 8.8 shows an efficient way to fill and then outline a rounded rectangle.
void DrawOutlinedRoundRect(GcHandle ctxt) { // First fill the rectangle, pushing it on the state // stack so we can recover it after painting. GcSetAntialiasing(ctxt, kEnableAntialiasing); GcSetColor(ctxt, 255, 0, 0, 255); GcRoundRect(ctxt, 0, 0, 10, 10, 3, 3); GcPushState(ctxt); // For even more efficient code, turn off antialiasing. GcSetAntialiasing(ctxt, kDisableAntialiasing); GcPaint(ctxt); // Now restore the previous rectangle, stroke to make // its outline and paint that path. GcPopState(ctxt); GcSetColor(0, 255, 0, 255); GcStroke(ctxt); GcPaint(ctxt); }
Take Advantage of Winding Rule
Palm OS Cobalt version 6.0 uses the odd winding rule. It is more efficient to take advantage of the winding rule than it is to define complex clipping regions. In addition, the winding rule supports antialiasing, and the clipping region does not.
The winding rule determines which points are part of the result when some parts of the path enclose other parts of it. Each point is assigned a winding number based on its location in relation to the the parts of the path. On Palm OS, you draw counterclockwise to decrease the winding number, and draw clockwise to increase it.
The odd winding rule specifies that only points with an odd winding number are considered part of the result. All other points are not part of the result.
Figure 8.6 shows a path that is affected by the winding rule. A triangle drawn counter clockwise is partially intersected by a square drawn clockwise, which wholly contains a a circle that is also drawn clockwise.
Figure 8.6 Winding rule effects

Winding numbers are determined starting from the top-left corner moving toward the bottom right. Because the triangle is drawn counterclockwise, it decrements the winding number so that points only within the triangle have a winding number of –1. Because the square is drawn clockwise, it increments the winding number. Therefore, the intersection of the square and the triangle has a winding number of 0 and points only within the square have a winding number of 1. Points within the circle are either 1 or 2.
The odd winding rule specifies that all points with an odd winding number are part of the result and should be painted. As you can see in Figure 8.6, those portions with a 0 or 2 winding number are not painted.
Figure 8.7 shows a path that is a series of squares and circles that enclose one another. All squares are drawn counterclockwise, and all circles are drawn clockwise. Because of this, all points that are not part of the intersection of a square and its immediately enclosed circle have a winding number of –1, but all points that are part of the the circle have a winding number of 0.
Figure 8.7 Winding rule effects

If you intend to take advantage of the winding rule to draw complex shapes, you should note the following:
- All
GcRect()
functions draw clockwise. If you want to draw a rectangle counterclockwise, useGcLineTo()
to construct it. -
GcArc()
always draws clockwise. There is currently no way to draw a counterclockwise arc. - Stroked paths are drawn clockwise. If a stroked path intersects with itself, the intersection has an even winding number and is therefore not painted.
- In the discussions and figures above, the terms "clockwise" and "counterclockwise" are from your point of view as you are drawing to or looking at the screen. Mathematically speaking, drawing clockwise first increases the y value as you move from the origin and counterclockwise drawing decreases the y value. On Palm OS screens, the origin is in the top-left corner, and the y value increases downward. Therefore, drawing that looks counterclockwise to you is actually clockwise.
If you're familiar with the OpenGL or PostScript languages, you may know that the winding number is increased on a counterclockwise path and decreased on a clockwise one. Palm OS follows this same rule if you use the mathematical definitions of clockwise and counterclockwise.
To avoid confusion, you can do a reflection and translation transformation to move the origin to the lower-left corner of the screen before doing any drawing that is affected by the winding rule.
Avoid Antialiasing
Because antialiasing is a significant performance drain, you should disable it if you know you are not going to need it.
For example, Listing 8.8 fills and then outlines a rectangle. It turns off antialiasing for the first path. The outline will cover the edges of the filled path, so there is no need to antialias the filled path.
Avoid Alpha Blending
To perform alpha blending, the rendering system must first read the value of the destination pixels and then blend with the requested color. This can be expensive. Use opaque figures where possible. Note that antialiasing generally uses alpha blending.
Efficient Cap and Join Modes
You should use kBevelJoin
and kButtCap
when drawing lines for which you don't have a particular preference about the join and cap styles. When using these styles, the system can optimize the stroking of simple figures to avoid full stroking calculations.
Avoid Transformations
Simple translation transformations are significantly more efficient than general transformations; scaling (with or without translation) transformations are not as efficient but still allow many internal optimizations to occur. In general, the more complicated the transformation (translation being the least complicated, then scaling, and rotation being the most complicated), the more the system will have to go through the full general drawing model instead of being able to use a more optimized code path.
Avoid Flushing the Buffer
As stated in "Graphics Context," the rendering system runs in a different process than the application. To reduce IPC overhead, drawing operations are buffered and sent to the rendering system in batches. The rendering system sends its buffered commands at the end of each event loop.
The functions GcCommit()
and GcFlush()
break this buffering, causing all currently buffered commands to be sent immediately. In addition, GcCommit()
waits for those commands to finish drawing. GcCommit()
and GcFlush()
can be extremely slow, so it is best to avoid calling them.
Most applications should flush the buffer only during debugging. Remove these calls before you release the application.
You might also flush the buffer if you know that your application is about to perform a lengthy operation that might delay it from reaching the end of the event loop.
Remember that all drawing to off-screen windows or to bitmaps is performed immediately, so there is no need to call GcCommit()
and GcFlush()
in those cases.
Summary of Drawing Functions
Obtaining a context |
|
|
|
Setting the State |
|
|
|
Path Construction |
|
Setting the Clipping Rectangle |
|
|
|
Flushing the buffer |
|
|