Thornton 2 dot Com
1K617

GEOS Graphics

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


GEOS Development
Back: First Up: GEOS Development Next: GEOS: Environment (WIP)

Table of Contents

Common Graphics

The most obvious GEOS 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 scheme is black pixels on a white background or dark grey pixels on a light grey background, resembling ink on paper.

Although all editions of GEOS run on color computers, none provide any interfaces 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 Commodore 64), the VDC (on the Commodore 128), or the user's color monitor (on the Apple IIe).

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 image and screen as a linear bitmap: Each byte is a row of 8 pixels side-by-side, and bytes are lined up from left to right to fill up a single row of pixels before starting the next row of pixels. Within each byte, each bit from high to low (7 to 0) corresponds to each pixel from left to right.

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 screens at the same time. 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, should 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 renders 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.

When dispBufferOn is set for drawing on both screens simultaneously, both screens should contain the same graphics data. If they don't, then the results of GEOS graphics routines could be accidental corruption of one screen or the other. When the foreground and background screens contain different data and you want to draw on both, you should 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.

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 cooruption 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:

Platform Width (X) Height (Y)
Commodore 64 and 128 in 40-column mode 320 200
Commodore 128 in 80-column mode 640 200
Apple IIe, IIc, and IIgs 560 192

The four corners of the screen are:

Corner ( X Coordinate , Y Coordinate )
Upper left: ( 0 , 0 )
Upper right: ( SC_PIX_WIDTH-1 , 0 )
Lower left: ( 0 , SC_PIX_HEIGHT-1 )
Lower right: ( SC_PIX_WIDTH-1 , SC_PIX_HEIGHT-1 )

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. Live 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 8-by-8 regions called cards. They are identical in size, structure, and alignment 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 (r0)
       plp                   ; restore interrup 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

Points

The simplest graphics drawing and reading routines are with pixels, or points on the screen. GEOS has two routines: DrawPoint for drawing or erasing a pixel, and TestPoint for reading a pixel.

Lines

The next step up from points is lines. GEOS has five routines for drawing horizontal and vertical lines and one routine for drawing lines at any angle: HorizontalLine, VerticalLine, InvertLine, ImprintLine, RecoverLine, and DrawLine.

Both HorizontalLine and VerticalLine take a one-byte bitmap pattern so you can draw solid, erasing, or dashed lines. The bitmap pattern is drawn in order from bits 7 to 0, left to right or top to bottom. InvertLine inverts a horizontal line. ImprintLine and RecoverLine copy a horizontal line to or from the background screen. DrawLine uses Bresenham's line drawing algorithm to draw, erase, or recover a reasonable approximation of a straight line between two arbitrary points. Because of how the screen is arranged in memory, drawing horizontal lines is generally much faster than drawing vertical lines (up to eight times), and drawing lines at arbitrary angles is generally slower than drawing vertical lines.

Rectangles

In GEOS, rectangles are always aligned on the horizontal and vertical axes. GEOS has five main routines for drawing rectangles, inline versions of four of them, and one routine for controlling how rectangles are filled. The rectangle drawing routines are Rectangle, FrameRectangle, InvertRectangle, ImprintRectangle, and RecoverRectangle. The inline versions are i_Rectangle, i_FrameRectangle, i_ImprintRectangle, and i_RecoverRectangle. The fill pattern routine is SetPattern. Additionally, Apple GEOS has the routines GetPattern and SetUserPattern for managing and changing fill patterns.

Bitmaps

Bitmaps are pictures representing part of a screen image. The GEOS Kernal has three routines for displaying bitmaps on the screen: BitmapUp, BitmapClip, and BitOtherClip. All three routines let you display bitmaps at any pixel on the Y axis, but the X axis is restricted to card boundaries (every 8 pixels). Bitmap widths are identically restricted on the X axis.

The Apple GEOS Kernal has three additional bitmap display routines without the card alignment restriction: NewBitUp, NewBitClip, and NewBitOtherClip.

In the context of these routines, bitmap images are hardware-agnostic linear pixel descriptions, not hardware-specific display memory maps. Bitmaps describe pixels 8 at a time left to right across the row, then each row one at a time from top to bottom. In each bitmap byte, pixels are described left to right from bit 7 to bit 0.

Although this is what bitmaps are, GEOS expects bitmaps to be compacted according to a simple run-length encoding scheme. When writing geoProgrammer source code in geoWrite, photo scraps pasted into the geoWrite document are saved automatically in this compacted format, ready for all Kernal routines taking bitmaps.

Compacted bitmaps are divided into three types of byte groups or packets based on the value of the packet's first byte:

Value Hex Count Packet Summary
0 $00 Reserved.
1-127 $01-$7f Value - 0 Repeat Next byte is repeated Count times.
128 $80 Reserved.
129-219 $81-$db Value - 128 Unique Next Count bytes are used verbatim.
220 $dc Reserved.
221-255 $dd-$ff Value - 220 Bigcount Next byte is BigCount value, and following Count bytes are a sequence of packets to repeat BigCount times.

The Bigcount packet must contain at least one other packet, which can be any number and combination of Repeat and/or Unique packets. However, Bigcount packets can't contain other Bigcount packets, only Repeat and Unique packets. There's only one turtle for the bitmap to stand on, as it were.

Bitmap Compaction

Bitmap compaction is basically a matter of identifying byte sequences in a linear bitmap stream that are repeated, transforming them into Repeat or Bigcount packets, and transforming every remaining byte sequence into Unique packets.

Repeat packets are limited to 127 repeats of a byte, Unique packets are limited to runs of 91 bytes, and Bigcount packets are limited to containing 34 bytes of packets.

It's possible that a linear bitmap stream will contain byte sequences of such uniqueness that none of them can be encoded in Repeat or Bigcount packets, and all of them must be encoded in Unique packets. This rare scenario will result in a compacted bitmap occupying more bytes than its uncompacted form, as every run of up to 91 bytes needs an extra byte prepended to it identifying it as a Unique packet. If you automate bitmap compaction, your buffer for the compacted bitmap should be at least the length of the uncompacted bitmap plus 1/64th that length in case this compaction resistance happens.

