AMOS:Picture Bank format

From Amiga Coding

Jump to: navigation, search

This bank has the name "Pac.Pic." and is created with the Compact extension's "Pack" command. AMOS Pac.Pic. images are a compressed form of Amiga raster graphics.


Pac.Pic. images are generated by the "Compact" extension in AMOS, using the PACK command, for example PACK 0 TO 6 will compress the image on screen 0 into AMOS bank number 6. These banks can then be saved as part of the AMOS source code, or saved as individual bank files.


AMOS records details about the physical hardware screen that the picture is displayed on: all the required details to re-perform the SCREEN OPEN and SCREEN DISPLAY commands, if the picture is unpacked to a screen number that doesn't exist.


It is also possible to only pack an area of the screen. Therefore, the Pac.Pic. format has seperate headers for the dimensions of the entire screen and the dimensions of the picture data itself.


In this format definition, all multi-byte integers are in big-endian format. All numbers given are in decimal unless prefixed by '0x'.

Contents

Overall format

OffsetSizeDescription
020 bytesstandard AMOS bank header
2090 bytesScreen header
11024 bytesPicture header
134x bytesPICDATA stream


Standard AMOS bank header format

OffsetSizeDescription
04 bytesThe literal ASCII text "AmBk"
42 bytesAMOS bank number, between 1 and 15
62 bytes0 if the bank has to be loaded in Chip memory, 1 if Fast memory is OK.
84 bytes
  • If bit 31 is set, try loading the bank in Fast memory.
  • If bit 30 is set, try loading the bank in Chip memory.
  • Bits 29 and 28 aren't used.
  • Bits 27 to 0 are the length of the bank itself.
128 bytesThe bank name. In this case, it should always be "Pac.Pic."


Screen header format

OffsetSizeDescription
04 bytesFixed screen header ID of 0x12031990 (31-December-1990, perhaps the birth of AMOS?)
42 bytesScreen width in pixels, e.g. 0x0140 for 320 pixels
62 bytesScreen height in pixels, e.g. 0x00C8 for 200 pixels
82 bytesHardware top-left x coordinate, using Amiga DIWSTRT register units, e.g. 0x0081
102 bytesHardware top-left y coordinate, using Amiga DIWSTRT register units, e.g. 0x0032
122 bytesHardware screen width
142 bytesHardware screen height
162 bytesUnknown purpose
182 bytesUnknown purpose
202 bytesValue of the Amiga BPLCON0 register, which details the hardware screen mode such as HAM, hires or interlaced
222 bytesNumber of colours on screen. Either 2, 4, 8, 16, 32, 64 (EHB) or 4096 (HAM).
242 bytesnumber of bitplanes, Between 1 and 6
2664 bytes32 2-byte palette entries in the Amiga COLORxx register format.


Picture header format

OffsetSizeDescription
04 bytesFixed picture header ID, 0x06071963, (7-June-1963, perhaps the birth of François Lionet?)
42 bytesThe X coordinate offset in bytes of the picture within the screen itself.
62 bytesThe Y coordinate offset in lines (vertical pixels) of the picture within the screen itself.
82 bytesThe picture width in bytes.
102 bytesThe picture height in "line lumps" (described below).
122 bytesThe number of lines in a "line lump".
142 bytesThe number of bitplanes in the picture.
164 bytesThe offset to the RLEDATA stream, relative to the picture header ID's offset.
204 bytesThe offset to the POINTS stream, relative to the picture header ID's offset.


Stream formats

As described above, there are three streams of data immediately following the headers: the PICDATA stream, the RLEDATA stream and the POINTS stream. The PICDATA stream begins immediately following the headers. The RLEDATA stream begins at the offset given in the picture header, relative to the start of the picture header itself. Finally, this is followed by the POINTS data stream, which also has its beginning defined by an entry in the picture header.


The Pac.Pic. format merely a form of run-length encoding. PICDATA is the actual picture data, but it is compressed. You either take a new byte from the PICDATA stream, or you repeat the previous byte. The choice you make is stored as a single bit in the RLEDATA stream. However! The RLEDATA stream is itself run-length encoded in the same way. Your stream of bits is stored as bytes in the RLEDATA stream, and you either take a new byte, or recycle the old byte, based on reading a single bit from the POINTS stream. The POINTS stream is not compressed.


The bits in the RLEDATA stream and POINTS stream are stored as as bytes. The bits should be read from most significant bit in the byte to the least significant bit.


Picture data ordering

Image:Pacpic.png
How Pac.Pic. turns picture data into a byte stream

