A short follow up and sample program which demonstrates how to write a RIFF palette with ease.

Example program that generates a random palette and saves it into a RIFF form
Example program that generates a random palette and saves it into a RIFF form

About RIFF Palettes

I covered the basics of the RIFF specification and how to read palettes in my previous article.

Performance Considerations

When I first started this journey and wrote how to read and write palette files in different formats, the code I provided generally read and wrote bytes one at a time. At the start of January (2016, time has a habit of getting away from me!) I wrote an article which described how to read and write farbfeld images.

While updating the source for this project, I created a series of benchmarks testing the serialisation code and proved the obvious fact that reading and writing a byte a time was really inefficient.

As a result of this, I'm now a little more careful when reading and writing files. The previous article on reading RIFF palettes tried to be efficient both in terms of IO (reading blocks of information at a time) and in terms of allocations (by using the same buffer object as much as possible), so hopefully that code is quite efficient.

Similarly, when writing the file as per the code below, I create a buffer large enough to hold the entire RIFF form - palettes generally aren't huge objects so this is fine. I then populate the buffer with the form and write it all at once.

There aren't any guards around this code though to ensure that buffers are reasonably sized and so if this code was being adapted (for example to read WAVE audio or AVI videos) then additional precautions would be required.

Writing int and ushort values into byte arrays

As we're going to construct the entire RIFF form in a byte array, we can't use classes such as StreamWriter to write values. I'm going to use a pair of helper methods that will break down an int into four bytes or a ushort into a pair of bytes which I will then place into the array at appropriate offsets.

Remember that RIFF uses little-endian ordering

csharp
public static void PutInt32(int value, byte[] buffer, int offset)
{
  buffer[offset + 3] = (byte)((value & 0xFF000000) >> 24);
  buffer[offset + 2] = (byte)((value & 0x00FF0000) >> 16);
  buffer[offset + 1] = (byte)((value & 0x0000FF00) >> 8);
  buffer[offset] = (byte)((value & 0x000000FF) >> 0);
}

public static void PutInt16(ushort value, byte[] buffer, int offset)
{
  buffer[offset + 1] = (byte)((value & 0x0000FF00) >> 8);
  buffer[offset] = (byte)((value & 0x000000FF) >> 0);
}

You could use the BitConverter class to break down the values, but that means extra allocations for the byte array returned by the GetBytes method.

Writing a RIFF palette

First we need to calculate the size of our data chunk for the palette, which is 4 + number_of_colors * 4. Each colour is comprised of 4 bytes, which accounts for the bulk of the chunk, but there's also 4 bytes for the palVersion and palNumEntries fields of the LOGPALETTE structure.

Once we have that size, we calculate the size of the complete RIFF form and create a byte array that will hold the entire form.

csharp
byte[] buffer;
int length;
ushort count;
ushort chunkSize;

count = (ushort)_palette.Length;
chunkSize = (ushort)(4 + count * 4);

// 4 bytes for RIFF
// 4 bytes for document size
// 4 bytes for PAL
// 4 bytes for data
// 4 bytes for chunk size
// 2 bytes for the version
// 2 bytes for the count
// (4*n) for the colors
length = 4 + 4 + 4 + 4 + 4 + 2 + 2 + count * 4;
buffer = new byte[length];

Next, we write the RIFF header. Remember that the document size is the size of the entire form minus 8 bytes representing the RIFF header.

csharp
// the riff header
buffer[0] = (byte)'R';
buffer[1] = (byte)'I';
buffer[2] = (byte)'F';
buffer[3] = (byte)'F';
WordHelpers.PutInt32(length - 8, buffer, 4); // document size

We then follow this with the form type

csharp
// the form type
buffer[8] = (byte)'P';
buffer[9] = (byte)'A';
buffer[10] = (byte)'L';
buffer[11] = (byte)' ';

So far so good. We won't be writing any meta data, only the data chunk with our basic RGB palette. First we'll write the chunk header, and then we'll write the first two fields describing the palette.

csharp
// data chunk header
buffer[12] = (byte)'d';
buffer[13] = (byte)'a';
buffer[14] = (byte)'t';
buffer[15] = (byte)'a';
WordHelpers.PutInt32(chunkSize, buffer, 16); // chunk size

// logpalette
buffer[20] = 0;
buffer[21] = 3; // os version (always 03)
WordHelpers.PutInt16(count, buffer, 22); // colour count

Now it's just a case of filling in the colour information

csharp
for (int i = 0; i < count; i++)
{
  Color color;
  int offset;

  color = _palette[i];

  offset = 24 + i * 4;

  buffer[offset] = color.R;
  buffer[offset + 1] = color.G;
  buffer[offset + 2] = color.B;
  
  // TODO: use buffer[offset + 3] for flags
}

And finally, we can write our buffer to the destination stream. Easy!

csharp
stream.Write(buffer, 0, length);

Update History

  • 2017-03-04 - First published
  • 2020-11-22 - Updated formatting

Like what you're reading? Perhaps you like to buy us a coffee?

Donate via Buy Me a Coffee

Donate via PayPal


Files


Comments