IFF STANDARDS FOR THE ST
By Tom Hudson
The Interchange File Format (IFF), originally developed for the Amiga computer, is now a vailable on the ST. Graphics block transfers-true "'clip art"- and Amiga picture compatibility are provided, courtesy of Tom Hudson. The author of DEGAS Elite and CAD-3D explains how to use this new graphics standard. Folder IFF.STQ on your START disk contains all necessary source code for the routines, two sample programs to read and write your own IFF picture blocks, and some sample picture segments.
While developing DEGAS Elite, the full-featured version of DEGAS, I decided the program should have true "clip art" capability, such as that found on the Macintosh. This ability to transfer portions of a graphics display (bit-blocks) from one program to another has been missing from the Atari ST. It's not that it hasn't been done, it's just that ST developers have not agreed upon a standard file format.
Originally, I created a very simple bit-block format for DEGAS Elite pictures that would work fine for Atari users. But what if someone wanted to import images from, say, the Commodore Amiga computer? A simple Atari-only format just wouldn't do.
I consulted several prominent ST developers for their opinions, and we decided that Atari bit-block images should be stored to disk in the Electronic Arts Interchange File Format (IFF).
The Interchange File Format is a set of file standards developed by Electronic Arts so that computers of all types can easily exchange a variety of files: graphics, music, text, and so forth. Originally implemented on the Amiga, this format will be the perfect way for the ST to communicate with any other computer that supports IFF.
Why support IFF files? Take a look at the computer industry. Every computer manufacturer has a different notion of how things should be done. You can't use an Amiga or Macintosh disk in an ST drive-they use different formats to store their data. You can't plug an IBM graphics card into an Apple II. The major drawing programs on the ST alone use different picture storage formats! The computer industry needs standards, and the IFF file is a move in the right direction. With it, we take an important step toward computer compatibility.
With a proper standard defined and adhered to by the many ST software developers, we will have a uniform method for exchanging graphics information between applications such as paint programs, page-layout programs, charting programs, or word processors. One program can create a graphic image and write it to a disk file, then another program can use that image in whatever way is required. In addition, it will be much easier to port software from one computer to another because they will use the same file-handling routines!
This article will describe a uniform method of storing graphic bit-blocks in IFF disk files, and provide commented C source listings demonstrating the reading and writing of the files. In addition, the START disk contains example block image files along with the ready-to-use source code, so that START readers can begin supporting the file format immediately.
(Editor's note: START agrees with Tom Hudson and the developers supporting him on this. We urge all ST developers to adopt the IFF format as an ST graphics standard. Non programmers and others who just want to use the IFF programs on their START disk will find instructions near the end of this article. We advise reading the article for information that will make the instructions clearer.)
Before examining IFF and adapting it to the ST, we must understand how the ST itself transfers bit blocks. Whenever the ST's graphics routines perform a bit-block transfer, they must be informed how the graphic information is laid out in the system memory. This is known as a memory form, and may be in several formats depending upon the image's resolution and how the program created the image.
Typically, the ST programmer uses the GEM VDI bit-block manipulation functions, vro_cpyfm() and vrt_cpyfm(), to move image blocks around. These functions are passed the format of the bit-mapped image with an array of values known as a Memory Form Definition Block (MFDB). Most applications will require the use of the vro_cpyfm() function (opaque color form to color form operation) rather than the vrt_cpyfm() function (transparent, single-plane form to color form) because the latter is mainly used to turn a monochrome image (such as a block of text) into a color image. We will only work with the vro_cpyfm() function in this article.
An MFDB is a ten-word array of values in which each word entry is a part of the information defining the bit-block. Its format is as follows:
MFDB-High word of memory form starting address
MFDB-Low word of memory form starring address
MFDB-Form width in pixels
MFDB-Form height in pixels
MFDB-Form width in words
MFDB-Form format flag
MFDB-Number of memory planes
The first two entries of the MFDB array are the longword address of the start of the bit-block broken into high and low words. These two word values tell the VDI where the bit-block memory form is located in memory. For memory forms that are standard ST screens, this value will be the base address of the screen RAM.
The third entry in the MFDB array is the form width in pixels. This is quickly calculated as:
Rightmost pixel number - Leftmost pixel number + 1
For an entire ST screen, this value will be either 320 (low-resolution mode) or 640 (medium- and high-resolution modes).
The fourth entry in the MFDB array, the form height in pixels, is equally easy to calculate:
Bottommost pixel number -Topmost pixel number +1
For an entire ST screen, the height in pixels will be either 200 (low-
and medium-resolution modes) or 400 (high-resolution mode).
someone wanted to
import images from,
say, the Commodore
The fifth entry tells the VDI in which format the bit-block image is stored. There are two formats supported by the VDI: standard form (1) and device-specific form (0). For our purposes, we will use only the device-specific form, since this is the format used in ST screen image data.
The sixth MFDB value, the form width in words, is the number of 16-bit words required to hold one line of pixels. In C, this value can be calculated as:
(Width in pixels + 15)>>4
Finally, the number of memory planes is either 4 (low-resolution), 2 (medium-resolution) or 1 (high-resolution monochrome).
The last three entries in the MFDB array are reserved for future use in the VDI. Set them to zero, to avoid unexpected results in future versions of the VDI.
Once you have properly defined the MFDBs for the source form (where the bit-block is being moved from) and destination form (where the bit-block is being moved to), you can use vro_cpyfm() to move a bit-block from one memory form to another You can use the same MFDB for both the source and the destination if needed, but it's best to perform a bit-block transfer (bitblt) using two separate memory forms to avoid overlap conflicts.
MOVING A BIT BLOCK
Moving a block around is actually very simple, but you must know where the block is and where you want it to go. To do this, you must set up two MFDB arrays, one for the source form and one for the destination form. Normally, one of the memory forms will be a screen RAM area, and the other will be a user-defined holding buffer for the bit-block.
Setting up a user-defined portion of RAM for the image buffer is quite easy. Calculate the size of the picture in bytes.
Number of planes * Width in words * 2 * Height in pixels
This value is then used as a parameter for the memory allocation function, Malloc(). Note that it isn't necessary to allocate an entire 32000 bytes (the full size of an ST screen) for your bit image. For a low-resolution (4 bit planes) bit-block 33 pixels wide (3 words) and 12 pixels high, the RAM required is only (4 * 3 * 2*12), or 288 bytes. Calculate the RAM needed, allocate it, and plug the starting address of the allocated RAM into the first two entries of the MFDB. (Well look at an example of this later)
Once you have both MFDBs defined with properly allocated sections of RAM, you need to set up another array which tells vro_cpyfm() which part of the source form you want to move, as well as where to place it in the destination memory form. This is an eight-entry array with the first four words defining the source rectangle, and the last four words defining the destination rectangle. For example, to move the rectangle with upper-left coordinates 30,40 and lower-right coordinates 50,45 to a rectangle on the destination form with upper-left coordinates 0,0 and lower-right coordinates 20,5 (be sure the two rectangles are the same size), the coordinate array (well call it COORD) will look like this:
COORD = 30
COORD = 40
COORD = 50
COORD = 45
COORD = 0
COORD = 0
COORD = 20
COORD = 5
Now all the preliminary data is set up. We have properly described the memory forms and the portion of the source form we want to move. We're ready to do the bitblt operation with the vro_cpyfm() call. It is structured as follows:
vro_cpyfm(handle, wr_mode, COORD array, source MFDB, destination MFDB)
The handle is the device handle of the graphics workstation you are
using, assigned when you open the virtual workstation with the v_opnvwk()
be the perfect way for
the ST to
any other computer
The wr_mode is the writing mode to use in the bitblt operation, numbered from 0-15, and described in the GEM VDI manual, page 6-6. For our purposes, we will always use mode 3 (replace mode), where the source block is copied into the destination form exactly as it appears in the source form. The other 15 writing modes can be used for special masking effects, and you can experiment with them to see what they do.
The COORD array is the eight-word array described earlier which tells the VDI the coordinates of the bit-block to move.
The source MFDB is the pointer to the MFDB of the form we're moving the block from.
The destination MFDB is the pointer to the MFDB of the form we're moving the block to.
When vro_cpyfm() is called, the bit-block will be copied from the source form to the destination form, and if the two MFDBs are different formats (as in the source form being a standard ST screen and the destination form being just the size of the bit-block), the VDI will convert the form's format accordingly.
INTERCHANGE FILE FORMAT
Now that you know the basics of setting up a memory form and copying a bit-block from one form to another, let's see how to store bit-blocks in a disk file for later use. This is where the IFF file format comes in.
The IFF routines on your START disk are a subset of the full IFF specification. These routines can handle bit-mapped images of various resolutions quite well but do not handle text or any of the other IFF file types. They are easy for the ST programmer to use because they take standard GEM VDI Memory Form Definition Blocks and ST palettes as input; the programmer doesn't have to deal directly with the IFF files. When reading the IFF file, the routines take care of most conditions that can occur, and return palette information in both a system palette table and a GEM color format. In short, the routines make reading and writing IFF files a quick and painless task for the ST programmer.
The IFF bit-block files we'll be using have a standard format, shown below. For ST IFF bit-block files, I suggest three standard extenders: For low-resolution (16-color) images, use BL1; for medium-resolution (4-color) images, use .BL2; and for high-resolution (monochrome) images, use .BL3. These correspond to the .P11/.P12/.P13 extenders used by the DEGAS paint program, which allow users to quickly identify the resolution of the image contained in the file. This will become important when images begin to appear on bulletin board systems with little documentation.
Each IFF bit-block file begins with the four-character identifier FORM followed by a four-byte longword indicating the length of the file (not counting the FORM header and length code). The program reading the IFF file should check for this header to be sure the file is in the proper IFF format.
Following the FORM header is the four-character code ILBM, which stands for InterLeaved Bit Map (not to be confused with ST Bit-Plane Interleaving). This code lets the reading program know the file contains an IFF bit-block image.
Next comes another four-character identifier, BMHD, which stands for Bit Map HeaDer It is followed by a four-byte longword indicating the length of the header. The length is expected to be 20 bytes with the following structure:
Byte Offset 0 word - Width of form in pixels 2 word - Height of form in pixels 4 word - X pixel location (assumed to be 0) 6 word - Y pixel position (assumed to be 0) 8 byte - Number of bit planes 9 byte - Masking flag- unused in our routines 10 byte - Compression flag-0=No compression, 1=RLE 11 byte - Unused filler byte 12 word - Transparent color pixel value (normally 0) 14 byte - X Aspect ratio (unused by our routines) 15 byte - Y Aspect ratio (unused by our routines) 16 word - Page width 18 word - Page height
Following the BMHD data chunk is the color palette information. This is another four-character identifier, CMAP (Color MAP), and it is also followed by a four-byte longword indicating the number of bytes in this data chunk. The data in this chunk is a group of three-byte structures, as shown below. Each three-byte RGB value represents one color:
byte - Red value for color
byte - Green value for color
byte - Blue value for color
The color information in each of these bytes must be aligned with the most significant bit. For example, the ST's palette registers contain red, green and blue settings ranging from 0-7, which requires three bits to store. In a byte, these bits are stored like so:
To make the value conform to the IFF format, we shift the color-register value to the left five bits, so that the most significant bit of the byte contains the most significant bit of the color data, like so:
For a low-resolution image, the color map contains 16 three-byte entries; for a medium-resolution image, it contains four three-byte entries; for a high-resolution image, it contains two three-byte entries.
One problem arises at this point. When transporting bit-block files from the Amiga (which allows up to 32 colors) to the ST (which allows only 16 colors), the ST routine must drop some of the colors and their corresponding bit planes. When this happens, the first 16 colors are used, and the rest are discarded. The same applies for the extra bit-planes. The IFF routines supplied here can read one resolution bit-block and turn it into another resolution, but be warned that reading a bit-block image file into a memory form with fewer bit planes (such as reading a BL1 file into monochrome mode) will probably render the image useless. You can read blocks with fewer bit planes into a form with more bit planes with no trouble-the colors will show up properly.
After the color map, the last data chunk in the IFF file is the bit-mapped image itself. This is preceded by a four-character identifier BODY and a four-byte longword specifying the length of the bit-block data.
The BODY can be written in two different forms: uncompressed and compressed. The compressed format uses a simple Run-Length Encoding RLE scheme (see Reference 2). Note that the IFF Reader on your START disk will read both uncompressed and RLE-format bit-block data, but, for simplicity's sake, the write routines will only write uncompressed data. If you like, you can modify the write routine so that it supports the RLE option.
This is a simple overview of the IFF bit-block format. As I said earlier, the IFF routines do most of the work for the programmer and a detailed knowledge of IFF format is not needed to use them. However, if you would like to get full information on compression format and other details, you should read the Electronic Arts "EA IFF 85 Standard for Interchange Format Files" document (see Reference 1).
IFF FILE HANDLING ROUTINES
The file IFFRTNS.C on your START disk is the C source code for the actual IFF I/O routines. To use this in your programs, compile it into a .O (object) file and place it on your linker disk. Whenever you want to use the IFF routines, link the IFFRTNS.O file with your program, and you're ready to go.
There are three routines in the IFFRTNS.C file that you will be concerned with: iff__rdl(), iff_rd2() and iff_wrt(). All these routines and their support functions are documented for your use.
The IFF I/O routines make several items available. The Syspal array is a 16-word array which holds the color-palette information as used by the Setpallete() call. That is, each entry ranges from $0000-0777, where $0000 is black and $0777 is white.
The Gempal array is an array of color values used by the GEM vs_color() function. This array has 16 groups of three values, where Gempal is a value from 0-1000 for the red portion of the color, Gempal is the green component, and Gempal is the blue component.
Finally, the xparent variable is an INT value that tells which color is assumed to be transparent. This will typically be the background color index, 0.
The colors in a block file may differ from the color palette that your program is using. Because of this, the block images may not look correct when loaded. With the color-palette information supplied with each block, you can do what is termed a "color re-map" - that is, you can process the pixels in the block image and replace the old pixels with the color from the current palette which most closely matches the original pixel color. This rather complex operation requires specialized assembly language and is beyond the scope of this article.
IFF I/O FUNCTIONS
The iff_id() function, used internally in the IFF I/O routines, reads a four-character data ID and the four-byte length into the chk structure.
The proc_CMAP() function is used after the color map (CMAP) chunk has been located. It reads the color values from the block file and transforms them into both the ST BIOS format (in the Syspal array) and the GEM format (in the Gempal array).
The decompress() function takes care of reading bit-block images which were written using the RLE compression scheme. Decompression is done one scan line at a time.
The rawread() function does the same thing as decompress(), but with block image data that is not compressed.
The rawwrite() function writes a scan line of uncompressed data to a disk file. As mentioned, there is no corresponding compress() function here, but you can add one to write image data in a compressed form, if you like. The Read routines can handle either format. Compression on writing was not supported primarily because small block files benefit little from RLE compression.
The toraster() function does the job of converting the raw, uncompressed scan line data into the ST's device-specific "interleaved" bit-map format. This screen data format is discussed in the article "GRAFCON ST' in the July, 1986 issue of Antic magazine.
The fmraster() function does the opposite of the toraster() function. That is, it takes a scan line of device-specific data and converts it into the form required by the IFF data file. After this routine, the data is ready for writing (or compression, if you choose to install RLE compression).
The iff_rdl() and iff_rd2() functions are the actual functions called to read an IFF file. This is a two-phase operation: You furnish an MFDB array and the handle of a file opened for input, and call iff_rdl(). This function reads the starting portion of an IFF file, sets up the MFDB array to let you know how the block is laid out, and returns. Using the information in the returned MFDB, you allocate the amount of RAM the block needs and call iff__rd2() to complete the read process. This operation is shown in detail below.
The iff__wrt() function is used to write a bit-block to disk. You furnish the handle of a file opened for output, the MFDB of the block, a color palette in the ST BIOS format, and the resolution of the image. This process is also shown in detail below.
READING IFF FILES
As mentioned, the IFF file-read process is a two-stage process. IFFREAD.PRG, on your START disk, is a program demonstrating the reading of IFF block files. Sample block files of the three different resolutions are supplied on the START disk. Files with .BL1 extensions are for low-resolution, .BL2 files are for medium-resolution, and .BL3 files are for monochrome. Try reading various resolution block files in the ST's three graphics modes to see how each looks. The following is an explanation of the source code found in IFFREAD.C.
IFFREAD starts by opening a virtual workstation and requesting the screen's location in memory and its resolution. The color palette is also placed in the oldpaI array for later restoration. Important: always restore the color palette after your program is done so that when the system returns to the Desktop, it is the same as it was before your program executed. This is not only considerate to the user, but it insures that the Desktop screen will be readable!
Next, the program builds the screen's MFDB array This is a fairly straightforward operation. As you can see, it plugs the appropriate values into all ten scrnmfdb array locations. Be sure to always place zeroes into the last three MFDB locations to insure compatibility with future GEM VDI releases.
Now we bring up the GEM file-selector dialog and let the user select the IFF block file to load. Once the filename is entered, the program displays the whichfm dialog to ask the user where to load the block image.
The whichfm dialog lets you choose how the block file will be loaded. If SCREEN is chosen, the block will be loaded into the screen's MFDB, regardless of the block's size. In this case, the block is read into the upper-left corner of the screen, showing that a block can be read into any size memory form. Of course, the only usable portion of the form is the block itself.
If FORM is chosen in the dialog, the program will allocate the amount of RAM required for the bit-block in a part of RAM separate from the screen, read the block into that RAM, then do a bitblt to copy the block to the center of the screen. This is a triple-edged demonstration, showing how to allocate RAM, bring in a bit-block to a special buffer, and how a bitblt operation works.
After SCREEN or FORM is chosen, the program attempts to open the file requested by the user. If it is opened successfully, the program attempts a first-stage IFF read using the iff_rdl() function. The program passes the file's handle (fhand) to the function along with a 10-entry MFDB array (workmfdb) which the function will set up according to the block's size.
The iff_rdl() function returns a value of zero if the first-stage IFF read was successful. If the value is negative, an error has occurred. The error numbers and meanings are found in the #defines of the IFFRTNS.C file:
After iff_rdl() executes successfully, Syspal and Gempal
will be filled with the color values found in the IFF file. The MFDB array
you specified in the iff_rdl() function call will be filled with
the data for the bit-block except for the first two array entries. These
entries, you should recall, contain the address of the memory form block,
which the IFF Reader routines cannot determine. This is up to you, and
we'll see how to do it in a moment.
reading and writing
IFF files a quick and
painless task for the
The first thing IFFREAD does is check to see if either the width or height of the image is too large for the ST screen. If so, the MFDB values for these settings are adjusted to the maximum size of the ST screen. Excess size conditions can occur if you try to read a large monochrome image into a low-resolution screen (since monochrome images are 640X400 pixels and low-resolution images are 320X200 pixels). This operation will truncate the block to a usable size.
Next, we set the number of bitplanes in the form to the same number as the resolution that is in use. If the form has excess bitplanes, they are ignored. In the demonstration program, this is accomplished by:
workmfdb = scrnmfdb;
If the user requested that the block be loaded into the screen (formtype= =1), the address of the memory form is set to that of the current screen, which is in the scrnmfdb form entries  and  To read the block into the screen, we must also make sure the block's width parameter (workmfdb) matches that of the screen.
If the block is to be read into a memory form separate from the screen (formtype= =2), we have to do a little more work. Specifically, we need to ask the operating system to allocate enough RAM for the bit-block's form. Fortunately, we can allocate just the right amount of RAM required for the bit-block-which can be as few as two bytes or as many as 32000 bytes. This prevents wasted memory.
To determine the amount of RAM needed to hold a bit-block, we do the calculation:
blocksize = MFDB * MFDB * MFDB * 2;
This value is then used as input to the GEMDOS Malloc() (memory allocation) call, which sets aside the RAM we need and returns the address of the allocated RAM. If the allocation is successful, we plug the RAM's address into locations  and  of the workmfdb array. The rest of the MFDB is left as is.
Now that the MFDB is set up with the address of the memory form and the number of bit planes we intend to use, we call iff_rd2(). This function actually reads the bit-block into the area we defined via workmfdb. If the read is successful, a zero is returned, otherwise, a negative value is returned (see the error code #defines in the IFFRTNS.C file).
After the phase 2 IFF file read has executed, the file we opened should be closed, since we're done with it.
If it is necessary to set the colors to those of the block, you can do it in two ways, as shown in the example program. The Setpallete() call is the easiest, but you can also use the vs_color() function to do the job. The result is the same in either case-it's a matter of personal preference. In the IFFREAD demo, we go ahead and set the colors in both ways so that you can see the block as it was stored.
If the block was read into the screen RAM, no further action is necessary, since the block is in view already.
If the block was read into a memory form other than the screen (formtyp = = 2), we'll have to copy it to the screen to view it. To do this, we set up the blit array with the coordinates of the block in its source form in blit through blit. We also set up the coordinates of where we want the block to be copied in the destination form (the screen) in blit through blit. For proper results, make sure the source block size and the destination block sizes are the same. IFFREAD copies the block to the screen so that it is centered horizontally and vertically. The actual instruction to do the bitblt operation is vro_cpyfm(), described earlier. It uses the workmfdb array for the source form, scrnmfdb for the destination form and the blit array for the coordinates to copy. Try changing the coordinate values to see how the bitblt operation works.
After you have viewed the loaded bit-block image, press any key to return to the Desktop.
WRITING BLOCK FILES
The IFFWRITE.C file on the START disk is the C source file for the IFFWRITE.PRG example program. With it, you can load any DEGAS-format picture and save portions of the picture to disk in a standard IFF-format block file. These files will be compatible with DEGAS Elite's block-handling capability and any other program that supports the IFF-format block files.
This program is quite straightforward. It is similar to IFFREAD in many places, so we'll cover mainly the important differences.
The program begins like IFFREAD by opening a virtual workstation, setting up the screen's MFDB, and so on. It does, however, perform an extra setup function. In order to prepare to write one of our block files, we must have a section of memory reserved to hold up to a screenful of data-or 32000 bytes. This is done with the Malloc() function. We ask the system for 32000 bytes of RAM, and it returns the starting address of the RAM. If this fails, the program informs the user with a dialog. Otherwise, we set the pointer workarea to the address of the RAM buffer.
Like IFFREAD, IFFWRITE displays the file-selector dialog. This time, however, it wants a DEGAS-format picture file as input, and automatically searches for a file with a P11, P12, or P13 extender, depending on the current system resolution. After the user enters the filename, the picture file is read into the current screen memory for the user to see. The 16-entry INT array picpal contains the color information for the picture.
After the picture appears, the user points the mouse to the upper-left corner of the area to be saved, presses and holds the button, dragging it to the lower-right corner of the block. A "rubber box" appears to define the rectangle that will be saved.
When the button is released, the program calculates the size of the bit-block and uses vro_cpyfm() to copy the block to the 32000-byte work area that was allocated at the start of the program. Before the bitblt operation can be executed, however, the program must set up the MFDB for the rectangle. This MFDB will be used to tell the IFF write routine where the block is and what its format is.
Now the program displays a second file selector dialog, this time expecting a block file name. When entering the filename, be sure to add the .BL1/.BL2/.BL3 extender so that you can identify the resolution of the block file later.
After the block filename is entered, the program creates the file and instructs the IFF write routine iff__wrt(), to write the file. This is a simple one-stage process. The programmer furnishes the handle of the file that is opened as output, along with the MFDB for the block, the color palette that is to be used for the block, and the block's resolution (0, 1, or 2). The IFF write routine does all the rest and returns a 0 to indicate success or a -1 to indicate failure. After the write call returns, the program closes the file and the write process is complete! What could be easier?
An interesting side-benefit of this program is that if you "grab" the entire screen with the rubber box and write it, the resulting block file is directly usable by all Amiga paint programs that support IFF, making the program a handy DEGAS-to-IFF converter! Aren't standards wonderful?
Remember: though these routines are flexible and will allow you to use any bit-block image regardless of resolution, loading an image with more bit planes than are used in your particular graphics mode may make the image unusable. If in doubt, just force the user to use BL1, .BL2 or .BL3 files, depending upon the resolution. This way, the images will always be usable.
Using the IFF file-read routines shown in this example, you can load many different bit-block images into separate areas of computer memory for use by your own programs. Whether you are an ST developer or an involved programming hobbyist, these routines should prove valuable for using bit-block files in your own programs.
EA IFF 85 Standard for Interchange Format Files from Jerry Morrison, Electronic Arts. Available through Commodore-Amiga, 1200 Wilson Drive, West Chester, PA 19380.
GRAFCON ST, Universal Graphics Converter by Patrick Bass, Antic Magazine, July 1986, pp. 67-72.
GEM VDI Manual, Digital Research, Inc., pp. 6-1 through 6-8.
Atari ST GEM Programmer's Reference by Norbert Szczepanowski and Bernd Gunther, Abacus Software, pp. 157-161.