The Hitchhiker's Guide to GEOS is one of very few references (the only reference?) offering a bitmap compaction routine. The HHGG's bitmap compaction strategy is transcribed in a separate page.

Bitmap Decompaction

The Hitchhiker's Guide to GEOS is one of very few references (the only reference?) demonstrating bitmap decompaction. While the HHGG gives a decompaction walkthrough demonstrating the theory of each packet's use, it doesn't use an actual image as an example. This guide uses an actual image as a decompaction walkthrough.

Given a compacted bitmap that happens to be 82 bytes:

WrenchBtnIcon:
    .byte $05, $ff, $81, $fe, $e2, $02, $81, $80, $04, $00
    .byte $81, $03, $93, $87, $c0, $00, $00, $07, $c3, $8c
    .byte $60, $00, $00, $0e, $63, $9f, $30, $00, $00, $1f
    .byte $f3, $81, $04, $ff, $8e, $03, $80, $f8, $00, $00
    .byte $3e, $03, $80, $ff, $ff, $ff, $fe, $03, $81, $04
    .byte $ff, $94, $03, $9f, $f0, $00, $00, $1f, $f3, $8f
    .byte $e0, $00, $00, $0f, $e3, $87, $c0, $00, $00, $07
    .byte $c3, $80, $04, $00, $81, $03, $06, $ff, $81, $7f
    .byte $05, $ff

And knowing that it decompacts to a 96-byte picture 6 cards wide by 16 lines tall:

Wrench Button Icon (native resolution)

Wrench Button Icon (magnified 4X)

GEOS doesn't know the picture's dimensions from the compacted bitmap data alone. When your program calls a bitmap display routine, it passes the picture's dimensions explicitly. From these dimensions, GEOS learns how many bytes the compacted bitmap will decompact into and fill. Given the dimensions separately, the bytes decode as follows.

$05, $ff

The first byte, $05, is between $01 and $7f, indicating this is a Repeat packet. The next byte, $ff, is repeated 5 times. This is the first 5 bytes of the first line:

######## ######## ######## ######## ########           $ff $ff $ff $ff $ff

Next:

$81, $fe

The byte starting the next packet, $81, is between $81 and $db, indicating this is a Unique packet. $81 minus $80 (or 129 minus 128) is 1, meaning that the next byte, $fe, is used verbatim. This completes the first line:

xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx #######.  $ff $ff $ff $ff $ff $fe

Next:

$e2, $02, $81, $80, $04, $00, $81, $03

The byte starting this packet, $e2, is between $dd and $ff, indicating this is a Bigcount packet. $e2 minus $dc (or 226 minus 220) is 6, indicating that the 6 bytes following the next are packets that will be repeated. The next byte, $02, indicates that they will be repeated twice.

The first of the six bytes, $81, indicates a Unique packet, and the next $81 - $80 = 1 byte is used verbatim: $80. The first of the remaining four bytes, $04, indicates a Repeat packet, and the next byte is repeated 4 times: $00 becomes $00 $00 $00 $00. The first of the remaining two bytes, $81, indicates a Unique packet, and the next $81 - $80 = 1 byte is used verbatim: $03. The Bigcount packet decompacts to $80 $00 $00 $00 $00 $03, and this is repeated twice, forming the second and third lines:

xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxx   $ff $ff $ff $ff $ff $fe
#....... ........ ........ ........ ........ ......##  $80 $00 $00 $00 $00 $03
#....... ........ ........ ........ ........ ......##  $80 $00 $00 $00 $00 $03

Next:

$93, $87, $c0, $00, $00, $07, $c3, $8c, $60, $00,
$00, $0e, $63, $9f, $30, $00, $00, $1f, $f3, $81

The byte starting this packet, $93, is between $81 and $db, indicating this is a Unique packet, and the next $93 - $80 (147 - 128) = 19 bytes are used verbatim. These 19 bytes represent themselves uncompacted and fill the next three lines and one byte of the fourth.

xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxx   $ff $ff $ff $ff $ff $fe
x                                                  xx  $80 $00 $00 $00 $00 $03
x                                                  xx  $80 $00 $00 $00 $00 $03
#....### ##...... ........ ........ .....### ##....##  $87 $c0 $00 $00 $07 $c3
#...##.. .##..... ........ ........ ....###. .##...##  $8c $60 $00 $00 $0e $63
#..##### ..##.... ........ ........ ...##### ####..##  $9f $30 $00 $00 $1f $f3
#......#                                               $81

Next:

$04, $ff

This packet byte, $04, is between $01 and $7f, indicating a repeat packet, and that the next byte, $ff, is repeated four times: $ff becomes $ff $ff $ff $ff:

xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxx   $ff $ff $ff $ff $ff $fe
x                                                  xx  $80 $00 $00 $00 $00 $03
x                                                  xx  $80 $00 $00 $00 $00 $03
x    xxx xx                              xxx xx    xx  $87 $c0 $00 $00 $07 $c3
x   xx    xx                            xxx   xx   xx  $8c $60 $00 $00 $0e $63
x  xxxxx   xx                          xxxxx xxxx  xx  $9f $30 $00 $00 $1f $f3
x      x ######## ######## ######## ########           $81 $ff $ff $ff $ff

Next:

$8e, $03, $80, $f8, $00, $00, $3e, $03, $80, $ff,
$ff, $ff, $fe, $03, $81

This packet byte, $8e, is between $81 and $db, indicating a Unique packet, and the next $8e - $80 (142 - 128) = 14 bytes are used verbatim:

xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxx   $ff $ff $ff $ff $ff $fe
x                                                  xx  $80 $00 $00 $00 $00 $03
x                                                  xx  $80 $00 $00 $00 $00 $03
x    xxx xx                              xxx xx    xx  $87 $c0 $00 $00 $07 $c3
x   xx    xx                            xxx   xx   xx  $8c $60 $00 $00 $0e $63
x  xxxxx   xx                          xxxxx xxxx  xx  $9f $30 $00 $00 $1f $f3
x      x xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx ......##  $81 $ff $ff $ff $ff $03
#....... #####... ........ ........ ..#####. ......##  $80 $f8 $00 $00 $3e $03
#....... ######## ######## ######## #######. ......##  $80 $ff $ff $ff $fe $03
#......#                                               $81

Next:

$04, $ff

This packet is identical to the second preceeding packet: the following byte, $ff, is repeated 4 times, expanding to $ff $ff $ff $ff:

xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxx   $ff $ff $ff $ff $ff $fe
x                                                  xx  $80 $00 $00 $00 $00 $03
x                                                  xx  $80 $00 $00 $00 $00 $03
x    xxx xx                              xxx xx    xx  $87 $c0 $00 $00 $07 $c3
x   xx    xx                            xxx   xx   xx  $8c $60 $00 $00 $0e $63
x  xxxxx   xx                          xxxxx xxxx  xx  $9f $30 $00 $00 $1f $f3
x      x xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx       xx  $81 $ff $ff $ff $ff $03
x        xxxxx                        xxxxx        xx  $80 $f8 $00 $00 $3e $03
x        xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxx        xx  $80 $ff $ff $ff $fe $03
x      x ######## ######## ######## ########           $81 $ff $ff $ff $ff

Next:

$94, $03, $9f, $f0, $00, $00, $1f, $f3, $8f, $e0,
$00, $00, $0f, $e3, $87, $c0, $00, $00, $07, $c3,
$80

This packet byte, $94, is between $81 and $db, indicating a Unique packet, and the next $94 - $80 (148 - 128) = 20 bytes are used verbatim:

xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxx   $ff $ff $ff $ff $ff $fe
x                                                  xx  $80 $00 $00 $00 $00 $03
x                                                  xx  $80 $00 $00 $00 $00 $03
x    xxx xx                              xxx xx    xx  $87 $c0 $00 $00 $07 $c3
x   xx    xx                            xxx   xx   xx  $8c $60 $00 $00 $0e $63
x  xxxxx   xx                          xxxxx xxxx  xx  $9f $30 $00 $00 $1f $f3
x      x xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx       xx  $81 $ff $ff $ff $ff $03
x        xxxxx                        xxxxx        xx  $80 $f8 $00 $00 $3e $03
x        xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxx        xx  $80 $ff $ff $ff $fe $03
x      x xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx ......##  $81 $ff $ff $ff $ff $03
#..##### ####.... ........ ........ ...##### ####..##  $9f $f0 $00 $00 $1f $f3
#...#### ###..... ........ ........ ....#### ###...##  $8f $e0 $00 $00 $0f $e3
#....### ##...... ........ ........ .....### ##....##  $87 $c0 $00 $00 $07 $c3
#.......                                               $80

Next:

$04, $00

This packet byte, $04, is between $01 and $7f, indicating a Repeat packet, and the next byte, $00, is repeated 4 times, becoming $00 $00 $00 $00:

xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxx   $ff $ff $ff $ff $ff $fe
x                                                  xx  $80 $00 $00 $00 $00 $03
x                                                  xx  $80 $00 $00 $00 $00 $03
x    xxx xx                              xxx xx    xx  $87 $c0 $00 $00 $07 $c3
x   xx    xx                            xxx   xx   xx  $8c $60 $00 $00 $0e $63
x  xxxxx   xx                          xxxxx xxxx  xx  $9f $30 $00 $00 $1f $f3
x      x xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx       xx  $81 $ff $ff $ff $ff $03
x        xxxxx                        xxxxx        xx  $80 $f8 $00 $00 $3e $03
x        xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxx        xx  $80 $ff $ff $ff $fe $03
x      x xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx       xx  $81 $ff $ff $ff $ff $03
x  xxxxx xxxx                          xxxxx xxxx  xx  $9f $f0 $00 $00 $1f $f3
x   xxxx xxx                            xxxx xxx   xx  $8f $e0 $00 $00 $0f $e3
x    xxx xx                              xxx xx    xx  $87 $c0 $00 $00 $07 $c3
x        ........ ........ ........ ........           $80 $00 $00 $00 $00

Next:

$81, $03

This packet byte, $81, is a Unique packet, and the next byte ($81 - $80), $03, is used verbatim:

xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxx   $ff $ff $ff $ff $ff $fe
x                                                  xx  $80 $00 $00 $00 $00 $03
x                                                  xx  $80 $00 $00 $00 $00 $03
x    xxx xx                              xxx xx    xx  $87 $c0 $00 $00 $07 $c3
x   xx    xx                            xxx   xx   xx  $8c $60 $00 $00 $0e $63
x  xxxxx   xx                          xxxxx xxxx  xx  $9f $30 $00 $00 $1f $f3
x      x xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx       xx  $81 $ff $ff $ff $ff $03
x        xxxxx                        xxxxx        xx  $80 $f8 $00 $00 $3e $03
x        xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxx        xx  $80 $ff $ff $ff $fe $03
x      x xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx       xx  $81 $ff $ff $ff $ff $03
x  xxxxx xxxx                          xxxxx xxxx  xx  $9f $f0 $00 $00 $1f $f3
x   xxxx xxx                            xxxx xxx   xx  $8f $e0 $00 $00 $0f $e3
x    xxx xx                              xxx xx    xx  $87 $c0 $00 $00 $07 $c3
x                                            ......##  $80 $00 $00 $00 $00 $03

Next:

$06, $ff

This packet byte, $06, is a Repeat packet, and the next byte, $ff, is repeated 6 times:

xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxx   $ff $ff $ff $ff $ff $fe
x                                                  xx  $80 $00 $00 $00 $00 $03
x                                                  xx  $80 $00 $00 $00 $00 $03
x    xxx xx                              xxx xx    xx  $87 $c0 $00 $00 $07 $c3
x   xx    xx                            xxx   xx   xx  $8c $60 $00 $00 $0e $63
x  xxxxx   xx                          xxxxx xxxx  xx  $9f $30 $00 $00 $1f $f3
x      x xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx       xx  $81 $ff $ff $ff $ff $03
x        xxxxx                        xxxxx        xx  $80 $f8 $00 $00 $3e $03
x        xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxx        xx  $80 $ff $ff $ff $fe $03
x      x xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx       xx  $81 $ff $ff $ff $ff $03
x  xxxxx xxxx                          xxxxx xxxx  xx  $9f $f0 $00 $00 $1f $f3
x   xxxx xxx                            xxxx xxx   xx  $8f $e0 $00 $00 $0f $e3
x    xxx xx                              xxx xx    xx  $87 $c0 $00 $00 $07 $c3
x                                                  xx  $80 $00 $00 $00 $00 $03
######## ######## ######## ######## ######## ########  $ff $ff $ff $ff $ff $ff

Next:

$81, $7f

This packet byte, $81, is a Unique packet, and the next byte, $7f, is used verbatim:

xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxx   $ff $ff $ff $ff $ff $fe
x                                                  xx  $80 $00 $00 $00 $00 $03
x                                                  xx  $80 $00 $00 $00 $00 $03
x    xxx xx                              xxx xx    xx  $87 $c0 $00 $00 $07 $c3
x   xx    xx                            xxx   xx   xx  $8c $60 $00 $00 $0e $63
x  xxxxx   xx                          xxxxx xxxx  xx  $9f $30 $00 $00 $1f $f3
x      x xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx       xx  $81 $ff $ff $ff $ff $03
x        xxxxx                        xxxxx        xx  $80 $f8 $00 $00 $3e $03
x        xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxx        xx  $80 $ff $ff $ff $fe $03
x      x xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx       xx  $81 $ff $ff $ff $ff $03
x  xxxxx xxxx                          xxxxx xxxx  xx  $9f $f0 $00 $00 $1f $f3
x   xxxx xxx                            xxxx xxx   xx  $8f $e0 $00 $00 $0f $e3
x    xxx xx                              xxx xx    xx  $87 $c0 $00 $00 $07 $c3
x                                                  xx  $80 $00 $00 $00 $00 $03
xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx  $ff $ff $ff $ff $ff $ff
.#######                                               $7f

Finally:

$05, $ff

The last two bytes of the compacted bitmap are a packet byte, $05, indicating a Repeat packet, and that the byte $ff is repeated 5 times:

xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxx   $ff $ff $ff $ff $ff $fe
x                                                  xx  $80 $00 $00 $00 $00 $03
x                                                  xx  $80 $00 $00 $00 $00 $03
x    xxx xx                              xxx xx    xx  $87 $c0 $00 $00 $07 $c3
x   xx    xx                            xxx   xx   xx  $8c $60 $00 $00 $0e $63
x  xxxxx   xx                          xxxxx xxxx  xx  $9f $30 $00 $00 $1f $f3
x      x xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx       xx  $81 $ff $ff $ff $ff $03
x        xxxxx                        xxxxx        xx  $80 $f8 $00 $00 $3e $03
x        xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxx        xx  $80 $ff $ff $ff $fe $03
x      x xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx       xx  $81 $ff $ff $ff $ff $03
x  xxxxx xxxx                          xxxxx xxxx  xx  $9f $f0 $00 $00 $1f $f3
x   xxxx xxx                            xxxx xxx   xx  $8f $e0 $00 $00 $0f $e3
x    xxx xx                              xxx xx    xx  $87 $c0 $00 $00 $07 $c3
x                                                  xx  $80 $00 $00 $00 $00 $03
xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx  $ff $ff $ff $ff $ff $ff
 xxxxxxx ######## ######## ######## ######## ########  $7f $ff $ff $ff $ff $ff

This completes the decompaction of this compacted bitmap.

Note that there is no sentinel or terminator indicating the end of a compacted bitmap. The only way GEOS knows that the final byte of the compacted bitmap has been found is that every byte of the decompacted bitmap image has been filled and accounted for. There is also no error detection or correction mechanism: If a packet byte gets corrupted or the math of a packet is wrong, then GEOS will produce a corrupted picture, and if too few bytes are present, then GEOS will keep reading RAM beyond it as compacted bitmap data until it produces a complete but garbled picture.

While this example shows all three packet types put to actual use, the Bigcount packet seems to be rarely used in practice. The GEOS 2.0 Photo Manager, geoPaint 2.0, and geoWrite 2.1 don't seem to produce or understand Photo Scrap files using Bigcount packets, despite the Kernal routines using it and BSW documentation explaining it.

The geoProgrammer errata note that geoAssembler doesn't correctly handle compacted bitmap images using Bigcount packets when the images are pasted in as photo scraps. It goes on to note that BSW applications don't use Bigcount packets at all in compacted bitmap images, saying, "this is not much of a problem." ArielMT's experience gained by creating and testing the bitmap decompaction example above says that geoAssembler will correctly handle compacted bitmap images using Bigcount packets when images containing any are entered as a series of .byte directives instead of pasted in as a photo scrap.

Screen Memory Access

The GEOS Kernal abstracts the screen memory of each platform and mode into a single virtual structure, but the cost of abstraction is slightly decreased performance. If the performance loss due to graphics abstraction is too much, then your program will have to access screen memory directly, or at least more directly.

GEOS has a routine in each edition that aids in direct screen memory access: GetScanLine, which returns the address of the first byte of a given line on the current screen.

Commodore 64 Graphics

