Carl Zahrt and Orson Scott Card
Here's a graphics utility that lets you create screen displays in any of the regular pixel graphics modes — and GRAPHICS 6.5 and 7.5 as well. It's simple enough for a child to use. It gives you complete control over color, mode, and display size. And a special Fill Mode lets you quickly draw long lines or fill large areas with color in moments.
Atari home computers have superb graphics. Creating screen displays from BASIC, page flipping, scrolling, redefining characters, continuous memory, and changing from mode to mode to get exactly the effect you want — once you've worked with graphics on the Atari, some other home computers can seem a bit confining.
But that doesn't mean using Atari graphics is easy, especially if you want large displays which extend far beyond the edges of the TV screen, or detailed drawings that would take hundreds of PLOT and DRAWTO statements to create from BASIC. Such things take painstaking work on graph paper and many POKEs into screen memory — or a good chunk of your paycheck for software to do it for you.
"Screenbyter" takes the pain out of creating beautiful graphics displays.
- You can work in any of the non-GTIA pixel modes.
- You have access to GRAPHICS 6.5 and 7.5, pixel modes that cannot be used with a simple GRAPHICS statement.
- You can type RUN and start drawing with the joystick — no programming experience is needed.
- You can fill in large areas quickly and easily.
- Since the main action of the program is in machine language, it moves very quickly, but a Slow Mode is provided so you can do detail work, pixel by pixel.
- You can change screen colors with the joystick.
- You aren't always limited by the size of the screen. In GRAPHICS 3 you can create scrolling displays many times larger than the TV screen, and all the modes except 7.5 and 8 allow some scrolling.
- When you save a display to disk, all the parameters — mode, size, and colors — are saved with the screen data, so that you can load them directly into your own programs.
Setup. Screenbyter begins by displaying a directory of all files on the disk with the extender ".PIX". This extender is automatically added to all files created by Screenbyter. If no directory appears, there are no previously saved files on the disk.
"What file should hold your finished screen? (Eight characters)." Respond to this prompt by giving the filename you want your new display to have, when you save it at the end of the editing session. Screenbyter automatically removes everything before a colon or after a period and replaces it with "D1:" and ".PIX", so that you only need to enter the eight-letter filename. If you use illegal characters, Screenbyter will ask you to try again; if you use more than eight characters, only the first eight characters will be used.
If the name you enter is the name of a file already on disk, Screenbyter will remind you of that. To change the name, press RETURN. Or, if you want your new display to overwrite the old file, press any other key to go on.
"Would you like to edit a screen you have already saved? (Y or N)." If you answer Y, Screenbyter asks you for the name of the saved file. If the file is not on disk in the form "D1:filename. PIX", Screenbyter will tell you and ask you to insert the correct disk or, if you wish, ask you again if you want to edit a previously saved screen.
Once the file is found, Screenbyter reads the first four bytes of the file to get the mode number, the number of bytes per line, and the number of lines in the display as it was saved. Press RETURN if you want to change these parameters. Press any other key to leave them the same.
Changing the parameters can have interesting effects. Remember that four-color modes all read the bytes the same way; if you want to draw your displays in GRAPHICS 3 (ANTIC 8) and then display them in a higher four-color mode, you can. Changing the length of a file either chops off the bottom or adds blank lines at the bottom of the display. Changing the line width, however, will usually result in garbage, since the vertical relationships will all be changed. The option is included, however, because sometimes even "garbage" can be fun.
If you are not editing a previously saved display, or if you are changing the parameters, you get the following series of prompts:
"What Antic mode will you work in?" This prompt is followed by a table that lists the eight ANTIC pixel modes and their graphics mode equivalent. ANTIC 8, for instance, is GRAPHICS 3; ANTIC F (15) is GRAPHICS 8. Two ANTIC modes, C(12) and E(14), have no GRAPHICS equivalent – they are the famous "GRAPHICS 6.5" and "GRAPHICS 7.5." (See Table 1.) Enter the ANTIC mode number: 8, 9, A, B, C, D, E, or F.
"How wide a line? (Minimum nn bytes, maximum nn bytes)." Depending on the mode you chose, Screenbyter will give you the minimum and maximum number of bytes per line. Remember that in the four-color modes, each byte is four pixels, while in the two-color modes, each byte is eight pixels. The minimum is based on the minimum number of bytes required to fill the screen. The maximum is based on the widest possible line that will allow the display to fit within 4K. If you enter numbers outside the legal range, Screenbyter will select the minimum or maximum, as appropriate.
With ANTIC E and F, the minimum and maximum are the same – you have no option, so any number you enter will result in the same number of bytes per line. This is because these two modes will not scroll – they both require more than 4K. Scrolling a screen that crosses a 4K boundary requires elaborate arrangements of screen memory that are beyond the scope of this program. Displays created in E and F will take up 65 sectors on disk; all other displays will take up 33 sectors or fewer.
|Table 1: Atari Pixel Modes|
|Memory, bytes (sectors)||240 (3)||480 (5)||960 (9)||1920 (17)||3840 (33)||3840 (33)||7680 (65)||7680 (65)|
|Bits/pixel (Pixels/byte)||2 (4)||1 (8)||2 (4)||1 (8)||1 (8)||2 (4)||2 (8)||1 (4)|
|Note: ANTIC C and E, the two "hidden" pixel modes, provide the same resolution. All the other pixel modes attempt to create as square a pixel as the W screen allows – the same number of color clocks wide as scan lines high. C and E, however, are twice as wide as they are high, making each pixel very short and wide. They come vety near the resolution of ANTIC F (GRAPHICS 8). The advantages are that, compared to F, C uses half the memory and E allows four colors|
"How many lines do you want to edit? (Minimum nn, maximum nn)." The minimum and maximum depend on the mode and the number of bytes per line already selected. Again, if you choose parameters outside the legal range, Screenbyter will select the minimum or maximum. And if you choose the maximum number of bytes per line, only the minimum number of lines per screen will be possible.
When all selections have been made, you are given one last chance to change your mind. All the parameters you chose are displayed on the screen. If they are correct, press START, and the program will go on. If you want to make changes, press OPTION and the program will start over.
Waiting. What's going on while you wait? Screenbyter configures the memory to reserve 10K (40 pages) at the top of memory to hold screen memory (up to 8K), the display list, and the machine language routine that actually puts your drawing on the screen. Screen memory is cleared and the machine language routines are loaded. If you chose to edit a previously saved screen, it is loaded into memory now. All this takes about six seconds. The rest of the time is spent writing the display list. The higher the ANTIC mode, the longer it takes to write the display list – ANTIC F requires about 200 POKEs in BASIC, plus the calculations to find out what numbers to POKE, and it can take as long as 20 seconds.
When Screenbyter is ready for you to edit, there will be a cursor in the upper-left-hand corner.
Moving the cursor. The joystick controls the cursor.
Drawing a line. Hold down the joystick button to draw; let it up to move the cursor without drawing.
Selecting a color. Press 1 or SHIFT-CAPS/ LOWR to select Color 1. Press 2 or CONTROL-CAPS/LOWR for Color 2. Press 3 or SHIFT-CONTROL-CAPS/LOWR for Color 3. Press 0 or CAPS/LOWR to select the background color. Drawing in the background color has the effect of erasing.
Color Mode. To change the actual colors that are displayed by Colors 1, 2, or 3, or the background color, press START. You will hear a buzz, and the cursor will no longer respond to the joystick. Instead, moving the joystick will change the colors displayed on the screen. Moving the joystick up or right will change the color from darker to brighter, then jump to the darkest value of the next color. Moving the joystick down or left will change the color from brighter to darker, then jump to the brightest value of the next color.
To change the background color, move the joystick forward or back; to change Color 3, move the joystick left or right. To change Color 2, move the joystick forward or back with the button pressed; to change Color 1, move the joystick left or right with the button pressed.
To return to Cursor Mode, press START again. No other commands will work during Color Mode.
Slow Mode. Press the space bar to enter Slow Mode. A delay loop in the program makes the cursor move much more slowly around the screen, with a click between moves. This mode allows you to create details. To return to Fast Mode, press the space bar again.
Fill Mode. Press the inverse key (Atari logo key) to enter Fill Mode. A low hum will come from the television. In this mode, when you press the joystick button, Screenbyter draws a dot of the selected color at the current cursor location, as usual, but it also searches to the right along the same line. If it finds another dot of the same color before it reaches the end of the line, it will fill in all the area between that dot and the current cursor position with dots of the same color. If no dot of the same color is found, no fill operation is performed.
This allows you to fill large or small areas of the screen with a single color. Simply draw the right-hand edge of the figure first; then enter Fill Mode and draw the left-hand border. It takes some practice to get used to using this function without accidentally erasing parts of your screen, but you may find that this can be the most useful feature of Screenbyter.
To exit Fill Mode, press the inverse key again. The hum will continue as long as you are in Fill Mode, and will stop only when you leave.
Insert a line. Press SHIFT-INSERT to insert a line at the current cursor position. The bottom line of the display will be pushed down and lost.
Delete a line. Press SHIFT-DELETE to delete the current cursor line. A blank line will be added at the bottom of the display.
Clear the screen. Press CONTROL-SHIFT-CLEAR to erase the screen completely. If you haven't already saved the display, it will be lost.
Saving the screen. Press SELECT to save the screen without ending the editing session. The current screen display will be saved as "D1: TEMPFILE.PIX". You can save as often as you like; Screenbyter will simply overwrite any existing TEMPFILE.PIX file.
Ending the editing session. Press OPTION to save the screen and end the editing session. (To exit without saving, press RESET.) The display will be saved as "D1:TEMPFILE.SCR." Then the regular GRAPHICS 0 screen will return and you will be given several prompts:
"Do you want to save the screen as D1:filename.PIX? (Y or N)." If you answer N, the saved display will be left as TEMPFILE.PIX. If you answer Y, Screenbyter will erase any existing file that has the same filename. Then Screenbyter will rename TEMPFILE.PIX with the filename you chose.
"Do you want to quit? (Y or N)." If you answer Y, Screenbyter will restore the old top of memory and exit to BASIC. If you answer N, you will get another prompt. To return to edit the screen you just left, press OPTION. That display will be reloaded into memory, the display list will be rewritten, and you can start over. To edit an entirely new screen, or to change the name of the save file, press START. In effect, Screenbyter will then start over.
What's Going On Inside The Program?
Like everything else in a computer, your display exists as a series of numbers stored in binary form in memory locations in the computer. The ANTIC chip scans screen memory as it is instructed to do by the display list. But it doesn't read the numbers as numbers. Instead, it reads them as patterns of "on" and "off" bits.
Four-color modes. In the four-color modes, each byte is read as code for four pixels. The eight-bit binary number is treated as four bit-pairs:
00 00 00 00
Each bit-pair provides the code for one pixel, or rectangle of color on the screen. In GRAPHICS 3, each pixel is the size of a character in GRAPHICS 0. In GRAPHICS 7.5, each pixel is one scan line high and one color clock wide, which gives very good resolution. But all four-color modes read the bit-pairs the same way.
00 means to display the background color (the color code stored at location 712).
01 means to display Color 1 (the color code stored at location 708).
10 means to display Color 2 (the color code stored at location 709).
11 means to display Color 3 (the color code stored at location 710).
This means that the number 216 (binary 11011000) is treated as four pixel color instructions: The first pixel is Color 3, the second pixel is Color 1, the third pixel is Color 2, and the last pixel is the background color.
Two-color modes. The two-color modes treat each bit as a separate pixel instruction, so that each byte controls eight pixels. An "on" bit, or 1, is read as a Color 1 instruction, while an "off" bit, or 0, is read as a background color instruction. In a two-color mode, the number 216 would be treated as eight pixel color instructions: Two "on" pixels, one "off" pixel, two more "on" pixels, and three "off" pixels. (See Table 1 for a listing of all the modes.)
Moving around the screen. Moving the cursor around the screen, then, isn't simply a matter of moving from one byte to the next in screen memory. Screenbyter also has to move from bit to bit or from bit-pair to bit-pair within the bytes. This can be done in BASIC by adding or subtracting values, but it is very slow. Machine language, however, has powerful commands that make it easy to move from bit to bit. DRAWTO and PLOT commands do these manipulations for you, but since Screenbyter is circumventing the BASIC graphics commands entirely, there was no practical choice but to execute the main drawing operations in machine language.
To understand what Screenbyter is doing, you need to understand a few machine language commands: EOR, ORA, and AND. The two OR instructions and the AND instruction are not the same as the AND and OR you use in Atari BASIC. In machine language, these are operations on the bits of an eight-bit number, and are often called "bitwise" AND and OR to help keep the difference in mind.
AND, OR, EOR Explained
All three operations compare two numbers, one stored in the accumulator and another somewhere else in memory. The operation results in a third number, which is stored in the accumulator in place of the number that was already there.
• AND, referred to as "bitwise AND," compares the two numbers, bit by bit. Any bit that is on in both numbers stays on in the resulting number. All other bits are turned off. In other words, only bits that are on in the first number and in the second number remain on in the result.
• ORA, referred to as "bitwise OR," compares the two numbers, but in this case any bit that is on in either number stays on in the result:
• EOR, referred to as "exclusive OR," compares the two numbers, and any bit that is on in one and only one number is left on in the result. Any bit that is on in both numbers or off in both numbers is off in the result:
How do these actually work, in practice?
Screenbyter maintains several masks. The Color Mask is in page 6, at memory location 1692. This byte is set from BASIC whenever the color is changed, and it is set so that every bit or bit-pair represents a pixel of the selected color. If the background color is selected, the Color Mask is 00000000. If Color 1 is selected, the Color Mask is 01010101. For Color 2, the Color Mask is 10101010, and for Color 3 it is 11111111. With two-color modes, the Color Mask is either 00000000 or 11111111.
The Cursor Mask is kept at location 1696. It is set to represent the current cursor pixel within the cursor byte. The bits in the current pixel are on; all others are off. In four-color modes, if the cursor is in the leftmost pixel of the cursor byte, the Cursor Mask will be set to 11000000; if it is in the rightmost pixel, the mask will be set to 00000011. The two middle pixels are 00110000 and 00001100. In two-color modes, a single "on" bit represents the cursor position.
Whenever you move the cursor left or right or diagonally, the Cursor Mask is shifted left or right, so that at any given moment, the Cursor Mask will mark which bit or bit-pair Screenbyter should change.
If you are drawing, Screenbyter first picks up the value of the current cursor byte and stores it at 1690. Then it picks up the Cursor Mask and EORs it with 11111111 (decimal 255). This reverses the Cursor Mask – any bit that was on is now off, and any bit that was off is now on.
Let's see that in action in a four-color mode, in which the background is black, Color 1 is red, Color 2 is green, and Color 3 is blue. The bit-pairs will be separated in these examples, to make it easier to keep track of the pixels.
|Cursor Mask||00 11 00 00|
|EOR||11 11 11 11|
|results in||11 00 11 11||(Reverse Cursor Mask)|
Screenbyter then ANDs the Reverse Cursor Mask with the number at 1690, which in effect makes a hole in the cursor position:
|Reverse Cursor Mask||11 00 11 11|
|AND||01 01 01 11||red||red||red||bule|
|results in||01 00 01 11||red||—||red||blue|
The two bits in the cursor position will always be turned off.
Now Screenbyter must prepare the pixel code to go in that hole. Screenbyter picks up the Cursor Mask and ANDs it with the Color Mask. Since all the bits in the Cursor Mask are off except the two bits of the current pixel, the resulting number will have only the bits that represent the current color, and only in the pixel position:
|Cursor Mask||00 11 00 00|
|AND Color Mask||10 10 10 10||green||green||green||green|
|results in||00 10 00 00||—||green||—||—|
Now we are ready to put the correct pixel code into the hole in the current cursor byte. To do this, we bitwise OR the current pixel we just got with the cursor byte with a hole in it from the operation before. Remember that with ORA, any byte that is on in either or both of the two numbers is on in the result:
|correct pixel||00 10 00 00||—||green||—||—|
|ORA current byte with hole||01 00 01 11||red||—||red||blue|
|results in||01 10 01 11||red||green||red||blue|
The result is then stored in 1690, and later in the program it is put into screen memory.
If you are not drawing (merely moving the cursor) the operation is a little different, but AND, EOR, and ORA perform the same functions.
Machine language is so fast that all this seems to happen instantaneously. In fact, the only reason the cursor doesn't fly around the screen out of control is because Screenbyter keeps leaving the machine language routine, returning to BASIC to check the keyboard for other commands. Even so, the cursor moves so quickly that it has to be slowed down in order to allow you to draw details.
Use of Page 6. The machine language routine at SCROLL uses a field in Page 6 to hold some important variables. The memory locations in Page 6 are explained in Table 2.
Screenbyter Displays In Your Own Programs
Here are two routines you can add to your own programs, which will allow you to load the displays you created with Screenbyter. The first routine, Load and Display List, works with any Screenbyter file. However, it sets up a custom display list with individual LMS instructions, suitable for scrolling. This makes the setup time rather long. So a Simple Load Routine is also included. It will work with any display file that was created using the minimum line width and number of lines per screen, except screens created in ANTIC C and E (GRAPHICS 6.5 and 7.5). You cannot use it if you intend to scroll horizontally. However, you can use it if you intend to scroll vertically or flip pages, and if your display was created with the minimum line width.
|Table 2: Page 6 Locations.|
|1670||WIDE-1 Used to check for the end of the logical line.|
|1671||Used in fill routine to keep track of right border of fill.|
|1672||Cursor location current byte on logical line.|
|1673||Used by the fill routine to hold the pattern of the rightmost byte of the fill line.|
|1674-1675||LINE-1 Used to check for last line of display.|
|1676-1677||Cursor location current logical line number.|
|1678||Bytes per screen line-1. Used by the scrolling routine to check for the end of the screen line.|
|1679||Cursor location: Current byte on screen line.|
|1680||Lines per screen-1, Used by the scrolling routine to check for the bottom of the screen display.|
|1681||Cursor location: current screen line number.|
|1682||Used by the fill routine to hold the pattern of the leftmost byte of the fill line.|
|1683||A temporary holding location.|
|1684||Used by the fill routine to hold the real value of the byte currently being tested.|
|1685||A temporary holding location.|
|1686-1687||The current screen starting address (the address of the upper-left-hand corner of the screen).|
|1688-1689||Cursor location: the address of the current cursor byte in screen memory.|
|1690||The real contents of the current cursor byte.|
|1691||The, reverse (cursor display) contents of the current cursor byte.|
|1693||The number of bits per pixel (1 or 2).|
|1694||Scroll flag (0 =do not scroll).|
|1695||Fill flag (0=do not till).|
|1698||Total number of lines per screen. Used in the scroll routine to change the correct number of LMS instructions in the display list.|
|1699||WIDE. Used in the scroll routine to increment the LMS addresses in the display list.|
|1700||Fill Test Mask Used in the fill routine to isolate and test each pixel until a pixel of the selected color is found.|
|1701||Starting Fill Test Mask. Either 192 (four-color mode) or 128 (two-color mode).|
|1702-1704||Machine language jump vector: JMP followed by the address of the fill subroutine held in the string FILL$.|
Both routines will configure memory to protect the screen display, read the display parameters from whatever display file you choose, and load the file into memory. It uses a load routine very similar to the one used by Fontbyter, so we won't explain them again here.
Notice that in loading displays created in ANTIC E and F (GRAPHICS 7.5 and 8), the screen display must cross a 4K boundary line. The ANTIC chip gets fussy at this point, and ignores anything after a 4K boundary line until the beginning of the line pointed to by the next LMS instruction. Therefore, screen memory must be arranged so that the 4K boundary line comes right at the end of a line; the display list routine will have set the value of SC, the start of screen memory, so that the 4K boundary line will fall right at the end of a line.
After the main listing of the BASIC program, you will find several programs to create disk files containing the machine language routines used in Screenbyter. If you prefer, you can easily add these DATA statements to your program and read them that way, or – as we prefer to do – load them into string constants and use them that way, without so many disk accesses. However, typing in strings that have lots of inverse and control characters in them can be tedious and often leads to typing errors, so these DATA statements are necessary in the published version of the program.
If you are also using "Fontbyter" (COMPUTE!, September 1983), you might notice that Screenbyter follows the same structure. That's because Fontbyter was used as the starting point, and changed wherever Screenbyter's needs were different. However, the line insert, line delete, and clear screen machine language routines are not identical, so don't try to use the similar Fontbyter routines for Screenbyter – you will hopelessly confuse your Atari if you do, and confused Ataris have unpleasant ways to express their frustration.