SPR# Resource Format
The following information is not based on any proprietary knowledge or restricted documentation—it was entirely derived from observation, experiment, and public information, thus it may be inaccurate or incomplete.
Analyzed by Greg Noel.
The SPR# resource format is an older version of the SPR2 resource format. It supports only a single channel (8-bit color), but it can encode some types of image more efficiently than SPR2, so it continues to be used in flat, single-plane objects (such as walls, floors, windows, doors, and thought balloons) where there is no need for the additional channels.
In the description below, integers can be either big-endian or little-endian. All of the integers in a given resource are of the same type, so all that has to be done is initially determine which type is present and decode all remaining integers the same way. The first value in the resource, the version, is always less than 65,535, so it is sufficient to look at the first two bytes. If the first two bytes are zero, it's big-endian, otherwise, it's little-endian.
| SPR# layout | ||
| Offset | Size | Value |
| 0 | 4 | Version |
| 4 | 4 | Frame count (N) |
| 8 | 4 | Palette ID |
| 12 | 4 * N | Offset table |
| var | var | Frame 0 |
| var | var | Frame 1 |
| var | var | . . . |
| var | var | Frame N-1 |
At least four versions (with values 502, 503, 504, and 505) have been found. Usually, different versions mean that there are structural differences (fields added or meaning changed). However, in this case, all of the versions appear to have the same structure (that is, they all are decoded identically), so it's not known what the different versions mean.
The frame count says how many sprite frames (separate graphical image pieces) there are in this sprite. Each image is effectively distinct, but the frames within a single sprite are traditionally the same image at different scales. The DGRP resource combines multiple frames from multiple sprites into a single composite image; it is this image that appears in the game.
A sprite can use a maximum of 256 colors. The palette ID is the ID of an associated PALT resource within the same .iff file as the SPR# resource that is used to convert the 8-bit color index into a full 24-bit color.
The Nth offset table entry is the number of bytes from the begining of the resource to the data for the Nth frame. Since frames are variable length, this makes it possible to go directly to the the Nth frame without having to scan over the intervening entries.
Each sprite frame is a distinct image. Traditionally, the different frames within a sprite are different sizes (zoom factors) of the same item, but this is not a requirement and it would be possible to put completely unrelated images together if desired.
| SPR# Frame | ||
| Offset | Size | Value |
| 0 | 2 | unknown, zero |
| 2 | 2 | unknown, zero |
| 4 | 2 | Height |
| 6 | 2 | Width |
| 8 | 2 | unknown |
| 10 | 2 | Row header |
| 12 | var | Row segments |
| var | 2 | Row header |
| var | var | Row segments |
| var | var | . . . |
| var | 2 | Row header |
| var | var | Row segments |
| var | 2 | End marker (05 00) |
Two unknown values, always zero. Possibly X and Y locations of initial position.
The height and width tell how big the image is. There are height rows of width pixels in the image.
Unknown, either 00 00 or 00 10. The value is the same in both big-endian and little-endian modes, suggesting that this value is part of the byte stream that follows rather than an integer, but the meaning is unknown.
The rest of the resource describes how to fill in the sprite frame image. It consists of information for exactly height rows plus an end marker.
Each row header is two bytes. The first byte of the row header is the encoding. There are two types of encoding, represented by the codes 0x09 and 0x04. The second byte is a count (N).
If the the encoding is 0x09, the count is the number of rows to fill in with background. In other words, the entirety of the next N rows are transparent. There are no row segments associated with this encoding, so the next row header occurs immediately.
If the the encoding is 0x04, the count is the number of bytes of compressed data (including the two bytes of the row header) to describe the image information for one row, so that the next row header occurs N bytes after this one.
The compressed data consists of one or more row segments each of which describes successive parts of the image. If the row segments do not describe the full width of a row, the remainder of the row is filled with background.
The end marker contains the value 0x05 in the first byte and the second byte is zero.
| SPR# Row Segment | ||
| Offset | Size | Value |
| 0 | 1 | Format code |
| 1 | 1 | Pixel count |
| 2 | var | Pixel data |
| var | opt | Alignment byte |
The format code and pixel count describe what to do with the next portion of the image line. Each field is a one-byte value. The format code is described below; the pixel count tells how many pixels are affected. The code and count are followed by zero or more bytes of data, depending on the format code.
The (known) format codes and their meanings are given in this list:
- 1 - fill pixels with background
- 2 - run-length encoding
- 3 - copy image pixels
Code one has no pixel data. The pixel count is the number of pixels that are transparent (show the background).
Code two has two bytes of pixel data. The first byte is the palette color index to fill into the next number of pixels. The second byte is always the same value as the first byte; it is apparently unused: tests show that the second color index is not alternated with the first, for example.
Code three has one byte of data per pixel, the palette color index. If the pixel count is odd, an alignment byte with value zero pads the length to even.
Decoder strategies
When writing a decoder for this format, there are two basic strategies that can be followed: Either go through each row, rendering all the segments in the row before going to the next row, or go through each code, rendering each segment as it is encountered and using the line codes as psuedo-segments to keep the image aligned.
Let's consider those two strategies in more detail.
The first strategy is the obvious one that follows the shape of the data itself. An outer loop proceeds down the row information, either filling lines with background or decoding the segments that make up a line. When it has filled the lines (or upon encountering the end marker) it terminates.
This involves two nested loops. The outer loop is concerned with lines, so it is evaluating the two line codes and possibly the end marker. It can either count the number of lines that have been processed and terminate when all the lines are done, or it can simply process lines until it hits the end marker.
The inner loop is concerned with a single line. It processes a series of segments until they are exhausted; if that doesn't fill the entire line, it fills the rest of the line with background.
This strategy is straightforward to implement, test, and maintain and is reasonably fast.
An alternative scheme is to go through all the codes in order, interpreting the segment codes, treating the line codes as specialized segments, and terminating when encountering the end marker. Note that the codes have been artfully selected with no overlap so that this is possible:
- 1 - fill pixels with background
- 2 - run-length encoding
- 3 - copy image pixels
- 4 - line with data
- 5 - end marker
- 9 - fill lines with background
If the alignment byte were interpreted, it would have a code of zero, one of the unused codes in the list above. This makes one wonder if the codes 6, 7, and 8 had meanings at one time and were eliminated when they turned out to have little or no effect. (Alternatively, they could still have meaning, but are so rare or specialized that they didn't occur in any samples.)
This strategy would be a bit more complex to implement and maintain, but would tend to have less overhead, so it should be a little faster.
Each strategy has its strengths and weaknesses. Whichever strategy is used, the time to transfer the pixels will dominate performance, so the choice of which to use will likely be driven by the interface requirements—an application that just displays an image may want the whole image instantiated at once, while an application that incorporates several images into a larger image may want to look at the images all together line-by-line in order to minimize the space impact.
The Sims™ is a trademark of Maxis and Electronic Arts.
This page was last modified Sunday, 27-Oct-2002 12:50:24 UTC.