GEOS provides you with nearly all the graphics routines your program needs, but if you need access to screen memory, you must ensure your program is actually running on a Commodore 64, or on a Commodore 128 in 40-column mode, and avoid stepping on the GEOS Kernal's expectations.

Detecting the Commodore 64 in GEOS

The variable version reveals which version of GEOS your program is running in, and beginning with v1.3, the variable c128Flag reveals whether it's GEOS or GEOS 128. You can detect which Commodore GEOS edition your program is running in with a short routine like this:

; Check for GEOS 128.
;
; Pass:     nothing
;
; Returns:  st    minus for GEOS 128, plus for GEOS (64)
;
; Example usage:
;       jsr   Is128       ; check
;       bpl   10$         ; skip if C64 GEOS
;       jsr   FixAspect   ; your 128-specific prep work
; 10$:
;
Is128:
        lda #$12          ; GEOS v1.2 and earlier are 64-only
        cmp version
        bpl 10$           ; v1.2 or earlier, so st clear (plus)
        lda c128Flag      ; else, test sign bit of c128Flag
10$:    rts

Commodore 64 Graphics Memory Layout

The foreground and background screens occupy 8,000 bytes each in main RAM. The foreground screen starts at SCREEN_BASE and extends to SCREEN_BASE+7999. The background screen starts at BACK_SCR_BASE and extends to BACK_SCR_BASE+7999. The VIC-II is set to standard high resolution bitmap mode, VIC bank 2, and uses SCREEN_BASE as its bitmap base address.

The screen is 320 pixels across by 200 pixels down, divided into 8-by-8 blocks called cards. The screen is 40 cards across by 25 cards down. Each bit of a byte corresponds to a pixel in the card row, high to low bits for left to right pixels. Each group of 8 bytes corresponds to the 8 rows of a card, top to bottom, and each group of 320 bytes corresponds to the 40 cards of a row on the screen, left to right. This means the pixel at (7,0) is set by byte 0 bit 0, its neighbor pixel down at (7,1) is set by byte 1 bit 0, and that pixel's neighbor to the right at (8,1) is set by byte 9 bit 7.

Each bit's value determines the pixel's color: set for the card's foreground color, cleared for the card's background color. Each bit's value also influences sprite collision detection and effects: Only foreground pixels (set bits) can collide with a sprite (see mobbakcol), and only foreground pixels can appear in front of a sprite (see mobprior).

GEOS uses the hardware sprites for the mouse and text cursors (sprites 0 and 1 respectively), so writing to the foreground screen memory directly will not result in cursor collision artifacts on the display.

Both screen memory regions are identical in layout. See the Commodore 64 Programmer's Reference Guide for more about programming graphics in standard bitmap mode.

Commodore 64 Colors in GEOS

The GEOS Kernal doesn't support colors, but your program can access the screen color matrix directly to use colors. The color matrix begins at COLOR_MATRIX and extends 1,000 bytes to COLOR_MATRIX+999. Each byte controls the foreground and background colors of each card on the screen: high nybble for foreground, low nybble for background.

Before your program exits, if it alters the color matrix, it must restore the color matrix to the default colors. You can do this by saving the byte at COLOR_MATRIX when your program starts or by relying on the value of screencolors, then filling the color matrix with this byte value during your program's clean-up phase. The Hitchhiker's Guide to GEOS offers an example snippet of code:

;On entry, save off the first byte of the color matrix
       MoveB  COLOR_MATRIX,saveColor
       .
       .
       .
;On exit, fill the color matrix with the saved value
       LoadW  r0,#1000              ;color matrix is 1000 bytes
       LoadW  r1,#COLOR_MATRIX
       MoveB  saveColor,r2L         ;fill with original color
       jsr    FillRAM

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 until you're done.

You will also need 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 from being overwritten with foreground screen graphics data, corrupting whatever code or data you're stealing the area for. You can expect seemingly random system crashes and data corruption if your debugging isn't thorough enough.

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 drawn to form the drop shadow effect, and again to recover the rectangle occupied by the dialog box proper. If your screen recovery routine is computationally intensive or time-consuming 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 r4 and r2H, the X and Y coordinates of the dialog box drop shadow's lower-right coordinate.
    2. The routine returns without doing anything else.
  3. If the values are not $ffff, $ff, then it knows this is the second call.
    1. The routine should load r4 and r2H with the values saved during the first call, replacing the values it was called with (the X and Y coordinates of the dialog box proper's lower-right corner).
    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 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.

Desk Accessories

GEOS can't run any desk accessories unless your program explicitly tells it to by calling GetFile or LdDeskAcc, so the only way to prevent one from writing to the background screen is to not load any while you're stealing it for extra RAM. If you load one anyway while needing the background screen untouched, you're relying on the desk accessory being well-behaved, which for this purpose is a risky proposition.

GEOS X-Axis Doubling

If you port your GEOS program from the Commodore 64 to GEOS 128 or Apple GEOS, then you will need to take advantage of special bits in GEOS Kernal graphics routines to correct the aspect ratio of your bitmaps and drawing logic. The 40-column mode of GEOS 128 uses the same aspect ratio as the Commodore 64, but the 80-column mode of GEOS 128 and the double high resolution mode of Apple GEOS squeeze twice as many pixels on the X axis as on the Y axis. Without correction, your bitmaps and drawing logic will be squished horizontally when ported. See the X-Axis Doubling sections of your porting target for details.

Commodore 128 Graphics

In 40-column mode, graphics in GEOS 128 is identical to GEOS on the Commodore 64, but in 80-column mode, graphics in GEOS 128 is very different. Your program must either force or detect the mode it runs in before making any assumptions.

GEOS 128 Mode via the File Header

Up through GEOS v1.2, GEOS defined O_GHFNAME (a 20-byte permanent filename field) as 12 bytes for the name, 4 bytes for the version, and 4 bytes reserved for null padding. Starting with v1.3, GEOS uses the final byte of the field as O_128_FLAGS, bit flags indicating which mode or modes your program is compatible with.

To remain compatible with earlier versions of GEOS, in which the byte's value must be $00, bit 7 is clear if your program can run in 40-column mode, and bit 6 is set if your program can run in 80-column mode. (If your program requires a Commodore 128, in 128 mode, running GEOS 128, you should assume that all versions of GEOS on the Commodore 64 will be able to load and run your program, so your program still needs to detect which edition it's running on.)

