Thornton 2 dot Com
1K512

GEOS Graphics

Ariel's GEOS Programmer's Reference Guide
Back: Preface Up: Contents Next: GEOS Kernal Routines
GEOS Development
Back: GEOS Development Up: GEOS Development Next: GEOS: Environment

This page is still a work in progress.

Table of Contents

Graphics Overview

The most obvious programming feature is graphics. GEOS uses bitmap graphics modes and provides a number of graphics routines to access the screen in a standardized platform-independent way.

However, GEOS treats the screen as a monochrome bitmap and has no routines for colors. The default color theme is black pixels on a white background or dark grey pixels on a light grey background, resembling ink on paper.

Although GEOS and GEOS 128 run on color computers, neither edition provides any access to color hardware. If you want your program to use any colors beyond the defaults set by Preference Manager, you have to access the hardware yourself through the VIC-II (on the 64) or VDC (on the 128).

The Virtual Screens

GEOS uses two virtual screens, one foreground and one background, for display buffering. The foreground screen is fed as directly as possible to the display hardware. The GEOS Kernal uses the background screen as a buffer for overwriting the foreground screen with temporary GUI controls such as menus and dialog boxes. Virtually all of the GEOS graphics routines let you draw and erase on either screen, or on both screens at the same time, and there are routines for imprinting parts of the foreground onto the background and for recovering parts of the background onto the foreground.

GEOS uses a left-handed Cartesian coordinate system for pixels: The horizontal axis, X, increases one pixel at a time to the right; the vertical axis, Y, increases one pixel at a time downward; and the origin coordinate, (0,0), is the pixel in the upper-left corner of the screen.

For patterns, bitmap images, and bitmap compression, GEOS treats the screen as a linear bitmap: Each byte is a row of 8 pixels side-by-side, and each byte is lined up side-by-side, filling a single row one pixel high before starting the next row of pixels. When pixels need to be set within a byte, the high bit (7) is the left-most pixel of the 8, and the low bit (0) is the right-most pixel of the 8.

The GEOS Kernal takes care of translating this simple coordinate system to the different and not-so-straightforward layouts of graphics memory on each platform.

Display Buffering and dispBufferOn

GEOS uses the variable dispBufferOn to determine whether your program draws on the foreground screen, the background screen, or both. Set bits in dispBufferOn according to the screen you want to draw on.

Applications will typically want to draw on both screens except when printing, so that recovering from menus and dialog boxes restore graphics data the user expects. Desk accessories, however, typically want to limit drawing to the foreground screen only.

There may be times when you want your program to draw only on the background screen. Pre-drawing complex objects on the background screen while the user studies the foreground screen is one such example.

Internally, GEOS draws menus and dialog boxes only on the foreground screen, relying on the background screen to hold a copy of what it draws over in the meantime. Since dialog boxes only appear when your program calls DoDlgBox and menus are only opened when GEOS is in MainLoop, your program has a bit of control over how GEOS recovers graphics data to the foreground screen. If your program must return to MainLoop while the screens contain different data, your program can temporarily disable the menu by clearing MENUON_BIT in mouseOn before returning, then setting MENUON_BIT in mouseOn when GEOS gives control back.

The Hitchhiker's Guide to GEOS cautions that when dispBufferOn is set for drawing on both screens simultaneously, both screens must contain the same graphics data or the results of GEOS's graphics routines will be unpredictable. It cautions that when the foreground and background screens contain different data and you want to draw on both, you must draw twice: once on the foreground screen only and again on the background screen only.

Imprinting and Recovering the Screens Manually

Graphics data can be copied from either screen to the other at any time by a routine. GEOS calls the copy from foreground to background "imprinting" and the copy from background to foreground "recovering." GEOS provides graphics routines for imprinting and recovering pixels, lines, and rectangular regions.

Using the Background Screen As Extra RAM

If you find your application needing more RAM than is typically reserved, you can steal the background screen buffer as extra RAM. However, you will need to make sure that GEOS and other routines in your program don't draw on it by accident. In your program, clear ST_WR_BACK in dispBufferOn and keep that bit cleared.

You will also have to override how GEOS recovers screen areas after menus, dialog boxes, and desk accessories. When GEOS recovers the screen, it vectors through RecoverVector. Normally, this vector points to RecoverRectangle, but you will have to point it to your own screen recovery routine instead.

