LBX Images: This document describes the LBX image format used in Moo2. The goal is that the code to decode images in liblbx can be written against this specification, thus guiding the implementation. All multi-byte integer types are stored in the image file from least-significant to most-significant byte. This document uses the names "uint8", "uint16" and "uint32" to refer to 8-bit, 16-bit and 32-bit unsigned integers, respectively. LBX Images begin with a 12-byte header which has the following layout: OFFSET TYPE DESCRIPTION ----------------------------------------------------- 0 uint16 Image width in pixels 2 uint16 Image height in pixels 4 uint16 Unknown (always 0?) 6 uint8 Number of frames 7 uint8 Unknown (always 0?) 8 uint8 Lead-in (less than the frame count) 9 uint8 Chunk size 10 uint16 Flags Immediately following the header is an sequence of uint32 offsets (one for each frame) indicating the start of each frame, followed by a final uint32 offset which marks the end of the file. The frame count, lead-in and chunk size describe how the image is animated, which is described later. The flags value is the bitwise-OR of zero or more of the following values: VALUE NAME DESCRIPTION ---------------------------------------------------------------------------- 0x0100 raw If set, this flag enables a simpler method of storing frame data in the image. 0x0400 overwrite If set, behave as if the chunk size was 1 instead of its actual value. 0x0800 building Unknown. This flag is set on the building images (perhaps to enable a special blending mode for shadows). 0x1000 palette If set, the image contains embedded palette data. 0x2000 loop If set, behave as if the lead-in was 0 instead of its actual value. If the palette flag is set, the palette data follows the frame offsets. The palette has a 4-byte header: OFFSET TYPE DESCRIPTION ----------------------------------------------------- 0 uint16 Index of the first embedded palette entry 2 uint16 Number of embedded palette entries. The sum of these two values must not exceed 256. Immediately following the palette header are the palette entries. Each palette entry is 4 bytes with the following layout: OFFSET TYPE DESCRIPTION ----------------------------------------------------- 0 uint8 Always 1. 1 uint8 Red component (0-63) 2 uint8 Green component (0-63) 3 uint8 Blue component (0-63) Note that component values are stored as an 8-bit integer but only range from 0-63 (6 bits per channel), with (0, 0, 0) being the darkest black and (63, 63, 63) the brightest white. The values in the image's embedded palette supersede the values in the "main" palette. Image data: Each LBX image consists of one or more frames. The start of each frame can be found by seeking to the appropriate offset in the file, as described above. There are two main methods of encoding frame data. The simplest is the raw encoding, which is used when the "raw" flag (0x0100) is set in the header. In raw encoding, the pixel data for the entire frame is stored verbatim in row-major order (one uint8 value per pixel). There are no headers to parse within the frame, and this format does not support transparency as each frame specifies a palette index for every pixel in the image. For example, a 3x2 raw image has exactly 6 bytes per frame, with the pixels laid out as in the following format. The numbers represent byte offsets within the file: +---+---+---+ | 0 | 1 | 2 | +---+---+---+ | 3 | 4 | 5 | +---+---+---+ The more common method is a line-based encoding, which is more flexible. In this format, a cursor is maintained to track where pixel data is to be written. The X value of the cursor specifies the number of pixels right from the left edge of the image, and the Y value of the cursor specifies the number of pixels down from the top edge of the image. Each frame in this encoding begins with a header. OFFSET TYPE DESCRIPTION ----------------------------------------------------- 0 uint16 Always 1. 2 uint16 Initial Y position of cursor. The cursor has an initial X value of 0. Immediately following the header is a sequence of one or more drawing commands. Each command consists of a 2-byte header. OFFSET TYPE DESCRIPTION ----------------------------------------------------- 0 uint16 Length: number of pixels to follow 2 uint16 Cursor offset If length is non-zero, first the offset is added to the current X value of the cursor, then the pixel data (1 byte per pixel) follows and is drawn starting at the current cursor position, with successive pixels increasing in X value. The cursor is updated as pixels are drawn. In general, new pixel data supersedes any previous value for a particular pixel. Pixels that have no data specified are transparent. Animated images may re-use pixel values from the previous frame, see below for details. If length is odd, there is a padding byte after the last pixel which must be skipped. If length is 0, then the offset is added to the Y value of the cursor, the X value of the cursor is reset to 0, and no pixel data follows. Notwithstanding the above, if both length is 0 and offset is exactly 1000, then there are no further drawing commands in this frame. Animation: There are 3 parameters relevant to animations found in the image header: the frame count, the lead-in, and the chunk size. Frame count specifies the total number of frames in the image, chunk size affects how frames are decoded and the lead-in affects how animations are displayed. Normally, each decoded frame is drawn on top of the previous frame, with the first frame drawn on a fully transparent slate. So if the first frame has any unspecified pixel values, those pixels are transparent, while if the second frame has any unspecified pixel values, those pixels retain the value from the first frame (transparent or otherwise). However, if the chunk size is set to a non-zero value, then the slate is reset to a fully transparent state when decoding a frame number which is divisible by the chunk size. So if this is set to 1, every frame must specify all non-transparent pixels as they will each be drawn on top of a transparent slate (just like the first frame). This parameter is the only way for pixels which are opaque in one frame to become transparent in the next. The lead-in specifies the frame to be displayed after the last frame in an animation. If this is different from the last frame, then the result is a looping animation. If this is the same as the last frame, then the result is an animation that stops at the end.