The effective meanings of this byte's value are:

Bit 7 Bit 6 Value Meaning
0 0 $00 Runs in 40-column mode only.
0 1 $40 Runs in both 40-column and 80-column modes.
1 0 $80 Does not run in GEOS 128. (Does not run in either mode.)
1 1 $c0 Runs in 80-column mode only.

Detecting GEOS and GEOS 128

The variable version reveals which version of GEOS your program is running in, and beginning with v1.3, the variable c128Flag reveals whether it's GEOS or GEOS 128. You can detect which Commodore GEOS edition your program is running in with a short routine like this:

; Check for GEOS 128.
;
; Pass:     nothing
;
; Returns:  st    minus for GEOS 128, plus for GEOS (64)
;
; Example usage:
;       jsr   Is128       ; check
;       bpl   10$         ; skip if C64 GEOS
;       jsr   FixAspect   ; your 128-specific prep work
; 10$:
;
Is128:
        lda #$12          ; GEOS v1.2 and earlier are 64-only
        cmp version
        bpl 10$           ; v1.2 or earlier, so st clear (plus)
        lda c128Flag      ; else, test sign bit of c128Flag
10$:    rts

Detecting 40-Column and 80-Column Modes

Once your program makes sure it's running in GEOS 128, it can detect whether it's in 40-column or 80-column mode with graphMode. For example:

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

Graphics Constants in geoProgrammer 128

The geosSym file in geoProgrammer 128 defines constants specific to each graphics mode and redefines the general constants based on how your program defines the C64 and C128 constants before including geosSym:

Values read for Values assigned to
C64 C128 SC_PIX_WIDTH SC_PIX_HEIGHT
FALSE ($00) FALSE undefined undefined
FALSE TRUE ($01) SC_80_WIDTH (640) SC_80_HEIGHT (200)
TRUE FALSE SC_40_WIDTH (320) SC_40_HEIGHT (200)
TRUE TRUE SC_40_WIDTH SC_40_HEIGHT

Commodore 128 Graphics Memory Layout

In 40-Column Mode

The layout is identical to the Commodore 64 graphics memory layout.

In 80-Column Mode

The VIC-II is inoperative, and the VDC takes over. All graphics data has to be marshaled through the VDC's control and data ports ($d600 and $d601) instead.

Inside the VDC RAM, the foreground screen occupies the first 16,000 bytes, from addresses 0 through 15999, and is laid out as a linear bitmap: The first 80 bytes describe the 640 pixels of the first row in sequence left to right, and the bits of each byte describe 8 consecutive pixels in the row, bits high to low for pixels left to right. Each set bit corresponds to a foreground color pixel, and each cleared bit corresponds to a background color pixel.

There are no hardware sprites in 80-column mode, so GEOS 128 simulates the mouse pointer and text cursor with soft-sprites. GEOS normally takes care of erasing and redrawing the sprites during text and graphics routines on the foreground screen. If you access the foreground screen without using the GEOS Kernal, you must call TempHideMouse first to avoid cursor collision artifacts on the display or in your graphics data.

Because the 80-column screen takes up exactly twice as much memory as the 40-column screen, GEOS 128 needs twice as much RAM to hold the background screen buffer. Because the foreground screen doesn't reside in either front RAM or back RAM in 80-column mode, it's able to get the RAM it needs by combining the 40-column foreground and background screen buffers into a single 80-column background screen buffer. The upper 100 rows of the background screen reside in the 8,000 bytes from SCREEN_BASE to SCREEN_BASE+7999, and the lower 100 rows of the background screen reside in the 8,000 bytes from BACK_SCR_BASE to BACK_SCR_BASE+7999. Note that these two areas of memory are not contiguous. If you access the background screen without using the GEOS Kernal, you must make sure your program knows which half it's using and when it crosses from one half to the other.

For bypassing the GEOS Kernal to access the foreground screen directly, the Hitchhiker's Guide to GEOS offers these three routines as an example:

;***********************************************************************
;
;Sta80Fore   -- stores byte to 128 80-column foreground screen
;Lda80Fore   -- loads byte from 128 80-column foreground screen
;
;Pass:
;      r5    = address in foreground memory
;      A     = data value (for Sta80Fore)
;
;Returns:
;      A     = data value (for Lda80Fore)
;
;Destroyed:
;      X
;
;Note: Call TempHideMouse to disable sprites before accessing
;      foreground screen directly.
;                                                        (mgl)
;***********************************************************************
;Constants for VDC internal registers
VDC_HI_UPDATE     = 18   ;update hi-byte of VDC pointer
VDC_LO_UPDATE     = 19   ;update lo-byte of VDC pointer
VDC_DATA          = 31   ;data byte at current VDC pointer
Sta80Fore:
; Send data byte to the VDC chip
      jsr   NewVDCAddress   ; Update VDC address with fg screen pointer (r5)
      ldx   #VDC_DATA       ; request VDC data register
      stx   VDC             ;
30$:  bit   VDC             ; test VDC status
      bpl   30$             ; loop till VDC ready for data byte
      sta   VDC+1           ; store data byte
      rts                   ; exit
Lda80Fore:
; Get data byte from the VDC chip
      jsr   NewVDCAddress   ; Update VDC address with fg screen pointer (r5)
      ldx   #VDC_DATA       ; request VDC data register
      stx   VDC             ;
30$:  bit   VDC             ; test VDC status
      bpl   30$             ; loop till VDC ready for data byte
      lda   VDC+1           ; get data byte
      rts                   ; exit