There are a number of challenges involved in preventing the background screen area from being overwritten with foreground screen graphics data, corrupting whatever code or data you use the area for. You can expect seemingly random system crashes and data corruption if your debugging isn't thorough enough.

SaveFG and RecoverFG

Apple GEOS includes the routines SaveFG and RecoverFG to save and recover the foreground screen using an arbitrary buffer instead of the background screen.

The Hitchhiker's Guide to GEOS notes that BSW applications for GEOS and GEOS 128 implement these routines internally. You may wish to implement your own routines if you just want a graphics data buffer smaller than the whole background screen.

Dialog Boxes

You will have to take special care with dialog boxes. Although they're the easiest to account for because their display is entirely under your program's control, GEOS draws them with an offset drop-shadow effect. You can easily compute a dialog box's size from its table, and you can buffer the screen region elsewhere if you want, but you also have to account for the drop-shadow region below and to the right of the box proper.

Additionally, when GEOS removes the dialog box, it vectors through RecoverVector twice: once to recover the rectangle occupied by the dialog box proper, and again to recover the rectangle drawn to form the drop shadow effect. If your screen recovery routine is computationally intensive each time it's called, you will have to figure out a way to detect when it's recovering from a dialog box, put the two regions together, and perform the recovery during only one of those calls.

One way to accomplish this is by installing a dialog box-specific routine in RecoverVector just before calling DoDlgBox, then reinstalling your general screen recovery routine just after. The specific routine should use a word and a byte of RAM to store some data:

  1. The routine calling DoDlgBox sets these to $ffff, $ff first.
  2. The dialog box-specific recovery routine checks them. If they're $ffff, $ff, then it knows this is the first call.
    1. The routine should replace them with the values of r3 and r2L, the X and Y coordinates of the dialog box proper's upper-left coordinate.
    2. The routine returns without doing anything.
  3. If the values are not $ffff, $ff, then it knows this is the second call.
    1. The routine should load r3 and r2L with the values saved during the first call, replacing the values called with, the X and Y coordinates of the drop shadow's upper-left coordinate.
    2. The routine calls your general screen recovery routine, recovering a single rectangle covering both boxes.

If your screen recovery routine takes the same amount of time redrawing the whole foreground screen as it does redrawing only a part of it, then you can also avoid doubling the time by:

  1. Installing a null pointer into RecoverVector,
  2. Calling DoDlgBox as normal,
  3. Preparing to call RecoverRectangle,
  4. Reinstalling your screen recovery routine into RecoverVector, and
  5. Calling your screen recovery routine, either directly or through RecoverVector with CallRoutine.

Menus

Menus are more challenging because they're invoked during MainLoop, not under direct program control. The Hitchhiker's Guide to GEOS notes that a possible workaround allowing your program to save the menu area in a small buffer is by taking advantage of dynamic submenus. When the user clicks on a dynamic submenu, GEOS calls the dynamic submenu's handler routine before showing the menu, and your routine can take this time to save screen graphics data while generating the submenu's dynamic contents.

Like with dialog boxes, GEOS vectors through RecoverVector potentially more than once when erasing submenus. The order is the reverse of the menu's display order. For example, if your menu structure has a main menu with a font submenu, each with a point size sub-submenu, and the user clicks to there, then GEOS will vector once to erase the point size sub-submenu and again to erase the font submenu.

The Screen Bounds

GEOS does not do bounds checking with screen coordinates; your programs must do that themselves. If you feed the GEOS Kernal screen coordinates outside the limits, then the results will be unpredictable and could result in program code corruption similar to stack, heap, and buffer overflows.

The maximum number of pixels in each dimension depends on the platform and GEOS mode. The geosSym file in geoProgrammer provides the constants SC_PIX_WIDTH and SC_PIX_HEIGHT to make bounds-checking platform-agnostic. The screen dimensions in GEOS (and the values of SC_PIX_WIDTH and SC_PIX_HEIGHT respectively) are:

The four corners of the screen are:

Note that the highest addressable pixel coordinate is one less than the number of pixels in that dimension. For example, if the screen is 320 pixels wide, then the right-most X coordinate is 319.

Coordinates Are Inclusive

Every GEOS graphics routine includes the coordinates of each endpoint within the object it draws. Line routines draw both of the pixels given, not just the pixels in between, and rectangle routines draw vertical lines through pixels at both X coordinates and horizontal lines through pixels at both Y coordinates.

