Moving 16-color Objects
BY JOE CHIAZZESE
Using AL routines within a C structure, this article demonstrates how to move 16-color objects (created with the C.O.L.R. Object Editor) swiftly around the screen. All related program files may be found within the COLRMOVR folder on your START disk.
While playing the popular 8-bit Atari game Karateka, a visually sophisticated Karate cartoon, I was inspired to duplicate the main character's actions on the Atari ST. Two day's work convinced me that I'm a terrible artist, but did at least produce a set of assembler routines to manipulate images created with Antic's C.O.L.R. Object Editor.
These routines should serve as a great learning tool for many people. To show how to use them from C, I've written a demonstration program which allows any size image (within reason) to follow the mouse around on the screen.
The complete program has three parts: the assembler routines to handle the images, the C routines to make the program logic easier to understand and the image itself. Creating the final program requires four steps: 1) drawing the image and converting it to source code, 2) assembling the image-handling routines, 3) compiling the main C program, and 4) linking all the files together.
CREATING THE IMAGE
Though you may follow this procedure using your own display image, I originally tried it with the karate character and sample files on the START disk include this data. After drawing the karate character's image, C.O.L.R. Object Editor's Create Source option was used to save the image as assembler source code statements with a label. This was not quite enough: in order to link the image's label to the C code, the label must be global. Do this by adding the .globl label directive to the image code data using any standard programming text editor.
The .globl directive can be put anywhere in the source code created
by C.O.L.R. Object Editor. I suggest the top so it can be found easily.
Since Alcyon C adds the underscore character in front of all its external
variables and routines, the image's label must begin with an underscore
so the image label matches that defined in the C program, as must the names
of the routines in the assembler source code. For example, the routine
I call save_screen in C must be _save_screen in assembler
to match it.
generic assembler routines
which would work with any size
I wanted generic assembler routines which would work with any size object, and not just the specific karate character example. The routines designed, therefore, had to accept parameters from an outside source. When the subroutine is called, the return address is pushed onto the stack. Since C places all parameters on the stack in reverse order, the parameters are accessed at the current stack position plus four bytes (the return address is 32 bits long requiring 4 bytes to hold it). For example: put(device,char); pushes char on the stack first followed by device with the stack growing downward.
THE ASSEMBLER ROUTINES
The assembler portion of the demonstration consists of five routines, each with a very specific function as discussed below.
_save_screen copies a block of specified width and height from the screen to a buffer. The buffer must he allocated by the C program and it is the programmer's responsibility to insure there is enough room. The buffer can either be an array or a block of memory allocated from the operating system, in which case a pointer is needed to tell the routine the buffer's location. All the routines expect the object's width to be specified in words, not pixels. The height is in lines. (Both of these numbers are displayed when creating source with the C.O.L.R. Object Editor.) All buffers and screen positions are passed as absolute addresses.
The C portion of the program is responsible for converting x and y coordinates into absolute screen addresses. The address is calculated and then passed to the assembler routines which, in themselves, have no provision for such conversions.
_restore_screen reverses the effect of _save_screen by replacing the previously saved block of screen memory
_shift_image is a little more complex. Given the address and size of the original object, it shifts the image to the right by the specified offset and stores the result where told. The result is an image one word wider than the original. This new image is used by all the other routines; the original only serves to create another image which can be shifted to the right by up to 15 pixels. Even if the offset is zero, the object should be put through this routine because it creates an image which is one word wider, and the assembler routines actually expect to be working on an object which is a word wider than the width specified. If the given width is two, for example, the routines manipulate an object of width three.
The shifted images are necessary because screen memory is arranged in word-sized, 16-bit chunks. Each shifted image corresponds to an image that begins on different bits, or pixels, of a word of screen memory
To have the background show through the empty portions of the object, you need a mask for the object. Rather than calculate the mask for every object the _make_mask routine automatically creates one. All it needs is the size and address of the object and a place to put the mask. Each mask uses half the memory space of its image.
Finally _draw_image takes the image, the mask, and the saved background, mixes them all together, and puts the final picture on the screen with background showing through. First, the background is loaded, ANDed with the mask, then ORed with the image. This causes any blank sections of the image to appear transparent.
I chose to mix in the background from the saved buffer and not the screen. Although this means the background has to be saved first for the routine to function properly there's no need to restore and save the screen again if the picture is drawn in the same screen block. So, if the picture is drawn at point 'A', it can be moved up to 15 pixels to the right and still be drawn on the same block using only an image shifted by a different offset.
THE C ROUTINES
The main routine is simple: it opens the workstation, initializes and if successful, calls follow_mouse() (which is really the main routine). It then cleans up and closes the workstation.
open_workstation() and close_workstation() are the standard GEM routines used in most programs. I make it a habit to put them in every program in case I need them later. finish_up frees the memory allocated by initialize().
initialize() does several things: it checks the resolution and returns you to the Desktop if the resolution is anything but low. It saves the screen's base address in scrbase, then allocates memory for all the images and masks. You will also find yourself back at the Desktop if sufficient memory is not available.
The default palette is saved so that it can be restored at program's end. A new palette is set and the background is filled with a wall pattern. The image is then shifted through all 16 different positions within a word and each one is stored for later use. At the same time a mask is generated for each of the images. Finally, to initialize the main loop, a screen block is saved once and oldpos is set to point to it.
On entry of follow_mouse(), we encounter a while loop which continues until a key is pressed. To avoid repeatedly redrawing the image at the same place, a DO loop has been placed within the while loop which will exit if the mouse is moved or a key is pressed. The x and y coordinates of the mouse are used to calculate the absolute screen position. We now wait for the next vertical blank to occur to minimize flicker. (A certain amount of flicker will still be noticeable when the image is at the top of the screen. To eliminate this calls for a complicated technique of multiple-screen image switching which is beyond the scope of this article.) If the object is in the same screen block, it is simply redrawn; otherwise we restore the background where the old image was, save the new block, point the old position to the latest one and then draw the image.
If you want to change the program so that your own picture gets dragged around the screen, go to the top of the C file and change the #define statements to reflect the size etc. of your own picture. OBJW is the width in words, OBJH is the height in lines and COEOBJECT is the label of your picture. Link it in and that's it. (Don't forger to make your object label a global!)