Skip to content

Scumm Image formats

jamesu edited this page Oct 16, 2010 · 2 revisions

Scumm Image formats

Scumm image formats

The SCUMM engines use loads of different image formats. Most of them use a bitpacked format with a more or less clever compression. Images are generally coded in 2 parts: the color information (SMAP or BOMP) and the ZPnn blocks which contain Z masks. These are one bit depth planes used to mask actors correctly with their surroundings.

LucasHacks! has an intersting article about this at http://scumm.mixnmojo.com/?page=articles/article1

1. SMAP

An SMAP contains a picture. This format is used for room backgrounds and objects. The coding is quite strange but it is probably efficient on old crappy hardware :) The picture in encoded in vertical stripes 8 pixels wide. The data starts with an offset table followed by the encoded stripes. It seems that the overall size of the SMAP is rounded to the next multiple of 2.

1.2 SMAP v6

  offset table : width/8 elements. Offset are relative to the start of the
                 SMAP block (-8 offset)
    offset     : 32le
    ....
  stripe
    header     : 8
    data

The stripe header is a bit strange. The lower bits are used to store the coding shift. This can be retrieved with a simple header % 10; the rest of the bits describe the encoder type used for the stripe. In DOTT, 4 types of encoding are used. I kept the names used in ScummVM (and added one): unkA, unkA6, unkB and unkC :) unkA and unkA6 can both be decoded by the unkA decoder.

1.2 SMAP v8

The structure is a bit different, hopefully that’s the only difference.

1.3 SMAP Codecs

1.3.1 UnkA

  • Opaque id: 10
  • Transparent id: 12

Decoder

  uint8_t color = read_bits(csh);
  uint8_t inc = 0,n;

  write_pixel(color,1);

  while(pixel_left) {

    n = 1;

    if(read_bit()) {
      if(!read_bit()) color = read_bits(csh);

      else {
        inc = (read_bits(3) - 4);

        if(inc) color += inc;
        else n = read_bits(8);
      }
    }

    write_pixel(color,n);
  }

1.3.2 UnkA6

  • Opaque id: 6
  • Transparent id: 8

A simplified version of the above algorithm. Probably a predecessor. Dunno why some of the images in DOTT are encoded with this inferior algorithm. Perhaps because it’s a bit faster to decode :)

Decoder

  uint8_t color = read_bits(csh);
  uint8_t inc = 0;

  while(pixel_left) {

    write_pixel(color,1);

    if(read_bit()) {
      if(!read_bit()) color = read_bits(csh);
      else color += (read_bits(3) - 4);
    }
  }

1.3.3 UnkB

  • Opaque id: 2
  • Transparent id: 4

Decoder

  uint8_t color = read_bits(csh);
  uint8_t inc = -1;

  while(pixel_left) {

    write_pixel(color,1);

    if(read_bit()) {
      if(!read_bit()) {
        color = read_bits(csh);
        inc = -1;
      } else {
        if(read_bit()) inc = -inc;
        color += inc;
      }
    }
  }

1.3.4 UnkC

  • Opaque id: 1
  • Transparent id: 3

This is the same as unkB except that it is coded by column instead of by line.

2. ZPnn

2.1 ZP0n v6

This encoding is very similar to the SMAP. Again we have an offset table followed by the encoded data. However, the offsets are only 16 bits wide and the special offset of 0 is used to code a stripe full of zeros.

  offset table : width/8 elements. Offsets are relative to the start of the
                 ZPnn block (-8 offset)
    offset: 16le
    ....
  stripe
    data

Decoder

The Z plane decodes to a bit packed, 1 bit per pixel image. The decoder decodes a single stripe. As these are 8 pixels wide, every line is a single byte. So this time the encoding is byte based; that’s a lot simpler :)

  uint8_t b,count;

  while(line_left) {

    count = read_byte();

    if(count & 0x80) { // write the same byte count times

      count &= 0x7F;
      b = read_byte();

      do {
        write_byte(b);
        count--;
      } while(count && line_left);

    } else {  // write count bytes as is from the input

      do {
        write_byte(read_byte());
        count--;
      } while(count && line_left);

    }
  }

2.2 ZPLN v8

The structure is a bit different but hopefully the codec is the same.

3. BOMP

The blast object images, these start in v6 with Sam & Max AFAIK. The encoding is pretty straightforward, it’s a simple line by line RLE encoding.

The header is quite simple and very similar in v6 and v8, except v8 uses 32 bits as usual.

3.1 BOMP v6

  unk     : 16
  width   : 16le
  height  : 16le
  padding : 2*16
  data    : the encoded image

3.2 BOMP v8

  width   : 32le
  height  : 32le
  data    : the encoded image

3.3 Codec

Each line starts with a 16le storing the size of the encoded line (without the size header itself) followed by the RLE data.

  lines
    encoded size : 16le
    line data    : size bytes

Decoder

Well I haven’t written a tested decoder now, so here is the one from ScummVM. This code only decodes a single line.

  uint8_t b,count;

  while(len > 0) {
    b = read_byte();
    count = (b >> 1) + 1;
    if(count > len) count = len;
    if(b & 1)
      write_pixel(read_byte(),count);
    else {
      while(count > 0) {
        write_pixel(read_byte(),1);
        count--;
      }
    }
    len -= count;
  }