98 lines
5.9 KiB
Markdown
98 lines
5.9 KiB
Markdown
## Bitmaps
|
|
|
|
Bitmaps are images using a palette of colours. System Shock uses bitmaps exclusively for its graphics and uses one byte
|
|
as index per pixel - thus 256 colours possible. The resources store such bitmaps with content type ```0x02``` and they are always compound, even if only one bitmap is stored.
|
|
|
|
Bitmaps are stored with a header, the pixel data (sometimes compressed), and an optional private palette.
|
|
|
|
### Header
|
|
|
|
**Bitmap Header** (`FrameDesc` struct) (28 bytes)
|
|
|
|
0000 [4]byte Unused. Always zero. (used as pointer in the engine)
|
|
0004 byte Type -- 0x02: Uncompressed; 0x04 Compressed
|
|
0005 byte Align -- Unused
|
|
0006 uint16 Flags
|
|
0008 sint16 Width
|
|
000A sint16 Height
|
|
000C uint16 Stride
|
|
000E byte WidthFactor
|
|
000F byte HeightFactor
|
|
0010 [4]int16 HotspotBox: Left, Top, Right, Bottom
|
|
0018 sint32 Private palette offset
|
|
|
|
The ```Flags``` is set to value ```0x0001``` then palette index ```0x00``` should be treated as a transparent pixel instead of painting it black.
|
|
> Compressed images appear to default to transparency if the resolution of the game is 320x200. Higher resolutions require the flag set to behave identical.
|
|
>
|
|
> Other factors may also govern whether the bitmap should be treated with transparency - videos and level textures have their special handling in this regard.
|
|
|
|
The ```WidthFactor``` and ```HeightFactor``` specify the index of the highest set bit of ```Width``` and ```Height```
|
|
respectively. For example, if Width is 320 (0x140), then the WidthFactor is 8 (1<<8 == 0x100).
|
|
|
|
`HotspotBox` is a placeholder for different applications. It can define a rectangle, or it only defines a point (using the first two values) for anchoring.
|
|
|
|
> For the existing images in the original resource files, all images have a ```Stride``` equal to ```Width```.
|
|
|
|
### Pixel Data
|
|
|
|
After the header, the pixel data is serialized. If the image is uncompressed (Type ```0x02```),
|
|
```Height``` x ```Stride``` bytes make up the image. Compressed images (Type ```0x04```) use a simple run-length-encoding (RLE).
|
|
|
|
#### Pixel Compression
|
|
|
|
The compressed byte-stream contains the following byte sequences:
|
|
|
|
00 nn zz write nn bytes of value zz
|
|
nn .. 0 < nn < 0x80: copy the following nn bytes
|
|
80 lo hi lo hi are a little-endian uint16 nnnn, to be handled as follows:
|
|
80 00 00 End of compressed data (Skip to end)
|
|
80 lo hi 0 < hi < 0x80: skip (nnnn & 0x7FFF) output pixel
|
|
80 lo hi .. 0x80 <= hi < 0xC0: copy the following (nnnn & 0x3FFF) bytes
|
|
80 lo C0 hi == 0xC0: undefined (not encountered)
|
|
80 lo hi zz 0xC0 < hi: write (nnnn & 0x3FFF) bytes of value zz
|
|
nn 0x80 < nn: skip (nn & 0x7F) output pixel
|
|
|
|
Three cases exist to compress pixel data: skip a list of pixel, compress a list of a constant
|
|
value and copy an arbitrary array of bytes. For each of the three, two encoding variants are used: a short and a long form.
|
|
If a count is to be serialized that is higher than the long-form can store, the encoder uses the long-form to reduce this number to finalize with the short-form.
|
|
|
|
> Skipping output pixel is relevant for compressing video clips. Skipped pixel will retain the colour value from the previous frame.
|
|
> For still images, initialize the output buffer with 0x00.
|
|
>
|
|
> To implement a compressor that creates output closest to the original content, use the following sequence. The term "reference" means either a previous frame bitmap, or a fictual frame with all bytes 0x00.
|
|
> * From an array of bytes with length X, calculate the count Z of bytes at the end that are identical to reference. Compress only N bytes, where N = X - (Z % 0x7FFF).
|
|
> The final sequence ```80 00 00``` implies to skip to the end. The encoder
|
|
> seems to have had a limit of counting only up to 0x7FFF, so there may be further (long) skip entries before.
|
|
> * Iterate through the pixel data from start to N:
|
|
> * Count the extent of static value bytes
|
|
> * Count the number of bytes identical to reference
|
|
> * Determine encoding method
|
|
> * If the pixel value shall be retained from the reference, skip the count of bytes
|
|
> * Otherwise, if the count is more than three, encode the count and the constant value
|
|
> * Otherwise, count the extent of an arbitrary array; Abort when more than three static or identical values are found in sequence; Encode this array.
|
|
> * Skip the input bytes that have been serialized and continue with the loop
|
|
> * Encode end of compressed data sequence.
|
|
|
|
|
|
### Private Palette
|
|
|
|
The format also allows for an optional palette, which is specific for the bitmap. If an image has no private palette, then this
|
|
entry is not present and the resource data ends directly after the pixel data.
|
|
Without a private palette, the colours of the bitmap are context specific. See [Palettes](Palettes.md) for more information.
|
|
|
|
Private palettes are used for the splash screens at the beginning of the game, where nothing else is shown.
|
|
The ```Private palette offset``` in the bitmap header specifies the absolute offset *from the beginning* of the resource to the private palette entry. This means in case of a compound resource, this offset measures across the previous resource blocks as well.
|
|
|
|
**Private Palette** (772 bytes)
|
|
|
|
0000 [4]byte Unknown. Always 0x00, 0x00, 0x00, 0x01
|
|
0004 [256*3]byte RGB values
|
|
|
|
> The only working examples for this can be found in ```splash.res```.
|
|
>
|
|
> The resource file ```gamescr.res``` also contains three bitmaps with a private palette, but these palettes are not used and are ignored by the engine.
|
|
> The three bitmaps in question are the help overlay (```ALT+o```), and are shown with the game world in the background. Since in these
|
|
> cases the world can't change its colour, the screen can't use a private palette. By extension, the side-effects of the "Berserk" patch
|
|
> also affect the colours of the help overlay.
|
|
> Furthermore, for these three bitmaps, the values of offset fields are wrong as well.
|