Download PhotoshopColorSwatchWriter.zip version 1.0.0.0, last updated 28/01/2014 (16.86 KB)

Download
  • md5: c5498aad2487ba4d5b87707f27e9eb30
// Writing PhotoShop Color Swatch (aco) files using C#
// http://cyotek.com/blog/writing-photoshop-color-swatch-aco-files-using-csharp
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Text;
using System.Windows.Forms;
using Cyotek.Windows.Forms;

namespace PhotoshopColorSwatchWriter
{
  internal sealed partial class GeneratorWindow : Form
  {
    #region Enums

    private enum ColorSpace
    {
      Rgb = 0,

      Hsb = 1,

      Cmyk = 2,

      Lab = 7,

      Grayscale = 8
    }

    private enum FileVersion
    {
      Version1 = 1,

      Version2
    }

    #endregion

    #region Instance Fields

    private List<Color> _comparePalette;

    private List<Color> _originalPalette;

    private Random _random;

    private string _tempFileName;

    #endregion

    #region Public Constructors

    public GeneratorWindow()
    {
      InitializeComponent();
    }

    #endregion

    #region Overridden Methods

    /// <summary>
    /// Raises the <see cref="E:System.Windows.Forms.Form.Load"/> event.
    /// </summary>
    /// <param name="e">An <see cref="T:System.EventArgs"/> that contains the event data. </param>
    protected override void OnLoad(EventArgs e)
    {
      base.OnLoad(e);

      _random = new Random();

      _tempFileName = Path.GetTempFileName();

      this.GenerateAndSavePalette();
    }

    #endregion

    #region Private Members

    private void GenerateAndSavePalette()
    {
      // generate our palette
      _originalPalette = this.GeneratePalette();

      // show a preview in the first pane
      originalPalettePanel.Colors = _originalPalette;

      // save and update the compare preview
      this.SaveAndComparePalette();
    }

    private List<Color> GeneratePalette()
    {
      List<Color> palette;

      palette = new List<Color>();
      for (int i = 0; i < 255; i++)
      {
        int r;
        int g;
        int b;

        r = _random.Next(0, 255);
        g = _random.Next(0, 255);
        b = _random.Next(0, 255);

        palette.Add(Color.FromArgb(r, g, b));
      }

      return palette;
    }

    /// <summary>
    /// Reads a 16bit unsigned integer in big-endian format.
    /// </summary>
    /// <param name="stream">The stream to read the data from.</param>
    /// <returns>The unsigned 16bit integer cast to an <c>Int32</c>.</returns>
    private int ReadInt16(Stream stream)
    {
      return (stream.ReadByte() << 8) | (stream.ReadByte() << 0);
    }

    /// <summary>
    /// Reads a 32bit unsigned integer in big-endian format.
    /// </summary>
    /// <param name="stream">The stream to read the data from.</param>
    /// <returns>The unsigned 32bit integer cast to an <c>Int32</c>.</returns>
    private int ReadInt32(Stream stream)
    {
      return ((byte)stream.ReadByte() << 24) | ((byte)stream.ReadByte() << 16) | ((byte)stream.ReadByte() << 8) | ((byte)stream.ReadByte() << 0);
    }

    private List<Color> ReadPhotoshopSwatchFile(string fileName)
    {
      List<Color> colorPalette;

      using (Stream stream = File.OpenRead(fileName))
      {
        FileVersion version;

        // read the version, which occupies two bytes
        version = (FileVersion)this.ReadInt16(stream);

        if (version != FileVersion.Version1 && version != FileVersion.Version2)
          throw new InvalidDataException("Invalid version information.");

        // the specification states that a version2 palette follows a version1
        // the only difference between version1 and version2 is the inclusion 
        // of a name property. Perhaps there's addtional color spaces as well
        // but we can't support them all anyway
        // I noticed some files no longer include a version 1 palette

        colorPalette = this.ReadSwatches(stream, version);
        if (version == FileVersion.Version1)
        {
          version = (FileVersion)this.ReadInt16(stream);
          if (version == FileVersion.Version2)
            colorPalette = this.ReadSwatches(stream, version);
        }
      }

      return colorPalette;
    }

