Download AdobeSwatchExchangeLoader-v2.zip version 2.0.0.0, last updated 21/10/2015 (72.79 KB)

Download
  • md5: 3591debc1f6749c23ad9bbd113dd760a
  • sha1: 9b8b0e53179b0b8d04c14324125ae5452df58305
  • sha256: 152c5051abec694120031b4b0ed923608627b0081994f99cf6d043cc1663ed20
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;

// Reading Adobe Swatch Exchange (ase) files using C#
// http://www.cyotek.com/blog/reading-adobe-swatch-exchange-ase-files-using-csharp

// Writing Adobe Swatch Exchange (ase) files using C#
// http://www.cyotek.com/blog/writing-adobe-swatch-exchange-ase-files-using-csharp

namespace AdobeSwatchExchangeLoader
{
  internal class AseDocument
  {
    #region Constructors

    public AseDocument()
    {
      this.Groups = new ColorGroupCollection();
      this.Colors = new ColorEntryCollection();
    }

    #endregion

    #region Properties

    public ColorEntryCollection Colors { get; set; }

    public ColorGroupCollection Groups { get; set; }

    #endregion

    #region Methods

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

    public void Load(Stream stream)
    {
      Stack<ColorEntryCollection> colors;
      ColorGroupCollection groups;
      ColorEntryCollection globalColors;
      int blockCount;

      groups = new ColorGroupCollection();
      globalColors = new ColorEntryCollection();
      colors = new Stack<ColorEntryCollection>();

      // add the global collection to the bottom of the stack to handle color blocks outside of a group
      colors.Push(globalColors);

      this.ReadAndValidateVersion(stream);

      blockCount = stream.ReadUInt32BigEndian();

      for (int i = 0; i < blockCount; i++)
      {
        this.ReadBlock(stream, groups, colors);
      }

      this.Groups = groups;
      this.Colors = globalColors;
    }

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

    public void Save(Stream stream)
    {
      this.WriteVersionHeader(stream);
      this.WriteBlocks(stream);
    }

    private int GetBlockLength(Block block)
    {
      int blockLength;

      // name data (2 bytes per character + null terminator, plus 2 bytes to describe that first number )
      blockLength = 2 + (((block.Name ?? string.Empty).Length + 1) * 2);

      if (block.ExtraData != null)
      {
        blockLength += block.ExtraData.Length; // data we can't process but keep anyway
      }

      return blockLength;
    }

    private int GetBlockLength(ColorEntry block)
    {
      int blockLength;

      blockLength = this.GetBlockLength((Block)block);

      blockLength += 6; // 4 bytes for the color space and 2 bytes for the color type

      // TODO: Include support for other color spaces

      blockLength += 12; // length of RGB data (3 * 4 bytes)

      return blockLength;
    }

    private void ReadAndValidateVersion(Stream stream)
    {
      string signature;
      int majorVersion;
      int minorVersion;

      // get the signature (4 ascii characters)
      signature = stream.ReadString(4);

      if (signature != "ASEF")
      {
        throw new InvalidDataException("Invalid file format.");
      }

      // read the version
      majorVersion = stream.ReadUInt16BigEndian();
      minorVersion = stream.ReadUInt16BigEndian();

      if (majorVersion != 1 && minorVersion != 0)
      {
        throw new InvalidDataException("Invalid version information.");
      }
    }

    private void ReadBlock(Stream stream, ColorGroupCollection groups, Stack<ColorEntryCollection> colorStack)
    {
      BlockType blockType;
      int blockLength;
      int offset;
      int dataLength;
      Block block;

      blockType = (BlockType)stream.ReadUInt16BigEndian();

      blockLength = stream.ReadUInt32BigEndian();

      // store the current position of the stream, so we can calculate the offset
      // from bytes read to the block length in order to skip the bits we didn't
      // read, support or know what they are
      offset = (int)stream.Position;

      // process the actual block
      switch (blockType)
      {
        case BlockType.Color:
          block = this.ReadColorBlock(stream, colorStack);
          break;
        case BlockType.GroupStart:
          block = this.ReadGroupBlock(stream, groups, colorStack);
          break;
        case BlockType.GroupEnd:
          block = null;
          colorStack.Pop();
          break;
        default:
          throw new InvalidDataException($"Unsupported block type '{blockType}'.");
      }

      // load in any custom data and attach it to the
      // current block (if available) as raw byte data
      dataLength = blockLength - (int)(stream.Position - offset);

      if (dataLength > 0)
      {
        byte[] extraData;

        extraData = new byte[dataLength];
        stream.Read(extraData, 0, dataLength);

        if (block != null)
        {
          block.ExtraData = extraData;
        }
      }
    }