NewVDCAddress:
; Transfer value in r5 to VDC internal hi/lo address register.
; Destroys: x
      ldx   #VDC_HI_UPDATE  ; ask VDC for high byte
      stx   VDC             ;
10$:  bit   VDC             ; check VDC status
      bpl   10$             ; and loop till VDC ready
      ldx   r5H             ; store hi-byte of address
      stx   VDC+1           ; to VDC chip
      ldx   #VDC_LO_UPDATE  ; ask VDC for low byte
      stx   VDC             ;
20$:  bit   VDC             ; check VDC status
      bpl   20$             ; and loop till VDC ready
      ldx   r5L             ; store lo-byte of address
      stx   VDC+1           ; to VDC chip
      rts                   ; exit

Commodore 128 Colors in GEOS 128

GEOS 128 in 40-column mode makes available the same color matrix as Commodore 64 colors in GEOS.

The 80-column screen of GEOS 128 has only two possible colors: foreground and background. VDC register 26 controls the colors, and GEOS 128 saves a copy at scr80colors.

See the Commodore 128 Programmer's Reference Guide, chapter 10, for more about programming graphics through the VDC in 80-column mode.

GEOS 128 X-Axis Doubling

When switching from 40-column to 80-column mode, the X axis resolution is doubled from 320 to 640 pixels, but the Y axis resolution stays the same at 200 pixels. The aspect ratio changes, and images appear squished without correction. GEOS 128 makes some special graphics bits available in nearly all of its graphics routines to correct the aspect ratio by doubling X-axis coordinates in 80-column mode and not doubling them in 40-column mode automatically.

Bitwise-or'ing these constants into X coordinate and bitmap width values will enable GEOS 128 to double or not as the current mode needs:

Constant Bit Value Size Effect
DOUBLE_W 15 (Hi 7) $8000 word Normalizes pixel X coordinates and pixel widths.
DOUBLE_B 7 $80 byte Normalizes card X coordinates and card widths.
ADD1_W 13 (Hi 5) $2000 word Adds one pixel after normalization to end up on the next odd X coordinate right.

GEOS 128's graphics routines call the NormalizeX routine to achieve this magic. Important: NormalizeX will also double signed X coordinates. For negative coordinates, such as may be passed to SmallPutChar, you must use an exclusive-or instead of a bitwise-or with these constants.

The Hitchhiker's Guide to GEOS offers as an example a routine that will draw and frame a filled rectangle with the same aspect ratio in both 40-column and 80-column modes:

X1    = 35         ; left edge
X2    = 301        ; right edge
Y1    = 40         ; top edge
Y2    = 100        ; bottom edge

;Draw a filled [[rectangle]] using the current pattern
      jsr    i_Rectangle           ;inline call
      .byte  Y1                    ;y1
      .byte  Y2                    ;y2
      .word  (X1|DOUBLE_W|ADD1_W)  ;x1 with 2x width + space on the left for frame
      .word  (X2|DOUBLE_W)         ;x2 with 2x width
      jsr    i_FrameRectangle      ;inline call
      .byte  Y1                    ;y1
      .byte  Y2                    ;y2
      .word  (X1|DOUBLE_W)         ;x1 with 2x width
      .word  (X2|DOUBLE_W|ADD1_W)  ;x2 with 2x width + offset for frame
      .byte  $ff                   ;solid line pattern
      rts                          ;exit

Note that these bits have no meaning on GEOS for the Commodore 64, and you will have to make sure not to use them, or redefine them to $0000 and $00, when your program detects it's not running on GEOS 128. If you try to use them in GEOS (64), it will simply interpret them as exceptionally large coordinate or width values with unpredictable results.

The Hitchhiker's Guide to GEOS offers an example of doing this at assembly time (with corrections):

DblDemo1:
;Will assemble differently depending on the status of the C64 and C128 assembly
;constants. If assembling for GEOS 64, doubling constants will be set to zero
;so that they will not affect the x-positions. If assembling for GEOS 128,
;doubling constants will be set according to geosConstants file so that graphic
;operations will double automatically in 128 mode.

.if    (C128 ^^ C64)         ;C64/C128 flags must be mutually exclusive!

       .if    !C128          ;if not assembling for GEOS 128, force doubling
                             ;constants to harmless values so GEOS 64 graphics
                             ;routines don't get confused.
              DOUBLE_B       = $00
              DOUBLE_W       = $0000
              ADD1_W         = $0000
       .endif

BM_XPOS       = (32/8)              ;byte x-position of bitmap (40-col)
BM_YPOS       = 20                  ;y-position of bitmap

Bitmap:

;photo scrap pasted here

BM_WIDTH      = picW         ;byte bitmap width (40-col)
BM_HEIGHT     = picH         ;bitmap height

FPATTERN      = %11111111    ;pattern for surrounding frame

DoBmap:

;place the bitmap on the screen, loading the registers with
;inline data (note double-width settings).
       jsr    i_BitmapUp                   ;inline call
       .word  Bitmap                       ;bitmap address
       .byte  (BM_XPOS|DOUBLE_B)           ;xpos
       .byte  BM_YPOS                      ;ypos
       .byte  (BM_WIDTH|DOUBLE_B)          ;width
       .byte  BM_HEIGHT                    ;height

90$:   rts           ;exit

.else  ;(both C128 & C64 constants were both true or both false)
       .echo "DblDemo1 routine not designed to assemble for both 64 and 128!"
.endif

In the example above, you would create it using geoWrite and paste a Photo Scrap image where indicated. For example:

Math professor clip-art from the GEOS 2.0 Demo Disk

Designing a program that runs under GEOS and GEOS 128 in both 40- and 80-column modes and that does graphics aspect ratio normalizing with X-axis doubling bits is trickier. One way to achieve this is with variables and ora/eor instructions instead of with constants and compiler logic. In this case, you would need to define variables for the constants, say double_b, double_w, and add1_w variables initialized to the values of the DOUBLE_B, DOUBLE_W, and ADD1_W constants respectively, detect whether your program is running on the 64 or 128, and reassign the variables to zeros when the 64 is detected.