    /// <summary>
    /// Reads a unicode string of the specified length.
    /// </summary>
    /// <param name="stream">The stream to read the data from.</param>
    /// <param name="length">The number of characters in the string.</param>
    /// <returns>The string read from the stream.</returns>
    private string ReadString(Stream stream, int length)
    {
      byte[] buffer;

      buffer = new byte[length * 2];

      stream.Read(buffer, 0, buffer.Length);

      return Encoding.BigEndianUnicode.GetString(buffer);
    }

    private List<Color> ReadSwatches(Stream stream, FileVersion version)
    {
      int colorCount;
      List<Color> results;

      results = new List<Color>();

      // read the number of colors, which also occupies two bytes
      colorCount = this.ReadInt16(stream);

      for (int i = 0; i < colorCount; i++)
      {
        ColorSpace colorSpace;
        int value1;
        int value2;
        int value3;
        int value4;

        // again, two bytes for the color space
        colorSpace = (ColorSpace)(this.ReadInt16(stream));

        value1 = this.ReadInt16(stream);
        value2 = this.ReadInt16(stream);
        value3 = this.ReadInt16(stream);
        value4 = this.ReadInt16(stream);

        if (version == FileVersion.Version2)
        {
          int length;

          // need to read the name even though currently our colour collection doesn't support names
          length = ReadInt32(stream);
          this.ReadString(stream, length);
        }

        switch (colorSpace)
        {
          case ColorSpace.Rgb:
            int red;
            int green;
            int blue;

            // RGB.
            // The first three values in the color data are red , green , and blue . They are full unsigned
            //  16-bit values as in Apple's RGBColor data structure. Pure red = 65535, 0, 0.

            red = value1 / 256; // 0-255
            green = value2 / 256; // 0-255
            blue = value3 / 256; // 0-255

            results.Add(Color.FromArgb(red, green, blue));
            break;

          case ColorSpace.Hsb:
            double hue;
            double saturation;
            double brightness;

            // HSB.
            // The first three values in the color data are hue , saturation , and brightness . They are full 
            // unsigned 16-bit values as in Apple's HSVColor data structure. Pure red = 0,65535, 65535.

            hue = value1 / 182.04; // 0-359
            saturation = value2 / 655.35; // 0-1
            brightness = value3 / 655.35; // 0-1

            results.Add(new HslColor(hue, saturation, brightness).ToRgbColor());
            break;
          case ColorSpace.Grayscale:

            int gray;

            // Grayscale.
            // The first value in the color data is the gray value, from 0...10000.

            gray = (int)(value1 / 39.0625); // 0-255

            results.Add(Color.FromArgb(gray, gray, gray));
            break;

          default:
            throw new InvalidDataException(string.Format("Color space '{0}' not supported.", colorSpace));
        }
      }

      return results;
    }

    private void SaveAndComparePalette()
    {
      // write the palette to a swatch file
      this.WriteSwatches(_originalPalette, _tempFileName);

      // load back the swatch file and display this in the second pane
      _comparePalette = this.ReadPhotoshopSwatchFile(_tempFileName);
      comparePalettePanel.Colors = _comparePalette;
    }

    /// <summary>
    /// Writes a 16bit unsigned integer in big-endian format.
    /// </summary>
    /// <param name="stream">The stream to write the data to.</param>
    /// <param name="value">The value to write</param>
    private void WriteInt16(Stream stream, short value)
    {
      stream.WriteByte((byte)(value >> 8));
      stream.WriteByte((byte)(value >> 0));
    }