As described above, the picture data is RLE encoded. However, the picture data is stored in a specific order to get better compression:

  • Bitplane 0 is compressed first, then bitplane 1, bitplane 2 ...
  • Within a bitplane, we compress a "lump" of complete horizontal lines. This "lump" has a specific height, which is stored in the picture header.
  • Within a lump, each byte in the picture is retrieved from top to bottom, left to right. So first we grab the pixels (0,0)-(7,0) in the first byte, the second byte is pixels (0,1)-(7,1), the third is (0,2)-(7,2), and so on up to the lump's height. Then, we grab (8,0)-(15,0), (8,1)-(15,1) ...


There are an integer number of lumps in a picture, and they all have the same height. A picture has to have an overall height that is a multiple of the lump height, even if the screen height is not a multiple of the lump height.


The Pac.Pic. compressor, when asked to compress a picture, will try compressing it with all possible lump heights it knows, before going on to compress the picture with the lump height found to give the best compression.


Compression

Having defined how the raw picture data is ordered, we treat that as a stream of bytes.


With that stream, we look at each byte. We always store the first picture byte to the PICDATA stream.


For each picture byte, we store 1 RLE bit, to say whether this picture byte is repeated or not. The RLE bits are stored in the RLEDATA stream, from most significant to least significant bit of a byte.


If a picture byte is different from the previous picture byte, we store a '1' as the RLE bit, and we output that picture byte to the PICDATA stream.


If a picture byte is the same as the previous picture byte, we store a '0' as the RLE bit, and do not write anything to the PICDATA stream.


Once the PICDATA and uncompressed RLEDATA streams are completed, we compress the RLEDATA by the same method as above, storing the decision bits for the compressed RLEDATA stream into the POINTS stream.


Decompression

As described above, you know the way in which the picture data needs to be drawn on the screen as the picture data is uncompressed byte-by-byte: draw a vertical column of bytes within the first lump, from top to bottom, then draw the next vertical column of bytes, until you reach the width of the picture. That's one lump. Repeat for as many lumps as are defined in the picture header. The second lump starts immediately below the first lump. After completing all lumps, you have one complete bitplane. Repeat for all bitplanes.


To decompress the raw picture data, you know you the first PICDATA and RLEDATA bytes are not compressed, and that the POINTS stream is not compressed. For each remaining PICDATA byte, you need to check the next bit from RLEDATA. Once you've used all the bits in the first RLEDATA byte, you need to get the MSB of the first byte of the POINTS stream to decide if you fetch a new RLEDATA byte, or if you recycle it. Here is some C code for decompressing the stream:


<pre>
/* you supply these */
unsigned char *bitplane_ptrs[6]; /* a selection of bitplane pointers to write to */
int bytes_per_line; /* number of bytes required to jump forward by one vertical line */
 
/* these come from the picture header */
int width_bytes; /* 2-byte value at offset 8 */
int lumps; /* 2-byte value at offset 10 */
int lump_height; /* 2-byte value at offset 12 */
int bitplanes; /* 2-byte value at offset 14 */
unsigned char *picdata; /* offset of picture header + 24 */
unsigned char *rledata; /* offset of picture header + 4-byte value at offset 16 */
unsigned char *points; /* offset of picture header + 4-byte value at offset 20 */
 
int i, j, k, l, rrbit, rbit, picbyte, rlebyte;
 
int rbit = 7;
int rrbit = 6;
int picbyte = *picdata++;
int rlebyte = *rledata++;
if (*points & 0x80) rlebyte = *rledata++;
 
for (i = 0; i < bitplanes; i++) {
unsigned char *lump_start = bitplane_ptrs[i];
for (j = 0; j < lumps; j++) {
unsigned char *lump_offset = lump_start;
for (k = 0; k < width_bytes; k++) {
unsigned char *d = lump_offset;
for (l = 0; l < lump_height; l++) {
/* if the current RLE bit is set to 1, read in a new picture byte */
if (rlebyte & (1 << rbit--)) picbyte = *picdata++;
 
/* write picture byte and move down by one line in the picture */
*d = picbyte;
d += bytes_per_line;
 
/* if we've run out of RLE bits, check the POINTS bits to see if a new RLE byte is needed */
if (rbit < 0) {
rbit = 7;
if (*points & (1 << rrbit--)) rlebyte = *rledata++;
if (rrbit < 0) rrbit = 7, points++;
}
}
lump_offset++;
}
lump_start += bytes_per_line * lump_height;
}
}
</pre>