The s16 format is very similar to the Creatures 1 .spr file format. The main difference is the graphics data is encoded in 16bpp instead of an index into a 256 color palette. All values of WORD, DWORD, etc are encoded in the same format as if you had written the direct memory representation of a 'C' short or long respectively on an intel PC (ie. little endian format).
The following is what I understand to be the format:
Description | Size | Type | Notes |
---|---|---|---|
Graphic encoding format | 4 bytes | DWORD | 0 = 555 format. 1 = 565 format. |
Number of images | 2 bytes | WORD | |
Image Information Header | 8 bytes * number of images. | See below for details. There is an image information header block for each image in the file. | |
Image 1 data | Image 1 width * height * 2 bytes | See below for details. | |
Image 2 data | Image 2 width * height * 2 bytes | There is an image data segment for each image information header that exists. | |
Image N data | Image N width * height * 2 bytes | There is an image data segment for each image information header that exists. |
This is repeated as many times as there are images in the file. There are 8 bytes in the header:
Description | Size | Type | Notes |
---|---|---|---|
Offset | 4 bytes | DWORD | The offset based from the beginning of the file to the start of this images graphical data. |
Width | 2 bytes | WORD | Width of the image. Must be a multiple of 4. |
Height | 2 bytes | WORD | Height of the image. |
Repeated for each image in the file. The size of this segment depends upon the width and height of the particular image. It should be width * height * 2 bytes (ie. width * height WORDs). Each pixel is represented by 2 bytes (hence 16bpp). These two bytes have the red, green and blue color values encoded within it. The encoding scheme is either '565' or '555' as defined in the first DWORD of the image file header.
The high 5 bits are the red color. The middle 6 bits are the green color and the low 5 bits are the blue color. This adds up to the full 16 bits. To extract the individual color value you can use the AND operation. For example, to extract the RED value you can do something line:
1011010010111001 - Original Pixel
1111100000000000 - And with this
----------------
1011000000000000 - Giving the red value portion.
To extract the green value:
1011010010111001 - Original Pixel
0000011111100000 - And with this
----------------
0000010010100000 - Giving the green value portion.
You would then need to shift these values to the right to get the true value:
1011000000000000 - Red value >> 11 bits
----------------
0000000000001011 - Red value.
0000010010100000 - Green value >> 5 bits
----------------
0000000000100101 - Green value.
These values would then need to be scaled to a value between 0 and 256 (ie. to 8 bits instead of the 5 or 6 bits) to display correctly if you were plotting them on the screen. Multiply the red and blue value by 8 and the green value by 4. Or you could not shift right as much above. You can work out the maths. I showed the full shift to the right above so you know why we are performing the shift and why we are performing the multiply (for the scaling to 0-256). Shifting a number to the right 11 and then multiplying by 8 is the same as shifting it right by only 8 bits.
The high 1 bit is ignored. The next 5 bits are the red color. The middle 5 bits are the green color and the low 5 bits are the blue color. The exact same maths applies as before except we are ANDing with different values, right shifting by different values and scaling by different amounts.
0011010010111001 - Original Pixel
0111100000000000 - And with this
----------------
0011010000000000 - Giving the red value portion.
To extract the green value:
0011010010111001 - Original Pixel
0000001111100000 - And with this
----------------
0000000010100000 - Giving the green value portion.
You would then need to shift these values to the right to get the true value:
0011010000000000 - Red value >> 10 bits
----------------
0000000000001101 - Red value.
0000000010100000 - Green value >> 5 bits
----------------
0000000000000101 - Green value.
To scale to between 0 and 256 you would multiply all values by 8 (or shift with different values as mentioned above).
Some example C++ code to read an image file might be:
struct bitmap_info
{
 bitmap_info() : buffer(0) {}
 ~bitmap_info() { delete[] buffer; }
 unsigned long offset;
 unsigned short width;
 unsigned short height;
 COLORREF* buffer;
 static long number_of_images;
};
long bitmap_info::number_of_images = 0;
ifstream file(filename, ios::binary);
// Read image format
long format;
file.read((char*)&format, 4);
bool is_565 = format == 1L;
file.read((char*)&bitmap_info::number_of_images, 2);
bitmap_info* bitmaps = new bitmap_info[bitmap_info::number_of_images];
for(int i = 0; i < bitmap_info::number_of_images; ++i)
{
 file.read((char*)&bitmaps[i].offset, 4);
 file.read((char*)&bitmaps[i].width, 2);
 file.read((char*)&bitmaps[i].height, 2);
 bitmaps[i].buffer = new COLORREF[bitmaps[i].width * bitmaps[i].height];
}
for(i = 0; i < bitmap_info::number_of_images; ++i)
{
 file.seekg(bitmaps[i].offset, ios::beg);
 for(int y = 0; y < bitmaps[i].height; ++y)
 {
  for(int x = 0; x < bitmaps[i].width; ++x)
  {
   short pixel = 0;
   file.read((char*)&pixel, 2);
   const unsigned char red = is_565 ?
    (unsigned char)((pixel & 0xf800) >> 11) :
    (unsigned char)((pixel & 0x7c00) >> 10);
   const unsigned char green = is_565 ?
    (unsigned char)((pixel & 0x7e0) >> 5) :
    (unsigned char)((pixel & 0x3e0) >> 5);
   const unsigned char blue = (unsigned char)(pixel & 0x1f);
   bitmaps[i].buffer[(y * bitmaps[i].width) + x] =
    RGB(red * 8, green * (is_565 ? 4 : 8), blue * 8);
  }
 }
}
file.close();
Note that COLORREF
and RGB
are structions and macros
from the Win32 API. The values stored in the 'buffer
' of the bitmap_info
structure can be plotted onto a GDI surface directly using SetPixel
or you can build up a bitmap with these values.