A Quick Introduction to IFF Jerry Morrison, Electronic Arts 10-17-88 IFF is the Amiga-standard "Interchange File Format", designed to work across many machines. Why IFF? Did you ever have this happen to your picture file? You can't load it into another paint program. You need a converter to adopt to "ZooPaint" release 2.0 or a new hardware feature. You must "export" and "import" to use it in a page layout program. You can't move it to another brand of computer. What about interchanging musical scores, digitized audio, and other data? It seems the only thing that does interchange well is plain ASCII text files. It's inexcusable. And yet this is "normal" in MS-DOS. What is IFF? IFF, the "Interchange File Format" standard, encourages multimedia interchange between different programs and different computers. It supports long-lived, extensible data. It's great for composite files like a page layout file that includes photos, an animation file that includes music, and a library of sound effects. IFF is a 2-level standard. The first layer is the "wrapper" or "envelope" structure for all IFF files. Technically, it's the syntax. The second layer defines particular IFF file types such as ILBM (standard raster pictures), ANIM (animation), SMUS (simple musical score), and 8SVX (8-bit sampled audio voice). IFF is also a design idea: programs should use interchange formats for their everyday storage. This way, users rarely need converters and import/export commands to change software releases, application programs, or hardware. What's the trick? File compatibility is easy to achieve if programmers let go of one notion-- dumping internal data structures to disk. A program's internal data structures should really be suited to what the program does and how it works. What's "best" changes as the program evolves new functions and methods. But a disk format should be suited to storage and interchange. Once we design internal formats and disk formats for their own separate purposes, the rest is easy. Reading and writing become behind-the-scenes conversions. But two conversions hidden in each program is much better than a pile of conversion programs. Does this seem strange? It's what ASCII text programs do! Text editors use line tables, piece tables, gaps, and other structures for fast editing and searching. Text generators and consumers construct and parse files. That's why the ASCII standard works so well. Also, every file must be self-sufficient. E.g., a picture file has to include its size and number of bits/pixel. What's an IFF file look like? IFF is based on data blocks called "chunks". Here's an example color map chunk: +------------------+ in an ILBM file, CMAP means char typeID[4] | 'CMAP' | "color map" |------------------+ unsigned long dataSize | 48 | 48 data bytes |------------------+ char data[] | 0,0,0,255, | 16 3-byte color values: black, | 255,255,255... | white, .... +------------------+ +------------------+ in an ILBM file, CMAP means | 'CMAP' | "color map" |------------------+ | 48 | 48 data bytes |------------------+ | 0,0,0,255, | 16 3-byte color values: black, | 255,255,255... | white, .... +------------------+ A chunk is made of a 4-character type identifier, a 32 bit data byte count, and the data bytes. It's like a Macintosh "resource" with a 32-bit size. Fine points: oEvery 16- and 32-bit number is stored in 68000 byte order--highest byte first. An Intel CPU must reverse the 2- or 4-byte sequence of each number. This applies to chunk dataSize fields and to numbers inside chunk data. It does not affect character strings and byte data because you can't reverse a 1-byte sequence. But it does affect the 32-bit math used in IFF's MakeID macro. The standard does allow CPU specific byte ordering hidden within a chunk itself, but the practice is discouraged. oEvery 16- and 32-bit number is stored on an even address. oEvery odd-length chunk must be followed by a 0 pad byte. This pad byte is not counted in dataSize. oAn ID is made of 4 ASCII characters in the range " " (space, hex 20) through "~" (tilde, hex 7E). Leading spaces are not permitted. oIDs are compared using a quick 32-bit equality test. Case matters. A chunk typically holds a C structure, Pascal record, or an array. For example, an 'ILBM' picture has a 'BMHD' bitmap header chunk (a structure) and a 'BODY' raster body chunk (an array). To construct an IFF file, just put a file type ID (like 'ILBM') into a wrapper chunk called a 'FORM' (Think "FILE"). Inside that wrapper place chunks one after another (with pad bytes as needed). The chunk size always tells you how many more bytes you need to skip over to get to the next chunk. +------------------+ | | | 'FORM' | FORM is a special chunk ID | | +------------------+ | 24070 | 24070 data bytes -- +------------------+ | | | | | 'ILBM' | FORM type is ILBM | | | | | +--------------+ | | | | 'BMHD' | | A BMHD bitmap header chunk | | |--------------| | (20 data bytes) | | | 20 | | | | |--------------| | | | | 320, 200, | | | | | 0 .... | | | | +--------------+ | | | | | | +--------------+ | | | | 'CMAP' | | A CMAP color map chunk 24070 < | |--------------| | (21 data bytes = 1 pad) bytes | | | 21 | | | | |--------------| | | | | 0, 0, 0, | | | | | 255 .... | | | | +--------------+ | | | 0 | A pad byte | | +--------------+ | | | | 'BODY' | | | | |--------------| | A BODY raster body chunk | | | 24000 | | (24000 data bytes) | | |--------------| | | | | 0, 0, 0.... | | | | +--------------+ | -- +------------------+ A FORM always contains one 4-character FORM type ID (a file type, in this case 'ILBM') followed by any number of data chunks. In this example, the FORM type is 'ILBM', which stands for "InterLeaved BitMap". (ILBM is an IFF standard for bitplane raster pictures.) This example has 3 chunks. Note the pad byte after the odd length chunk. Within FORMs ILBM, 'BMHD' identifies a bitmap header chunk, 'CMAP' a color map, and 'BODY' a raster body. In general, the chunk IDs in a FORM are local to the FORM type ID. The exceptions are the 4 global chunk IDs 'FORM', 'LIST', 'CAT ', and 'PROP'. (A FORM may contain other FORM chunks. E.g., an animation FORM might contain picture FORMs and sound FORMs.) How to read an IFF file? Given the C subroutine "GetChunkHeader()": /* Skip any remaining bytes of the current chunk, skip any pad byte, and read the next chunk header. Returns the chunk ID or END_MARK. */ ID GetChunkHeader(); we read the chunks in a FORM ILBM with a loop like this: do switch (id = GetChunkHeader()) { case 'CMAP': ProcessCMAP(); break; case 'BMHD': ProcessBMHD(); break; case 'BODY': ProcessBODY(); break; /* default: just ignore the chunk */ } until (id == END_MARK); This loop processes each chunk by dispatching to a routine that reads the specific type of chunk data. We don't assume a particular order of chunks. This is a simple parser. Note that even if you have fully processed a chunk, you should respect it's chunk size, even if the size is larger than you expected. This sample ignores important details like I/O errors. There are also higher- level errors to check, e.g., if we hit END_MARK without reading a BODY, we didn't get a picture. Every IFF file is a 'FORM', 'LIST', or 'CAT ' chunk. You can recognize an IFF file by those first 4 bytes. ('FORM' is far and away the most common. We'll get to LIST and CAT below.) If the file contains a FORM, dispatch on the FORM type ID to a chunk-reader loop like the one above. File extensibility IFF files are extensible and forward/backward compatible: oChunk contents should be designed for compatibility across environments and for longevity. Every chunk should have a path for future expansion; at minimum this will be an unused bit or two. oThe standards team for a FORM type can extend one of the chunks that contains a structure by appending new, optional structure fields. oAnyone can define new FORM types as well as new chunk types within a FORM type. Storing private chunks within a FORM is OK, but be sure to register your activities with Commodore Applications and Technical Support. oA chunk can be superseded by a new chunk type, e.g., to store more bits per RGB color register. New programs can output the old chunk (for backward compatibility) along with the new chunk. oIf you must change data in an incompatible way, change the chunk ID or the FORM type ID. Advanced Topics: CAT, LIST, and PROP (not all that important) Sometimes you want to put several "files" into one, such as a picture library. This is what CAT is for. It "concatenates" FORM and LIST chunks. +--------------------+ | | | 'CAT ' | concatenation | | +--------------------+ | 48160 | 48160 data bytes +--------------------+ | | | 'ILBM' | This concatenation contains FORMs ILBM | | | +--------------+ | | | 'FORM' | | A FORM ILBM | |--------------| | | | 24070 | | | |--------------| | | | 'ILBM' | | | |--------------| | | | .... | | | +--------------+ | | | | +--------------+ | | | 'FORM' | | Another FORM ILBM | |--------------| | | | 24070 | | | |--------------| | | | 'ILBM' | | | |--------------| | | | .... | | | +--------------+ | | | +--------------------+ This example CAT holds two ILBMs. It can be shown outline-style: CAT ILBM ..FORM ILBM \ ....BMHD | a complete FORM ILBM picture ....CMAP | ....BODY / ..FORM ILBM ....BMHD ....CMAP ....BODY Sometimes you want to share the same color map across many pictures. LIST and PROP do this: LIST ILBM ..PROP ILBM default properties for FORMs ILBM ....CMAP an ILBM CMAP chunk (there could be a BMHD chunk here, too) ..FORM ILBM ....BMHD (there could be a CMAP here to override the default) ....BODY ..FORM ILBM ....BMHD (there could be a CMAP here to override the default) ....BODY A LIST holds PROPs and FORMs (and occasionally LISTs and CATs). A PROP ILBM contains default data (in the above example, just one CMAP chunk) for all FORMs ILBM in the LIST. Any FORM may override the PROP-defined default with its own CMAP. All PROPs must appear at the beginning of a LIST. Each FORM type standardizes (among other things) which of its chunks are "property chunks" (may appear in PROPs) and which are "data chunks" (may not appear in PROPs). "EA IFF 85" Standard for Interchange Format Files Document Date:January 14, 1985 (Updated Oct, 1988 Commodore-Amiga, Inc.) From:Jerry Morrison, Electronic Arts Status:Released to the public domain, and in use 1. Introduction Standards are Good for Software Developers As home computer hardware evolves into better and better media machines, the demand increases for higher quality, more detailed data. Data development gets more expensive, requires more expertise and better tools, and has to be shared across projects. Think about several ports of a product on one CD-ROM with 500M Bytes of common data! Development tools need standard interchange file formats. Imagine scanning in images of "player" shapes, transferring them to an image enhancement package, moving them to a paint program for touch up, then incorporating them into a game. Or writing a theme song with a Macintosh score editor and incorporating it into an Amiga game. The data must at times be transformed, clipped, filled out, and moved across machine kinds. Media projects will depend on data transfer from graphic, music, sound effect, animation, and script tools. Standards are Good for Software Users Customers should be able to move their own data between independently developed software products. And they should be able to buy data libraries usable across many such products. The types of data objects to exchange are open-ended and include plain and formatted text, raster and structured graphics, fonts, music, sound effects, musical instrument descriptions, and animation. The problem with expedient file formats--typically memory dumps is that they're too provincial. By designing data for one particular use (such as a screen snapshot), they preclude future expansion (would you like a full page picture? a multi-page document?). In neglecting the possibility that other programs might read their data, they fail to save contextual information (how many bit planes? what resolution?). Ignoring that other programs might create such files, they're intolerant of extra data (a different picture editor may want to save a texture palette with the image), missing data (such as no color map), or minor variations (perhaps a smaller image). In practice, a filed representation should rarely mirror an in-memory representation. The former should be designed for longevity; the latter to optimize the manipulations of a particular program. The same filed data will be read into different memory formats by different programs. The IFF philosophy: "A little behind-the-scenes conversion when programs read and write files is far better than NxM explicit conversion utilities for highly specialized formats". So we need some standardization for data interchange among development tools and products. The more developers that adopt a standard, the better for all of us and our customers. Here is "EA IFF 1985" Here is our offering: Electronic Arts' IFF standard for Interchange File Format. The full name is "EA IFF 1985". Alternatives and justifications are included for certain choices. Public domain subroutine packages and utility programs are available to make it easy to write and use IFF-compatible programs. Part 1 introduces the standard. Part 2 presents its requirements and background. Parts 3, 4, and 5 define the primitive data types, FORMs, and LISTs, respectively, and how to define new high level types. Part 6 specifies the top level file structure. Section 7 lists names of the group responsible for this standard. Appendix A is included for quick reference and Appendix B. References American National Standard Additional Control Codes for Use with ASCII, ANSI standard 3.64-1979 for an 8-bit character set. See also ISO standard 2022 and ISO/DIS standard 6429.2. The C Programming Language, Brian W. Kernighan and Dennis M. Ritchie, Bell Laboratories. Prentice-Hall, Englewood Cliffs, NJ, 1978. C, A Reference Manual, Samuel P. Harbison and Guy L. Steele Jr., Tartan Laboratories. Prentice-Hall, Englewood Cliffs, NJ, 1984. Compiler Construction, An Advanced Course, edited by F. L. Bauer and J. Eickel (Springer-Verlag, 1976). This book is one of many sources for information on recursive descent parsing. DIF Technical Specification (c) 1981 by Software Arts, Inc. DIF(tm) is the format for spreadsheet data interchange developed by Software Arts, Inc. DIF(tm) is a trademark of Software Arts, Inc. "FTXT" IFF Formatted Text, from Electronic Arts. IFF supplement document for a text format. "ILBM" IFF Interleaved Bitmap, from Electronic Arts. IFF supplement document for a raster image format. M68000 16/32-Bit Microprocessor Programmer's Reference Manual (c) 1984, 1982, 1980, 1979 by Motorola, Inc. PostScript Language Manual (c) 1984 Adobe Systems Incorporated. PostScript(tm) is a trademark of Adobe Systems, Inc. Times and Helvetica. are registered trademarks of Allied Corporation. Inside Macintosh (c) 1982, 1983, 1984, 1985 Apple Computer, Inc., a programmer's reference manual. Apple. is a trademark of Apple Computer, Inc. MacPaint(tm) is a trademark of Apple Computer, Inc. Macintosh(tm) is a trademark licensed to Apple Computer, Inc. InterScript: A Proposal for a Standard for the Interchange of Editable Documents (c) 1984 Xerox Corporation. Introduction to InterScript (c) 1985 Xerox Corporation. Amiga. is a registered trademark of Commodore-Amiga, Inc. Electronics Arts(tm) is a trademark of Electronic Arts. 2. Background for Designers Part 2 is about the background, requirements, and goals for the standard. It's geared for people who want to design new types of IFF objects. People just interested in using the standard may wish to quickly scan this section. What Do We Need? A standard should be long on prescription and short on overhead. It should give lots of rules for designing programs and data files for synergy. But neither the programs nor the files should cost too much more than the expedient variety. Although we are looking to a future with CD-ROMs and perpendicular recording, the standard must work well on floppy disks. For program portability, simplicity, and efficiency, formats should be designed with more than one implementation style in mind. It ought to be possible to read one of many objects in a file without scanning all the preceding data. (In practice, pure stream I/O is adequate although random access makes it easier to write files.) Some programs need to read and play out their data in real time, so we need good compromises between generality and efficiency. As much as we need standards, they can't hold up product schedules. So we also need a kind of decentralized extensibility where any software developer can define and refine new object types without some "standards authority" in the loop. Developers must be able to extend existing formats in a forward- and backward-compatible way. A central repository for design information and example programs can help us take full advantage of the standard. For convenience, data formats should heed the restrictions of various processors and environments. For example, word-alignment greatly helps 68000 access at insignificant cost to 8088 programs. Other goals include the ability to share common elements over a list of objects and the ability to construct composite objects. And finally, "Simple things should be simple and complex things should be possible" - Alan Kay. Think Ahead Let's think ahead and build programs that read and write files for each other and for programs yet to be designed. Build data formats to last for future computers so long as the overhead is acceptable. This extends the usefulness and life of today's programs and data. To maximize interconnectivity, the standard file structure and the specific object formats must all be general and extensible. Think ahead when designing an object. File formats should serve many purposes and allow many programs to store and read back all the information they need; even squeeze in custom data. Then a programmer can store the available data and is encouraged to include fixed contextual details. Recipient programs can read the needed parts, skip unrecognized stuff, default missing data, and use the stored context to help transform the data as needed. Scope IFF addresses these needs by defining a standard file structure, some initial data object types, ways to define new types, and rules for accessing these files. We can accomplish a great deal by writing programs according to this standard, but do not expect direct compatibility with existing software. We'll need conversion programs to bridge the gap from the old world. IFF is geared for computers that readily process information in 8-bit bytes. It assumes a "physical layer" of data storage and transmission that reliably maintains "files" as sequences of 8-bit bytes. The standard treats a "file" as a container of data bytes and is independent of how to find a file and whether it has a byte count. This standard does not by itself implement a clipboard for cutting and pasting data between programs. A clipboard needs software to mediate access, and provide a notification mechanism so updates and requests for data can be detected. Data Abstraction The basic problem is how to represent information in a way that's program- independent, compiler- independent, machine-independent, and device-independent. The computer science approach is "data abstraction", also known as "objects", "actors", and "abstract data types". A data abstraction has a "concrete representation" (its storage format), an "abstract representation" (its capabilities and uses), and access procedures that isolate all the calling software from the concrete representation. Only the access procedures touch the data storage. Hiding mutable details behind an interface is called "information hiding". What is hidden are the non-portable details of implementing the object, namely the selected storage representation and algorithms for manipulating it. The power of this approach is modularity. By adjusting the access procedures we can extend and restructure the data without impacting the interface or its callers. Conversely, we can extend and restructure the interface and callers without making existing data obsolete. It's great for interchange! But we seem to need the opposite: fixed file formats for all programs to access. Actually, we could file data abstractions ("filed objects") by storing the data and access procedures together. We'd have to encode the access procedures in a standard machine-independent programming language a la PostScript. Even with this, the interface can't evolve freely since we can't update all copies of the access procedures. So we'll have to design our abstract representations for limited evolution and occasional revolution (conversion). In any case, today's microcomputers can't practically store true data abstractions. They can do the next best thing: store arbitrary types of data in "data chunks", each with a type identifier and a length count. The type identifier is a reference by name to the access procedures (any local implementation). The length count enables storage-level object operations like "copy" and "skip to next" independent of object type or contents. Chunk writing is straightforward. Chunk reading requires a trivial parser to scan each chunk and dispatch to the proper access/conversion procedure. Reading chunks nested inside other chunks may require recursion, but no look ahead or backup. That's the main idea of IFF. There are, of course, a few other details.... Previous Work Where our needs are similar, we borrow from existing standards. Our basic need to move data between independently developed programs is similar to that addressed by the Apple Macintosh desk scrap or "clipboard" [Inside Macintosh chapter "Scrap Manager"]. The Scrap Manager works closely with the Resource Manager, a handy filer and swapper for data objects (text strings, dialog window templates, pictures, fonts?) including types yet to be designed [Inside Macintosh chapter "Resource Manager"]. The Resource Manager is akin to Smalltalk's object swapper. We will probably write a Macintosh desk accessory that converts IFF files to and from the Macintosh clipboard for quick and easy interchange with programs like MacPaint and Resource Mover. Macintosh uses a simple and elegant scheme of four-character "identifiers" to identify resource types, clipboard format types, file types, and file creator programs. Alternatives are unique ID numbers assigned by a central authority or by hierarchical authorities, unique ID numbers generated by algorithm, other fixed length character strings, and variable length strings. Character string identifiers double as readable signposts in data files and programs. The choice of 4 characters is a good tradeoff between storage space, fetch/compare/store time, and name space size. We'll honor Apple's designers by adopting this scheme. "PICT" is a good example of a standard structured graphics format (including raster images) and its many uses [Inside Macintosh chapter "QuickDraw"]. Macintosh provides QuickDraw routines in ROM to create, manipulate, and display PICTs. Any application can create a PICT by simply asking QuickDraw to record a sequence of drawing commands. Since it's just as easy to ask QuickDraw to render a PICT to a screen or a printer, it's very effective to pass them betweenprograms, say from an illustrator to a word processor. An important feature is the ability to store "comments" in a PICT which QuickDraw will ignore. (Actually, it passes them to your optional custom "comment handler".) PostScript, Adobe System's print file standard, is a more general way to represent any print image (which is a specification for putting marks on paper) [PostScript Language Manual]. In fact, PostScript is a full-fledged programming language. To interpret a PostScript program is to render a document on a raster output device. The language is defined in layers: a lexical layer of identifiers, constants, and operators; a layer of reverse polish semantics including scope rules and a way to define new subroutines; and a printing-specific layer of built-in identifiers and operators for rendering graphic images. It is clearly a powerful (Turing equivalent) image definition language. PICT and a subset of PostScript are candidates for structured graphics standards. A PostScript document can be printed on any raster output device (including a display) but cannot generally be edited. That's because the original flexibility and constraints have been discarded. Besides, a PostScript program may use arbitrary computation to supply parameters like placement and size to each operator. A QuickDraw PICT, in comparison, is a more restricted format of graphic primitives parameterized by constants. So a PICT can be edited at the level of the primitives, e.g., move or thicken a line. It cannot be edited at the higher level of, say, the bar chart data which generated the picture. PostScript has another limitation: not all kinds of data amount to marks on paper. A musical instrument description is one example. PostScript is just not geared for such uses. "DIF" is another example of data being stored in a general format usable by future programs [DIF Technical Specification]. DIF is a format for spreadsheet data interchange. DIF and PostScript are both expressed in plain ASCII text files. This is very handy for printing, debugging, experimenting, and transmitting across modems. It can have substantial cost in compaction and read/write work, depending on use. We won't store IFF files this way but we could define an ASCII alternate representation with a converter program. InterScript is the Xerox standard for interchange of editable documents [Introduction to InterScript]. It approaches a harder problem: How to represent editable word processor documents that may contain formatted text, pictures, cross-references like figure numbers, and even highly specialized objects like mathematical equations? InterScript aims to define one standard representation for each kind of information. Each InterScript-compatible editor is supposed to preserve the objects it doesn't understand and even maintain nested cross-references. So a simple word processor would let you edit the text of a fancy document without discarding the equations or disrupting the equation numbers. Our task is similarly to store high level information and preserve as much content as practical while moving it between programs. But we need to span a larger universe of data types and cannot expect to centrally define them all. Fortunately, we don't need to make programs preserve information that they don't understand. And for better or worse, we don't have to tackle general- purpose cross-references yet. 3. Primitive Data Types Atomic components such as integers and characters that are interpretable directly by the CPU are specified in one format for all processors. We chose a format that's the same as used by the Motorola MC68000 processor [M68000 16/32-Bit Microprocessor Programmer's Reference Manual]. The high byte and high word of a number are stored first. N.B.: Part 3 dictates the format for "primitive" data types where--and only where--used in the overall file structure. The number of such occurrences of dictated formats will be small enough that the costs of conversion, storage, and management of processor-specific files would far exceed the costs of conversion during I/O by "foreign" programs. A particular data chunk may be specified with a different format for its internal primitive types or with processor or environment specific variants if necessary to optimize local usage. Since that hurts data interchange, it's not recommended. (Cf. Designing New Data Sections, in Part 4.) Alignment All data objects larger than a byte are aligned on even byte addresses relative to the start of the file. This may require padding. Pad bytes are to be written as zeros, but don't count on that when reading. This means that every odd-length "chunk" must be padded so that the next one will fall on an even boundary. Also, designers of structures to be stored in chunks should include pad fields where needed to align every field larger than a byte. For best efficiency, long word data should be arranged on long word (4 byte) boundaries. Zeros should be stored in all the pad bytes. Justification: Even-alignment causes a little extra work for files that are used only on certain processors but allows 68000 programs to construct and scan the data in memory and do block I/O. Any 16-bit or greater CPU will have faster access to aligned data. You just add an occasional pad field to data structures that you're going to block read/write or else stream read/write an extra byte. And the same source code works on all processors. Unspecified alignment, on the other hand, would force 68000 programs to (dis)assemble word and long word data one byte at a time. Pretty cumbersome in a high level language. And if you don't conditionally compile that step out for other processors, you won't gain anything. Numbers Numeric types supported are two's complement binary integers in the format used by the MC68000 processor--high byte first, high word first--the reverse of 8088 and 6502 format. UBYTE 8 bits unsigned WORD16 bits signed UWORD16 bits unsigned LONG32 bits signed The actual type definitions depend on the CPU and the compiler. In this document, we'll express data type definitions in the C programming language. [See C, A Reference Manual.] In 68000 Lattice C: typedef unsigned charUBYTE;/* 8 bits unsigned*/ typedef shortWORD;/* 16 bits signed*/ typedef unsigned shortUWORD;/* 16 bits unsigned*/ typedef longLONG;/* 32 bits signed*/ Characters The following character set is assumed wherever characters are used, e.g., in text strings, IDs, and TEXT chunks (see below). Characters are encoded in 8-bit ASCII. Characters in the range NUL (hex 0) through DEL (hex 7F) are well defined by the 7-bit ASCII standard. IFF uses the graphic group " " (SP, hex 20) through "~" (hex 7E). Most of the control character group hex 01 through hex 1F have no standard meaning in IFF. The control character LF (hex 0A) is defined as a "newline" character. It denotes an intentional line break, that is, a paragraph or line terminator. (There is no way to store an automatic line break. That is strictly a function of the margins in the environment the text is placed.) The controlcharacter ESC (hex 1B) is a reserved escape character under the rules of ANSI standard 3.64-1979 American National Standard Additional Control Codes for Use with ASCII, ISO standard 2022, and ISO/DIS standard 6429.2. Characters in the range hex 7F through hex FF are not globally defined in IFF. They are best left reserved for future standardization. (Note that the FORM type FTXT (formatted text) defines the meaning of these characters within FTXT forms.) In particular, character values hex 7F through hex 9F are control codes while characters hex A0 through hex FF are extended graphic characters like E, as per the ISO and ANSI standards cited above. [See the supplementary document "FTXT" IFF Formatted Text.] Dates A "creation date" is defined as the date and time a stream of data bytes was created. (Some systems call this a "last modified date".) Editing some data changes its creation date. Moving the data between volumes or machines does not. The IFF standard date format will be one of those used in MS-DOS, Macintosh, or AmigaDOS (probably a 32-bit unsigned number of seconds since a reference point). Issue: Investigate these three. Type IDs A "type ID", "property name", "FORM type", or any other IFF identifier is a 32-bit value: the concatenation of four ASCII characters in the range " " (SP, hex 20) through "~" (hex 7E). Spaces (hex 20) should not precede printing characters; trailing spaces are OK. Control characters are forbidden. typedef CHAR ID[4]; IDs are compared using a simple 32-bit case-dependent equality test. FORM type IDs are restricted. Since they may be stored in filename extensions lower case letters and punctuation marks are forbidden. Trailing spaces are OK. Carefully choose those four characters when you pick a new ID. Make them mnemonic so programmers can look at an interchange format file and figure out what kind of data it contains. The name space makes it possible for developers scattered around the globe to generate ID values with minimal collisions so long as they choose specific names like "MUS4" instead of general ones like "TYPE" and "FILE". Commodore Applications and Technical Support has undertaken the task of maintaining the registry of FORM type IDs and format descriptions. See the IFF registry document for more information. Sometimes it's necessary to make data format changes that aren't backward compatible. As much as we work for compatibility, unintended interactions can develop. Since IDs are used to denote data formats in IFF, new IDs are chosen to denote revised formats. Since programs won't read chunks whose IDs they don't recognize (see Chunks, below), the new IDs keep old programs from stumbling over new data. The conventional way to chose a "revision" ID is to increment the last character if it's a digit or else change the last character to a digit. E.g., first and second revisions of the ID "XY" would be "XY1" and "XY2". Revisions of "CMAP" would be "CMA1" and "CMA2". Chunks Chunks are the building blocks in the IFF structure. The form expressed as a C typedef is: typedef struct { IDckID;/* 4 character ID */ LONGckSize;/* sizeof(ckData) */ UBYTEckData[/* ckSize */]; } Chunk; We can diagram an example chunk --a "CMAP" chunk containing 12 data bytes -- like this: +------------------+ - ckID: | 'CMAP' | | |------------------+ | ckSize: | 12 | | - |------------------+ | ckData: | | 0, 0, 0, 32 | 20 bytes 12 |------------------+ | bytes | 0, 0, 64, 0 | | | |------------------+ | | | 0, 0, 64, 0 | | - +------------------+ - That's 4 bytes of ckID, 4 bytes of ckSize and 12 data bytes. The total space used is 20 bytes. The ckID identifies the format and purpose of the chunk. As a rule, a program must recognize ckID to interpret ckData. It should skip over all unrecognized chunks. The ckID also serves as a format version number as long as we pick new IDs to identify new formats of ckData (see above). The following ckIDs are universally reserved to identify chunks with particular IFF meanings: "LIST", "FORM", "PROP", "CAT ", and " ". The special ID " " (4 spaces) is a ckID for "filler" chunks, that is, chunks that fill space but have no meaningful contents. The IDs "LIS1" through "LIS9", "FOR1" through "FOR9", and "CAT1" through "CAT9" are reserved for future "version number" variations. All IFF-compatible software must account for these chunk IDs. The ckSize is a logical block size--how many data bytes are in ckData. If ckData is an odd number of bytes long, a 0 pad byte follows which is not included in ckSize. (Cf. Alignment.) A chunk's total physical size is ckSize rounded up to an even number plus the size of the header. So the smallest chunk is 8 bytes long with ckSize = 0. For the sake of following chunks, programs must respect every chunk's ckSize as a virtual end-of-file for reading its ckData even if that data is malformed, e.g., if nested contents are truncated. We can describe the syntax of a chunk as a regular expression with "#" representing the ckSize, the length of the following {braced} bytes. The "[0]" represents a sometimes needed pad byte. (The regular expressions in this document are collected in Appendix A along with an explanation of notation.) Chunk::= ID #{ UBYTE* } [0] One chunk output technique is to stream write a chunk header, stream write the chunk contents, then random access back to the header to fill in the size. Another technique is to make a preliminary pass over the data to compute the size, then write it out all at once. Strings, String Chunks, and String Properties In a string of ASCII text, linefeed (0x0A) denotes a forced line break (paragraph or line terminator). Other control characters are not used. (Cf. Characters.) For maximum compatibility with line editors, two linefeed characters are often used to indicate a paragraph boundary. The ckID for a chunk that contains a string of plain, unformatted text is "TEXT". As a practical matter, a text string should probably not be longer than 32767 bytes. The standard allows up to 2^31 - 1 bytes. The ckID "TEXT" is globally reserved for this use. When used as a data property (see below), a text string chunk may be 0 to 255 characters long. Such a string is readily converted to a C string or a Pascal STRING[255]. The ckID of a property must have a unique property name, not "TEXT". When used as a part of a chunk or data property, restricted C string format is normally used. That means 0 to 255 characters followed by a NULL byte (ASCII value 0). Data Properties (advanced topic) Data properties specify attributes for following (non-property) chunks. A data property essentially says "identifier = value", for example "XY = (10, 200)", telling something about following chunks. Properties may only appear inside data sections ("FORM" chunks, cf. Data Sections) and property sections ("PROP" chunks, cf. Group PROP). The form of a data property is a type of Chunk. The ckID is a property name as well as a property type. The ckSize should be small since data properties are intended to be accumulated in RAM when reading a file. (256 bytes is a reasonable upper bound.) Syntactically: Property::= Chunk When designing a data object, use properties to describe context information like the size of an image, even if they don't vary in your program. Other programs will need this information. Think of property settings as assignments to variables in a programming language. Multiple assignments are redundant and local assignments temporarily override global assignments. The order of assignments doesn't matter as long as they precede the affected chunks. (Cf. LISTs, CATs, and Shared Properties.) Each object type (FORM type) is a local name space for property IDs. Think of a "CMAP" property in a "FORM ILBM" as the qualified ID "ILBM.CMAP". A "CMAP" inside some other type of FORM may not have the same meaning. Property IDs specified when an object type is designed (and therefore known to all clients) are called "standard" while specialized ones added later are "nonstandard". Links Issue: A standard mechanism for "links" or "cross references" is very desirable for things like combining images and sounds into animations. Perhaps we'll define "link" chunks within FORMs that refer to other FORMs or to specific chunks within the same and other FORMs. This needs further work. EA IFF 1985 has no standard link mechanism. For now, it may suffice to read a list of, say, musical instruments, and then just refer to them within a musical score by sequence number. File References Issue: We may need a standard form for references to other files. A "file ref" could name a directory and a file in the same type of operating system as the reference's originator. Following the reference would expect the file to be on some mounted volume, or perhaps the same directory as the file that made the reference. In a network environment, a file reference could name a server, too. Issue: How can we express operating-system independent file references? Issue: What about a means to reference a portion of another file? Would this be a "file ref" plus a reference to a "link" within the target file? 4. Data Sections The first thing we need of a file is to check: Does it contain IFF data and, if so, does it contain the kind of data we're looking for? So we come to the notion of a "data section". A "data section" or IFF "FORM" is one self-contained "data object" that might be stored in a file by itself. It is one high level data object such as a picture or a sound effect, and generally contains a grouping of chunks. The IFF structure "FORM" makes it self-identifying. It could be a composite object like a musical score with nested musical instrument descriptions. Group FORM A data section is a chunk with ckID "FORM" and this arrangement: FORM ::= "FORM" #{ FormType (LocalChunk | FORM | LIST | CAT)* } FormType ::= ID LocalChunk ::= Property | Chunk The ID "FORM" is a syntactic keyword like "struct" in C. Think of a "struct ILBM" containing a field "CMAP". If you see "FORM" you will know to expect a FORM type ID (the structure name, "ILBM" in this example) and a particular contents arrangement or "syntax" (local chunks, FORMs, LISTs, and CATs). A "FORM ILBM", in particular, might contain a local chunk "CMAP", an "ILBM.CMAP" (to use a qualified name). So the chunk ID "FORM" indicates a data section. It implies that the chunk contains an ID and some number of nested chunks. In reading a FORM, like any other chunk, programs must respect its ckSize as a virtual end-of-file for reading its contents, even if they're truncated. The FORM type is a restricted ID that may not contain lower case letters or punctuation characters. (Cf. Type IDs. Cf. Single Purpose Files.) The type-specific information in a FORM is composed of its "local chunks": data properties and other chunks. Each FORM type is a local name space for local chunk IDs. So "CMAP" local chunks in other FORM types may be unrelated to "ILBM.CMAP". More than that, each FORM type defines semantic scope. If you know what a FORM ILBM is, you will know what an ILBM.CMAP is. Local chunks defined when the FORM type is designed (and therefore known to all clients of this type) are called "standard" while specialized ones added later are "nonstandard". Among the local chunks, property chunks give settings for various details like text font while the other chunks supply the essential information. This distinction is not clear cut. A property setting can be cancelled by a later setting of the same property. E.g., in the sequence: prop1 = x (Data A) prop1 = z prop1 = y (Data B) prop1 is = x for Data A, and y for Data B. The setting prop1 = z has no effect. For clarity, the universally reserved chunk IDs "LIST", "FORM", "PROP", "CAT ", " ", "LIS1" through "LIS9", "FOR1" through "FOR9", and "CAT1" through "CAT9" may not be FORM type IDs. Part 5, below, talks about grouping FORMs into LISTs and CATs. They let you group a bunch of FORMs but don't impose any particular meaning or constraints on the grouping. Read on. Composite FORMs A FORM chunk inside a FORM is a full-fledged data section. This means you can build a composite object such as a multi-frame animation sequence by nesting available picture FORMs and sound effect FORMs. You can insert additional chunks with information like frame rate and frame count. Using composite FORMs, you leverage on existing programs that create and edit the component FORMs. Those editors may even look into your composite object to copy out its type of component. Such editors are not allowed to replace their component objects within your composite object. That's because the IFF standard lets you specify consistency requirements for the composite FORM such as maintaining a count or a directory of the components. Only programs that are written to uphold the rules of your FORM type may create or modify such FORMs. Therefore, in designing a program that creates composite objects, you are strongly requested to provide a facility for your users to import and export the nested FORMs. Import and export could move the data through a clipboard or a file. Here are several existing FORM types and rules for defining new ones: FTXT An FTXT data section contains text with character formatting information like fonts and faces. It has no paragraph or document formatting information like margins and page headers. FORM FTXT is well matched to the text representation in Amiga's Intuition environment. See the supplemental document "FTXT" IFF Formatted Text. ILBM "ILBM" is an InterLeaved BitMap image with color map; a machine-independent format for raster images. FORM ILBM is the standard image file format for the Commodore-Amiga computer and is useful in other environments, too. See the supplemental document "ILBM" IFF Interleaved Bitmap. PICS The data chunk inside a "PICS" data section has ID "PICT" and holds a QuickDraw picture. Issue: Allow more than one PICT in a PICS? See Inside Macintosh chapter "QuickDraw" for details on PICTs and how to create and display them on the Macintosh computer. The only standard property for PICS is "XY", an optional property that indicates the position of the PICT relative to "the big picture". The contents of an XY is a QuickDraw Point. Note: PICT may be limited to Macintosh use, in which case there'll be another format for structured graphics in other environments. Other Macintosh Resource Types Some other Macintosh resource types could be adopted for use within IFF files; perhaps MWRT, ICN, ICN#, and STR#. Issue: Consider the candidates and reserve some more IDs. Designing New Data Sections Supplemental documents will define additional object types. A supplement needs to specify the object's purpose, its FORM type ID, the IDs and formats of standard local chunks, and rules for generating and interpreting the data. It's a good idea to supply typedefs and an example source program that accesses the new object. See "ILBM" IFF Interleaved Bitmap for such an example. Anyone can pick a new FORM type ID but should reserve it with Commodore Applications and Technical Support (CATS) at their earliest convenience. While decentralized format definitions and extensions are possible in IFF, our preference is to get design consensus by committee, implement a program to read and write it, perhaps tune the format before it becomes locked in stone, and then publish the format with example code. Some organization should remain in charge of answering questions and coordinating extensions to the format. If it becomes necessary to incompatibly revise the design of some data section, its FORM type ID will serve as a version number (Cf. Type IDs). E.g., a revised "VDEO" data section could be called "VDE1". But try to get by with compatible revisions within the existing FORM type. In a new FORM type, the rules for primitive data types and word-alignment (Cf. Primitive Data Types) may be overridden for the contents of its local chunks-- but not for the chunk structure itself--if your documentation spells out the deviations. If machine-specific type variants are needed, e.g., to store vast numbers of integers in reverse bit order, then outline the conversion algorithm and indicate the variant inside each file, perhaps via different FORM types. Needless to say, variations should be minimized. In designing a FORM type, encapsulate all the data that other programs will need to interpret your files. E.g., a raster graphics image should specify the image size even if your program always uses 320 x 200 pixels x 3 bitplanes. Receiving programs are then empowered to append or clip the image rectangle, to add or drop bitplanes, etc. This enables a lot more compatibility. Separate the central data (like musical notes) from more specialized information (like note beams) so simpler programs can extract the central parts during read-in. Leave room for expansion so other programs can squeeze in new kinds of information (like lyrics). And remember to keep the property chunks manageably short--let's say <= 256 bytes. When designing a data object, try to strike a good tradeoff between a super- general format and a highly-specialized one. Fit the details to at least one particular need, for example a raster image might as well store pixels in the current machine's scan order. But add the kind of generality that makes the format usable with foreseeable hardware and software. E.g., use a whole byte for each red, green, and blue color value even if this year's computer has only 4-bit video DACs. Think ahead and help other programs so long as the overhead is acceptable. E.g., run compress a raster by scan line rather than as a unit so future programs can swap images by scan line to and from secondary storage. Try to design a general purpose "least common multiple" format that encompasses the needs of many programs without getting too complicated. Be sure to leave provisions for future expansion. Let's coalesce our uses around a few such formats widely separated in the vast design space. Two factors make this flexibility and simplicity practical. First, file storage space is getting very plentiful, so compaction is not always a priority. Second, nearly any locally-performed data conversion work during file reading and writing will be cheap compared to the I/O time. It must be OK to copy a LIST or FORM or CAT intact, e.g., to incorporate it into a composite FORM. So any kind of internal references within a FORM must be relative references. They could be relative to the start of the containing FORM, relative from the referencing chunk, or a sequence number into a collection. With composite FORMs, you leverage on existing programs that create and edit the components. If you write a program that creates composite objects, please provide a facility for users to import and export the nested FORMs. Finally, don't forget to specify all implied rules in detail. 5. LISTs, CATs, and Shared Properties (Advanced topics) Data often needs to be grouped together, for example, consider a list of icons. Sometimes a trick like arranging little images into a big raster works, but generally they'll need to be structured as a first class group. The objects "LIST" and "CAT " are IFF-universal mechanisms for this purpose. Note: LIST and CAT are advanced topics the first time reader will want to skip. Property settings sometimes need to be shared over a list of similar objects. E.g., a list of icons may share one color map. LIST provides a means called "PROP" to do this. One purpose of a LIST is to define the scope of a PROP. A "CAT ", on the other hand, is simply a concatenation of objects. Simpler programs may skip LISTs and PROPs altogether and just handle FORMs and CATs. All "fully-conforming" IFF programs also know about "CAT ", "LIST", and "PROP". Any program that reads a FORM inside a LIST must process shared PROPs to correctly interpret that FORM. Group CAT A CAT is just an untyped group of data objects. Structurally, a CAT is a chunk with chunk ID "CAT " containing a "contents type" ID followed by the nested objects. The ckSize of each contained chunk is essentially a relative pointer to the next one. CAT::= "CAT " #{ ContentsType (FORM | LIST | CAT)* } ContentsType ::= ID-- a hint or an "abstract data type" ID In reading a CAT, like any other chunk, programs must respect its ckSize as a virtual end-of-file for reading the nested objects even if they're malformed or truncated. The "contents type" following the CAT's ckSize indicates what kind of FORMs are inside. So a CAT of ILBMs would store "ILBM" there. It's just a hint. It may be used to store an "abstract data type". A CAT could just have blank contents ID (" ") if it contains more than one kind of FORM. CAT defines only the format of the group. The group's meaning is open to interpretation. This is like a list in LISP: the structure of cells is predefined but the meaning of the contents as, say, an association list depends on use. If you need a group with an enforced meaning (an "abstract datatype" or Smalltalk "subclass"), some consistency constraints, or additional data chunks, use a composite FORM instead (Cf. Composite FORMs). Since a CAT just means a concatenation of objects, CATs are rarely nested. Programs should really merge CATs rather than nest them. Group LIST A LIST defines a group very much like CAT but it also gives a scope for PROPs (see below). And unlike CATs, LISTs should not be merged without understanding their contents. Structurally, a LIST is a chunk with ckID "LIST" containing a "contents type" ID, optional shared properties, and the nested contents (FORMs, LISTs, and CATs), in that order. The ckSize of each contained chunk is a relative pointer to the next one. A LIST is not an arbitrary linked list--the cells are simply concatenated. LIST ::= "LIST" #{ ContentsType PROP* (FORM | LIST | CAT)* } ContentsType ::= ID Group PROP PROP chunks may appear in LISTs (not in FORMs or CATs). They supply shared properties for the FORMs in that LIST. This ability to elevate some property settings to shared status for a list of forms is useful for both indirection and compaction. E.g., a list of images with the same size and colors can share one "size" property and one "color map" property. Individual FORMs can override the shared settings. The contents of a PROP is like a FORM with no data chunks: PROP::= "PROP" #{ FormType Property* } It means, "Here are the shared properties for FORM type <FormType>". A LIST may have at most one PROP of a FORM type, and all the PROPs must appear before any of the FORMs or nested LISTs and CATs. You can have subsequences of FORMs sharing properties by making each subsequence a LIST. Scoping: Think of property settings as variable bindings in nested blocks of a programming language. In C this would look like: #define Roman0 #define Helvetica1 void main() { int font=Roman;/* The global default */ { printf("The font number is %d\n",font); } { int font=Helvetica;/* local setting */ printf("The font number is %d\n",font); } { printf("The font number is %d\n",font); } } /* * Sample output:The font number is 0 *The font number is 1 *The font number is 0 */ An IFF file could contain: LIST { PROP TEXT { FONT {TimesRoman}/* shared setting*/ } FORM TEXT { FONT {Helvetica}/* local setting*/ CHRS {Hello }/* uses font Helvetica*/ } FORM TEXT { CHRS {there.}/* uses font TimesRoman*/ } } The shared property assignments selectively override the reader's global defaults, but only for FORMs within the group. A FORM's own property assignments selectively override the global and group-supplied values. So when reading an IFF file, keep property settings on a stack. They are designed to be small enough to hold in main memory. Shared properties are semantically equivalent to copying those properties into each of the nested FORMs right after their FORM type IDs. Properties for LIST Optional "properties for LIST" store the origin of the list's contents in a PROP chunk for the pseudo FORM type "LIST". They are the properties originating program "OPGM", processor family "OCPU", computer type "OCMP", computer serial number or network address "OSN ", and user name "UNAM". In our imperfect world, these could be called upon to distinguish between unintended variations of a data format or to work around bugs in particular originating/receiving program pairs. Issue: Specify the format of these properties. A creation date could also be stored in a property, but let's ask that file creating, editing, and transporting programs maintain the correct date in the local file system. Programs that move files between machine types are expected to copy across the creation dates. 6. Standard File Structure File Structure Overview An IFF file is just a single chunk of type FORM, LIST, or CAT. Therefore an IFF file can be recognized by its first 4 bytes: "FORM", "LIST", or "CAT ". Any file contents after the chunk's end are to be ignored. (Some file transfer programs add garbage to the end of transferred files. This specification protects against such common damage). The simplest IFF file would be one that does no more than encapsulate some binary data (perhaps even an old-fashioned single-purpose binary file). Here is a binary dump of such a minimal IFF example: 0000: 464F524D 0000001A 534E4150 43524143 FORM....SNAPCRAC 0010: 0000000D 68656C6C 6F2C776F 726C6421 ....hello,world! 0020: 0A00 .. The first 4 bytes indicate this is a "FORM"; the most common IFF top level structure. The following 4 bytes indicate that the contents totals 26 bytes. The form type is listed as "SNAP". Our form "SNAP" contains only one chunk at the moment; a chunk of type "CRAC". >From the size ($0000000D) the amount of data must be 13 bytes. In this case, the data happens to correspond to the ASCII string "hello, world!<lf>". Since the number 13 is odd, a zero pad byte is added to the file. At any time new chunks could be added to form SNAP without affecting any other aspect of the file (other than the form size). It's that simple. Since an IFF file can be a group of objects, programs that read/write single objects can communicate to an extent with programs that read/write groups. You're encouraged to write programs that handle all the objects in a LIST or CAT. A graphics editor, for example, could process a list of pictures as a multiple page document, one page at a time. Programs should enforce IFF's syntactic rules when reading and writing files. Users should be told when a file is corrupt. This ensures robust data transfer. For minor damage, you may wish to give the user the option of using the suspect data, or cancelling. Presumably a user could read in a damaged file, then save whatever was salvaged to a valid file. The public domain IFF reader/writer subroutine package does some syntatic checks for you. A utility program"IFFCheck" is available that scans an IFF file and checks it for conformance to IFF's syntactic rules. IFFCheck also prints an outline of the chunks in the file, showing the ckID and ckSize of each. This is quite handy when building IFF programs. Example programs are also available to show details of reading and writing IFF files. A merge program "IFFJoin" will be available that logically appends IFF files into a single CAT group. It "unwraps" each input file that is a CAT so that the combined file isn't nested CATs. If we need to revise the IFF standard, the three anchoring IDs will be used as "version numbers". That's why IDs "FOR1" through "FOR9", "LIS1" through "LIS9", and "CAT1" through "CAT9" are reserved. IFF formats are designed for reasonable performance with floppy disks. We achieve considerable simplicity in the formats and programs by relying on the host file system rather than defining universal grouping structures like directories for LIST contents. On huge storage systems, IFF files could be leaf nodes in a file structure like a B-tree. Let's hope the host file system implements that for us! There are two kinds of IFF files: single purpose files and scrap files. They differ in the interpretation of multiple data objects and in the file's external type. Single Purpose Files A single purpose IFF file is for normal "document" and "archive" storage. This is in contrast with "scrap files" (see below) and temporary backing storage (non-interchange files). The external file type (or filename extension, depending on the host file system) indicates the file's contents. It's generally the FORM type of the data contained, hence the restrictions on FORM type IDs. Programmers and users may pick an "intended use" type as the filename extension to make it easy to filter for the relevant files in a filename requester. This is actually a "subclass" or "subtype" that conveniently separates files of the same FORM type that have different uses. Programs cannot demand conformity to its expected subtypes without overly restricting data interchange since they cannot know about the subtypes to be used by future programs that users will want to exchange data with. Issue: How to generate 3-letter MS-DOS extensions from 4-letter FORM type IDs? Most single purpose files will be a single FORM (perhaps a composite FORM like a musical score containing nested FORMs like musical instrument descriptions). If it's a LIST or a CAT, programs should skip over unrecognized objects to read the recognized ones or the first recognized one. Then a program that can read a single purpose file can read something out of a "scrap file", too. Scrap Files (not currently used) A "scrap file" is for maximum interconnectivity in getting data between programs; the core of a clipboard function. Scrap files may have type "IFF " or filename extension ".IFF". A scrap file is typically a CAT containing alternate representations of the same basic information. Include as many alternatives as you can readily generate. This redundancy improves interconnectivity in situations where we can't make all programs read and write super-general formats. [Inside Macintosh chapter "Scrap Manager".] E.g., a graphically-annotated musical score might be supplemented by a stripped down 4-voice melody and by a text (the lyrics). The originating program should write the alternate representations in order of "preference": most preferred (most comprehensive) type to least preferred (least comprehensive) type. A receiving program should either use the first appearing type that it understands or search for its own "preferred" type. A scrap file should have at most one alternative of any type. (A LIST of same type objects is OK as one of the alternatives.) But don't count on this when reading; ignore extra sections of a type. Then a program that reads scrap files can read something out of single purpose files. Rules for Reader Programs Here are some notes on building programs that read IFF files. For LIST and PROP work, you should also read up on recursive descent parsers. [See, for example, Compiler Construction, An Advanced Course.] oThe standard is very flexible so many programs can exchange data. This implies a program has to scan the file and react to what's actually there in whatever order it appears. An IFF reader program is a parser. oFor interchange to really work, programs must be willing to do some conversion during read-in. If the data isn't exactly what you expect, say, the raster is smaller than those created by your program, then adjust it. Similarly, your program could crop a large picture, add or drop bitplanes, or create/discard a mask plane. The program should give up gracefully on data that it can't convert. oIf it doesn't start with "FORM", "LIST", or "CAT ", it's not an IFF-85 file. oFor any chunk you encounter, you must recognize its type ID to understand its contents. oFor any FORM chunk you encounter, you must recognize its FORM type ID to understand the contained "local chunks". Even if you don't recognize the FORM type, you can still scan it for nested FORMs, LISTs, and CATs of interest. oDon't forget to skip the implied pad byte after every odd-length chunk, this is not included in the chunk count! oChunk types LIST, FORM, PROP, and CAT are generic groups. They always contain a subtype ID followed by chunks. oReaders ought to handle a CAT of FORMs in a file. You may treat the FORMs like document pages to sequence through, or just use the first FORM. oMany IFF readers completely skip LISTs. "Fully IFF-conforming" readers are those that handle LISTs, even if just to read the first FORM from a file. If you do look into a LIST, you must process shared properties (in PROP chunks) properly. The idea is to get the correct data or none at all. oThe nicest readers are willing to look into unrecognized FORMs for nested FORM types that they do recognize. For example, a musical score may contain nested instrument descriptions and animation or desktop publishing files may contain still pictures. This extra step is highly recommended. Note to programmers: Processing PROP chunks is not simple! You'll need some background in interpreters with stack frames. If this is foreign to you, build programs that read/write only one FORM per file. For the more intrepid programmers, the next paragraph summarizes how to process LISTs and PROPs. Allocate a stack frame for every LIST and FORM you encounter and initialize it by copying the stack frame of the parent LIST or FORM. At the top level, you'll need a stack frame initialized to your program's global defaults. While reading each LIST or FORM, store all encountered properties into the current stack frame. In the example ShowILBM, each stack frame has a place for a bitmap header property ILBM.BMHD and a color map property ILBM.CMAP. When you finally get to the ILBM's BODY chunk, use the property settings accumulated in the current stack frame. An alternate implementation would just remember PROPs encountered, forgetting each on reaching the end of its scope (the end of the containing LIST). When a FORM XXXX is encountered, scan the chunks in all remembered PROPs XXXX, in order, as if they appeared before the chunks actually in the FORM XXXX. This gets trickier if you read FORMs inside of FORMs. Rules for Writer Programs Here are some notes on building programs that write IFF files, which is much easier than reading them. oAn IFF file is a single FORM, LIST, or CAT chunk. oAny IFF-85 file must start with the 4 characters "FORM", "LIST", or "CAT ", followed by a LONG ckSize. There should be no data after the chunk end. oChunk types LIST, FORM, PROP, and CAT are generic. They always contain a subtype ID followed by chunks. These three IDs are universally reserved, as are "LIS1" through "LIS9", "FOR1" through "FOR9", "CAT1" through "CAT9", and " ". oDon't forget to write a 0 pad byte after each odd-length chunk. oDo not try to edit a file that you don't know how to create. Programs may look into a file and copy out nested FORMs of types that they recognize, but they should not edit and replace the nested FORMs and not add or remove them. Breaking these rules could make the containing structure inconsistent. You may write a new file containing items you copied, or copied and modified, but don't copy structural parts you don't understand. oYou must adhere to the syntax descriptions in Appendix A. E.g., PROPs may only appear inside LISTs. There are at least four common techniques for writing an IFF group: (1) build the data in a file mapped into virtual memory. (2) build the data in memory blocks and use block I/O. (3) stream write the data piecemeal and (don't forget!) random access back to set the group (or FORM) length count. (4) make a preliminary pass to compute the length count then stream write the data. Issue: The standard disallows "blind" chunk copying for consistency reasons. Perhaps we can define a ckID convention for chunks that are OK to replicate without knowledge of the contents. Any such chunks would need to be internally consistent, and not be bothered by changed external references. Issue: Stream-writing an IFF FORM can be inconvenient. With random access files one can write all the chunks then go back to fix up the FORM size. With stream access, the FORM size must be calculated before the file is written. When compression is involved, this can be slow or inconvenient. Perhaps we can define an "END " chunk. The stream writer would use -1 ($FFFFFFFF) as the FORM size. The reader would follow each chunk, when the reader reaches an "END ", it would terminate the last -1 sized chunk. Certain new IFF FORMs could require that readers understand "END ". Appendix A. Reference Type Definitions The following C typedefs describe standard IFF structures. Declarations to use in practice will vary with the CPU and compiler. For example, 68000 Lattice C produces efficient comparison code if we define ID as a "LONG". A macro "MakeID" builds these IDs at compile time. /* Standard IFF types, expressed in 68000 Lattice C.*/ typedef unsigned char UBYTE;/* 8 bits unsigned*/ typedef short WORD;/* 16 bits signed*/ typedef unsigned short UWORD;/* 16 bits unsigned*/ typedef long LONG;/* 32 bits signed*/ typedef char ID[4];/* 4 chars in ' ' through '~' */ typedef struct { IDckID; LONGckSize;/* sizeof(ckData)*/ UBYTEckData[/* ckSize */]; } Chunk; /* ID typedef and builder for 68000 Lattice C. */ typedef LONG ID; /* 4 chars in ' ' through '~'*/ #define MakeID(a,b,c,d) ( (a)<<24 | (b)<<16 | (c)<<8 | (d) ) /* Globally reserved IDs. */ #define ID_FORM MakeID('F','O','R','M') #define ID_LIST MakeID('L','I','S','T') #define ID_PROP MakeID('P','R','O','P') #define ID_CAT MakeID('C','A','T',' ') #define ID_FILLER MakeID(' ',' ',' ',' ') Syntax Definitions Here's a collection of the syntax definitions in this document. Chunk ::= ID #{ UBYTE* } [0] Property ::= Chunk FORM ::= "FORM" #{ FormType (LocalChunk | FORM | LIST | CAT)* } FormType ::= ID LocalChunk ::= Property | Chunk CAT ::= "CAT " #{ ContentsType (FORM | LIST | CAT)* } ContentsType ::= ID-- a hint or an "abstract data type" ID LIST ::= "LIST" #{ ContentsType PROP* (FORM | LIST | CAT)* } PROP ::= "PROP" #{ FormType Property* } In this extended regular expression notation, the token "#" represents a count of the following {braced} data bytes. Literal items are shown in "quotes", [square bracketed items] are optional, and "*" means 0 or more instances. A sometimes-needed pad byte is shown as "[0]". Example Diagrams Here's a box diagram for an example IFF file, a raster image FORM ILBM. This FORM contains a bitmap header property chunk BMHD, a color map property chunk CMAP, and a raster data chunk BODY. This particular raster is 320 x 200 pixels x 3 bit planes uncompressed. The "0" after the CMAP chunk represents a zero pad byte; included since the CMAP chunk has an odd length. The text to the right of the diagram shows the outline that would be printed by the IFFCheck utility program for this particular file. +----------------------------+ | | | 'FORM' 24070 | FORM 24070 ILBM | | - +----------------------------+ | | | | | 'ILBM' | | | | | | +----------------------+ | | | | 'BMHD' 20 | | .BMHD 20 | | |----------------------| | | | | 320, 200, 0, 0, 3, | | | | | 0, 0, 0, .... | | | | +----------------------+ | | | | 24070 < | +----------------------+ | bytes | | | 'CMAP' 21 | | .CMAP 21 | | |----------------------| | | | | 0, 0, 0; 32, 0, 0; | | | | | 64, 0, 0 ... | | | | +----------------------+ | | | 0 | | | +----------------------+ | | | | 'BODY' 24000 | | | | |----------------------| | .BODY 24000 | | | 0, 0, 0.... | | | | +----------------------+ | | | | -- +----------------------------+ This second diagram shows a LIST of two FORMs ILBM sharing a common BMHD property and a common CMAP property. Again, the text on the right is an outline a la IFFCheck. +------------------------------+ | 'LIST' 48114 | +------------------------------+ | | | 'ILBM' | | | | +------------------------+ | | | 'PROP' 62 | | .PROP 62 ILBM | +------------------------+ | | | | | | | 'ILBM' | | | | +--------------------+ | | | | | 'BMHD' 20 | | | ..BMHD 20 | | |--------------------| | | | | | 320, 200, 0, 0, 3,| | | | | | 0, 0, 0, .... | | | | | +--------------------+ | | | | | | | | +--------------------+ | | | | | 'CMAP' 21 | | | ..CMAP 21 | | |--------------------| | | | | | 0, 0, 0; 32, 0, 0;| | | | | | 64, 0, 0 .... | | | | | +--------------------+ | | | | 0 | | | +------------------------+ | | | | | | +------------------------+ | | | 'FORM' 24012 | | .FORM 24012 ILBM | +------------------------+ | | | | | | | 'ILBM' | | | | +--------------------+ | | | | | 'BODY' 24000 | | | ..BODY 24000 | | |--------------------| | | | | | 0, 0, 0.... | | | | | +--------------------+ | | | +------------------------+ | | | | | | +------------------------+ | | | 'FORM' 24012 | | .FORM 24012 ILBM | +------------------------+ | | | | | | | 'ILBM' | | | | +--------------------+ | | | | | 'BODY' 24000 | | | ..BODY 24000 | | |--------------------| | | | | | 0, 0, 0.... | | | | | +--------------------+ | | | +------------------------+ | | | | | +------------------------------+ "ILBM" IFF Interleaved Bitmap Date:January 17, 1986 (CRNG data updated Oct, 1988 by Jerry Morrison) (Appendix E added and CAMG updated Oct, 1988 by Commodore-Amiga, Inc.) From:Jerry Morrison, Electronic Arts Status:Released and in use 1. Introduction "EA IFF 85" is Electronic Arts' standard for interchange format files. "ILBM" is a format for a 2 dimensional raster graphics image, specifically an InterLeaved bitplane BitMap image with color map. An ILBM is an IFF "data section" or "FORM type", which can be an IFF file or a part of one. ILBM allows simple, highly portable raster graphic storage. An ILBM is an archival representation designed for three uses. First, a stand- alone image that specifies exactly how to display itself (resolution, size, color map, etc.). Second, an image intended to be merged into a bigger picture which has its own depth, color map, and so on. And third, an empty image with a color map selection or "palette" for a paint program. ILBM is also intended as a building block for composite IFF FORMs like "animation sequences" and "structured graphics". Some uses of ILBM will be to preserve as much information as possible across disparate environments. Other uses will be to store data for a single program or highly cooperative programs while maintaining subtle details. So we're trying to accomplish a lot with this one format. This memo is the IFF supplement for FORM ILBM. Section 2 defines the purpose and format of property chunks bitmap header "BMHD", color map "CMAP", hotspot "GRAB", destination merge data "DEST", sprite information "SPRT", and Commodore Amiga viewport mode "CAMG". Section 3 defines the standard data chunk "BODY". These are the "standard" chunks. Section 4 defines the non- standard data chunks. Additional specialized chunks like texture pattern can be added later. The ILBM syntax is summarized in Appendix A as a regular expression and in Appendix B as a box diagram. Appendix C explains the optional run encoding scheme. Appendix D names the committee responsible for this FORM ILBM standard. Details of the raster layout are given in part 3, "Standard Data Chunk". Some elements are based on the Commodore Amiga hardware but generalized for use on other computers. An alternative to ILBM would be appropriate for computers with true color data in each pixel, though the wealth of available ILBM images makes import and export important. Reference: "EA IFF 85" Standard for Interchange Format Files describes the underlying conventions for all IFF files. Amiga. is a registered trademark of Commodore-Amiga, Inc. Electronic Arts(tm) is a trademark of Electronic Arts. Macintosh(tm) is a trademark licensed to Apple Computer, Inc. MacPaint(tm) is a trademark of Apple Computer, Inc. 2. Standard Properties ILBM has several defined property chunks that act on the main data chunks. The required property "BMHD" and any optional properties must appear before any "BODY" chunk. (Since an ILBM has only one BODY chunk, any following properties would be superfluous.) Any of these properties may be shared over a LIST of several IBLMs by putting them in a PROP ILBM (See the EA IFF 85 document). BMHD The required property "BMHD" holds a BitMapHeader as defined in the following documentation. It describes the dimensions of the image, the encoding used, and other data necessary to understand the BODY chunk to follow. typedef UBYTE Masking; /* Choice of masking technique. */ #define mskNone 0 #define mskHasMask 1 #define mskHasTransparentColor 2 #define mskLasso 3 typedef UBYTE Compression; /* Choice of compression algorithm applied to the rows of all source and mask planes. "cmpByteRun1" is the byte run encoding described in Appendix C. Do not compress across rows! */ #define cmpNone 0 #define cmpByteRun1 1 typedef struct { UWORD w, h; /* raster width & height in pixels */ WORD x, y; /* pixel position for this image */ UBYTE nPlanes; /* # source bitplanes */ Masking masking; Compression compression; UBYTE Flags; /* CMAP flags (formerly pad1, unused) */ UWORD transparentColor; /* transparent "color number" (sort of) */ UBYTE xAspect, yAspect; /* pixel aspect, a ratio width : height */ WORD pageWidth, pageHeight; /* source "page" size in pixels */ } BitMapHeader; Fields are filed in the order shown. The UBYTE fields are byte-packed (the C compiler must not add pad bytes to the structure). The fields w and h indicate the size of the image rectangle in pixels. Each row of the image is stored in an integral number of 16 bit words. The number of words per row is words=((w+15)/16) or Ceiling(w/16). The fields x and y indicate the desired position of this image within the destination picture. Some reader programs may ignore x and y. A safe default for writing an ILBM is (x, y) = (0, 0). The number of source bitplanes in the BODY chunk is stored in nPlanes. An ILBM with a CMAP but no BODY and nPlanes = 0 is the recommended way to store a color map. Note: Color numbers are color map index values formed by pixels in the destination bitmap, which may be deeper than nPlanes if a DEST chunk calls for merging the image into a deeper image. The field masking indicates what kind of masking is to be used for this image. The value mskNone designates an opaque rectangular image. The value mskHasMask means that a mask plane is interleaved with the bitplanes in the BODY chunk (see below). The value mskHasTransparentColor indicates that pixels in the source planes matching transparentColor are to be considered "transparent". (Actually, transparentColor isn't a "color number" since it's matched with numbers formed by the source bitmap rather than the possibly deeper destination bitmap. Note that having a transparent color implies ignoring one of the color registers. The value mskLasso indicates the reader may construct a mask by lassoing the image as in MacPaint(tm). To do this, put a 1 pixel border of transparentColor around the image rectangle. Then do a seed fill from this border. Filled pixels are to be transparent. Issue: Include in an appendix an algorithm for converting a transparent color to a mask plane, and maybe a lasso algorithm. A code indicating the kind of data compression used is stored in compression. Beware that using data compression makes your data unreadable by programs that don't implement the matching decompression algorithm. So we'll employ as few compression encodings as possible. The run encoding byteRun1 is documented in Appendix C. The field pad1 is a pad byte reserved for future use. It must be set to 0 for consistency. The transparentColor specifies which bit pattern means "transparent". This only applies if masking is mskHasTransparentColor or mskLasso. Otherwise, transparentColor should be 0 (see above). The pixel aspect ratio is stored as a ratio in the two fields xAspect and yAspect. This may be used by programs to compensate for different aspects or to help interpret the fields w, h, x, y, pageWidth, and pageHeight, which are in units of pixels. The fraction xAspect/yAspect represents a pixel's width/height. It's recommended that your programs store proper fractions in the BitMapHeader, but aspect ratios can always be correctly compared with the test: xAspect * yDesiredAspect = yAspect * xDesiredAspect Typical values for aspect ratio are width : height = 10 : 11 for an Amiga 320 x 200 display and 1 : 1 for a Macintosh(tm) display. The size in pixels of the source "page" (any raster device) is stored in pageWidth and pageHeight, e.g., (320, 200) for a low resolution Amiga display. This information might be used to scale an image or to automatically set the display format to suit the image. Note that the image can be larger than the page. CMAP The optional (but encouraged) property "CMAP" stores color map data as triplets of red, green, and blue intensity values. The n color map entries ("color registers") are stored in the order 0 through n-1, totaling 3n bytes. Thus n is the ckSize/3. Normally, n would equal 2^(nPlanes). A CMAP chunk contains a ColorMap array as defined below. Note that these typedefs assume a C compiler that implements packed arrays of 3-byte elements. typedef struct { UBYTE red, green, blue; /* color intensities 0..255 */ } ColorRegister; /* size = 3 bytes */ typedef ColorRegister ColorMap[n]; /* size = 3n bytes */ The color components red, green, and blue represent fractional intensity values in the range 0 through 255 256ths. White is (255, 255, 255) and black is (0, 0, 0). If your machine has less color resolution, use the high order bits. Shift each field right on reading (or left on writing) and assign it to (from) a field in a local packed format like Color4, below. This achieves automatic conversion of images across environments with different color resolutions. On reading an ILBM, use defaults if the color map is absent or has fewer color registers than you need. Ignore any extra color registers. (See Appendix E for a better way to write colors) The example type Color4 represents the format of a color register in the memory of an Amiga with the original graphics chip set, i.e., 4 bit video DACs. (The ":4" tells smarter C compilers to pack the field into 4 bits.) typedef struct { unsigned pad1 :4, red :4, green :4, blue :4; } Color4; /* Amiga RAM format. Not filed. */ With the latest generation of Amigas, there is a new complication. Amigas with the AA chip set support a full 8 bits of color resolution. In order to distinguish older CMAPs with 4-bit values from newer CMAP chunks with full 8-bit values, use the high order bit of BMHD.Flags (1 << 7). If this bit is set, it means this CMAP is a full 8-bit value. If this bit is clear, it is probably an older 4-bit CMAP with values either left justified or scaled. Remember that every chunk must be padded to an even length, so a color map with an odd number of entries would be followed by a 0 byte, not included in the ckSize. GRAB The optional property "GRAB" locates a "handle" or "hotspot" of the image relative to its upper left corner, e.g., when used as a mouse cursor or a "paint brush". A GRAB chunk contains a Point2D. typedef struct { WORD x, y; /* relative coordinates (pixels) */ } Point2D; DEST The optional property "DEST" is a way to say how to scatter zero or more source bitplanes into a deeper destination image. Some readers may ignore DEST. The contents of a DEST chunk is DestMerge structure: typedef struct { UBYTE depth; /* # bitplanes in the original source */ UBYTE pad1; /* unused; for consistency put 0 here */ UWORD planePick; /* how to scatter source bitplanes into destination */ UWORD planeOnOff; /* default bitplane data for planePick */ UWORD planeMask; /* selects which bitplanes to store into */ } DestMerge; The low order depth number of bits in planePick, planeOnOff, and planeMask correspond one-to-one with destination bitplanes. Bit 0 with bitplane 0, etc. (Any higher order bits should be ignored.) "1" bits in planePick mean "put the next source bitplane into this bitplane", so the number of "1" bits should equal nPlanes. "0" bits mean "put the corresponding bit from planeOnOff into this bitplane". Bits in planeMask gate writing to the destination bitplane: "1" bits mean "write to this bitplane" while "0" bits mean "leave this bitplane alone". The normal case (with no DEST property) is equivalent to planePick = planeMask = 2^(nPlanes) - 1. Remember that color numbers are formed by pixels in the destination bitmap (depth planes deep) not in the source bitmap (nPlanes planes deep). SPRT The presence of an "SPRT" chunk indicates that this image is intended as a sprite. It's up to the reader program to actually make it a sprite, if even possible, and to use or overrule the sprite precedence data inside the SPRT chunk: typedef UWORD SpritePrecedence; /* relative precedence, 0 is the highest */ Precedence 0 is the highest, denoting a sprite that is foremost. Creating a sprite may imply other setup. E.g., a 2 plane Amiga sprite would have transparentColor = 0. Color registers 1, 2, and 3 in the CMAP would be stored into the correct hardware color registers for the hardware sprite number used, while CMAP color register 0 would be ignored. CAMG A "CAMG" chunk is specifically for Commodore Amiga ILBM's. All Amiga-based reader and writer software should deal with CAMG. The Amiga supports many different video display modes including interlace, Extra Halfbrite, hold and modify (HAM), plus a variety of new modes under the 2.0 operating system. A CAMG chunk contains a single long word (length=4) which specifies the Amiga display mode of the picture. Prior to 2.0, it was possible to express all available Amiga ViewModes in 16 bits of flags (Viewport->Modes or NewScreen->ViewModes). Old-style writers and readers place a 16-bit Amiga ViewModes value in the low word of the CAMG, and zeros in the high word. The following Viewmode flags should always be removed from old-style 16-bit ViewModes values when writing or reading them: EXTENDED_MODE | SPRITES | VP_HIDE | GENLOCK_AUDIO | GENLOCK_VIDEO (=0x7102, mask=0x8EFD) New ILBM readers and writers, should treat the full CAMG longword as a 32-bit ModeID to support new and future display modes. New ILBM writers, when running under the 2.0 Amiga operating system, should directly store the full 32-bit return value of the graphics function GetVPModeID(vp) in the CAMG longword. When running under 1.3, store a 16-bit Viewmodes value masked as described above. ILBM readers should only mask bits out of a CAMG if the CAMG has a zero upper word (see exception below). New ILBM readers, when running under 2.0, should then treat the 32-bit CAMG value as a ModeID, and should use the graphics ModeNotAvailable() function to determine if the mode is available. If the mode is not available, fall back to another suitable display mode. When running under 1.3, the low word of the CAMG may generally be used to open a compatible display. Note that one popular graphics package stores junk in the upper word of the CAMG of brushes, and incorrect values (generally zero) in the low word. You can screen for such junk values by testing for non-zero in the upper word of a ModeID in conjunction with the 0x00001000 bit NOT set in the low word. The following code fragment demonstrates ILBM reader filtering of inappropriate bits in 16-bit CAMG values. #include <graphics/view.h> #include <graphics/displayinfo.h> /* Knock bad bits out of old-style CAMG modes before checking availability. * (some ILBM CAMG's have these bits set in old 1.3 modes, and should not) * If not an extended monitor ID, or if marked as extended but missing * upper 16 bits, screen out inappropriate bits now. */ if((!(modeid & MONITOR_ID_MASK)) || ((modeid & EXTENDED_MODE)&&(!(modeid & 0xFFFF0000)))) modeid &= (~(EXTENDED_MODE|SPRITES|GENLOCK_AUDIO|GENLOCK_VIDEO|VP_HIDE)); /* Check for bogus CAMG like some brushes have, with junk in * upper word and extended bit NOT set not set in lower word. */ if((modeid & 0xFFFF0000)&&(!(modeid & EXTENDED_MODE))) { /* Bad CAMG, so ignore CAMG and determine a mode based on * based on pagesize or aspect */ modeid = NULL; if(wide >= 640) modeid |= HIRES; if(high >= 400) modeid |= LACE; } /* Now, ModeNotAvailable() may be used to determine if the mode is available. * * If the mode is not available, you may prompt the user for a mode * choice, or search the 2.0 display database for an appropriate * replacement mode, or you may be able to get a relatively compatible * old display mode by masking out all bits except * HIRES | LACE | HAM | EXTRA_HALFBRITE */ if((modeid & 0xFFFF0000)&&(!(modeid & 0x00001000))) { /* Then probably invalid ModeID */ } 3. Standard "BODY" Data Chunk Raster Layout Raster scan proceeds left-to-right (increasing X) across scan lines, then top-to-bottom (increasing Y) down columns of scan lines. The coordinate system is in units of pixels, where (0,0) is the upper left corner. The raster is typically organized as bitplanes in memory. The corresponding bits from each plane, taken together, make up an index into the color map which gives a color value for that pixel. The first bitplane, plane 0, is the low order bit of these color indexes. A scan line is made of one "row" from each bitplane. A row is one plane's bits for one scan line, but padded out to a word (2 byte) boundary (not necessarily the first word boundary). Within each row, successive bytes are displayed in order and the most significant bit of each byte is displayed first. A "mask" is an optional "plane" of data the same size (w, h) as a bitplane. It tells how to "cut out" part of the image when painting it onto another image. "One" bits in the mask mean "copy the corresponding pixel to the destination". "Zero" mask bits mean "leave this destination pixel alone". In other words, "zero" bits designate transparent pixels. The rows of the different bitplanes and mask are interleaved in the file (see below). This localizes all the information pertinent to each scan line. It makes it much easier to transform the data while reading it to adjust the image size or depth. It also makes it possible to scroll a big image by swapping rows directly from the file without the need for random-access to all the bitplanes. BODY The source raster is stored in a "BODY" chunk. This one chunk holds all bitplanes and the optional mask, interleaved by row. The BitMapHeader, in a BMHD property chunk, specifies the raster's dimensions w, h, and nPlanes. It also holds the masking field which indicates if there is a mask plane and the compression field which indicates the compression algorithm used. This information is needed to interpret the BODY chunk, so the BMHD chunk must appear first. While reading an ILBM's BODY, a program may convert the image to another size by filling (with transparentColor) or clipping. The BODY's content is a concatenation of scan lines. Each scan line is a concatenation of one row of data from each plane in order 0 through nPlanes-1 followed by one row from the mask (if masking = hasMask ). If the BitMapHeader field compression is cmpNone, all h rows are exactly (w+15)/16 words wide. Otherwise, every row is compressed according to the specified algorithm and the stored widths depend on the data compression. Reader programs that require fewer bitplanes than appear in a particular ILBM file can combine planes or drop the high-order (later) planes. Similarly, they may add bitplanes and/or discard the mask plane. Do not compress across rows, and don't forget to compress the mask just like the bitplanes. Remember to pad any BODY chunk that contains an odd number of bytes and skip the pad when reading. 4. Nonstandard Data Chunks The following data chunks were defined after various programs began using FORM ILBM so they are "nonstandard" chunks. See the registry document for the latest information on additional non-standard chunks. CRNG A "CRNG" chunk contains "color register range" information. It's used by Electronic Arts' Deluxe Paint program to identify a contiguous range of color registers for a "shade range" and color cycling. There can be zero or more CRNG chunks in an ILBM, but all should appear before the BODY chunk. Deluxe Paint normally writes 4 CRNG chunks in an ILBM when the user asks it to "Save Picture". typedef struct { WORD pad1; /* reserved for future use; store 0 here */ WORD rate; /* color cycle rate */ WORD flags; /* see below */ UBYTE low, high; /* lower and upper color registers selected */ } CRange; The bits of the flags word are interpreted as follows: if the low bit is set then the cycle is "active", and if this bit is clear it is not active. Normally, color cycling is done so that colors move to the next higher position in the cycle, with the color in the high slot moving around to the low slot. If the second bit of the flags word is set, the cycle moves in the opposite direction. As usual, the other bits of the flags word are reserved for future expansion. Here are the masks to test these bits: #define RNG_ACTIVE 1 #define RNG_REVERSE 2 The fields low and high indicate the range of color registers (color numbers) selected by this CRange. The field active indicates whether color cycling is on or off. Zero means off. The field rate determines the speed at which the colors will step when color cycling is on. The units are such that a rate of 60 steps per second is represented as 2^14 = 16384. Slower rates can be obtained by linear scaling: for 30 steps/second, rate = 8192; for 1 step/second, rate = 16384/60 * 273. CCRT Commodore's Graphicraft program uses a similar chunk "CCRT" (for Color Cycling Range and Timing). This chunk contains a CycleInfo structure. typedef struct { WORD direction; /* 0 = don't cycle. 1 = cycle forwards */ /* (1, 2, 3). -1 = cycle backwards (3, 2, 1) */ UBYTE start, end; /* lower and upper color registers selected */ LONG seconds; /* # seconds between changing colors plus... */ LONG microseconds; /* # microseconds between changing colors */ WORD pad; /* reserved for future use; store 0 here */ } CycleInfo; This is very similar to a CRNG chunk. A program would probably only use one of these two methods of expressing color cycle data, new programs should use CRNG. You could write out both if you want to communicate this information to both Deluxe Paint and Graphicraft. Appendix A. ILBM Regular Expression Here's a regular expression summary of the FORM ILBM syntax. This could be an IFF file or a part of one. ILBM ::= "FORM" #{ "ILBM" BMHD [CMAP] [GRAB] [DEST] [SPRT] [CAMG] CRNG* CCRT* [BODY] } BMHD ::= "BMHD" #{ BitMapHeader } CMAP ::= "CMAP" #{ (red green blue)* } [0] GRAB ::= "GRAB" #{ Point2D } DEST ::= "DEST" #{ DestMerge } SPRT ::= "SPRT" #{ SpritePrecedence } CAMG ::= "CAMG" #{ LONG } CRNG ::= "CRNG" #{ CRange } CCRT ::= "CCRT" #{ CycleInfo } BODY ::= "BODY" #{ UBYTE* } [0] The token "#" represents a ckSize LONG count of the following {braced} data bytes. E.g., a BMHD's "#" should equal sizeof(BitMapHeader). Literal strings are shown in "quotes", [square bracket items] are optional, and "*" means 0 or more repetitions. A sometimes-needed pad byte is shown as "[0]". The property chunks BMHD, CMAP, GRAB, DEST, SPRT, CAMG and any CRNG and CCRT data chunks may actually be in any order but all must appear before the BODY chunk since ILBM readers usually stop as soon as they read the BODY. If any of the 6 property chunks are missing, default values are inherited from any shared properties (if the ILBM appears inside an IFF LIST with PROPs) or from the reader program's defaults. If any property appears more than once, the last occurrence before the BODY is the one that counts since that's the one that modifies the BODY. Appendix B. ILBM Box Diagram Here's a box diagram for a simple example: an uncompressed image 320 x 200 pixels x 3 bitplanes. The text to the right of the diagram shows the outline that would be printed by the Sift utility program for this particular file. +--------------------+ | | | 'FORM' 24070 | FORM 24070 ILBM | | -- +--------------------+ | | | | | 'ILBM' | | | | | | +----------------+ | | | | 'BMHD' 20 | | .BMHD 20 | | |----------------| | | | | 320, 200, | | | | | 0 .... | | | | +----------------+ | | | | | | +----------------+ | 24070 < | | 'CMAP' 21 | | .CMAP 21 bytes | | |----------------| | | | | 0, 0, 0, | | | | | 255 .... | | | | +----------------+ | | | 0 | | | +----------------+ | | | | 'BODY' 24000 | | .BODY 24000 | | |----------------| | | | | 0, 0, 0.... | | | | +----------------+ | | | | -- +--------------------+ The "0" after the CMAP chunk is a pad byte. Appendix C. IFF Hints Hints on ILBM files from Jerry Morrison, Oct 1988. How to avoid some pitfalls when reading ILBM files: oDon't ignore the BitMapHeader.masking field. A bitmap with a mask (such as a partially-transparent DPaint brush or a DPaint picture with a stencil) will read as garbage if you don't de-interleave the mask. oDon't assume all images are compressed. Narrow images aren't usually run-compressed since that would actually make them longer. oDon't assume a particular image size. You may encounter overscan pictures and PAL pictures. There's a better way to read a BODY than the example IFF code. The GetBODY routine should call a GetScanline routine once per scan line, which calls a GetRow routine for each bitplane in the file. This in turn calls a GetUnpackedBytes routine, which calls a GetBytes routine as needed and unpacks the result. (If the picture is uncompressed, GetRow calls GetBytes directly.) Since theunpacker knows how many packed bytes to read, this avoids juggling buffers for a memory-to-memory UnPackBytes routine. Caution: If you make many AmigaDOS calls to read or write a few bytes at a time, performance will be mud! AmigaDOS has a high overhead per call, even with RAM disk. So use buffered read/write routines. Different hardware display devices have different color resolutions: Device R:G:B bits maxColor ------- ---------- -------- Mac SE 1 1 IBM EGA 2:2:2 3 Atari ST 3:3:3 7 Amiga 4:4:4 15 CD-I 5:5:5 31 IBM VGA 6:6:6 63 Mac II 8:8:8 255 An ILBM CMAP defines 8 bits of Red, Green and Blue (i.e., 8:8:8 bits of R:G:B). When displaying on hardware which has less color resolution, just take the high order bits. For example, to convert ILBM's 8-bit Red to the Amiga's 4-bit Red, right shift the data by 4 bits (R4 := R8 >> 4). To convert hardware colors to ILBM colors, the ILBM specification says just set the high bits (R8 := R4 << 4). But you can transmit higher contrast to foreign display devices by scaling the data [0..maxColor] to the full range [0..255]. In other words, R8 := (Rn x 255) w maxColor. (Example #1: EGA color 1:2:3 scales to 85:170:255. Example #2: Amiga 15:7:0 scales to 255:119:0). This makes a big difference where maxColor is less than 15. In the extreme case, Mac SE white (1) should be converted to ILBM white (255), not to ILBM gray (128). CGA and EGA subtleties IBM EGA colors in 350 scan line mode are 2:2:2 bits of R:G:B, stored in memory as xxR'G'B'RBG. That's 3 low-order bits followed by 3 high-order bits. IBM CGA colors are 4 bits stored in a byte as xxxxIRGB. (EGA colors in 200 scan line modes are the same as CGA colors, but stored in memory as xxxIxRGB.) That's 3 high-order bits (one for each of R, G, and B) plus one low-order " Intensity" bit for all 3 components R, G, and B. Exception: IBM monitors show IRGB = 0110 as brown, which is really the EGA color R:G:B = 2:1:0, not dark yellow 2:2:0. 24-bit ILBMS When storing deep images as ILBMs (such as images with 8 bits each of R,G, and B), the bits for each pixel represent an absolute RGB value for that pixel rather than an index into a limited color map. The order for saving the bits is critical since a deep ILBM would not contain the usual CMAP of RGB values (such a CMAP would be too large and redundant). To interpret these "deep" ILBMs, it is necessary to have a standard order in which the bits of the R, G, and B values will be stored. A number of different orderings have already been used in deep ILBMs, so it was necessary to us chose one of these orderings as a standard. The following bit ordering has been chosen as the default bit ordering for deep ILBMs. Default standard deep ILBM bit ordering: saved first -------------------------------------------------> saved last R0 R1 R2 R3 R4 R5 R6 R7 G0 G1 G2 G3 G4 G5 G6 G7 B0 B1 B2 B3 B4 B5 B6 B7 One other existing deep bit ordering that you may encounter is the 21-bit NewTek format. NewTek deep ILBM bit ordering: saved first ------------------------------------------------------> saved last R7 G7 B7 R6 G6 B6 R5 G5 B5 R4 G4 B4 R3 G3 B3 R2 G2 B2 R1 G1 B1 R0 G0 B0 Note that you may encounter CLUT chunks in deep ILBM's. See the Third Party Specs appendix for more information on CLUT chunks. Appendix D. ByteRun1 Run Encoding The run encoding scheme byteRun1 is best described by pseudo code for the decoder Unpacker (called UnPackBits in the Macintosh(tm) toolbox): UnPacker: LOOP until produced the desired number of bytes Read the next source byte into n SELECT n FROM [0..127] => copy the next n+1 bytes literally [-1..-127] => replicate the next byte -n+1 times -128 => no operation ENDCASE; ENDLOOP; In the inverse routine Packer, it's best to encode a 2 byte repeat run as a replicate run except when preceded and followed by a literal run, in which case it's best to merge the three into one literal run. Always encode 3 byte repeats as replicate runs. Remember that each row of each scan line of a raster is separately packed. Intro to IFF Amiga ILBM Files and Amiga Viewmodes ================================================= The IFF (Interchange File Format) for graphic images on the Amiga is called FORM ILBM (InterLeaved BitMap). It follows a standard parsable IFF format. Sample hex dump of beginning of an ILBM: ======================================== Important note! You can NOT ever depend on any particular ILBM chunk being at any particular offset into the file! IFF files are composed, in their simplest form, of chunks within a FORM. Each chunk starts starts with a 4-letter chunkID, followed by a 32-bit length of the rest of the chunk. You PARSE IFF files, skipping past unneeded or unknown chunks by seeking their length (+1 if odd length) to the next 4-letter chunkID. 0000: 464F524D 00016418 494C424D 424D4844 FORM..d.ILBMBMHD 0010: 00000014 01400190 00000000 06000100 .....@.......... 0020: 00000A0B 01400190 43414D47 00000004 .....@..CAMG.... 0030: 00000804 434D4150 00000030 001100EE ....CMAP...0.... 0040: EEEE0000 22000055 33333355 55550033 .... ..P000PPP.0 0050: 99885544 77777711 66EE2266 EE6688DD ..P@ppp.`. `.`.. 0060: AAAAAAAA 99EECCCC CCDDAAEE 424F4459 ............BODY 0070: 000163AC F8000F80 148A5544 2ABDEFFF ..c.......UD*... etc. Interpretation: 'F O R M' length 'I L B M''B M H D'<-start of BitMapHeader chunk 0000: 464F524D 00016418 494C424D 424D4844 FORM..d.ILBMBMHD length WideHigh XorgYorg PlMkCoPd <- Planes Mask Compression Pad 0010: 00000014 01400190 00000000 06000100 .....@.......... TranAspt PagwPagh 'C A M G' length <- start of C-AMiGa View modes chunk 0020: 00000A0B 01400190 43414D47 00000004 .....@..CAMG.... Viewmode 'C M A P' length R g b R <- Viewmode 800=HAM | 4=LACE 0030: 00000804 434D4150 00000030 001100EE ....CMAP...0.... g b R g b R g b R g b R g b R g <- Rgb's are for reg0 thru regN 0040: EEEE0000 22000055 33333355 55550033 .... ..P000PPP.0 b R g b R g b R g b R g b R g b 0050: 99885544 77777711 66EE2266 EE6688DD ..P@ppp.`. `.`.. R g b R g b R g b R g b 'B O D Y' 0060: AAAAAAAA 99EECCCC CCDDAAEE 424F4459 ............BODY length start of body data <- Compacted (Compression=1 above) 0070: 000163AC F8000F80 148A5544 2ABDEFFF ..c.......UD*... 0080: FFBFF800 0F7FF7FC FF04F85A 77AD5DFE ...........Zw.]. etc. Notes on CAMG Viewmodes: HIRES=0x8000 LACE=0x4 HAM=0x800 HALFBRITE=0x80 Interpreting ILBMs ================== ILBM is a fairly simple IFF FORM. All you really need to deal with to extract the image are the following chunks: (Note - Also watch for AUTH Author chunks and (c) Copyright chunks and preserve any copyright information if you rewrite the ILBM) BMHD - info about the size, depth, compaction method (See interpreted hex dump above) CAMG - optional Amiga viewmodes chunk Most HAM and HALFBRITE ILBMs should have this chunk. If no CAMG chunk is present, and image is 6 planes deep, assume HAM and you'll probably be right. Some Amiga viewmodes flags are HIRES=0x8000, LACE=0x4, HAM=0x800, HALFBRITE=0x80. Note that new Amiga 2.0 ILBMs may have more complex 32-bit numbers (modeid) stored in the CAMG. However, the bits described above should get you a compatible old viewmode. CMAP - RGB values for color registers 0 to n (each component left justified in a byte) If a deep ILBM (like 12 or 24 planes), there should be no CMAP and instead the BODY planes are interpreted as the bits of RGB in the order R0...Rn G0...Gn B0...Bn BODY - The pixel data, stored in an interleaved fashion as follows: (each line individually compacted if BMHD Compression = 1) plane 0 scan line 0 plane 1 scan line 0 plane 2 scan line 0 ... plane n scan line 0 plane 0 scan line 1 plane 1 scan line 1 etc. Body Compression ================ The BODY contains pixel data for the image. Width, Height, and depth (Planes) is specified in the BMHD. If the BMHD Compression byte is 0, then the scan line data is not compressed. If Compression=1, then each scan line is individually compressed as follows: More than 2 bytes the same stored as BYTE code value n from -1 to -127 followed by byte to be repeated (-n) + 1 times. Varied bytes stored as BYTE code n from 0 to 127 followed by n+1 bytes of data. The byte code -128 is a NOP. Interpreting the Scan Line Data: ================================ If the ILBM is not HAM or HALFBRITE, then after parsing and uncompacting if necessary, you will have N planes of pixel data. Color register used for each pixel is specified by looking at each pixel thru the planes. I.e., if you have 5 planes, and the bit for a particular pixel is set in planes 0 and 3: PLANE 4 3 2 1 0 PIXEL 0 1 0 0 1 then that pixel uses color register binary 01001 = 9 The RGB value for each color register is stored in the CMAP chunk of the ILBM, starting with register 0, with each register's RGB value stored as one byte of R, one byte G, and one byte of B, with each component scaled to 8-bits. (ie. 4-bit Amiga R, G, and B components are each stored in the high nibble of a byte. The low nibble may also contain valid data if the color was stored with 8-bit-per-gun color resolution). BUT - if the picture is HAM or HALFBRITE, it is interpreted differently. === ========= Hopefully, if the picture is HAM or HALFBRITE, the package that saved it properly saved a CAMG chunk (look at a hex dump of your file with ACSII interpretation - you will see the chunks - they all start with a 4-ASCII- character chunk ID). If the picture is 6 planes deep and has no CAMG chunk, it is probably HAM. If you see a CAMG chunk, the "CAMG" is followed by the 32-bit chunk length, and then the 32-bit Amiga Viewmode flags. HAM pics with a 16-bit CAMG will have the 0x800 bit set in CAMG ViewModes. HALBRITE pics will have the 0x80 bit set. To transport a HAM or HALFBRITE picture to another machine, you must understand how HAM and HALFBRITE work on the Amiga. How Amiga HAM mode works: ========================= Amiga HAM (Hold and Modify) mode lets the Amiga display all 4096 RGB values. In HAM mode, the bits in the two last planes describe an R G or B modification to the color of the previous pixel on the line to create the color of the current pixel. So a 6-plane HAM picture has 4 planes for specifying absolute color pixels giving up to 16 absolute colors which would be specified in the ILBM CMAP chunk. The bits in the last two planes are color modification bits which cause the Amiga, in HAM mode, to take the RGB value of the previous pixel (Hold and), substitute the 4 bits in planes 0-3 for the previous color's R G or B component (Modify) and display the result for the current pixel. If the first pixel of a scan line is a modification pixel, it modifies the RGB value of the border color (register 0). The color modification bits in the last two planes (planes 4 and 5) are interpreted as follows: 00 - no modification. Use planes 0-3 as normal color register index 10 - hold previous, replacing Blue component with bits from planes 0-3 01 - hold previous, replacing Red component with bits from planes 0-3 11 - hold previous. replacing Green component with bits from planes 0-3 How Amiga HALFBRITE mode works: =============================== This one is simpler. In HALFBRITE mode, the Amiga interprets the bit in the last plane as HALFBRITE modification. The bits in the other planes are treated as normal color register numbers (RGB values for each color register is specified in the CMAP chunk). If the bit in the last plane is set (1), then that pixel is displayed at half brightness. This can provide up to 64 absolute colors. Other Notes: ============ Amiga ILBMs images must be a even number of bytes wide. Smaller images (such as brushes) are padded to an even byte width. ILBMs created with Electronic Arts IBM and Amiga "DPaintII" packages are compatible (though you may have to use a '.lbm' filename extension on an IBM). The ILBM graphic files may be transferred between the machines (or between the Amiga and IBM sides your Amiga if you have a CBM Bridgeboard card installed) and loaded into either package. Color Lookup Table chunk TITLE: CLUT IFF chunk proposal "CLUT" IFF 8-Bit Color Look Up Table Date:July 2, 1989 From:Justin V. McCormick Status:Public Proposal Supporting Software: FG 2.0 by Justin V. McCormick for PP&S Introduction: This memo describes the IFF supplement for the new chunk "CLUT". Description: A CLUT (Color Look Up Table) is a special purpose data module containing table with 256 8-bit entries. Entries in this table can be used directly as a translation for one 8-bit value to another. Purpose: To store 8-bit data look up tables in a simple format for later retrieval. These tables are used to translate or bias 8-bit intensity, contrast, saturation, hue, color registers, or other similar data in a reproducable manner. Specifications: /* Here is the IFF chunk ID macro for a CLUT chunk */ #define ID_CLUT MakeID('C','L','U','T') /* * Defines for different flavors of 8-bit CLUTs. */ #define CLUT_MONO0L/* A Monochrome, contrast or intensity LUT */ #define CLUT_RED1L/* A LUT for reds */ #define CLUT_GREEN2L/* A LUT for greens */ #define CLUT_BLUE3L/* A LUT for blues */ #define CLUT_HUE4L/* A LUT for hues */ #define CLUT_SAT5L/* A LUT for saturations */ #define CLUT_UNUSED66L/* How about a Signed Data flag */ #define CLUT_UNUSED77L/* Or an Assumed Negative flag*/ /* All types > 7 are reserved until formally claimed */ #define CLUT_RESERVED_BITS 0xfffffff8L /* The struct for Color Look-Up-Tables of all types */ typedef struct { ULONG type;/* See above type defines */ ULONG res0;/* RESERVED FOR FUTURE EXPANSION */ UBYTE lut[256];/* The 256 byte look up table */ } ColorLUT; CLUT Example: Normally, the CLUT chunk will appear after the BMHD of an FORM ILBM before the BODY chunk, in the same "section" as CMAPs are normally found. However, a FORM may contain only CLUTs with no other supporting information. As a general guideline, it is desirable to group all CLUTs together in a form without other chunk types between them. If you were using CLUTs to store RGB intensity corrections, you would write three CLUTs in a row, R, G, then B. Here is a box diagram for a 320x200x8 image stored as an IFF ILBM with a single CLUT chunk for intensity mapping: +-----------------------------------+ |'FORM'64284 | FORM 64284 ILBM +-----------------------------------+ |'ILBM' | +-----------------------------------+ | +-------------------------------+ | | | 'BMHD'20 | | .BMHD 20 | | 320, 200, 0, 0, 8, 0, 0, ... | | | | ------------------------------+ | | | 'CLUT'264 | | .CLUT 264 | | 0, 0, 0; 32, 0, 0; 64,0,0; .. | | | +-------------------------------+ | | +-------------------------------+ | | |'BODY'64000 | | .BODY 64000 | |0, 0, 0, ... | | | +-------------------------------+ | +-----------------------------------+ Design Notes: ------------- I have deliberately kept this chunk simple (KISS) to facilitate implementation. In particular, no provision is made for expansion to 16-bit or 32-bit tables. My reasoning is that a 16-bit table can have 64K entries, and thus would benefit from data compression. My suggestion would be to propose another chunk or FORM type better suited for large tables rather than small ones like CLUT. Newtek Dynamic Ham color chunks Newtek for Digiview IV (dynamic Ham) ILBM.DYCP - dynamic color palette 3 longwords (file setup stuff) ILBM.CTBL - array of words, one for each color (0rgb) Dots per inch chunk ILBM DPI chunk (1-16-90) ============== ILBM.DPI Dots Per Inch to allow output of an image at the same resolution it was scanned at typedef struct { UWORD dpi_x; UWORD dpi_y; } DPIHeader ; For example, an image scanned at horizontal resolution of 240dpi and vertical resolution of 300dpi would be saved as: 44504920 00000004 00F0 012C D P I size dpi_x dpi_y DPaint perspective chunk (EA) IFF FORM / CHUNK DESCRIPTION ============================ Form/Chunk ID: Chunk DPPV (DPaint II ILBM perspective chunk) Date Submitted: 12/86 Submitted by: Dan Silva Chunk Description: The DPPV chunk describes the perspective state in a DPaintII ILBM. Chunk Spec: /* The chunk identifier DPPV */ #define ID_DPPV MakeID('D','P','P','V') typedef LONG LongFrac; typedef struct ( LongFrac x,y,z; ) LFPoint; typedef LongFrac APoint[3]; typedef union { LFPoint l; APoint a; } UPoint; /* values taken by variable rotType */ #define ROT_EULER 0 #define ROT_INCR 1 /* Disk record describing Perspective state */ typedef struct { WORD rotType; /* rotation type */ WORD iA, iB, iC; /* rotation angles (in degrees) */ LongFrac Depth; /* perspective depth */ WORD uCenter, vCenter; /* coords of center perspective, * relative to backing bitmap, * in Virtual coords */ WORD fixCoord; /* which coordinate is fixed */ WORD angleStep; /* large angle stepping amount */ UPoint grid; /* gridding spacing in X,Y,Z */ UPoint gridReset; /* where the grid goes on Reset */ UPoint gridBrCenter; /* Brush center when grid was last on, * as reference point */ UPoint permBrCenter; /* Brush center the last time the mouse * button was clicked, a rotation performed, * or motion along "fixed" axis */ LongFrac rot[3][3]; /* rotation matrix */ } PerspState; SUPPORTING SOFTWARE =================== DPaint II by Dan Silva for Electronic Arts DPaint IV enhanced color cycle chunk (EA) DRNG Chunk for FORM ILBM ======================== Submitted by Lee Taran Purpose: Enhanced Color Cycling Capabilities ------------------------------------- * DPaintIV supports a new color cycling model which does NOT require that color cycles contain a contiguous range of color registers. For example: If your range looks like: [1][3][8][2] then at each cycle tick temp = [2], [2] = [8], [8] = [3], [3] = [1], [1] = temp * You can now cycle a single register thru a series of rgb values. For example: If your range looks like: [1] [orange] [blue] [purple] then at each cycle tick color register 1 will take on the next color in the cycle. ie: t=0: [1] = curpal[1] t=1: [1] = purple t=2: [1] = blue t=3: [1] = orange t=4: goto t=0 * You can combine rgb cycling with traditional color cycling. For example: Your range can look like: [1] [orange] [blue] [2] [green] [yellow] t=0: [1] = curpal[1], [2] = curpal[2] t=1: [1] = yellow, [2] = blue t=2: [1] = green, [2] = orange t=3: [1] = curpal[2], [2] = curpal[1] t=4: [1] = blue, [2] = yellow t=5: [1] = orange, [2] = green t=6: goto t=0 Note: * DPaint will save out an old style range CRNG if the range fits the CRNG model otherwise it will save out a DRNG chunk. * no thought has been given (yet) to interlocking cycles /* --------------------------------------------------------------------- IFF Information: DPaintIV DRNG chunk DRNG ::= "DRNG" # { DRange DColor* DIndex* } a <cell> is where the color or register appears within the range The RNG_ACTIVE flags is set when the range is cyclable. A range should only have the RNG_ACTIVE if it: 1> contains at least one color register 2> has a defined rate 3> has more than one color and/or color register If the above conditions are met then RNG_ACTIVE is a user/program preference. If the bit is NOT set the program should NOT cycle the range. The RNG_DP_RESERVED flags should always be 0!!! --------------------------------------------------------------------- */ typedef struct { UBYTE min; /* min cell value */ UBYTE max; /* max cell value */ SHORT rate; /* color cycling rate, 16384 = 60 steps/second */ SHORT flags; /* 1=RNG_ACTIVE,4=RNG_DP_RESERVED */ UBYTE ntrue; /* number of DColor structs to follow */ UBYTE nregs; /* number of DIndex structs to follow */ } DRange; typedef struct { UBYTE cell; UBYTE r,g,b; } DColor; /* true color cell */ typedef struct { UBYTE cell; UBYTE index; } DIndex; /* color register cell */ Encapsulated Postscript chunk ILBM EPSF Chunk =============== Pixelations Kevin Saltzman 617-277-5414 Chunk to hold encapsulated postscript Used by PixelScript in their clip art. Holds a postscript representation of the ILBM's graphic image. EPSF length ; Bounding box WORD lowerleftx; WORD lowerlefty; WORD upperrightx; WORD upperrighty; CHAR [] ; ascii postscript file RGB image forms, Turbo Silver (Impulse) FORM RGBN and FORM RGB8 ----------------------- RGBN and RGB8 files are used in Impulse's Turbo Silver and Imagine. They are almost identical to FORM ILBM's except for the BODY chunk and slight differences in the BMHD chunk. A CAMG chunk IS REQUIRED. The BMHD chunk specfies the number of bitplanes as 13 for type RGBN and 25 for type RGB8, and the compression type as 4. The FORM RGBN uses 12 bit RGB values, and the FORM RGB8 uses 24 bit RGB values. The BODY chunk contains RGB values, a "genlock" bit, and repeat counts. In Silver, when "genlock" bit is set, a "zero color" is written into the bitplanes for genlock video to show through. In Diamond and Light24 (Impulse 12 & 24 bit paint programs), the genlock bit is ignored if the file is loaded as a picture (and the RGB color is used instead), and if the file is loaded as a brush the genlock bit marks pixels that are not part of the brush. For both RGBN and RGB8 body chunks, each RGB value always has a repeat count. The values are written in different formats depending on the magnitude of the repeat count. For the RGBN BODY chunk: For each RGB value, a WORD (16-bits) is written: with the 12 RGB bits in the MSB (most significant bit) positions; the "genlock" bit next; and then a 3 bit repeat count. If the repeat count is greater than 7, the 3-bit count is zero, and a BYTE repeat count follows. If the repeat count is greater than 255, the BYTE count is zero, and a WORD repeat count follows. Repeat counts greater than 65536 are not supported. For the RGB8 body chunk: For each RGB value, a LONG-word (32 bits) is written: with the 24 RGB bits in the MSB positions; the "genlock" bit next, and then a 7 bit repeat count. In a previous version of this document, there appeared the following line: "If the repeat count is greater than 127, the same rules apply as in the RGBN BODY." But Impulse has never written more than a 7 bit repeat count, and when Imagine and Light24 were written, they didn't support reading anything but 7 bit counts. Sample BODY code: if(!count) { if (Rgb8) { fread (&w, 4, 1, RGBFile); lock = w & 0x00000080; rgb = w >> 8; count = w & 0x0000007f; } else { w = (UWORD) getw (RGBFile); lock = w & 8; rgb = w >> 4; count = w & 7; } if (!count) if (!(count = (UBYTE) getc (RGBFile))) count = (UWORD) getw (RGBFile); } The pixels are scanned from left to right across horizontal lines, processing from top to bottom. The (12 or 24 bit) RGB values are stored with the red bits as the MSB's, the green bits next, and the blue bits as the LSB's. Special note: As of this writing (Sep 88), Silver does NOT support anything but black for color zero. Cel animation form A N I M An IFF Format For CEL Animations Revision date: 4 May 1988 prepared by: SPARTA Inc. 23041 de la Carlota Laguna Hills, Calif 92653 (714) 768-8161 contact: Gary Bonham also by: Aegis Development Co. 2115 Pico Blvd. Santa Monica, Calif 90405 213) 392-9972 1.0 Introduction The ANIM IFF format was developed at Sparta originally for the production of animated video sequences on the Amiga computer. The intent was to be able to store, and play back, sequences of frames and to minimize both the storage space on disk (through compression) and playback time (through efficient de-compression algorithms). It was desired to maintain maximum compatibility with existing IFF formats and to be able to display the initial frame as a normal still IFF picture. Several compression schemes have been introduced in the ANIM format. Most of these are strictly of historical interest as the only one currently being placed in new code is the vertical run length encoded byte encoding developed by Jim Kent. 1.1 ANIM Format Overview The general philosophy of ANIMs is to present the initial frame as a normal, run-length-encoded, IFF picture. Subsequent frames are then described by listing only their differences from a previous frame. Normally, the "previous" frame is two frames back as that is the frame remaining in the hidden screen buffer when double-buffering is used. To better understand this, suppose one has two screens, called A and B, and the ability to instantly switch the display from one to the other. The normal playback mode is to load the initial frame into A and duplicate it into B. Then frame A is displayed on the screen. Then the differences for frame 2 are used to alter screen B and it is displayed. Then the differences for frame 3 are used to alter screen A and it is displayed, and so on. Note that frame 2 is stored as differences from frame 1, but all other frames are stored as differences from two frames back. ANIM is an IFF FORM and its basic format is as follows (this assumes the reader has a basic understanding of IFF format files): FORM ANIM . FORM ILBM first frame . . BMHD normal type IFF data . . ANHD optional animation header chunk for timing of 1st frame. . . CMAP . . BODY . FORM ILBM frame 2 . . ANHD animation header chunk . . DLTA delta mode data . FORM ILBM frame 3 . . ANHD . . DLTA ... The initial FORM ILBM can contain all the normal ILBM chunks, such as CRNG, etc. The BODY will normally be a standard run-length-encoded data chunk (but may be any other legal compression mode as indicated by the BMHD). If desired, an ANHD chunk can appear here to provide timing data for the first frame. If it is here, the operation field should be =0. The subsequent FORMs ILBM contain an ANHD, instead of a BMHD, which duplicates some of BMHD and has additional parameters pertaining to the animation frame. The DLTA chunk contains the data for the delta compression modes. If the older XOR compression mode is used, then a BODY chunk will be here. In addition, other chunks may be placed in each of these as deemed necessary (and as code is placed in player programs to utilize them). A good example would be CMAP chunks to alter the color palette. A basic assumption in ANIMs is that the size of the bitmap, and the display mode (e.g. HAM) will not change through the animation. Take care when playing an ANIM that if a CMAP occurs with a frame, then the change must be applied to both buffers. Note that the DLTA chunks are not interleaved bitmap representations, thus the use of the ILBM form is inappropriate for these frames. However, this inconsistency was not noted until there were a number of commercial products either released or close to release which generated/played this format. Therefore, this is probably an inconsistency which will have to stay with us. 1.2 Recording ANIMs To record an ANIM will require three bitmaps - one for creation of the next frame, and two more for a "history" of the previous two frames for performing the compression calculations (e.g. the delta mode calculations). There are five frame-to-frame compression methods currently defined. The first three are mainly for historical interest. The product Aegis VideoScape 3D utilizes the third method in version 1.0, but switched to method 5 on 2.0. This is the only instance known of a commercial product generating ANIMs of any of the first three methods. The fourth method is a general short or long word compression scheme which has several options including whether the compression is horizontal or vertical, and whether or not it is XOR format. This offers a choice to the user for the optimization of file size and/or playback speed. The fifth method is the byte vertical run length encoding as designed by Jim Kent. Do not confuse this with Jim's RIFF file format which is different than ANIM. Here we utilized his compression/ decompression routines within the ANIM file structure. The following paragraphs give a general outline of each of the methods of compression currently included in this spec. 1.2.1 XOR mode This mode is the original and is included here for historical interest. In general, the delta modes are far superior. The creation of XOR mode is quite simple. One simply performs an exclusive-or (XOR) between all corresponding bytes of the new frame and two frames back. This results in a new bitmap with 0 bits wherever the two frames were identical, and 1 bits where they are different. Then this new bitmap is saved using run-length-encoding. A major obstacle of this mode is in the time consumed in performing the XOR upon reconstructing the image. 1.2.2 Long Delta mode This mode stores the actual new frame long-words which are different, along with the offset in the bitmap. The exact format is shown and discussed in section 2 below. Each plane is handled separately, with no data being saved if no changes take place in a given plane. Strings of 2 or more long-words in a row which change can be run together so offsets do not have to be saved for each one. Constructing this data chunk usually consists of having a buffer to hold the data, and calculating the data as one compares the new frame, long-word by long-word, with two frames back. 1.2.3 Short Delta mode This mode is identical to the Long Delta mode except that short-words are saved instead of long-words. In most instances, this mode results in a smaller DLTA chunk. The Long Delta mode is mainly of interest in improving the playback speed when used on a 32-bit 68020 Turbo Amiga. 1.2.4 General Delta mode The above two delta compression modes were hastily put together. This mode was an attempt to provide a well-thought-out delta compression scheme. Options provide for both short and long word compression, either vertical or horizontal compression, XOR mode (which permits reverse playback), etc. About the time this was being finalized, the fifth mode, below, was developed by Jim Kent. In practice the short-vertical-run-length-encoded deltas in this mode play back faster than the fifth mode (which is in essence a byte-vertical-run-length-encoded delta mode) but does not compress as well - especially for very noisy data such as digitized images. In most cases, playback speed not being terrifically slower, the better compression (sometimes 2x) is preferable due to limited storage media in most machines. Details on this method are contained in section 2.2.2 below. 1.2.5 Byte Vertical Compression This method does not offer the many options that method 4 offers, but is very successful at producing decent compression even for very noisy data such as digitized images. The method was devised by Jim Kent and is utilized in his RIFF file format which is different than the ANIM format. The description of this method in this document is taken from Jim's writings. Further, he has released both compression and decompression code to public domain. Details on this method are contained in section 2.2.3 below. 1.3 Playing ANIMs Playback of ANIMs will usually require two buffers, as mentioned above, and double-buffering between them. The frame data from the ANIM file is used to modify the hidden frame to the next frame to be shown. When using the XOR mode, the usual run- length-decoding routine can be easily modified to do the exclusive-or operation required. Note that runs of zero bytes, which will be very common, can be ignored, as an exclusive or of any byte value to a byte of zero will not alter the original byte value. The general procedure, for all compression techniques, is to first decode the initial ILBM picture into the hidden buffer and double- buffer it into view. Then this picture is copied to the other (now hidden) buffer. At this point each frame is displayed with the same procedure. The next frame is formed in the hidden buffer by applying the DLTA data (or the XOR data from the BODY chunk in the case of the first XOR method) and the new frame is double-buffered into view. This process continues to the end of the file. A master colormap should be kept for the entire ANIM which would be initially set from the CMAP chunk in the initial ILBM. This colormap should be used for each frame. If a CMAP chunk appears in one of the frames, then this master colormap is updated and the new colormap applies to all frames until the occurrance of another CMAP chunk. Looping ANIMs may be constructed by simply making the last two frames identical to the first two. Since the first two frames are special cases (the first being a normal ILBM and the second being a delta from the first) one can continually loop the anim by repeating from frame three. In this case the delta for creating frame three will modify the next to the last frame which is in the hidden buffer (which is identical to the first frame), and the delta for creating frame four will modify the last frame which is identical to the second frame. Multi-File ANIMs are also supported so long as the first two frames of a subsequent file are identical to the last two frames of the preceeding file. Upon reading subsequent files, the ILBMs for the first two frames are simply ignored, and the remaining frames are simply appended to the preceeding frames. This permits splitting ANIMs across multiple floppies and also permits playing each section independently and/or editing it independent of the rest of the ANIM. Timing of ANIM playback is easily achieved using the vertical blank interrupt of the Amiga. There is an example of setting up such a timer in the ROM Kernel Manual. Be sure to remember the timer value when a frame is flipped up, so the next frame can be flipped up relative to that time. This will make the playback independent of how long it takes to decompress a frame (so long as there is enough time between frames to accomplish this decompression). 2.0 Chunk Formats 2.1 ANHD Chunk The ANHD chunk consists of the following data structure: UBYTE operation The compression method: =0 set directly (normal ILBM BODY), =1 XOR ILBM mode, =2 Long Delta mode, =3 Short Delta mode, =4 Generalized short/long Delta mode, =5 Byte Vertical Delta mode =6 Stereo op 5 (third party) =74 (ascii 'J') reserved for Eric Graham's compression technique (details to be released later). UBYTE mask (XOR mode only - plane mask where each bit is set =1 if there is data and =0 if not.) UWORD w,h (XOR mode only - width and height of the area represented by the BODY to eliminate unnecessary un-changed data) WORD x,y (XOR mode only - position of rectangular area representd by the BODY) ULONG abstime (currently unused - timing for a frame relative to the time the first frame was displayed - in jiffies (1/60 sec)) ULONG reltime (timing for frame relative to time previous frame was displayed - in jiffies (1/60 sec)) UBYTE interleave (unused so far - indicates how may frames back this data is to modify. =0 defaults to indicate two frames back (for double buffering). =n indicates n frames back. The main intent here is to allow values of =1 for special applications where frame data would modify the immediately previous frame) UBYTE pad0 Pad byte, not used at present. ULONG bits 32 option bits used by options=4 and 5. At present only 6 are identified, but the rest are set =0 so they can be used to implement future ideas. These are defined for option 4 only at this point. It is recommended that all bits be set =0 for option 5 and that any bit settings used in the future (such as for XOR mode) be compatible with the option 4 bit settings. Player code should check undefined bits in options 4 and 5 to assure they are zero. The six bits for current use are: bit # set =0 set =1 =============================================== 0 short data long data 1 set XOR 2 separate info one info list for each plane for all planes 3 not RLC RLC (run length coded) 4 horizontal vertical 5 short info offsets long info offsets UBYTE pad[16] This is a pad for future use for future compression modes. 2.2 DLTA Chunk This chunk is the basic data chunk used to hold delta compression data. The format of the data will be dependent upon the exact compression format selected. At present there are two basic formats for the overall structure of this chunk. 2.2.1 Format for methods 2 & 3 This chunk is a basic data chunk used to hold the delta compression data. The minimum size of this chunk is 32 bytes as the first 8 long-words are byte pointers into the chunk for the data for each of up to 8 bitplanes. The pointer for the plane data starting immediately following these 8 pointers will have a value of 32 as the data starts in the 33-rd byte of the chunk (index value of 32 due to zero-base indexing). The data for a given plane consists of groups of data words. In Long Delta mode, these groups consist of both short and long words - short words for offsets and numbers, and long words for the actual data. In Short Delta mode, the groups are identical except data words are also shorts so all data is short words. Each group consists of a starting word which is an offset. If the offset is positive then it indicates the increment in long or short words (whichever is appropriate) through the bitplane. In other words, if you were reconstructing the plane, you would start a pointer (to shorts or longs depending on the mode) to point to the first word of the bitplane. Then the offset would be added to it and the following data word would be placed at that position. Then the next offset would be added to the pointer and the following data word would be placed at that position. And so on... The data terminates with an offset equal to 0xFFFF. A second interpretation is given if the offset is negative. In that case, the absolute value is the offset+2. Then the following short-word indicates the number of data words that follow. Following that is the indicated number of contiguous data words (longs or shorts depending on mode) which are to be placed in contiguous locations of the bitplane. If there are no changed words in a given plane, then the pointer in the first 32 bytes of the chunk is =0. 2.2.2 Format for method 4 The DLTA chunk is modified slightly to have 16 long pointers at the start. The first 8 are as before - pointers to the start of the data for each of the bitplanes (up to a theoretical max of 8 planes). The next 8 are pointers to the start of the offset/numbers data list. If there is only one list of offset/numbers for all planes, then the pointer to that list is repeated in all positions so the playback code need not even be aware of it. In fact, one could get fancy and have some bitplanes share lists while others have different lists, or no lists (the problems in these schemes lie in the generation, not in the playback). The best way to show the use of this format is in a sample playback routine. SetDLTAshort(bm,deltaword) struct BitMap *bm; WORD *deltaword; { int i; LONG *deltadata; WORD *ptr,*planeptr; register int s,size,nw; register WORD *data,*dest; deltadata = (LONG *)deltaword; nw = bm->BytesPerRow >>1; for (i=0;i<bm->Depth;i++) { planeptr = (WORD *)(bm->Planes[i]); data = deltaword + deltadata[i]; ptr = deltaword + deltadata[i+8]; while (*ptr != 0xFFFF) { dest = planeptr + *ptr++; size = *ptr++; if (size < 0) { for (s=size;s<0;s++) { *dest = *data; dest += nw; } data++; } else { for (s=0;s<size;s++) { *dest = *data++; dest += nw; } } } } return(0); } The above routine is for short word vertical compression with run length compression. The most efficient way to support the various options is to replicate this routine and make alterations for, say, long word or XOR. The variable nw indicates the number of words to skip to go down the vertical column. This one routine could easily handle horizontal compression by simply setting nw=1. For ultimate playback speed, the core, at least, of this routine should be coded in assembly language. 2.2.2 Format for method 5 In this method the same 16 pointers are used as in option 4. The first 8 are pointers to the data for up to 8 planes. The second set of 8 are not used but were retained for several reasons. First to be somewhat compatible with code for option 4 (although this has not proven to be of any benefit) and second, to allow extending the format for more bitplanes (code has been written for up to 12 planes). Compression/decompression is performed on a plane-by-plane basis. For each plane, compression can be handled by the skip.c code (provided Public Domain by Jim Kent) and decompression can be handled by unvscomp.asm (also provided Public Domain by Jim Kent). Compression/decompression is performed on a plane-by-plane basis. The following description of the method is taken directly from Jim Kent's code with minor re-wording. Please refer to Jim's code (skip.c and unvscomp.asm) for more details: Each column of the bitplane is compressed separately. A 320x200 bitplane would have 40 columns of 200 bytes each. Each column starts with 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. Do bear in mind that the data is compressed vertically rather than horizontally, so to get to the next byte in the destination we add the number of bytes per row instead of one! ANIM brush format Dpaint Anim Brush IFF Format From a description by the author of DPaint, Dan Silva, Electronic Arts The "Anim Brushes" of DPaint III are saved on disk in the IFF "ANIM" format. Basically, an ANIM Form consists of an initial ILBM which is the first frame of the animation, and any number of subsequent "ILBM"S (which aren't really ILBM's) each of which contains an ANHD animation header chunk and a DLTA chunk comprised of the encoded difference between a frame and a previous one. To use ANIM terminology (for a description of the ANIM format, see the IFF Anim Spec, by Gary Bonham). Anim Brushes use a "type 5" encoding, which is a vertical, byte-oriented delta encoding (based on Jim Kent's RIFF). The deltas have an interleave of 1, meaning deltas are computed between adjacent frames, rather than between frames 2 apart, which is the usual ANIM custom for the purpose of fast hardware page-flipping. Also, the deltas use Exclusive Or to allow reversable play. However, to my knowledge, all the existing Anim players in the Amiga world will only play type 5 "Anim"s which have an interleave of 0 (i.e. 2) and which use a Store operation rather than Exclusive Or, so no existing programs will read Anim Brushes anyway. The job of modifying existing Anim readers to read Anim Brushes should be simplified, however. Here is an outline of the structure of the IFF Form output by DPaint III as an "Anim Brush". The IFF Reader should of course be flexible enough to tolerate variation in what chunks actually appear in the initial ILBM. FORM ANIM . FORM ILBM first frame . . BMHD . . CMAP . . DPPS . . GRAB . . CRNG . . CRNG . . CRNG . . CRNG . . CRNG . . CRNG . . DPAN my own little chunk. . . CAMG . . BODY . FORM ILBM frame 2 . . ANHD animation header chunk . . DLTA delta mode data . FORM ILBM frame 3 . . ANHD animation header chunk . . DLTA delta mode data . FORM ILBM frame 4 . . ANHD animation header chunk . . DLTA delta mode data ... . FORM ILBM frame N . . ANHD animation header chunk . . DLTA delta mode data --- Here is the format of the DPAN chunk: typedef struct { UWORD version; /* current version=4 */ UWORD nframes; /* number of frames in the animation.*/ ULONG flags; /* Not used */ } DPAnimChunk; The version number was necessary during development. At present all I look at is "nframes". --- Here is the ANHD chunk format: typedef struct { UBYTE operation; /* =0 set directly =1 XOR ILBM mode, =2 Long Delta mode, =3 Short Delta mode =4 Generalize short/long Delta mode, =5 Byte Vertical Delta (riff) =74 (Eric Grahams compression mode) */ UBYTE mask; /* XOR ILBM only: plane mask where data is*/ UWORD w,h; WORD x,y; ULONG abstime; ULONG reltime; UBYTE interleave; /* 0 defaults to 2 */ UBYTE pad0; /* not used */ ULONG bits; /* meaning of bits: bit# =0 =1 0 short data long data 1 store XOR 2 separate info one info for for each plane for all planes 3 not RLC RLC (run length encoded) 4 horizontal vertical 5 short info offsets long info offsets -------------------------*/ UBYTE pad[16]; } AnimHdr; for Anim Brushes, I set: animHdr.operation = 5; /* RIFF encoding */ animHdr.interleave = 1; animHdr.w = curAnimBr.bmob.pict.box.w; animHdr.h = curAnimBr.bmob.pict.box.h; animHdr.reltime = 1; animHdr.abstime = 0; animHdr.bits = 4; /* indicating XOR */ -- everything else is set to 0. NOTE: the "bits" field was actually intended ( by the original creator of the ANIM format, Gary Bonham of SPARTA, Inc.) for use with only with compression method 4. I am using bit 2 of the bits field to indicate the Exclusive OR operation in the context of method 5, which seems like a reasonable generalization. For an Anim Brush with 10 frames, there will be an initial frame followed by 10 Delta's (i.e ILBMS containing ANHD and DLTA chunks). Applying the first Delta to the initial frame generates the second frame, applying the second Delta to the second frame generates the third frame, etc. Applying the last Delta thus brings back the first frame. The DLTA chunk begins with 16 LONG plane offets, of which DPaint only uses the first 6 (at most). These plane offsets are either the offset (in bytes) from the beginning of the DLTA chunk to the data for the corresponding plane, or Zero, if there was no change in that plane. Thus the first plane offset is either 0 or 64. (The following description of the method is based on Gary Bonham's rewording of Jim Kent's RIFF documentation.) Compression/decompression is performed on a plane-by-plane basis. Each byte-column of the bitplane is compressed separately. A 320x200 bitplane would have 40 columns of 200 bytes each. In general, the bitplanes are always an even number of bytes wide, so for instance a 17x20 bitplane would have 4 columns of 20 bytes each. Each column starts with 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 kinds, and followed by a varying amount of data depending on which kind: 1. SKIP - 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. DUMP - this is a byte with the hi bit set. The hi bit is masked off and the remainder is a count of the number of bytes of data to XOR directly. It is followed by the bytes to copy. 3. RUN - this is a 0 byte followed by a count byte, followed by a byte value to repeat "count" times, XOR'ing it into the destination. Bear in mind that the data is compressed vertically rather than horizontally, so to get to the next byte in the destination you add the number of bytes per row instead of one. The Format of DLTA chunks is as described in section 2.2.2 of the Anim Spec. The encoding for type 5 is described in section 2.2.3 of the Anim Spec. 2-D Object standard format FORM DR2D Description by Ross Cunniff and John Orr A standard IFF FORM to describe 2D drawings has been sorely needed for a long time. Several commercial drawing packages have been available for some time but none has established its file format as the Amiga standard. The absence of a 2D drawing standard hinders the development of applications that use 2D drawings as it forces each application to understand several private standards instead of a single one. Without a standard, data exchange for both the developer and the user is difficult, if not impossible. The DR2D FORM fills this void. This FORM was developed by Taliesin, Inc. for use as the native file format for their two-dimensional structured drawing package, ProVector. Saxon Industries and Soft Logik Publishing Corporation are planning to support this new FORM in the near future. Many of the values stored in the DR2D FORM are stored as IEEE single precision floating point numbers. These numbers consist of 32 bits, arranged as follows: _______________________________________________________________________ | s e e e e e e e | e m m m m m m m | m m m m m m m m | m m m m m m m m | ----------------------------------------------------------------------- 31 24 23 16 15 8 7 0 where: sis the sign of the number where 1 is negative and 0 is positive. eis the 8 bit exponent in excess 127 form. This number is the power of two to which the mantissa is raised (Excess 127 form means that 127 is added to the exponent before packing it into the IEEE number.) mis the 23 bit mantissa. It ranges from 1.0000000 to 1.999999..., where the leading base-ten one is assumed. An IEEE single precision with the value of 0.0000000 has all its bits cleared. The DR2D Chunks FORM (0x464F524D) /* All drawings are a FORM */ struct FORMstruct { ULONGID; /* DR2D */ ULONGSize; }; DR2D (0x44523244) /* ID of 2D drawing */ The DR2D chunks are broken up into three groups: the global drawing attribute chunks, the object attribute chunks, and the object chunks. The global drawing attribute chunks describe elements of a 2D drawing that are common to many objects in the drawing. Document preferences, palette information, and custom fill patterns are typical document-wide settings defined in global drawing attribute chunks. The object attribute chunks are used to set certain properties of the object chunk(s) that follows the object attribute chunk. The current fill pattern, dash pattern, and line color are all set using an object attribute chunk. Object chunks describe the actual DR2D drawing. Polygons, text, and bitmaps are found in these chunks. The Global Drawing Attribute Chunks The following chunks describe global attributes of a DR2D document. DRHD (0x44524844) /* Drawing header */ The DRHD chunk contains the upper left and lower right extremes of the document in (X, Y) coordinates. This chunk is required and should only appear once in a document in the outermost layer of the DR2D file (DR2Ds can be nested). struct DRHDstruct { ULONGID; ULONGSize; /* Always 16 */ IEEEXLeft, YTop, XRight, YBot; }; The point (XLeft,YTop) is the upper left corner of the project and the point (XRight,YBot) is its lower right corner. These coordinates not only supply the size and position of the document in a coordinate system, they also supply the project's orientation. If XLeft < XRight, the X-axis increases toward the right. If YTop < YBot, the Y-axis increases toward the bottom. Other combinations are possible; for example in Cartesian coordinates, XLeft would be less than XRight but YTop would be greater than YBot. PPRF (0x50505249) /* Page preferences */ The PPRF chunk contains preference settings for ProVector. Although this chunk is not required, its use is encouraged because it contains some important environment information. struct PPRFstruct { ULONGID; ULONGSize; charPrefs[Size]; }; DR2D stores preferences as a concatenation of several null-terminated strings, in the Prefs[] array. The strings can appear in any order. The currently supported strings are: Units=<unit-type> Portrait=<boolean> PageType=<page-type> GridSize=<number> where: <unit-type> is either Inch, Cm, or Pica <boolean> is either True or False <page-type> is either Standard, Legal, B4, B5, A3, A4, A5, or Custom <number> is a floating-point number The DR2D FORM does not require this chunk to explicitly state all the possible preferences. In the absence of any particular preference string, a DR2D reader should fall back on the default value. The defaults are: Units=Inch Portrait=True PageType=Standard GridSize=1.0 CMAP (0x434D4150) /* Color map (Same as ILBM CMAP) */ This chunk is identical to the ILBM CMAP chunk as described in the IFF ILBM documentation. struct CMAPstruct { ULONGID; ULONGSize; UBYTEColorMap[Size]; }; ColorMap is an array of 24-bit RGB color values. The 24-bit value is spread across three bytes, the first of which contains the red intensity, the next contains the green intensity, and the third contains the blue intensity. Because DR2D stores its colors with 24-bit accuracy, DR2D readers must not make the mistake that some ILBM readers do in assuming the CMAP chunk colors correspond directly to Amiga color registers. FONS (0x464F4E53) /* Font chunk (Same as FTXT FONS chunk) */ The FONS chunk contains information about a font used in the DR2D FORM. ProVector does not include support for Amiga fonts. Instead, ProVector uses fonts defined in the OFNT FORM which is documented later in this article. struct FONSstruct { ULONGID; ULONGSize; UBYTEFontID;/* ID the font is referenced by */ UBYTEPad1; /* Always 0 */ UBYTEProportional;/* Is it proportional? */ UBYTESerif;/* does it have serifs? */ CHARName[Size-4];/* The name of the font */ }; The UBYTE FontID field is the number DR2D assigns to this font. References to this font by other DR2D chunks are made using this number. The Proportional and Serif fields indicate properties of this font. Specifically, Proportional indicates if this font is proportional, and Serif indicates if this font has serifs. These two options were created to allow for font substitution in case the specified font is not available. They are set according to these values: 0The DR2D writer didn't know if this font is proportional/has serifs. 1No, this font is not proportional/does not have serifs. 2Yes, this font is proportional/does have serifs. The last field, Name[], is a NULL terminated string containing the name of the font. DASH (0x44415348) /* Line dash pattern for edges */ This chunk describes the on-off dash pattern associated with a line. struct DASHstruct { ULONGID; ULONGSize; USHORTDashID;/* ID of the dash pattern */ USHORTNumDashes; /* Should always be even */ IEEEDashes[NumDashes]; /* On-off pattern */ }; DashID is the number assigned to this specific dash pattern. References to this dash pattern by other DR2D chunks are made using this number. The Dashes[] array contains the actual dash pattern. The first number in the array (element 0) is the length of the ``on'' portion of the pattern. The second number (element 1) specifies the ``off'' portion of the pattern. If there are more entries in the Dashes array, the pattern will continue. Even-index elements specify the length of an ``on'' span, while odd-index elements specify the length of an ``off'' span. There must be an even number of entries. These lengths are not in the same units as specified in the PPRF chunk, but are multiples of the line width, so a line of width 2.5 and a dash pattern of 1.0, 2.0 would have an ``on'' span of length 1.0 x 2.5 = 2.5 followed by an ``off'' span of length 2.0 x 2.5 = 5. The following figure shows several dash pattern examples. Notice that for lines longer than the dash pattern, the pattern repeats. [figure 1 - dash patterns] By convention, DashID 0 is reserved to mean `No line pattern at all', i.e. the edges are invisible. This DASH pattern should not be defined by a DR2D DASH chunk. Again by convention, a NumDashes of 0 means that the line is solid. AROW (0x41524F57) /* An arrow-head pattern */ The AROW chunk describes an arrowhead pattern. DR2D open polygons (OPLY) can have arrowheads attached to their endpoints. See the description of the OPLY chunk later in this article for more information on the OPLY chunk. #define ARROW_FIRST 0x01 /* Draw an arrow on the OPLY's first point */ #define ARROW_LAST 0x02 /* Draw an arrow on the OPLY's last point */ struct AROWstruct { ULONGID; ULONGSize; UBYTEFlags; /* Flags, from ARROW_*, above */ UBYTEPad0;/* Should always 0 */ USHORTArrowID;/* Name of the arrow head */ USHORTNumPoints; IEEEArrowPoints[NumPoints*2]; }; The Flags field specifies which end(s) of an OPLY to place an arrowhead based on the #defines above. ArrowID is the number by which an OPLY will reference this arrowhead pattern. The coordinates in the array ArrowPoints[] define the arrowhead's shape. These points form a closed polygon. See the section on the OPLY/CPLY object chunks for a descriptionof how DR2D defines shapes. The arrowhead is drawn in the same coordinate system relative to the endpoint of the OPLY the arrowhead is attached to. The arrowhead's origin (0,0) coincides with the OPLY's endpoint. DR2D assumes that the arrowhead represented in the AROW chunk is pointing to the right so the proper rotation can be applied to the arrowhead. The arrow is filled according to the current fill pattern set in the ATTR object attribute chunk. FILL (0x46494C4C) /* Object-oriented fill pattern */ The FILL chunk defines a fill pattern. This chunk is only valid inside nested DR2D FORMs. The GRUP object chunk section of this article contans an example of the FILL chunk. struct FILLstruct { ULONGID; ULONGSize; USHORTFillID; /* ID of the fill */ }; FillID is the number by which the ATTR object attribute chunk references fill patterns. The FILL chunk must be the first chunk inside a nested DR2D FORM. A FILL is followed by one DR2D object plus any of the object attribute chunks (ATTR, BBOX) associated with the object. [Figure 2 - fill patterns] DR2D makes a ``tile'' out of the fill pattern, giving it a virtual bounding box based on the extreme X and Y values of the FILL's object (Fig. A). The bounding box shown in Fig. A surrounding the pattern (the two ellipses) is invisible to the user. In concept, this rectangle is pasted on the page left to right, top to bottom like floor tiles (Fig. B). Again, the bounding boxes are not visible. The only portion of this tiled pattern that is visible is the part that overlaps the object (Fig. C) being filled. The object's path is called a clipping path, as it ``clips'' its shape from the tiled pattern (Fig. D). Note that the fill is only masked on top of underlying objects, so any ``holes'' in the pattern will act as a window, leaving visible underlying objects. LAYR (0x4C415952) /* Define a layer */ A DR2D project is broken up into one or more layers. Each DR2D object is in one of these layers. Layers provide several useful features. Any particular layer can be ``turned off'', so that the objects in the layer are not displayed. This eliminates the unnecessary display of objects not currently needed on the screen. Also, the user can lock a layer to protect the layer's objects from accidental changes. struct LAYRstruct { ULONG ID; ULONG Size; USHORT LayerID; /* ID of the layer */ char LayerName[16]; /* Null terminated and padded */ UBYTE Flags; /* Flags, from LF_*, below */ UBYTE Pad0; /* Always 0 */ }; LayerID is the number assigned to this layer. As the field's name indicates, LayerName[] is the NULL terminated name of the layer. Flags is a bit field who's bits are set according to the #defines below: #define LF_ACTIVE 0x01 /* Active for editing */ #define LF_DISPLAYED 0x02 /* Displayed on the screen */ If the LF_ACTIVE bit is set, this layer is unlocked. A set LF_DISPLAYED bit indicates that this layer is currently visible on the screen. A cleared LF_DISPLAYED bit implies that LF_ACTIVE is not set. The reason for this is to keep the user from accidentally editing layers that are invisible. The Object Attribute Chunks ATTR (0x41545452) /* Object attributes */ The ATTR chunk sets various attributes for the objects that follow it. The attributes stay in effect until the next ATTR changes the attributes, or the enclosing FORM ends, whichever comes first. /* Various fill types */ #define FT_NONE0 /* No fill*/ #define FT_COLOR 1 /* Fill with color from palette */ #define FT_OBJECTS2 /* Fill with tiled objects*/ struct ATTRstruct { ULONGID; ULONGSize; UBYTEFillType; /* One of FT_*, above*/ UBYTEJoinType; /* One of JT_*, below*/ UBYTEDashPattern; /* ID of edge dash pattern */ UBYTEArrowHead; /* ID of arrowhead to use */ USHORTFillValue; /* Color or object with which to fill */ USHORTEdgeValue; /* Edge color index*/ USHORTWhichLayer; /* ID of layer it's in*/ IEEEEdgeThick; /* Line width*/ }; FillType specifies what kind of fill to use on this ATTR chunk's objects. A value of FT_NONE means that this ATTR chunk's objects are not filled. FT_COLOR indicates that the objects should be filled in with a color. That color's ID (from the CMAP chunk) is stored in the FillValue field. If FillType is equal to FT_OBJECTS, FillValue contains the ID of a fill pattern defined in a FILL chunk. JoinType determines which style of line join to use when connecting the edges of line segments. The field contains one of these four values: /* Join types */ #define JT_NONE 0 /* Don't do line joins */ #define JT_MITER 1 /* Mitered join */ #define JT_BEVEL 2 /* Beveled join */ #define JT_ROUND 3 /* Round join */ DashPattern and ArrowHead contain the ID of the dash pattern and arrow head for this ATTR's objects. A DashPattern of zero means that there is no dash pattern so lines will be invisible. If ArrowHead is 0, OPLYs have no arrow head. EdgeValue is the color of the line segments. WhichLayer contains the ID of the layer this ATTR's objects are in. EdgeThick is the width of this ATTR's line segments. BBOX (0x42424F48) /* Bounding box of next object in FORM */ The BBOX chunk supplies the dimensions and position of a bounding box surrounding the DR2D object that follows this chunk in the FORM. A BBOX chunk can apply to a FILL or AROW as well as a DR2D object. The BBOX chunk appears just before its DR2D object, FILL, or AROW chunk. struct BBOXstruct { ULONGID; ULONGSize; IEEEXMin, YMin, /* Bounding box of obj. */ XMax, YMax; /* including line width*/ }; In a Cartesian coordinate system, the point (XMin, YMin) is the coordinate of the lower left hand corner of the bounding box and (XMax, YMax) is the upper right. These coordinates take into consideration the width of the lines making up the bounding box. XTRN (0x5854524E) /* Externally controlled object */ The XTRN chunk was created primarily to allow ProVector to link DR2D objects to ARexx functions. struct XTRNstruct { ULONG ID; ULONG Size; short ApplCallBacks; /* From #defines, below */ short ApplNameLength; char ApplName[ApplNameLength]; /* Name of ARexx func to call */ }; ApplName[] contains the name of the ARexx script ProVector calls when the user manipulates the object in some way. The ApplCallBacks field specifies the particular action that triggers calling the ARexx script according to the #defines listed below. /* Flags for ARexx script callbacks */ #define X_CLONE 0x0001 /* The object has been cloned */ #define X_MOVE 0x0002 /* The object has been moved */ #define X_ROTATE 0x0004 /* The object has been rotated */ #define X_RESIZE 0x0008 /* The object has been resized */ #define X_CHANGE 0x0010 /* An attribute (see ATTR) of the object has changed */ #define X_DELETE 0x0020 /* The object has been deleted */ #define X_CUT 0x0040 /* The object has been deleted, but stored in the clipboard */ #define X_COPY 0x0080 /* The object has been copied to the clipboard */ #define X_UNGROUP 0x0100 /* The object has been ungrouped */ For example, given the XTRN object: FORM xxxx DR2D { XTRN xxxx { X_RESIZE | X_MOVE, 10, "Dimension" } ATTR xxxx { 0, 0, 1, 0, 0, 0, 0.0 } FORM xxxx DR2D { GRUP xxxx { 2 } STXT xxxx { 0, 0.5, 1.0, 6.0, 5.0, 0.0, 4, "3.0" } OPLY xxxx { 2, { 5.5, 5.5, 8.5, 5.5 } } } } ProVector would call the ARexx script named Dimension if the user resized or moved this object. What exactly ProVector sends depends upon what the user does to the object. The following list shows what string(s) ProVector sends according to which flag(s) are set. The parameters are described below. X_CLONE ``appl CLONE objID dx dy'' X_MOVE ``appl MOVE objID dx dy'' X_ROTATE ``appl ROTATE objID cx cy angle'' X_RESIZE ``appl RESIZE objID cx cy sx sy'' X_CHANGE ``appl CHANGE objID et ev ft fv ew jt fn'' X_DELETE ``appl DELETE objID'' X_CUT ``appl CUT objID'' X_COPY ``appl COPY objID'' X_UNGROUP ``appl UNGROUP objID'' where: appl is the name of the ARexx script CLONE, MOVE, ROTATE, RESIZE, etc. are literal strings objID is the object ID that ProVector assigns to this object (dx, dy) is the position offset of the CLONE or MOVE (cx, cy) is the point around which the object is rotated or resized angle is the angle (in degrees) the object is rotated sx and sy are the scaling factors in the horizontal and vertical directions, respectively. et is the edge type (the dash pattern index) ev is the edge value (the edge color index) ft is the fill type fv is the fill index ew is the edge weight jt is the join type fn is the font name The X_CHANGE message reflects changes to the attributes found in the ATTR chunk. If the user resized the XTRN object shown above by factor of 2, ProVector would call the ARexx script Dimension like this: Dimension RESIZE 1985427 7.0 4.75 2.0 2.0 The Object Chunks The following chunks define the objects available in the DR2D FORM. VBM (0x56424D20) /* Virtual BitMap */ The VBM chunk contains the position, dimensions, and file name of an ILBM image. struct VBMstruct { IEEEXPos, YPos, /* Virtual coords */ XSize, YSize, /* Virtual size */ Rotation; /* in degrees */ USHORTPathLen; /* Length of dir path */ charPath[PathLen]; /* Null-terminated path of file */ }; The coordinate (XPos, YPos) is the position of the upper left hand corner of the bitmap and the XSize and YSize fields supply the x and y dimensions to which the image should be scaled. Rotation tells how many degrees to rotate the ILBM around its upper left hand corner. ProVector does not currently support rotation of bitmaps and will ignore this value. Path contains the name of the ILBM file and may also contain a partial or full path to the file. DR2D readers should not assume the path is correct. The full path to an ILBM on one system may not match the path to the same ILBM on another system. If a DR2D reader cannot locate an ILBM file based on the full path name or the file name itself (looking in the current directory), it should ask the user where to find the image. CPLY (0x43504C59) /* Closed polygon */ OPLY (0x4F504C59) /* Open polygon */ Polygons are the basic components of almost all 2D objects in the DR2D FORM. Lines, squares, circles, and arcs are all examples of DR2D polygons. There are two types of DR2D polygons, the open polygon (OPLY) and the closed polygon (CPLY). The difference between a closed and open polygon is that the computer adds a line segment connecting the endpoints of a closed polygon so that it is a continuous path. An open polygon's endpoints do not have to meet, like the endpoints of a line segment. struct POLYstruct { ULONGID; ULONGSize; USHORTNumPoints; IEEEPolyPoints[2*NumPoints]; }; The NumPoints field contains the number of points in the polygon and the PolyPoints array contains the (X, Y) coordinates of the points of the non-curved parts of polygons. The even index elements are X coordinates and the odd index elements are Y coordinates. [Figure 3 - Bezier curves] DR2D uses Bezier cubic sections, or cubic splines, to describe curves in polygons. A set of four coordinates (P1 through P4) defines the shape of a cubic spline. The first coordinate (P1) is the point where the curve begins. The line from the first to the second coordinate (P1 to P2) is tangent to the curve at the first point. The line from P3 to P4 is tangent to the cubic section, where it ends at P4. The coordinates describing the cubic section are stored in the PolyPoints[] array with the coordinates of the normal points. DR2D inserts an indicator point before a set of cubic section points to differentiate a normal point from the points that describe a curve. An indicator point has an X value of 0xFFFFFFFF. The indicator point's Y value is a bit field. If this bit field's low-order bit is set, the points that follow the indicator point make up a cubic section. The second lowest order bit in the indicator point's bit field is the MOVETO flag. If this bit is set, the point (or set of cubic section points) starts a new polygon, or subpolygon. This subpolygon will appear to be completely separate from other polygons but there is an important connection between a polygon and its subpolygon. Subpolygons make it possible to create holes in polygons. An example of a polygon with a hole is the letter ``O''. The ``O'' is a filled circular polygon with a smaller circular polygon within it. The reason the inner polygon isn't covered up when the outer polygon is filled is that DR2D fills are done using the even-odd rule. The even-odd rule determines if a point is ``inside'' a polygon by drawing a ray outward from that point and counting the number of path segments the ray crosses. If the number is even, the point is outside the object and shouldn't be filled. Conversely, an odd number of crossings means the point is inside and should be filled. DR2D only applies the even-odd rule to a polygon and its subpolygons, so no other objects are considered in the calculations. Taliesin, Inc. supplied the following algorithm to illustrate the format of DR2D polygons. OPLYs, CPLYs, AROWs, and ProVector's outline fonts all use the same format: typedef union { IEEE num; LONG bits; } Coord; #define INDICATOR 0xFFFFFFFF #define IND_SPLINE 0x00000001 #define IND_MOVETO 0x00000002 /* A common pitfall in attempts to support DR2D has been to fail to recognize the case when an INDICATOR point indicates the following coordinate to be the first point of BOTH a Bezier cubic and a sub-polygon, ie. the value of the flag = (IND_CURVE | IND_MOVETO) */ Coord Temp0, Temp1; int FirstPoint, i, Increment; /* Initialize the path */ NewPath(); FirstPoint = 1; /* Draw the path */ i = 0; while( i < NumPoints ) { Temp0.num = PolyPoints[2*i]; Temp1.num = PolyPoints[2*i + 1]; if( Temp0.bits == INDICATOR ) { /* Increment past the indicator */ Increment = 1; if( Temp1.bits & IND_MOVETO ) { /* Close and fill, if appropriate */ if( ID == CPLY ) { FillPath(); } else { StrokePath(); } /* Set up the new path */ NewPath(); FirstPoint = 1; } if( Temp1.bits & IND_CURVE ) { /* The next 4 points are Bezier cubic control points */ if( FirstPoint ) MoveTo( PolyPoints[2*i + 2], PolyPoints[2*i + 3] ); else LineTo( PolyPoints[2*i + 2], PolyPoints[2*i + 3] ); CurveTo( PolyPoints[2*i + 4], PolyPoints[2*i + 5], PolyPoints[2*i + 6], PolyPoints[2*i + 7], PolyPoints[2*i + 8], PolyPoints[2*i + 9] ); FirstPoint = 0; /* Increment past the control points */ Increment += 4; } } else { if( FirstPoint ) MoveTo( PolyPoints[2*i], PolyPoints[2*i + 1] ); else LineTo( PolyPoints[2*i], PolyPoints[2*i + 1] ); FirstPoint = 0; /* Increment past the last endpoint */ Increment = 1; } /* Add the increment */ i += Increment; } /* Close the last path */ if( ID == CPLY ) { FillPath(); } else { StrokePath(); } GRUP (0x47525550) /* Group */ The GRUP chunk combines several DR2D objects into one. This chunk is only valid inside nested DR2D FORMs, and must be the first chunk in the FORM. struct GROUPstruct { ULONGID; ULONGSize; USHORTNumObjs; }; The NumObjs field contains the number of objects contained in this group. Note that the layer of the GRUP FORM overrides the layer of objects within the GRUP. The following example illustrates the layout of the GRUP (and FILL) chunk. FORM { DR2D /* Top-level drawing... */ DRHD { ... } /* Confirmed by presence of DRHD chunk */ CMAP { ... } /* Various other things... */ FONS { ... } FORM { DR2D /* A nested form... */ FILL { 1 } /* Ah! The fill-pattern table */ CPLY { ... } /* with only 1 object */ } FORM { DR2D /* Yet another nested form */ GRUP { ..., 3 } /* Ah! A group of 3 objects */ TEXT { ... } CPLY { ... } OPLY { ... } } FORM { DR2D /* Still another nested form */ GRUP { ..., 2 } /* A GRUP with 2 objects */ OPLY { ... } TEXT { ... } } } STXT (0x53545854) /* Simple text */ The STXT chunk contains a text string along with some information on how and where to render the text. struct STXTstruct { ULONGID; ULONGSize; UBYTEPad0;/* Always 0 (for future expansion) */ UBYTEWhichFont; /* Which font to use */ IEEECharW, CharH, /* W/H of an individual char */ BaseX, BaseY, /* Start of baseline */ Rotation; /* Angle of text (in degrees) */ USHORTNumChars; charTextChars[NumChars]; }; The text string is in the character array, TextChars[]. The ID of the font used to render the text is WhichFont. The font's ID is set in a FONS chunk. The starting point of the baseline of the text is (BaseX, BaseY). This is the point around which the text is rotated. If the Rotation field is zero (degrees), the text's baseline will originate at (BaseX, BaseY) and move to the right. CharW and CharH are used to scale the text after rotation. CharW is the average character width and CharH is the average character height. The CharW/H fields are comparable to an X and Y font size. TPTH (0x54505448) /* A text string along a path */ This chunk defines a path (polygon) and supplies a string to render along the edge of the path. struct TPTHstruct { ULONG ID; ULONG Size; UBYTE Justification;/* see defines, below */ UBYTE WhichFont;/* Which font to use */ IEEE CharW, CharH;/* W/H of an individual char*/ USHORT NumChars; /* Number of chars in the string */ USHORT NumPoints; /* Number of points in the path */ char TextChars[NumChars];/* PAD TO EVEN #! */ IEEE Path[2*NumPoints]; /* The path on which the text lies */ }; WhichFont contains the ID of the font used to render the text. Justification controls how the text is justified on the line. Justification can be one of the following values: #define J_LEFT 0x00 /* Left justified */ #define J_RIGHT0x01 /* Right justified */ #define J_CENTER 0x02/* Center text */ #define J_SPREAD 0x03 /* Spread text across path */ CharW and CharH are the average width and height of the font characters and are akin to X and Y font sizes, respectively. A negative FontH implies that the font is upsidedown. Note that CharW must not be negative. NumChars is the number of characters in the TextChars[] string, the string containing the text to be rendered. NumPoints is the number of points in the Path[] array. Path[] is the path along which the text is rendered. The path itself is not rendered. The points of Path[] are in the same format as the points of a DR2D polygon. A Simple DR2D Example Here is a (symbolic) DR2D FORM: FORM { DR2D DRHD 16 { 0.0, 0.0, 10.0, 8.0 } CMAP 6 { 0,0,0, 255,255,255 } FONS 9 { 1, 0, 1, 0, "Roman" } 0 DASH 12 { 1, 2, {1.0, 1.0} } ATTR 14 { 0, 0, 1, 0, 0, 0, 0, 0.0 } BBOX 16 { 2.0, 2.0, 8.0, 6.0 } FORM { DR2D GRUP 2 { 2 } BBOX 16 { 3.0, 4.0, 7.0, 5.0 } STXT 36 { 0,1, 0.5, 1.0, 3.0, 5.0, 0.0, 12, "Hello, World" } BBOX 16 { 2.0, 2.0, 8.0, 6.0 } OPLY 42 { 5, {2.0,2.0, 8.0,2.0, 8.0,6.0, 2.0,6.0, 2.0,2.0 } } } [Figure 4 - Simple DR2D drawing] The OFNT FORM OFNT(0x4F464E54) /* ID of outline font file */ ProVector's outline fonts are stored in an IFF FORM called OFNT. This IFF is a separate file from a DR2D. DR2D's FONS chunk refers only to fonts defined in the OFNT form. OFHD(0x4F464844) /* ID of OutlineFontHeaDer */ This chunk contains some basic information on the font. struct OFHDstruct { char FontName[32]; /* Font name, null padded */ short FontAttrs; /* See FA_*, below */ IEEE FontTop, /* Typical height above baseline */ FontBot, /* Typical descent below baseline */ FontWidth; /* Typical width, i.e. of the letter O */ }; #define FA_BOLD 0x0001 #define FA_OBLIQUE 0x0002 #define FA_SERIF0x0004 The FontName field is a NULL terminated string containing the name of this font. FontAttrs is a bit field with flags for several font attributes. The flags, as defined above, are bold, oblique, and serif. The unused higher order bits are reserved for later use. The other fields describe the average dimensions of the characters in this font. FontTop is the average height above the baseline, FontBot is the average descent below the baseline, and FontWidth is the average character width. KERN(0x4B45524C) /* Kerning pair */ The KERN chunk describes a kerning pair. A kerning pair sets the distance between a specific pair of characters. struct KERNstruct { short Ch1, Ch2; /* The pair to kern (allows for 16 bits...) */ IEEE XDisplace, /* Amount to displace -left +right */ YDisplace; /* Amount to displace -down +up */ }; The Ch1 and Ch2 fields contain the pair of characters to kern. These characters are typically stored as ASCII codes. Notice that OFNT stores the characters as a 16-bit value. Normally, characters are stored as 8-bit values. The wary programmer will be sure to cast assigns properly to avoid problems with assigning an 8-bit value to a 16-bit variable. The remaining fields, XDisplace and YDisplace, supply the baseline shift from Ch1 to Ch2. CHDF(0x43484446) /* Character definition */ This chunk defines the shape of ProVector's outline fonts. struct CHDFstruct { short Ch;/* The character we're defining (ASCII) */ short NumPoints; /* The number of points in the definition */ IEEE XWidth, /* Position for next char on baseline - X */ YWidth;/* Position for next char on baseline - Y */ /* IEEE Points[2*NumPoints]*/ /* The actual points */ }; #defineINDICATOR 0xFFFFFFFF /* If X == INDICATOR, Y is an action */ #defineIND_SPLINE 0x00000001 /* Next 4 pts are spline control pts */ #defineIND_MOVETO 0x00000002 /* Start new subpoly */ #define IND_STROKE 0x00000004 /* Stroke previous path */ #define IND_FILL 0x00000008 /* Fill previous path */ Ch is the value (normally ASCII) of the character outline this chunk defines. Like Ch1 and Ch2 in the KERN chunk, Ch is stored as a 16-bit value. (XWidth,YWidth) is the offset to the baseline for the following character. OFNT outlines are defined using the same method used to define DR2D's polygons (see the description of OPLY/CPLY for details). Because the OFNT FORM does not have an ATTR chunk, it needed an alternative to make fills and strokes possible. There are two extra bits used in font indicator points not found in polygon indicator points, the IND_STROKE and IND_FILL bits (see defines above). These two defines describe how to render the current path when rendering fonts. The current path remains invisible until the path is either filled and/or stroked. When the IND_FILL bit is set, the currently defined path is filled in with the current fill pattern (as specified in the current ATTR chunk). A set IND_STROKE bit indicates that the currently defined path itself should be rendered. The current ATTR's chunk dictates the width of the line, as well as several other attributes of the line. These two bits apply only to the OFNT FORM and should not be used in describing DR2D polygons.