home *** CD-ROM | disk | FTP | other *** search
Text File | 1980-12-03 | 72.3 KB | 1,408 lines |
- 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 <x, y> 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.
-