Cards As Screen Subdivisions

Some GEOS Kernal routines divide the screen into 8x8 regions called cards. These are identical in size and structure to character cells and 8-byte graphics regions in Commodore 64 graphics and character memory.

The 40-column Commodore screen is 40 cards to a row and 25 card rows high, the 80-column Commodore screen is 80 cards to a row and 25 card rows high, and the Apple II screen is 70 cards to a row and 24 card rows high.

Cards are always aligned to 8-pixel boundaries. Card width, card height, and card position are also units some routines use. Converting pixel coordinates to card coordinates is a simple matter of dividing by 8 and rounding down, or three logical shifts right. Converting pixel widths and heights to card widths and heights is the same but rounding up, or three logical shifts right and adding 1.

The Hitchhiker's Guide to GEOS offers the following code snippet as an example of pixel to card conversion:


************************************************************************
;MseToCardPos:
;converts current mouse positions to card positions
;
;Pass:        nothing
;
;Uses:        MouseXPos, MouseYPos
;
;Returns:     r0L    mouse card x-position (byte)
;             r0H    mouse card y-position (byte)
;
;Destroys:    a,x,y
;                                                              (mgl)
************************************************************************
MseToCardPos:
       php                   ; save current interrupt disable status
       sei                   ; disable IRQs so mouseXPos doesn't change
       MoveW  MouseXPos,r0   ; copy mouse x-position to zp work reg (ro)
       plp                   ; reset interrupt status asap.
       ldx    #r0            ; divide x-position (r0) by 8
       ldy    #3             ; (shift right 3 times)
       jsr    DShiftRight    ; this gives us the card x-position in r0L
       lda    MouseYPos      ; get mouse y-position
       lsr    a              ; and shift right 3 times
       lsr    a              ; which is a divide by 8
       lsr    a              ; and gives us the card y-position in a
       sta    r0H            ; set down card y-position
       rts                   ; exit

geoProgrammer 128 Notes

The geosSym file in geoProgrammer 128 also provides width and height maximums specific to each screen mode in GEOS 128: SC_40_WIDTH and SC_40_HEIGHT for 40-column mode, and SC_80_WIDTH and SC_80_HEIGHT for 80-column mode.

In geoProgrammer 128, the actual values of SC_PIX_WIDTH and SC_PIX_HEIGHT depend on the values of the C64 and C128 constants.

If C64=$00 and C128=$01, indicating that your program is designed to run only on GEOS 128, then the constants have the 80-column values.

If C64=$01 and C128=$01, indicating that your program is designed to run on both GEOS and GEOS 128, then the constants have the 40-column values.

Detect GEOS 128

The Hitchhiker's Guide to GEOS offers the following code snippets to detect whether your program is running on GEOS or GEOS 128:


.if (0)
************************************************************************
Check128:
       Check for GEOS 128.

Pass:
       nothing

Returns:
       st     minus flag set if running under GEOS 128.

Example usage:

       jsr    Check128
       bpl    10$          ;ignore if under GEOS 64
       jsr    DoDoubling   ;else, patch x-coordinates with doubling bits
10$:

************************************************************************
.endif

Check128:
       lda    #$12         ; c128Flag not guaranteed to be valid in
                           ; versions 1.2 and lower
       cmp    version      ; first see if version <= 1.2
       bpl    10$          ; if so, branch and say C64. Note this is a
                           ; signed comparison. (it WILL NOT work if
                           ; GEOS goes beyond version $7f!)
       lda    c128Flag     ; else set minus based on high bit c128Flag
10$:
       rts

It also says you can check graphMode to determine whether GEOS 128 is in 40-column or 80-column mode:


       bit    graphMode    ; check 40/80 mode bits
       bpl    C64Mode      ; branch if in 40-column mode
                           ; else, handle as 80-column...

40-Column X-Axis Doubling in 80-Column Mode

The GEOS Kernal in GEOS 128 provides the routine NormalizeX to automatically double X-coordinate values so that your program can have the same aspect ratio in 80-column mode as in 40-column mode. GEOS graphics routines use it automatically, but it helps explain how GEOS handles 40-column graphics in 80-column mode.

Machine Dependencies

The GEOS Kernal's graphics routines hide much of the computer's graphics hardware from you, allowing you to write a program that will run on computers with different graphics hardware without significant changes.