    private Block ReadColorBlock(Stream stream, Stack<ColorEntryCollection> colorStack)
    {
      ColorEntry block;
      string colorMode;
      byte r;
      byte g;
      byte b;
      ColorType colorType;
      string name;
      ColorEntryCollection colors;

      // get the name of the color
      // this is stored as a null terminated string
      // with the length of the byte data stored before
      // the string data in a 16bit int
      name = stream.ReadStringBigEndian();

      // get the mode of the color, which is stored
      // as four ASCII characters
      colorMode = stream.ReadString(4);

      // read the color data
      // how much data we need to read depends on the
      // color mode we previously read
      switch (colorMode)
      {
        case "RGB ":
          // RGB is comprised of three floating point values ranging from 0-1.0
          float value1;
          float value2;
          float value3;
          value1 = stream.ReadSingleBigEndian();
          value2 = stream.ReadSingleBigEndian();
          value3 = stream.ReadSingleBigEndian();
          r = (byte)Convert.ToInt32(value1 * 255);
          g = (byte)Convert.ToInt32(value2 * 255);
          b = (byte)Convert.ToInt32(value3 * 255);
          break;
        case "CMYK":
          // CMYK is comprised of four floating point values
          throw new InvalidDataException($"Unsupported color mode '{colorMode}'.");
        case "LAB ":
          // LAB is comprised of three floating point values
          throw new InvalidDataException($"Unsupported color mode '{colorMode}'.");
        case "Gray":
          // Grayscale is comprised of a single floating point value
          throw new InvalidDataException($"Unsupported color mode '{colorMode}'.");
        default:
          throw new InvalidDataException($"Unsupported color mode '{colorMode}'.");
      }

      // the final "official" piece of data is a color type
      colorType = (ColorType)stream.ReadUInt16BigEndian();

      block = new ColorEntry
              {
                R = r,
                G = g,
                B = b,
                Name = name,
                Type = colorType
              };

      colors = colorStack.Peek();
      colors.Add(block);

      return block;
    }

    private Block ReadGroupBlock(Stream stream, ColorGroupCollection groups, Stack<ColorEntryCollection> colorStack)
    {
      ColorGroup block;
      string name;

      // read the name of the group
      name = stream.ReadStringBigEndian();

      // create the group and add it to the results set
      block = new ColorGroup
              {
                Name = name
              };

      groups.Add(block);

      // add the group color collection to the stack, so when subsequent color blocks
      // are read, they will be added to the correct collection
      colorStack.Push(block.Colors);

      return block;
    }

    private void WriteBlock(Stream stream, ColorEntry block)
    {
      int blockLength;

      blockLength = this.GetBlockLength(block);

      stream.WriteBigEndian((ushort)BlockType.Color);
      stream.WriteBigEndian(blockLength);

      this.WriteNullTerminatedString(stream, block.Name);

      stream.Write("RGB ");

      stream.WriteBigEndian((float)(block.R / 255.0));
      stream.WriteBigEndian((float)(block.G / 255.0));
      stream.WriteBigEndian((float)(block.B / 255.0));

      stream.WriteBigEndian((ushort)block.Type);

      this.WriteExtraData(stream, block.ExtraData);
    }

    private void WriteBlock(Stream stream, ColorGroup block)
    {
      int blockLength;

      blockLength = this.GetBlockLength(block);

      // write the start group block
      stream.WriteBigEndian((ushort)BlockType.GroupStart);
      stream.WriteBigEndian(blockLength);
      this.WriteNullTerminatedString(stream, block.Name);
      this.WriteExtraData(stream, block.ExtraData);

      // write the colors in the group
      foreach (ColorEntry color in block.Colors)
      {
        this.WriteBlock(stream, color);
      }

      // and write the end group block
      stream.WriteBigEndian((ushort)BlockType.GroupEnd);
      stream.WriteBigEndian(0); // there isn't any data, but we still need to specify that
    }

    private void WriteBlocks(Stream stream)
    {
      int blockCount;

      blockCount = (this.Groups.Count * 2) + this.Colors.Count + this.Groups.Sum(group => group.Colors.Count);

      stream.WriteBigEndian(blockCount);

      // write the global colors first
      // not sure if global colors + groups is a supported combination however
      foreach (ColorEntry color in this.Colors)
      {
        this.WriteBlock(stream, color);
      }

      // now write the groups
      foreach (ColorGroup group in this.Groups)
      {
        this.WriteBlock(stream, group);
      }
    }

    private void WriteExtraData(Stream stream, byte[] data)
    {
      if (data != null && data.Length > 0)
      {
        stream.Write(data, 0, data.Length);
      }
    }

    private void WriteNullTerminatedString(Stream stream, string value)
    {
      if (value == null)
      {
        value = string.Empty;
      }

      stream.WriteBigEndian(value + '\0');
    }

    private void WriteVersionHeader(Stream stream)
    {
      stream.Write("ASEF");
      stream.WriteBigEndian((ushort)1);
      stream.WriteBigEndian((ushort)0);
    }

    #endregion
  }
}

Donate

Donate