THE IFF ILBM LIBRARY MANUAL ­­­­­­­­­­­­­­­­­­­­­­­­­­­­­ by Jeff Glatt, dissidents 6 Sycamore Drive East New Hartford, NY 13413 What's an ilbm.library? ------------------------ The IFF ilbm.library is a disk-based runtime library. This means that any application, upon being run, can open and call routines in this library just like a program might utilize routines in the Intuition or Graphics libraries. The primary purpose of the ilbm.library is to provide functions to read and write ILBM picture files, but there is additional support to read non-ILBM IFF and ANIM forms. The functions are based upon the original Electronic Arts code (i.e. OpenRIFF, GetBODY, etc.) as well as a few Commodore enhancements (i.e. Scheppner's getBitMap, handleCAMG, etc.). There have been a few changes to the internal logic of some routines, but generally, they perform the same functions as the original code. A few more significant changes were made to certain routines in order to accomodate ANIM and non-ILBM files, and also to streamline the parsing of LISTS, CATS, and PROPS. Furthermore, some higher level routines have been added to make it extremely easy to load and write IFF pictures via just two library routines. Finally, the biggest change between the original code and this library is that the code has been rewrit- ten in the tightest possible 68000 assembly. The size of the library is < 7000 bytes. A program that uses this library can be smaller and faster than if the program used the original EA code.¹ Because the library uses no global data and is therefore re-entrant, many applications can use the library simultaneously. Finally, access to the lowest level functions is provided. The IFF routines, OpenRIFF, GetChunkHdr, IFFReadBytes, IFFWriteBytes, etc. are all included. These routines are equivilent to the original EA code except that the lib code is smaller and faster. Because of this, it is pos- sible to use this library for any non-ILBM reader and writer with a result- ing improvement in the application's size and speed. In the future, if the Amiga becomes utilized in any non-graphics applications, additional high level routines may be added to handle non-ILBM forms (i.e. 8SVX, SMUS, etc). Suggestions/comments may be sent to the above address. Although a library of this sort has long been promised from both CATS and certain 3rd party developers, as usual nothing has been delivered by those folks. Electronic Arts is a company that deserves credit for helping make life easier for the Amiga programmer and end user. The company has promoted the Amiga since the computer's introduction more so than CBM (although very few organizations have promoted the Amiga less than its manufacturer). By esta- blishing IFF standards on behalf of CBM and releasing source code for reading and writing IFF files, Electronic Arts has helped make the Amiga the success that it is in the video/graphics market. Indeed, these efforts are probably the reason why CBM is still in the business of making computers. Unfortunately, the people who wrote the documents describing the IFF file formats have an appalling lack of respect for the English language. No effort was made to be concise and clear. Sentences seem to have been constructed by heaving several, long phrases together, and subsequently inserting a verb or two. Then, if the text didn't sound "technical" or "serious" enough, adjectives were invented, and brazenly thrust into any sentence of less than 40 words. This mish-mash of technical newspeak is typical of computer programmers who never learned to write in any language other than Pascal. In fact, if the trend toward inventing technical jargon continues, we may see the day when technical literature will have the words BEGIN and END surrounding each "standard written language module" (i.e. paragraph). Many misguided programmers (and others engaged in scientific disciplines) are also taught that any use of the first person (i.e. I or we) is too conversational, and therefore unprofessional. Apparently, the function of technical literature is not to converse, but to bore. Because the original IFF documents exhibit all of these flaws, I will attempt to paraphrase those documents. I do not guarantee that I will have correctly ascertained the intentions of the lifeform that wrote those documents, so you should consult the original text. BEGIN IFF stands for "InterChange Format Files". This is some of that technical jargon that I was alluding to earlier. Basically, an IFF file is a set of data that is in a form that many, unrelated programs can read. An IFF file should not have anything in it that was intended specifically for just one, particular program. If a program must save some "personal" data in an IFF file, it must be saved in a manner which allows another program to "skip over" this data. There are several different types of IFF files. ILBM files store picture data. SMUS files store musical scores. 8SVX store sampled sounds. Each of these files must start with an ID which indicates that it is indeed an IFF file, followed by an ID that indicates which type of file. So what is an ID? An ID is four, printable ascii characters. If you use the CLI Type command (opt h) to print out an IFF file to the CLI window, you will notice that every so often you will see 4 letters in a row. These 4 letters are an ID. Every IFF file must start with one of the following 3 IDs. 'FORM' 'LIST' 'CAT ' If the first 4 chars (bytes) in a file are not one of these, then it is not an IFF file. These IDs are referred to as group IDs in EA literature. END After this group ID, there is a ULONG that indicates how many bytes are in the entire file. This count does not include the 4 byte group ID, nor this LONG. This LONG is useful if you wish to load the rest of the file into mem- ory to examine it. After this LONG, there is an ID that indicates which type of IFF file this is. As mentioned earlier, "ILBM", "SMUS", and "8SVX" are 3 types of IFF files. There are many more, and programmers are always inventing new types for lack of better things to do. What you find after the type ID depends on which type it is (i.e. From here on, an ILBM will be different than an 8SVX). Here is the beginning of a typical ILBM file. 'FORM' <- OK. This really is an IFF file. 13000 <- There are 13000 more bytes after this ULONG 'ILBM' <- It is an ILBM (picture) file One thing that all IFF files do have in common after the group ID, byte count, and type ID, is that data is organized into chunks. OK, more jargon. What's a chunk? A chunk consists of an ID, a ULONG that tells how many bytes of data are in the chunk, and then all those data bytes. For example, here is a CMAP chunk (which would be found in an ILBM file). 'CMAP' <- This is the 4 byte chunk ID 6 <- This tells how many data bytes are in the chunk (chunkSize) 0,0,0,1,1,4 <- Here are the 6 data bytes Notice that the chunk size doesn't include the 4 byte ID or the ULONG for the chunk Size. So, all IFF files are made up of several chunks. There are a few other details to note. A chunk cannot have an odd number of data bytes (such as 3). If necessary, an extra zero byte must be written to make an even number of data bytes. The chunk Size doesn't include this extra byte. So for example, if you want to write 3 bytes in a CMAP chunk, it would look like this: 'CMAP' 3 <- Note that chunk Size is 3 0,1,33,0 <- Note that there is an extra zero byte In the preceding example, the group ID was 'FORM'. There are other group IDs as well. A 'CAT ' is a collection of many different FORMs all stuck together consecutively in 1 IFF file. For example, if you had an animation with 6 sound effects, you would want to save the animation frames in an ANIM FORM, and you would want to save the sound effects in several 8SVX FORMs (one per sound effect). Note: a better way to store sound samples would be the SAMP format. See Fish Disc #203. You could save the animation and sound in 7 separate files. The ANIM file would start this way: FORM 120000 <- Whatever the size happens to be (this is expressed in 32 bits) ANIM Each 8SVX file would start this way: FORM 8000 <- whatever size 8SVX If the user wanted to copy the data to another disc, he would have to copy 7 files. On the other hand, you could save all the data in one CAT file. CAT 4+120008+8008+2028+... <- The total size of the ANIM and the 6 8SVX files ' ' <- Type of CAT. 4 spaces for the type ID means "a grab bag" of IFF FORMs are going to be inside of this CAT FORM 120000 ANIM ...all the chunks in the ANIM file placed here (note: ANIMs have imbedded ILBM FORMs) FORM 8000 8SVX ...all the chunks in the first sound effect here FORM 2020 8SVX ...all the chunks in the second sound effect here ...etc. for the other 4 sound effects To further complicate matters, there are LISTs. LISTs are a lot like CATs except that there is an additional group ID associated with LISTs. That ID is a PROP. LISTs can have imbedded PROPS just like an ILBM can have an im- bedded CMAP chunk. A PROP header looks very much like a FORM header in that you must follow it with a type ID. For example, here is an ILBM PROP with a CMAP in it. PROP <- Here's a PROP 4+14 <- Here's how many bytes follow in the PROP ILBM <- It's an ILBM PROP CMAP <- Here's a CMAP chunk inside this ILBM PROP 6 <- There are 6 bytes following in this CMAP chunk 0,0,0,1,1,4 LISTs are meant to encompass similiar FORMs (i.e. several 8SVX files stuck together). Often, when you have similiar FORMs stuck together, some of the chunks in the individual FORMs are the same. For example, assume that we have 2 8SVX sound effects. 8SVX FORMs can have a NAME chunk which contains the ascii string that is the name of the sound effect. Also assume that both sounds are called "car crash". Wouldn't it be nice if we didn't have to have the same NAME chunk in each 8SVX FORM like so: CAT <- We put the 2 files into 1 CAT 4+1004+504 8SVX <- It's an CAT of several 8SVX FORMs FORM <- here's the start of the first sound effect file 1000 8SVX ...some chunks NAME <- here's the name chunk for the 1st sound effect 9 'car crash',0 ...more chunks FORM <- here's the start of the second sound effect file 500 8SVX ...some chunks NAME <- here's the name chunk for the 2nd sound effect. Look 9 familiar? 'car crash',0 ...more chunks With a LIST, we can have PROPs. A PROP is group ID that allows us to place chunks that pertain to all the FORMs in the LIST. So, we can rip out the NAME chunks inside both 8SVX FORMs and replace it with one NAME chunk inside of a PROP. LIST <- Notice that we use a LIST instead of a CAT 4+30+990+490+... 8SVX PROP <- Here's where we put chunks intended for ALL the 22 subsequent FORMS; inside a PROP. 8SVX <- type of PROP NAME <- here's the name chunk inside of the PROP 9 'car crash',0 FORM <- here's the start of the first sound effect file 982 <- size is 18 bytes less because no NAME chunk here 8SVX ...some chunks, but no NAME chunk FORM <- here's the start of the second sound effect file 482 8SVX ...some chunks, but no NAME for this guy either Notice that the PROP group ID is followed by a type ID (in this case 8SVX). This means that the PROP only affects any 8SVX FORMs. If you were to sneak in an SMUS FORM at the end, the NAME chunk would not apply to it. Also, if you included a NAME chunk in one of the 8SVX FORMs, it would override the PROP. For example, assume that you have a LIST containing 10 8SVX FORMs. All but 1 of them is named "CBM needs an advertising budget". You can store a NAME chunk in a PROP 8SVX for "CBM needs an advertising budget". Then, in the one 8SVX FORM whose name is not "CBM needs an advertising budget", you can include a NAME chunk to override the PROP. It should be noted that you can take several LISTs and squash them toge- ther inside of a CAT or another LIST.² Personally, I have never seen a data file with this level of nesting, and doubt that it would be of much use. If you need this level of complexity, forget IFF and make your own proprietary data format, then give info to anyone who wants to access your data. No doubt, somewhere, there is a moron devising a plot to inflict a CAT of LISTs upon an unsuspecting public now that VirusX has defeated his more devious aspirations. ««««««««««««««««««««««««««««««««««««««»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»» AND NOW FOR SOMETHING COMPLETELY DIFFERENT... At the highest levels, the type of IFF file that this library is designed to read and write is an ILBM. An ILBM must have a BMHD and a BODY chunk. It may also have CMAP, CAMG, CRNG, and other chunks. When reading a file, the library can look for ILBMs inside of CATs and LISTs, sidestepping all of the other non-ILBM FORMs. It rummages around inside of such a file looking for the first ILBM that it can find. You can enable the library to inform you of other types of FORMs as it finds them (in case you want to use an 8SVX sound that was stored with an ILBM), or you can say, "Don't bother coming back to me until you've found a picture in that file and displayed it in a window, or looked through the whole file and found no picture." When it finds an ILBM, it can "skip" over any chunks whose ID it doesn't understand. On the other hand, you can tell the library, "If you come across an ID that you don't understand, give it to me and I'll take care of that chunk". (A program may store "private" data which it does not want other programs to "choke" on by storing the data in a chunk with an undo- cumented ID. This way, another program will not "recognize" what the chunk is, and can use the chunkSize to determine how many subsequent data bytes to "skip" in order to get to the next chunk.) The library takes care of many details such as writing extra zero bytes if you tell it to write an odd-sized chunk, and error checking. These routines ASSUME that they're the only ones reading/writing to the file. Most library routines return an IFFP code. This is a number from 0 to -11, or in some cases, a 4 byte ascii ID. 0 usually means success. The negative numbers mean errors. See the include files for details. Your calling routines should check all returned IFFP error codes. Don't press on after an error! These routines try to have no side effects if an error, except that partial I/O is sometimes unavoidable. All of these routines may return DOS_ERROR. In that case, ask DOS for the specific error code, if desired, via the DOS library's IOErr() routine. There is an ilbm.library function that will help in presenting error msgs to a user. The library has functions that can isolate you from the drudgery of dealing with ILBM files completely. These functions are referred to as "high level" functions. Some functions give you more control over how the image is saved or loaded. These routines require you to deal with certain structures (to be described later). These functions, called "mid-level", are more complica- ted to use than the high level ones, but still isolate you from dealing with IFF chunks, etc. If neither of these levels is suitable for your needs, then you can use the "low level" routines. You must fully understand the IFF spec in order to use these routines, but you should be able to construct any con- ceivable IFF reader/writer with them. The high level, writing (save) routines of the library are very straight- forward, and require no special structures or programming techniques. The high level, reading (load) routines of the library use an ILBMFrame structure. See the INCLUDE files for a description of this structure. The C include file is "ILBM_lib.h". The assembly include is "IFF.i". Basic users should read "BasicUsers". An ILBMFrame is a structure for reading ILBM FORMS within an IFF file. Using high level routines, the library does the actual parsing (loading and processing) of the IFF file. It loads the image into an open window based upon the info that it finds in CMAP, BMHD, CAMG, etc chunks. It looks for ILBM FORMs inside of LISTS and CATS, and also handles any ILBM PROPS. You MUST supply a master ILBMFrame for the library's load routines. At the high level are two routines called SaveWindowToIFF() and LoadIFFToWindow(). These 2 routines are for writing and reading IFF ILBM files respectively. In the case of SaveWindowToIFF(), you pass the address of the window whose image you wish saved, and the name of the file. This routine will save the window image as an IFF ILBM file. It manages all the low level library routines for writing chunks so that you need not be bo- thered with any of the details of ILBM files. In the case of an error, it also deletes the aborted file. Likewise, LoadIFFToWindow() handles all of the low level reading of an ILBM file. This routine is passed the name of the file to read, and an ILBMFrame structure. LoadIFFToWindow will take care of initializing all of the fields of the master ILBMFrame except for the iScreen, iWindow, and iUserFlags fields. You must set up these fields initially in order to tell the library if you want a window opened for the ILBM image, a visible screen bar and mouse pointer, and a few other options. If the iWindow field in the ILBMFrame structure is not zero (i.e. it is the address of an opened window), then the image will be scaled to fit inside that window. What this means is that you can load a HIRES image into an opened LORES window, and vice versa. The library will scale the picture to "fit" the window. If the ILBMFrame's iWindow is NULL (0), then a screen and backdrop window will be opened for the image. The screen attributes (i.e. HAM, INTERLACE, etc.) will be determined by the ILBM file so that the picture will be dis- played without scaling. When this routine returns (successfully), the addresses of the window and screen will be in the ILBMFrame's iWindow and iScreen fields. You may then ModifyIDCMP() or whatever at this point. ************************ SaveWindowToIFF ************************** IFFP = SaveWindowToIFF(fileName, window) d0 d1 a0 Saves a window's image as an IFF ILBM file. Returns an IFFP code (0 if OK, a negative, non-zero number for an error. Z-flag set accordingly.) This procedure calls SaveILBM(), passing in an location of <0, 0>, and a NULL mask. It also assumes you want to write out all the bitplanes in the BitMap. Also, it applies byte run compression to the BODY. If an error in saving, it deletes the partial file. The fileName should be a complete path as might be typed at the CLI (i.e. df0:extras/myName). This is an ascii, NULL-terminated string. window is the address of the opened window (as returned from Intuition's OpenWindow). ;************************* LoadIFFToWindow ******************************** ;IFFP = LoadIFFToWindow( fileName, ILBMFrame ) ; d0 d1 a1 ; Loads an ILBM file into the ILBMFrame's iWindow. If iWindow is NULL, opens ; a screen/backdrop window for the image. This routine calls LoadILBM(). If neither one of these routines serves your purpose, then you will need to use some lower level routines instead. The library's lowest level routines are the same routines as the original EA code, IFFr.c and IFFw.c, except that they are smaller and faster asm modules. At this point, I need to warn you that the arguments passed to some routines have been changed, as well as the order of the arguments. This was done to make the code smaller and faster in assembly by using the movem instruction. Here are some routines at the mid-level. They isolate you from dealing with low level structures and loading chunks, but give you the option of install- ing custom routines to handle FORMs, PROPs, or undocumented IDs inside of FORMs. See the program, ANIMInfo, for an example of using a custom handler for FORMs. ******************************* SaveILBM() ****************************** Writes an entire BitMap as a FORM ILBM in an IFF file. Unlike SaveWindowTo- IFF, this routine allows you to save an uncompressed image, or with a different xy position than 0,0 (for saving a portion of the whole image), or saving additional chunks. This version works for any display mode (orig code by C. Scheppner). Normal return result is IFF_OKAY. The utility program IFFCheck would print the following outline of the resulting file: FORM ILBM BMHD CAMG CMAP ...other chunks that you save BODY (compressed) or (uncompressed) IFFP=SaveILBM(ViewModes,Compress,fileHandle,Mask,colors,BitMap,xyPoint,handler) d0 d0 d1 d2 d3 a0 a1 a2 a3 ViewModes - The viewmodes of the screen as an unsigned LONG Compress - Compression type. 0=None 1=cmpByteRun1 fileHandle - DOS handle of an opened file Mask - address of a mask plane, or NULL colors - address of the colorTable to save as a CMAP chunk BitMap - address of the Bitmap structure whose planes are to be saved as an ILBM BODY xyPoint - the address of two UWORD values that describe the xy position to be saved in the BMHD chunk handler - the address of a routine to be called before the BODY is written out. This allows an application to save additional chunks such as CRNG, etc. Passed a context structure, you must open the Context (OpenRGroup) and use PutCk, PutCkKnown, or calls to IFFWriteBytes (after PutCkHdr, and ending with PutCkEnd) to write out the chunk. If an error, you must delete the partial file yourself. ;******************************* SaveANIM() ****************************** ; Writes an ANIM file. Writes the passed BitMap as the first frame (FORM ; ILBM). Assumes user wants to write out all planes of the BitMap. Calls ; application routine for subsequent frames. Normal return result is ; IFF_OKAY. This version works for any display mode. ; ; The utility program IFFCheck would print the following outline of the ; resulting file: ; ; LIST ; PROP ILBM ; BMHD ; CAMG ; CMAP ; ANHD ;if passed ; FORM ANIM ; FORM ILBM ; BODY (compressed) or (uncompressed) ; ....application routine's saved chunks (i.e. additional Frames) ; ;IFFP = SaveANIM(ViewModes,Compress,fileHandle,Mask,colors,BitMap,xyPoint, ; d0 d0 d1 d2 d3 a0 a1 a2 ; ; FrameHandler,ANHDaddress) ; a3 a4 This is similiar to SaveILBM except your FrameHandler is called repeatedly (so you can write numerous frames) while return = IFF_OKAY. Return a 1 to stop the Handler loop successfully. Returning an IFFP error aborts the save. Note that the file written is a LIST with a PROP ILBM. This is so that each frame need not contain identical data. If the passed ANHD address is not NULL, the ANHD chunk will be written in the PROP. In this way, a simple ANIM can be written where the first frame need only contain a BODY chunk, and subsequent frames contain a DLTA. If all successful, final return is IFF_OKAY. If an error, you must delete the partial file. *************************** LoadILBM() ***************************** IFFP = LoadILBM(fileHandle, vectors, masterILBMFrame) d0 d1 a0 a1 Can read an ILBM file. Unlike LoadIFFToWindow, you can arrange for your own custom routines to handle FORMs, PROPs, and certain ILBM chunks. This entry point is useful for ANIM readers where you don't want the lib to actually load the first frame into some window's bitmap's planes. This is done by replacing the default lib FORMhandler routine with your own handler. This routine will return IFF_DONE if an image has been successfully load- ed into a window by the lib's default 'FORM' handler. All other IFFP codes indicate that an image wasn't loaded. EXTREMELY modified version of ReadPict.c by Jerry Morrison, Steve Shaw, and Steve Hayes, Electronic Arts. Modified by C. Scheppner and Jeff Glatt * The default library 'FORM' handler routine can open a window/screen of the correct size using information placed in the ILBMFrame. The BODY chunk data is then decompressed as it is read into this window's BitMap structure's bit planes. If the application already has a window open, then the lib will scale the picture to fit inside the window's bitmap's planes (i.e. if you are trying to stuff a HIRES picture into a LORES window). The default FORMhandler also loads in CCRT or CRNG chunks (converts CCRT to CRNG) and CAMG chunks (i.e. for HAM) placing data in the ILBMFrame structure. * Handles LISTS, CATS, and ILBM PROPS automatically. * Can call your custom routines when it gets to a FORM or non-ILBM PROP if you don't want the lib to decompress any image data into bit-planes. Some ANIM players may want to decompress BODY and DLTA chunks during playback. * Can call your custom routine for ANHD, DLTA, or any other ILBM chunk that the lib's default 'FORM' handler doesn't "understand". So, for each frame of an ANIM file, the application can control where and how the associated chunks are loaded. One of the parameters to this routine is a Vector structure. This is simply a structure that holds the addresses of whatever routines you would like the lib to execute while parsing an IFF file. If the vector address is 0, then the default lib routine is used. You must initialize the structure before calling LoadILBM(). One field holds the address of a routine to handle nonILBM 'PROP's or an ILBM PROP chunk that is "unknown" to the lib. The lib doesn't know about the following ILBM chunks: ANHD, DEST, GRAB, SPRT, and DLTA. It will skip these in an ILBM PROP if you don't install a PROPhandler. Also, it will skip nonILBM PROPs if you don't have a PROPhandler. If you don't care, set this field to NULL. Another field is for the your FORMhandler routine address. If not NULL, this routine is called instead of the lib's default 'FORM' handler. You are expected to handle all parsing of FORMs as the library finds them. Another field is for a CHUNKhandler. This is called by lib's default 'FORM' handler when it encounters "unknown" chunks inside an ILBM (see PROPhandler). If NULL, unknown chunks are skipped. If you don't use the lib's default 'FORM' handler, then this field is free to use. The last field is for the NonILBMhandler. This is called by lib's default 'FORM' handler when it encounters a FORM other than ILBM (i.e. 8SVX, etc). If NULL, nonILBM FORMs are skipped over in search of ILBMs. If you don't use the lib's default 'FORM' handler, then this field is free to use. By installing routines via the vectors structure, you can "take away" certain parsing duties from the lib, while it handles LISTS, CATS, or other messy details. For example, you could install just a CHUNKhandler and set the other fields to 0. The lib would parse all LISTS, PROPS, CATS, and FORMS. It would only call your routine if it encountered an ANHD, DEST, GRAB, SPRT, or DLTA. Later, I will detail the parameters passed to your handlers, and what value should be returned. The lib handles LISTs and PROPs, diving into a LIST, if present, to read the first FORM ILBM. (E.g. a DeluxePrint library of images is a LIST of FORMs ILBM.) It also dives into non-ILBM FORMs, if present, looking for a nested FORM ILBM. (E.g. a DeluxeVideo C.S. animated object file is a FORM ANBM containing a FORM ILBM for each image frame.) Also, an ANIM is considered a non-ILBM form with imbedded ILBMs. This lib's default 'FORM' handler can automatically load the first frame of an ANIM. LoadILBM() zeros out the ILBMFrame's iFlags (but not iUserFlags), iBMAP, iNumColors, and iCycleCnt fields. You must initialize the iUserFlags field prior to calling this routine. Various bits of this field affect certain options (see Include File for details). Other bits are set by the lib to inform you of certain facts. (i.e. the ANIMB bit would be set by the default 'FORM' handler if an ANIM FORM was encountered in the file.) Here's how LoadILBM() works. The lib initializes the aforementioned fields of the ILBMFrame, and creates a special PROP list to take care of any PROPS in the file. Next, the opened file is parsed. If an ILBM PROP (inside of a LIST) is encountered, an ILBMPropFrame is allocated, linked into the PROP list, and data in the PROP is copied to this Frame. An ILBMPropFrame is an ILBMFrame with a few extra fields in order to allow this structure to be linked into a list. There are lib routines to facilitate allocating and freeing these structures, as well as searching the PROP list for certain types of Frames. The lib automatically handles all ILBM PROPS. For other PROPS, it calls your PROPhandler. If no PROPhandler, it "ignores" that non-ILBM PROP. The lib only handles the following chunks inside an ILBM PROP: BMHD CMAP CAMG CRNG CCRT For other types (i.e. ANHD, DEST, etc), it will call your PROPhandler. One of the parameters passed to your PROPhandler is the PROP ID. You can use this to determine if the lib is sending you a non-ILBM PROP, or an ILBM PROP with an unknown chunk. In the case of an unknown ILBM chunk, the PROP ID will be 'ILBM' and the ID of the chunk will be passed as well. Your PROPhandler should return an IFFP code. There are other parameters passed to your PROP handler also. One parameter is the address of the PROP list. You should add any allocated ILBMPropFrames to this list (using GetPROPStruct) so that the lib can free them for you when it is done loading the file. When the lib encounters a FORM, it checks to see if you have installed a FORMhandler. If so, control is passed to your routine. Let's assume that this is the case. Your custom FORM handler is passed the address of an initialized Context structure (to be discussed later). Although no further initialization of this structure is required on your part, you may need it for calling certain lib functions. Save the address somewhere handy. You are also passed your master ILBMFrame so that you can save data to it from within your FORMhandler. You are passed the type ID of the FORM (i.e. is it an 'ILBM'?). Also, you have the address of the PROP list. One of the first things you'll probably want to do is search it for any PropFrame with the same type ID as the FORM. The function SearchPROP does this. You may then copy that Frame's data into the Frame that is used to hold data within your FORMhandler. CopyILBMProp() can copy an ILBMPropFrame to your master ILBMFrame. Then, as you parse chunks in your FORMhandler, the new data will overwrite the corresponding PROP data (as it should). You also are passed the Vectors structure address in case you need it further. Your FORMhandler is expected to parse the FORM (using a few low level routines) and eventually return an IFFP code. See ANIMInfo for an example of a custom FORM routine. Of course, if you don't have a custom FORMhandler, then the default lib routine is used. As you may have guessed, this is the big beast of the lib. Here is the logic that this routine employs. 1). It checks to see if the FORM is an ILBM. A). If not, it checks to see if you have a NonILBMhandler installed, and passes control as above. If no NonILBMhandler, it parses the FORM for imbedded ILBMs, setting the ANIMB of iUserFlags if an ANIM. It does nothing else with NonILBM FORMs. B). If it is an ILBM FORM, it goes to step 2. 2). It checks the PROP list for the last ILBMPropFrame, and if one is found, copies the data to the master ILBMFrame. 3). It parses the FORM's chunks. A). A CAMG's ViewModes is placed in the ILBMFrame B). A CMAP is loaded into the ILBMFrame, and iNumColors determined. C). A BMHD is loaded into the ILBMFrame with BMHDFLAG of iFlags set. D). CCRT and CRNG chunks are loaded (up to 8) into the ILBMFrame, and iCycleCnt is determined. E). A BODY chunk is the last chunk the lib cares about. When it finds one of these, it loads/decompresses the image into the ILBMFrame's iWindow. If iWindow is NULL, then a backdrop window/screen compatible with the image is opened. The ILBMFrame's iWindow and iScreen fields are set to these addresses. The lib returns IFF_DONE which knocks it out of the load process, unless the ANIMB of iUserFlags is set. In this case, the lib assumes that there are more frames to follow, and continues scanning to the end of the ANIM. Eventually, IFF_DONE is returned for a successful image load. F). If the chunk wasn't one of the proceeding types, the lib calls your CHUNKhandler. If CHUNKhandler is NULL, it ignores this chunk. Your CHUNKhandler must return an IFFP code. If the ANIMB bit of iUserFlags is set, and you wish to continue scanning the remaining ANIM frames, then return IFF_DONE for any DLTA chunks, and IFF_OKAY for all others. Remember that the lib calls your CHUNKhandler for ANHD and DLTA, so for each DLTA you encounter, that is the end of another ANIM Frame (imbedded ILBM FORM). If ANIMB is not set, then you are in a plain ILBM file. Return IFF_OKAY unless you get an error from the low level routines. Returning IFF_DONE (or other errors) will force the load to terminate if an ILBM. If an ANIM, returning END_MARK (or other errors) will do the same. Here are the parameters passed to your custom vectors. Return an IFFP code. IFFP = PROPhandler(chunkID,PropID,Context,Vectors,Frame,PROPList) d0 d0 d2 a0 a2 a3 a4 IFFP = FORMhandler(chunkID,Context,Vectors,Frame,PROPList) d0 d0 a0 a2 a3 a4 NonILBMFormHandler and CHUNKhandler same args as FORMhandler. ««««««««««««««««««««««««««««««««««««»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»» OH NO! IT'S PROPS! You are responsible for keeping track of any ILBMFrame (or other struct- ures) that you create for loading a file. In particular, you should allo- cate some sort of frame structure to hold data whenever a PROP is encountered. There are 4 routines for allocating, examining, and freeing frame structures from what I call a PROPList. A PROPList is a linked list structure used to manage Frames allocated to hold PROP data. When using the high or mid level routines, the lib creates a PROPList for you. In order to link a Frame structure (i.e. an ILBMFrame) into this list, we need to "extend" the Frame with a few extra fields at the beginning of the structure. We add the following structure "on top" of the frame, and refer to the whole thing as a PropFrame. ID dc.b [the 4 byte type ID] ;type of PropFrame size dc.w [size] ;the size of the entire structure (with the ;subsequent data part) So an ILBMPropFrame would be as follows: ID dc.b 'ILBM' size dc.w SizeOfILBMFrame+10 ;an ILBMFrame structure immediately follows. This is the data part. So an ILBMPropFrame has two extra fields prepended to it. It is 10 bytes larger than an ILBMFrame. Here is the routine used to allocate a PropFrame. Since the lib handles ILBM PROPs for you, it calls this routine when it needs an ILBMPropFrame. Note that the passed size would be 10+sizeofILBMFrame. ************************* GetPROPStruct ************************** Frame = GetPROPStruct(size,typeID,PROPList) d0 d0 d1 a1 Allocates a PropFrame of passed size, and stores its ID, links it at the tail of the passed PROPList, and increments PROPList's entries. Returns the address of the new Frame (data part), or 0 if error. Note that the address of the data part is returned (i.e. skips those first 2 "extra" fields) so that you can treat the returned pointer just like a regular frame. If you were allocating an ILBMPropFrame, the returned address would be that of an ILBMFrame. When writing a custom PROP routine, you must define the PropFrame for nonILBM PROPs (i.e. what does an SMUSPropFrame look like? You decide.) Then, allocate that Structure via GetPROPStruct() from within your PROP routine. The lib always allocates an ILBMPropFrame for you. Now, whenever you encounter a FORM, your FORMhandler should search the PROPList for any PropFrames with the same ID. The following routine does this ************************ SearchPROP ************************* frame = SearchPROP(ID,PROPList) d0 d0 a1 Searches the passed PROPList for the last PropFrame with the same ID as passed. If it finds one of the same type, returns the address of the data part (the frame part), or 0 if none of that type found in the list. If SearchPROP returns an address, you'll want to copy that Frame to the Frame you intend to use to parse the FORM. There is a routine for copying ILBMFrames. You'll need to write routines for other Frames that you devise. ************************ CopyILBMProp *************************** CopyILBMProp(FromFrame,ToFrame) d0 a1 Copies FromFrame to ToFrame. Both frames must be ILBM frames. It transfers the iUserFlags field of FromFrame to ToFrame without altering any set bits in ToFrame's iUserFlags. Finally, the lib frees all of the PropFrames in the PROPList for you. But in case you are maintaining a second PROPList which you need to free, here's a routine that you can use. ************************* FreePROPList ************************** FreePROPList(PROPList) a1 Frees all the PropFrames in the passed PROPList. When using LoadILBM() or LoadIFF(), the lib allocates/initializes the PROPList. These routines also free up that PROPList and all its PropFrames upon termination of the load. «««««««««««««««««« Writing a Reader/Writer from scratch »»»»»»»»»»»»»»»»»»» If the high level functions LoadIFFToWindow() and SaveWindowToIFF(), or the mid-level functions SaveILBM(), LoadILBM(), and LoadIFF() aren't suita- ble, you may use the low level routines to create a Reader/Writer. Unless otherwise stated, the normal (successful) return codes for the low level routines is IFF_OKAY. This means that assembly programmers can always bne to an error routine (z-flag set appropriately). The library's low level routines utilize a 36 byte structure called a "Context". The Context structure that the library uses is identical to the original EA structure. Check the INCLUDE files for a description. For the high and mid-level routines, the lib allocates a new Context (and initializes it by OpenRIFF or OpenRGroup) for every group (FORM, CAT, LIST, or PROP) encountered. This is done for you even if you have custom vectors for FORMs and PROPs. You will only need to deal with Context structures if you are using the lowest level library routines. This structure is for reading and writing groups and their chunks. It's just a linked node type of structure for reading (nested) chunks. A Context structure must be created whenever a- nother level is encountered. The parentContext field contains the address of another Context structure if this is not the top level. For example, if a LIST is encountered, a Context structure is created. For the first FORM in the LIST, another context structure is created. The parentContext field of the FORM's Context would contain the address of the LIST's Context structure. The parentContext field of the LIST's Context structure would be NULL (as long as it wasn't inside of that moron's CAT). You must read or skip over all the chunks in the FORM before you try to examine what comes next in the LIST. You can read a chunk via GetChunkHdr() and IFFReadBytes. You can skip a chunk by calling GetChunkHdr() a second time for the next chunk's ID, or SkipFwd(). The UserData field is set to the parent's Userdata field by certain routines. In this way, you can pass the contents from level to sublevel. If you use the low level routines properly, the lib will take care of padding out chunks to even bytes, and keeping track of the total bytes read in or written out. The bound field of the Context merits special mention in that it appears to have no useful purpose. If you place any value other than UNKNOWN there, it serves as the MAX number of bytes that can be written. So for example, if the bound = 10000, then you will be prevented from writing out a total of more than 10000 bytes. For most IFF applications, it isn't initially known how many bytes will eventually be written. All of the IFF writers which I've seen always set this to UNKNOWN. Also, it doesn't seem useful to have the code impose a barrier. AmigaDOS already returns an error if you attempt to write beyond a disc's capacity. I can only assume that this "feature" was included so that this code would work on other computers with stranger DOS than the Amiga. This field is used in PutCkEnd to deter- mine whether the lib needs to adjust UNKNOWN chunkSizes to the real value after it writes out a chunk. If EA had used a bit flag for UNKNOWN rather than a value to be compared against the bound field, then the code could be smaller and faster. Then again, the original code was written in C, a lan- guage invented by and for people who can't be bothered with trivial details like setting bits. ******* Low Level Reader Routines ******** For reading a chunk, the procedure is to allocate a Context structure, initialize it with OpenRIFF or OpenRGroup, read the chunks with GetChunkHdr (and its kin) and IFFReadBytes, and close the Context with CloseRGroup or EndRGroup. IFFP = OpenRIFF(fileHandle, Context) d0 d1 a0 Given an open file, this initializes a Context spanning the whole file. ASSUMES context was allocated by caller but not initialized. ASSUMES caller doesn't deallocate the context before calling CloseRGroup. Returns NOT_IFF IFFP code if the file is too small for even a chunk header. FileSize = FileLength(fileHandle) d0 d1 Returns the length of the whole file or else a negative IFFP error code of NO_FILE or DOS_ERROR. SIDE EFFECT: Thanks to AmigaDOS, we have to change the file's position to find its length. Assembly programmers: please note that this routine does not restore a6 upon exist, so save it before you call this. This routine is normally only used by OpenRIFF() but I included access to it because you might find it useful for other non-IFF uses. IFFP = OpenRGroup(parent, new) passed 2 Context structures d0 a0 a1 Open the remainder of the current chunk as a group read context. This will be called just after the group's subtype ID has been read (automatically by GetChunkHdr for LIST, FORM, PROP, and CAT) so the remainder is a sequence of chunks. This sets new's UserData = parent's UserData. ASSUMES new context allocated by caller but not initialized. ASSUMES caller doesn't deallocate the context or access the parent context before calling CloseRGroup on the new context. BAD_IFF ERROR if context end is odd or extends past parent. IFFP = CloseRGroup(context) d0 a0 Close a group read context, updating its parent context. After calling this, the old context may be deallocated and the parent context can be accessed again. It's okay to call this particular procedure after an error has occurred reading the group. ID = GetChunkHdr(context) d0 a0 Skip any remaining bytes of the previous chunk and any padding, then read the next chunk header into context's chunkID and chunkSize fields. If the chunkID is LIST, FORM, CAT, or PROP, this automatically reads the subtype ID into context's subID field. Caller should dispatch on groupID (and typeID) to an appropriate handler. RETURNS the chunkID (the 4 ascii bytes of the new chunk header) if it found another chunk. Otherwise, it will return one of the following errors: 1). END_MARK if there are no more chunks in this context 2). NOT_IFF if at the top level and it isn't a FORM, LIST, or CAT 3). BAD_IFF if a malformed chunk, the chunkSize is negative or too big for containing context, ID isn't positive, or we hit end-of-file. Note that if an error, bit #31 of d0 will be set and so you can either 1). move.l d0,d1 bmi to an error routine. 2). btst.l #31,d0 bne to an error routine. The Z and N Flags are set appropriately for low level functions. See also GetFChunkHdr, GetF1ChunkHdr, and GetPChunkHdr, below. IFFP = IFFReadBytes(nBytes, context, buffer) d0 d0 a0 a1 Read the specified number of data bytes of current chunk. (Use OpenGroup, etc. instead to read the contents of a group chunk.) You can call this several times to read the data piecemeal. CLIENT_ERROR if nBytes < 0. SHORT_CHUNK if nBytes > remaining bytes which could be due to a application bug or a chunk that's shorter than it ought to be (bad form). (If CLIENT_ERROR or SHORT_CHUNK, IFFReadBytes won't read any bytes.) IFFP = SkipFwd(bytes, context) d0 d1 a0 Skip over bytes in a chunk. Won't go backwards. Updates context's position but not context's bytesSoFar. NOTE: The original EA code's SkipGroup() routine has been eliminated since the library is set up to parse LISTS and PROPS. IFFP = LoadIFF(file, vector, dataAddress) d0 d1 a0 a1 Given an open file, allocates a group context and uses it to read the FORM, LIST, or CAT and it's contents. The idea is to parse the file's contents, and for each FORM, LIST, CAT, or PROP encountered, call the FORMhandler, PROPhandler, Cat, or List procedure passing the GroupContext address. If you want to handle FORMs, LISTs, and CATs nested within FORMs, the FORMhandler must dispatch to FORMhandler, List, and Cat procedures (using GetF1ChunkHdr). The dataAddress parameter is optional. It could be a Frame of some sort. The lib will pass this to your FORMhandler. Normal return is IFF_OKAY (if whole file scanned) or IFF_DONE (if a custom routine said "done" first). ID = GetFChunkHdr(context) d0 a0 Call GetFChunkHdr instead of GetChunkHdr to read each chunk inside a FORM. It just calls GetChunkHdr and returns BAD_IFF if it gets a PROP chunk. ID = GetF1ChunkHdr(context) d0 a0 GetF1ChunkHdr is like GetFChunkHdr, but it automatically dispatches to the getFORM, List, or Cat procedure (and returns the result) if it encounters a FORM, LIST, or CAT. ID = GetPChunkHdr(context) d0 a0 Call GetPChunkHdr instead of GetChunkHdr to read each chunk inside a PROP. It just calls GetChunkHdr and returns BAD_IFF if it gets a group chunk. IFFP = GetCMAP(Context, colorMap, pNColorRegs) d0 d0 a0 a1 Reads in a CMAP chunk and creates the colorMap at passed address. pNColorRegs is passed in as a pointer to the number of ColorRegisters caller has space to hold. GetCMAP sets to the number actually read. IFFP = GetBODY( bitmap, mask, context, BMHD ) d0 d0 d1 a0 a1 Reads the BODY into the passed bitmap's planes, decompressing if necessary. Passed the addresses of a BitMap, mask plane (or NULL), the context structure, and the loaded BitMap header chunk. BOOL = UnPackRow(dstBytes0, srcBytes0, Source, Dest) d0 d1 d3 a2 a3 Converts data from "cmpByteRun1" run compression. control bytes: [0..127] : followed by n+1 bytes of data. [-1..-127] : followed by byte to be repeated (-n)+1 times. -128 : NOOP. Unpacks one row, returning the source and destination addresses when it produces dstBytes bytes. This routine no longer uses POINTERS to POINTERS as in the original Elec Arts code. Also, args in a different order. ********* Low Level Writing (Save) Routines ********* These routines will random access back to set a chunk size value when the caller doesn't know it ahead of time (UNKNOWN size). The overall scheme is to open an output Group Context via OpenWIFF or OpenWGroup, call either PutCk or {PutCkHdr {IFFWriteBytes}* PutCkEnd} for each chunk, then use CloseWGroup to close the Group Context. To write a group (LIST, FORM, PROP, or CAT), call StartWGroup, write out its chunks, then call EndWGroup. StartWGroup automatically writes the group header and opens a nested context for writing the contents. EndWGroup closes the nested context and completes the group chunk. IFFP = OpenWIFF(limit, fileHandle, new) new is the address of a Context d0 d0 d1 a0 structure Given a file open for output, initialize a new, write context. The "limit" arg imposes a fence or upper limit on the logical file position for writing data in this context. Pass in UNKNOWN to be limited only by disk capacity. ASSUMES new context structure allocated by caller but not initialized. ASSUMES caller doesn't deallocate the context before calling CloseWGroup. The caller is only allowed to write out one FORM, LIST, or CAT in this top level context (see StartWGroup and PutCkHdr). CLIENT_ERROR if limit is odd. IFFP = StartWGroup(groupType, groupSize, subtype, parent, new) d0 d0 d1 d2 a0 a1 parent and new are the addresses of Context structures, groupType and subtype are IDs, groupsize is a LONG Start writing a group (presumably LIST, FORM, PROP, or CAT), opening a nested context. The groupSize includes all nested chunks + the subtype ID. The subtype of a LIST or CAT is a hint at the contents' FORM type(s). Pass in FILLER (" ") if it's a mixture of different kinds. This writes the chunk header via PutCkHdr, writes the subtype ID via IFFWriteBytes, and calls OpenWGroup. The caller may then write the nested chunks and finish by calling EndWGroup. The OpenWGroup call sets new's ILBMFrame to parent's ILBMFrame. ASSUME new context structure allocated by caller but not initialized. ASSUME caller doesn't deallocate the context or access the parent context before calling CloseWGroup. ERROR conditions: See PutCkHdr, IFFWriteBytes, OpenWGroup. IFFP = OpenWGroup(parent, new) parent and new are GroupContext structures d0 a0 a1 Open the remainder of the current chunk as a group write context. This is normally only called by StartWGroup. Any fixed limit to this group chunk or a containing context will impose a limit on the new context. This will be called just after the group's subtype ID has been written so the remaining contents will be a sequence of chunks. This sets new's UserData = parent's UserData. ASSUME new context structure allocated by caller but not initialized. ASSUME caller doesn't deallocate the context or access the parent context before calling CloseWGroup. CLIENT_ERROR if context end is odd or PutCkHdr wasn't called first. IFFP = EndWGroup(old) d0 a0 End a group started by StartWGroup. This just calls CloseWGroup and PutCkEnd. ERROR conditions: See CloseWGroup and PutCkEnd. IFFP = CloseWGroup(old) old is the address of a Context structure d0 a0 Close a write context and update its parent context. This is normally only called by EndWGroup. If this is a top level context (created by OpenWIFF) we'll set the file's EOF (end of file) but won't close the file. After calling this, the old context may be deallocated and its parent context can be accessed again. Amiga DOS Note: There's no call to set the EOF. We just position to the desired end and return. Caller must Close file at that position. CLIENT_ERROR if PutCkEnd wasn't called first. IFFP = PutCk(chunkID, chunkSize, context, data) d0 d0 d1 a0 a1 Writes a whole chunk to a Context. This writes the chunk ID, chunkSize, data bytes, and (if needed) a pad byte. It also updates the Context to reflect how many bytes have been written to the file. Returns CLIENT_ERROR if chunkSize = UNKNOWN. This is because you must know how many bytes of data you wish to write in order to use this routine. (i.e. Use this routine instead of a PutCkHdr/IFFWriteBytes/PutCkEnd series of calls when you know exactly how many bytes will be in the chunk). See also PutCkHdr errors. IFFP = PutCkHdr(chunkID, chunkSize, context) d0 d0 d1 a0 Writes just an 8 byte chunk header. The chunk header consists of the 4 byte ascii ID, and the chunkSize LONG. You should follow this will any number of calls to IFFWriteBytes in order to write out the chunk data. Finally, when all the chunk data is output, call PutCkEnd. If you don't yet know how big the chunk is, pass in chunkSize = UNKNOWN, then PutCkEnd will set the chunkSize for you later. This method is used when you really don't know how many data bytes will eventually get written out. (i.e. maybe you're compressing the data as it's being written out and you don't want to bother with knowing or keeping track of how many bytes have been written out. The lib does this for you as long as you specify chunkSize = UNKNOWN and you use only the IFFWriteBytes routine to write data to the file). Otherwise, IFFWriteBytes and PutCkEnd will ensure that the specified number of bytes get written. CLIENT_ERROR if the chunk would overflow the Context's bound, if PutCkHdr was previously called without a matching PutCkEnd, if chunkSize < 0 (except UNKNOWN), if you're trying to write something other than one FORM, LIST, or CAT in a top level (file level) context, or if chunkID <= 0 (these illegal ID values are used for error codes). IFFP = IFFWriteBytes(nBytes, context, data) d0 d0 a0 a1 Write nBytes number of data bytes for the current chunk and update the Context. Returns CLIENT_ERROR if any of the following conditions: 1). Writing nBytes of data would overflow the Context's limit or current chunk's chunkSize. If you have specified these fields to be UNKNOWN, then this condition is not applicable. 2). If PutCkHdr wasn't called first. 3). nBytes < 0 (i.e. ridiculously large). IFFP = PutCkEnd(context) d0 a0 Complete the current chunk, write a pad byte if needed, and update the Context. If current chunk's chunkSize = UNKNOWN, this goes back and sets the chunkSize in the file. CLIENT_ERROR if PutCkHdr wasn't called first, or if the application hasn't written 'chunkSize' number of bytes with IFFWriteBytes. IFFP = InitBMHdr(masking, compression, transparentColor, d0 d0 d1 d2 pageWidth, pageHeight, bmHdr0, bitmap) d3 d4 a0 a1 Initializes a BitMap Header (BMHD) chunk to the passed values, and sets the BMHD's aspect ratio. IFFP PutCMAP(depth, context, colorMap) d0 d0 a0 a1 Writes out the passed colorMap (actually colorTable) as a CMAP chunk. IFFP = PutBODY(mask, context, bmHdr, bitmap) d0 d0 d1 a0 a1 Writes the BODY chunk to disk in compressed or uncompressed form. bytes, newSource, newDest = PackRow(rowSize, pSource, pDest) d0 a0 a1 d0 a0 a1 Given addresses of source and dest, packs one row, returning the new source and destination addresses in a0 and a1. RETURNs count of packed bytes in d0. Please note that this routine needs the actual addresses to the source and destination buffers unlike the original EA code which wanted PTRS to PTRS. That technique is unnecessary for assembly applications which can access multiple return values in several registers. I decided to put the ineffic- iency where it really belongs; in the C application. For C programmers, the new source and dest addresses can be found at sourceptr and destptr respec- tively. These are globals contained in the module ILBMInterface.asm which must be assembled and linked with your application. These globals can be accessed after a call to PackRow so that you'll have the addresses to pass on the next, subsequent call. Of course, you'll be able to access the returned number of packed bytes as a normal return. Ultimately what this means is that if you call PackRow from a C application, you can never make that application fully re-entrant (unless you put a FORBID/PERMIT around the call). «««««««««««««««««««««««««««««««««»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»» ANIM SUPPORT There are a few routines to assist in the construction of an ANIM player. These routines can unpack BODY and DLTA chunks, modifying a BitMap's planes in order to construct the next frame of an animation. Also there is a routine to setup a BitMap structure initially based on a loaded BMHD chunk. The normal procedure for an anim player would be to replace the lib's default 'FORM' handler with a custom handler. This handler should load the data part (skip the 8 byte header) of the first frame's BMHD and BODY, and the ANHD and DLTA for all subsequent frames. Now you can play back the anim as follows: 1). Allocate a raster large enough to hold the decompressed image. You can determine the size by the fields in the BMHD. height = BMHD's h or pageHeight, whichever is larger planedepth = numPlanes * height width = BMHD's w or pageWidth, whichever is larger RowBytes = (15+width)/8, rounded down to the nearest integer RasterSize = RowBytes * planeDepth get CHIP mem for the raster 2). Setup the BitMap structure to be used for displaying the ANIM by calling the ilbm lib's SetupBitMap. Pass your allocated raster to SetupBitMap. You will need to set up the View, ViewPort, and colorMap yourself. 3). Decompress the BODY into the BitMap's planes (i.e. your allocated raster). For double-buffered animation, you will need to setup an identical Bitmap/raster and copy the orig image into the new raster. 4). Display the BitMap's planes (i.e. the first frame). 5). Modify the BitMap's planes via DecompDLTA to show subsequent frames. ;******************************************************************** DecodeVKPlane(linebytes,ytable,in,out) d0 d1 a0 a1 By Jim Kent. Modified JG. Copyright 1987 Dancing Flame all rights reserved. Decompresses a DLTA chunk in vertical-byte-run-with-skips compression mode. where in is a bit-plane's worth of vertical-byte-run-with-skips data and out is a bit-plane that STILL has the image from last frame on it. Linebytes is the number of bytes-per-line (UWORD) in the out bitplane, and it should certainly be noted that the passed variable ytable must be initialized to point to a multiplication table of 0*linebytes, 1*linebytes ... n*linebytes before this routine is called. Each entry in ytable is a UWORD. The format of "in": Each column of the bitplane is compressed separately. A 320x200 bitplane would have 40 columns of 200 bytes each. The linebytes parameter is used to count through the columns, it is not in the "in" data, which is simply a concatenation of columns. Each columns is an op-count followed by a number of ops. If the op-count is zero, that's ok, it just means there's no change in this column from the last frame. The ops are of three classes, and followed by a varying amount of data depending on which class. 1. Skip ops - this is a byte with the hi bit clear that says how many rows to move the "dest" pointer forward, ie to skip. It is non- zero 2. Uniq ops - this is a byte with the hi bit set. The hi bit is masked down and the remainder is a count of the number of bytes of data to copy literally. It's of course followed by the data to copy. 3. Same ops - this is a 0 byte followed by a count byte, followed by a byte value to repeat count times. ;********************************************************************** MakeYTable(width,height,table) d0 d1 a0 Makes a ytable for use with DecodeVKPlane. Table should be an memblock capable of holding 500 WORDs (1000 bytes). Here are the 3 "main" routines that you'll use in an ANIM player. *********************************************************************** SetupBitmap(raster,bitmap,BMHD) d0 a0 a1 Initializes passed bitmap's depth, rows, and BytesPerRow based on passed BMHD's x, y, and numPlanes, then stores addresses into bitmap's planes[] of each plane within the passed raster. (All the planes in the raster form 1 contiguous CHIP mem block). Raster must be large enough for numPlanes * BMHD's (x+15)/8 * BMHD's y. After an ANIM is loaded, and you allocate a raster (via AllocRaster maybe) based on the BMHD's x, y, and numPlanes, you can use this routine to setup a BitMap structure in order to decompress the BODY with DecompBODY and start calling DecompDLTA. ;********************************************************************** DecompBODY(BODY,BMHD,Bitmap) a0 a1 a2 Decompresses a BODY chunk's data into the BitMap's planes based on the passed BMHD (BitMapHeader) chunk's w,y,numPlanes. This can be used to make the first frame of an ANIM. ;********************************************************************** DecompDLTA(DLTA,Bitmap) a0 a2 Decompresses a DLTA chunk's data into the BitMap's planes (which must still have the previous frame's image). This routine calls MakeYTable and DecodeVKPlane, and can be used to "make" the next frame of an animation being double-buffered. «««««««««««««««««««««««««««««««««««»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»» IMAGE MANIPULATION When you ask the lib to load an image into an already opened window, some- times the image won't be the exact same size as the window. The lib scales the image to fill the window. Here is a routine you can use for scaling a section of one BitMap to fit into a section of another RastPort. It is passed a standard, graphics structure called a Rectangle. BOOL ScaleImage(dest_rectangle,source_rectangle,dest_rport,source_bitmap) a0 a1 a2 a4 Passed addresses of the source bitmap, source rectangle structure (current dimensions), destination rastport (to fit source into), and dest rectangle struct (desired dimensions). Scales the rectangular chunk (as described by source_rectangle) of source bitmap into the rectangular chunk (as described by dest_rectangle) of destination rastport. It achieves this by remapping the color value of each pixel, discarding or adding extra pixels per the scaling dimensions using an array for the colors. This does not create a larger or smaller palette of colors if the # of planes is different. It simply fits one image of given width and height into a different size raster. Alters the passed sorce BitMap's planes while transfering. It only does one line at a time of the source so it is SLOW. Returns a 1 if success, 0 if error (no mem for color array). ««««««««««««««««««««««««««««««««»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»» ERROR MESSAGES Finally, there is a routine to help display error messages to the user. This routine returns the address of a NULL-terminated string which des- cribes the IFFP error number. You can then display this string to the user. String = GetIFFPMsg(IFFP) d0 d0 ««««««««««««««««««««««««««««««««««»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»» MISC STUFF Here's a routine to make the mouse pointer in the passed window "disappear". Use Intuition's ClearPointer() or SetPointer() to change it again. BlankPointer(window) a0 ««««««««««««««««««««««««««««««««««««««»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»» ADDITIONAL NOTES AND THINGS TO MAKE YOU QUEASY OR APPREHENSIVE When a picture is scaled to fit inside a window (as in loading a HIRES image into a LORES window), the lib's scaling routine makes multiple calls to graphics lib routines which appear to do temporary mem alloc/dealloc. Unfortunately, the deallocation is not done in the same order as the alloca- tion. The net result: severe memory fragmentation. It is likely that you will not be able to scale a LORES picture into a HIRES screen twice in a row. Unfortunately, the Amiga operating system is not smart enough to re-coalese free blocks when it could. You have to reboot the computer. You want smart memory coalesing, buy a MAC. Despite this, I decided that scaling the picture yielded more pleasing results than cropping it. The scaling is meant to fit an image with a given width and height into a screen with a different width and/or height. The lib does not currently adjust for different depths (number of planes). For this reason, an image with a depth of 5 will probably end up with "weird" colors when loaded into a screen with a depth of 4. Perhaps in the future, a routine will be added to interpolate color tables if there appears to be a need for such a feature. Note that the process of scaling a picture to fit a different size screen is notably slow. Get used to it. An effort was made to test all of the lib functions in a variety of ways, but I'm sure that there are things which have escaped me. Please send any bug reports to the above address. I am not offering free consultation to anyone, but if you have a particular problem or question, I will try to assist. If a particular piece of code appears not to work with the library, I would be interested in seeing the code. There will almost certainly be new, improved lib versions in the future. These will be sent to Fred Fish. Obviously, more programming examples are needed for the custom handlers, (especially in those ugly, high level languages) but since everything you now have is free, it would be presumptious of you to expect more. If you write any code utilizing this library which you would like to share with others, send it directly to Fred Fish. The modules that you should have are as follows: ILBMLib.Doc this text file ilbm.library the library (to be copied to your boot disk's libs drawer) IFF.i assembly language include file ILBM_Lib.h C Include file ShowPic.c a C example of using the lib ILBMInterface.asm the awful "poot" that is required for any C example BasicILBM an AmigaBasic example ilbm.bmap the bmap file for the Basic example ilbm_lib.fd the Basic fd file BasicUsers additional info for Basic programmers ShowPic.asm an assembly example ANIMInfo.asm an assembly example of installing a custom FORM handler ANIMInfo an executable of the above ShowPic a re-entrant ILBM viewer with color-cycling (operates like CBM's "Display" program) ««««««««««««««««««««««««««««««««««»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»» PARTING SHOTS The ilbm.library is based upon much code placed in the public domain by several individuals. The programmers who offer such favors are the ones who are really responsible for the Amiga still being a viable product. Al- though most of these people are not on Commodore's payroll, they have managed to support the Amiga much more visibly and consistantly than Commodore's overpaid, middle management. A local Amiga user's group started up an amiga-oriented BBS, and within days, received a personal greeting from Jay Miner. Although Mr. Miner is no longer directly involved with the marketing of the Amiga, I understand that he keeps close ties to the Amiga community. Am I overly sensitive, or does anyone else sense that Commodore management lacks the personal commitment exhibited by Jay Miner? Aren't these the same type of people who were in charge when Commodore tottered on the brink of collapse a few years back? What has changed since then (besides the half a dozen presidents)? About 1 year ago, I sent in a disk full of assembly code to CATS. I assumed that CATS would decide whether anything was of any use to the amiga community, and distribute it via bulletin boards or Fred Fish. After seeing nothing, I decided to send the code to Fred Fish. Within a month, all of the code appeared on a Fish disk. How is it that a single individual with limited resources can maintain more visible and efficient support than a corporation? Is Fred Fish a super being from another galaxy, or is Commodore inefficient? What other kind of information is being irretrievably sucked into CATS? And what can you say about Charlie Heath and his friends who rewrote more of the Amiga's DOS than the 1.3 dev team? Is the problem with Commodore's paltry R & D budget? How does that compare with management salaries? I'm sure that you've heard about Commodore's problems with the IRS. There are also dozens more examples of sloppy, indifferent, incompetent management which have either thwarted the success of the Amiga, or alienated people who could help promote the Amiga. Consider that WordPerfect Corp. has chopped Amiga development to a minimum, siting lack of support from CBM as one reason. One of my favorite stories is the following: I am mostly interested in music software development, and follow that market closely. For the past few years, musicians have been increasingly utilizing computers for many tasks. Companies like Apple have noticed this growing market, and specifically targeted promotion in this area. Commodore has not. Given the Amiga's dismal showing in this market, worried amiga developers finally talked Commodore into running advertisements in leading music publications. The result was a hideously uninspired, two page ad that was almost universely described as "unappealing" by musicians. I personally have never met a musician who liked the ad. The ad ran for a very short time fortunately. After some months and a new ad firm, Commodore produced a much improved, though smaller ad. After a very brief run, "someone" at Commodore decided that the bigger ad was better, and against the advice of the advertising editors of a major music publication, decided to resurrect it. Is this not an example of some dumb ass manager making a decision which he is obviously unqualified to make? By the way, that music magazine is a publication with a readership of about 100,000 people per month. The reason that I bring all of this up is that I believe that the amiga community needs to stop being nice to Commodore simply because that company determines the fate of the Amiga. Let's tell Commodore management what a bunch of buffoons we think that they are. Maybe they'll work harder simply for the sake of improving their "image" if not for the sake of future CBM products. And to Commodore stockholders I say this: company stock has not risen so much that it can't be flattened by new developments at IBM and Apple, with a subsequent lack of activity at CBM. Ask your company about their R&D budget, about how well they work with 3rd party vendors, about when, how, and if they plan on promoting their products, about whether they have any idea what they want to do with both current and future product lines. If CBM doesn't want to market the Amiga, maybe they shouldn't. Once again, I want to mention the people who helped make this public domain offering possible: Jerry Morrison, Steve Shaw, and Steve Hayes, Electronic Arts. C. Scheppner, CATS. Jim Kent, Dancing Flame. This is one of those few efforts in which I didn't steal "something" from Bryce Nesbitt. Bryce is such a good programmer and documentator that it is a fluke that he actually works for CBM. ;=============================NOTES=============================== ¹ The routines that read and write data to disc (IFFReadBytes and IFFWrite- bytes) use unbuffered I/O. An application that uses the buffered version of EA's code will probably be faster but certainly not as memory effic- ient. Wouldn't you rather wait an extra 15 seconds to load an ANIM file rather than not be able to view it at all because the loader is sucking up RAM? Besides, if you want to waste memory with buffers that remain idle except during disk I/O, why not use the CLI ADDBUFFERS command. This way, you can speed up the lib to comparable levels with the buffered IFF code, and also allow other disc I/O code to benefit. I realize that the concept of conserving RAM is not popular amoung non-assembly programmers, most of whom write applications which unnecessarily require 1 MEG to run well. ² In order to handle PROPs more efficiently and with less stack use than the original EA code, I made the following limitation. There should be no LIST imbedded within a LIST. In a file that violates this rule, PROP data from the outer LIST may affect chunks inside the inner LIST. Since I have never seen imbedded LISTs, I decided that such abominations don't deserve special treatment.