Download RiffPaletteWriter.zip version 1.0.0.0, last updated 04/03/2017 (14.94 KB)

Download
  • md5: 2864394d3c1cc948887ced13c5d5bedf
  • sha1: dc44b4f4d8d6318030d6460a5f88f43aa345ce2f
  • sha256: 8781446c207bc5faf4021226bdbd3789c6acdd21efbaeaa101dbcca0a2749abf
using System.Collections;
using System.Collections.Generic;
using System.Drawing;
using System.IO;

// Loading Microsoft RIFF Palette (pal) files with C#
// http://cyotek.com/blog/loading-microsoft-riff-palette-pal-files-with-csharp

// Writing Microsoft RIFF Palette (pal) files with C#
// http://cyotek.com/blog/writing-microsoft-riff-palette-pal-files-with-csharp

namespace Cyotek.Demonstrations.RiffPaletteWriter
{
  internal sealed class RiffPalette : IEnumerable<Color>
  {
    #region Fields

    private Color[] _palette;

    #endregion

    #region Constructors

    public RiffPalette()
    {
      _palette = new Color[0];
    }

    public RiffPalette(Color[] colors)
    {
      _palette = (Color[])colors.Clone();
    }

    #endregion

    #region Methods

    public void Load(string fileName)
    {
      using (Stream stream = File.OpenRead(fileName))
      {
        this.Load(stream);
      }
    }

    public void Load(Stream stream)
    {
      byte[] buffer;
      bool eof;

      buffer = new byte[1032]; // create a buffer big enough to hold a default 256 color palette

      // first read and validate the form header
      // this is comprised of
      // * RIFF header
      // * document size
      // * FORM header
      stream.Read(buffer, 0, 12);
      if (buffer[0] != 'R' || buffer[1] != 'I' || buffer[2] != 'F' || buffer[3] != 'F')
      {
        throw new InvalidDataException("Source stream is not a RIFF document.");
      }

      // TODO: Validate the document size if required

      if (buffer[8] != 'P' || buffer[9] != 'A' || buffer[10] != 'L' || buffer[11] != ' ')
      {
        throw new InvalidDataException("Source stream is not a palette.");
      }

      eof = false;

      while (!eof)
      {
        // a RIFF document can have one or more chunks.
        // for our purposes, each chunk has a 4 letter
        // identifier and a size of the data part of the
        // chunk. We can use these two values to either
        // process the palette data, or skip data we
        // don't recognise

        // TODO: This procedure only supports simple, not extended, palettes

        if (stream.Read(buffer, 0, 8) == 8)
        {
          int chunkSize;

          chunkSize = buffer.GetInt32(4);

          // see if we have the palette data
          if (buffer[0] == 'd' && buffer[1] == 'a' && buffer[2] == 't' && buffer[3] == 'a')
          {
            if (chunkSize > buffer.Length)
            {
              buffer = new byte[chunkSize];
            }

            if (stream.Read(buffer, 0, chunkSize) != chunkSize)
            {
              throw new InvalidDataException("Failed to read enough data to match chunk size.");
            }

            _palette = this.ReadPalette(buffer);

            // only one palette in a file, so break out of the chunk scan
            eof = true;
          }
          else
          {
            // not the palette data? advance the stream to the next chunk

            // riff chunks are word-aligned. if the size is odd
            // then a padding byte is included after the chunk
            // data. This is junk data, so we just skip it
            if (chunkSize % 2 != 0)
            {
              chunkSize++;
            }

            stream.Position += chunkSize;
          }
        }
        else
        {
          // nothing to read, abort
          eof = true;
        }
      }
      ;
    }

    public void Save(string fileName)
    {
      using (Stream stream = File.Create(fileName))
      {
        this.Save(stream);
      }
    }

    public void Save(Stream stream)
    {
      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];

      // 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);

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

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

      // logpalette
      buffer[20] = 0;
      buffer[21] = 3;
      WordHelpers.PutInt16(count, buffer, 22);

      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;
      }

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

    private Color[] ReadPalette(byte[] buffer)
    {
      Color[] palette;
      ushort count;

      // The buffer should hold a LOGPALETTE structure containing
      // OS version (2 bytes, always 03)
      // Color count (2 bytes)
      // Color data (4 bytes * color count)

      count = buffer.GetInt16(2);

      palette = new Color[count];

      for (int i = 0; i < count; i++)
      {
        byte r;
        byte g;
        byte b;
        int offset;

        offset = i * 4 + 4;
        r = buffer[offset];
        g = buffer[offset + 1];
        b = buffer[offset + 2];

        // TODO: The fourth byte are flags, which we have no use for here

        palette[i] = Color.FromArgb(r, g, b);
      }

      return palette;
    }

    #endregion

    #region IEnumerable<Color> Interface

    public IEnumerator<Color> GetEnumerator()
    {
      // ReSharper disable once ForCanBeConvertedToForeach
      // ReSharper disable once LoopCanBeConvertedToQuery
      for (int i = 0; i < _palette.Length; i++)
      {
        yield return _palette[i];
      }
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
      return this.GetEnumerator();
    }

    #endregion
  }
}

Donate

Donate