home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Club Amiga de Montreal - CAM
/
CAM_CD_1.iso
/
files
/
302.lha
/
ILBMLib
/
ILBMLib.doc.pp
/
ILBMLib.doc
Wrap
Text File
|
1980-12-03
|
74KB
|
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.