Apple IIe Graphics

The Apple GEOS Virtual Screen

Documentation for GEOS for the Apple II implies the existence of a background screen buffer, but it's incomplete. The Apple II has two graphics pages for double high resolution mode: 1 and 1X at $2000--$3fff, which is called SCREEN_BASE in GEOS, and 2 and 2X at $4000-$5fff, but no documentation mentions how or if GEOS uses page 2/2X graphics memory. GEOS maintains a background screen buffer at $a000--$bfff, but no documentation explicitly calls this area BACK_SCR_BASE.

Each screen occupies 16,384 bytes at the 8,192 contiguous byte addresses in both main and auxiliary RAM. The foreground screen buffer starts at SCREEN_BASE and extends to SCREEN_BASE+8191 or SCREEN_BASE+$1fff. The background screen buffer starts at BACK_SCR_BASE and extends to BACK_SCR_BASE+8191 or BACK_SCR_BASE+$1fff. The screen memory is laid out as interlaced rows. Each row is 80 bytes across, occupying 40 byte addresses, with each byte mapping 7 pixels and each byte address mapping 14 pixels. At each address, the byte in auxiliary RAM maps the 7 left pixels, and the byte in main RAM maps the 7 right pixels. In each byte, bit 7 is not used. Bits 0-6 encode 7 pixels side by side, one bit per pixel, but in reverse order of Commodores: Bit 0 is the left pixel of the group, and bit 6 is the right pixel of the group.

The organization and programming of screen memory is too complex for this guide, but Apple GEOS provides several GEOS Kernal routines for working with both screens and screen memory directly. See GetScanLine for an example. Apple GEOS uses more than 7 KB of tables to map virtual screen pixel coordinates to screen memory addresses quickly.

The Apple II platform doesn't have hardware sprites like the Commodore 64, so to maintain cross-platform consistentcy, Apple GEOS implements virtual sprites in software, soft-sprites, like GEOS 128. GEOS normally takes care of erasing and redrawing the sprites during text and graphics routines on the foreground screen. If you access the foreground screen memory directly, with or without a GEOS Kernal routine for that purpose, you must use TempHideMouse first to avoid corrupting the display or your graphics data with soft-sprite artifacts.

See the Apple IIe or Apple IIc Technical Reference Manual for details on programming graphics memory in double high resolution mode.

Apple GEOS Screen Memory Access

Because screen memory access is complex in Apple GEOS, the GEOS Kernal includes routines for making direct screen access easier.

The routines GetScreenLine and PutScreenLine extract and insert a single line in screen memory without having to switch banks yourself to access consecutive bytes. The routines CopyLine, CopyScreenBlock, and CopyFullScreen allow your program to scroll the screen by copying one screen region to another.

The routines ReadScanLine and ReadBackLine extract a scanline from the screen memory and translate it to a linear bitmap format one line high.

Apple GEOS X-Axis Doubling

Because Apple GEOS uses the double high resolution graphics mode, the aspect ratio is similar to the Commodore 128 in 80-column mode: a bitmap image or graphics drawing logic ported from the Commodore 64 will appear squished horizontally without correction. Unlike porting to GEOS 128, your Apple GEOS program doesn't have to do any run-time correction to your graphics drawing logic; since the Apple II-series and Commodore 8-bit platforms are incompatible, an Apple GEOS program will never run in any Commodore GEOS, and a Commodore GEOS program will never run in Apple GEOS.

What this means is, if you're porting a GEOS (64) program to Apple GEOS, you must edit your graphics drawing logic to fit the (unchanging) resolution and aspect ratio of the Apple display. However, the Apple GEOS Kernal's graphics routines will let you do display-time correction of bitmap graphics data to save you from having to edit images by hand while porting.

In addition to the card-aligned bitmap display routines common to Commodore GEOS Kernals, like BitmapUp, Apple GEOS has pixel-aligned bitmap display routines, like NewBitUp. The former routines take byte values for X coordinates and widths, and the latter routines take word values for X coordinates and widths.

Apple GEOS makes some special graphics bits available in its bitmap display routines to correct the aspect ratio by doubling bitmap image widths. Apple GEOS also makes special graphics bits available to indicate whether a bitmap graphics image is stored in main RAM or auxiliary RAM.

Bitwise-or'ing these constants into bitmap width values will tell Apple GEOS to stretch the bitmap to double on the X axis:

Constant Bit Value Size Effect
DOUBLE_W 15 (Hi 7) $8000 word Normalize pixel widths by doubling.
DOUBLE_B 7 $80 byte Normalize card widths by doubling pixels.

Bitwise-or'ing these constants into X coordinate values will tell Apple GEOS that the bitmap is stored in auxiliary RAM:

Constant Bit Value Size Effect
INAUX_W 15 (Hi 7) $8000 word Image address is in auxiliary RAM.
INAUX_B 7 $80 byte Image address is in auxiliary RAM.

The Hitchhiker's Guide to GEOS offers a short example of these bits in action. If you port a GEOS (64) bitmap graphics image to an Apple GEOS program and store it unmodified at an address in auxiliary RAM labeled MyAuxBitmap, you could display it at the proper aspect ratio like this:

;Put a bitmap up, using an address in auxiliary memory and doubling its
;width

       LoadW  r0,#MyAuxBitmap             ;aux address of bitmap
       LoadW  r3,#(MY_XPOS|INAUX_W)       ;x-position + in-aux flag
       LoadB  r1H,#MY_YPOS                ;y-position
       LoadB  r2,#(MY_CWIDTH*8)|DOUBLE_W  ;width = card width*8 + dbl bit
       LoadB  r1L,#MY_HEIGHT              ;height
       jsr    NewBitUp                    ;put bitmap on screen