    /// <summary>
    /// Writes a 32bit unsigned integer in big-endian format.
    /// </summary>
    /// <param name="stream">The stream to write the data to.</param>
    /// <param name="value">The value to write</param>
    private void WriteInt32(Stream stream, int value)
    {
      stream.WriteByte((byte)((value & 0xFF000000) >> 24));
      stream.WriteByte((byte)((value & 0x00FF0000) >> 16));
      stream.WriteByte((byte)((value & 0x0000FF00) >> 8));
      stream.WriteByte((byte)((value & 0x000000FF) >> 0));
    }

    private void WritePalette(Stream stream, ICollection<Color> palette, FileVersion version, ColorSpace colorSpace)
    {
      int swatchIndex;

      this.WriteInt16(stream, (short)version);
      this.WriteInt16(stream, (short)palette.Count);

      swatchIndex = 0;

      foreach (Color color in palette)
      {
        short value1;
        short value2;
        short value3;
        short value4;

        swatchIndex++;

        switch (colorSpace)
        {
          case ColorSpace.Rgb:
            value1 = (short)(color.R * 256);
            value2 = (short)(color.G * 256);
            value3 = (short)(color.B * 256);
            value4 = 0;
            break;
          case ColorSpace.Hsb:
            value1 = (short)(color.GetHue() * 182.04);
            value2 = (short)(color.GetSaturation() * 655.35);
            value3 = (short)(color.GetBrightness() * 655.35);
            value4 = 0;
            break;
          case ColorSpace.Grayscale:
            if (color.R == color.G && color.R == color.B)
            {
              // already grayscale
              value1 = (short)(color.R * 39.0625);
            }
            else
            {
              // color is not grayscale, convert
              value1 = (short)(((color.R + color.G + color.B) / 3.0) * 39.0625);
            }
            value2 = 0;
            value3 = 0;
            value4 = 0;
            break;
          default:
            throw new InvalidOperationException("Color space not supported.");
        }

        this.WriteInt16(stream, (short)colorSpace);
        this.WriteInt16(stream, value1);
        this.WriteInt16(stream, value2);
        this.WriteInt16(stream, value3);
        this.WriteInt16(stream, value4);

        if (version == FileVersion.Version2)
        {
          string name;

          name = color.IsNamedColor ? color.Name : string.Format("Swatch {0}", swatchIndex);

          this.WriteInt32(stream, name.Length);
          this.WriteString(stream, name);
        }
      }
    }

    /// <summary>
    /// Writes a unicode string to the specified stream in big-endian format
    /// </summary>
    /// <param name="stream">The stream.</param>
    /// <param name="value">The value.</param>
    private void WriteString(Stream stream, string value)
    {
      stream.Write(Encoding.BigEndianUnicode.GetBytes(value), 0, value.Length * 2);
    }

    private void WriteSwatches(List<Color> palette, string fileName)
    {
      ColorSpace space;

      if (grayscaleRadioButton.Checked)
        space = ColorSpace.Grayscale;
      else if (hslRadioButton.Checked)
        space = ColorSpace.Hsb;
      else
        space = ColorSpace.Rgb;

      using (Stream stream = File.Create(fileName))
      {
        this.WritePalette(stream, palette, FileVersion.Version1, space);
        this.WritePalette(stream, palette, FileVersion.Version2, space);
      }
    }

    #endregion

    #region Event Handlers

    private void aboutToolStripMenuItem_Click(object sender, EventArgs e)
    {
      using (Form dialog = new AboutDialog())
        dialog.ShowDialog(this);
    }

    private void exitToolStripMenuItem_Click(object sender, EventArgs e)
    {
      this.Close();
    }

    private void refreshButton_Click(object sender, EventArgs e)
    {
      this.GenerateAndSavePalette();
    }

    private void refreshToolStripMenuItem_Click(object sender, EventArgs e)
    {
      this.GenerateAndSavePalette();
    }

    private void rgbRadioButton_CheckedChanged(object sender, EventArgs e)
    {
      this.SaveAndComparePalette();
    }

    #endregion
  }
}

Donate

Donate