Writing Adobe Swatch Exchange (ase) file using C#

In my last post, I described how to read Adobe Swatch Exchange files using C#. Now I’m going to update that sample progtram to save ase files as well as load them.

An example of a multi-group ASE file created by the sample application

An example of a multi-group ASE file created by the sample application

Writing big endian values

I covered the basics of writing big-endian values in my original post on writing Photoshop aco files, so I’ll not cover that again but only mention the new pieces.

Firstly, we now need to store float values. I mentioned the trick that BitConverter.ToSIngle does where it converts a int to a pointer, and then the pointer to a float. I’m going to do exactly the reverse in order to write the float to a stream – convert the float to a pointer, then convert it to an int, then write the bytes of the int.

public static void WriteBigEndian(this Stream stream, float value)
{
  unsafe
  {
    stream.WriteBigEndian(*(int*)&value);
  }
}

We also need to store unsigned 2-byte integers, so we have another extension for that.

public static void WriteBigEndian(this Stream stream, ushort value)
{
  stream.WriteByte((byte)(value >> 8));
  stream.WriteByte((byte)(value >> 0));
}

Finally, let’s not forget our length prefixed strings!

public static void WriteBigEndian(this Stream stream, string value)
{
  byte[] data;

  data = Encoding.BigEndianUnicode.GetBytes(value);

  stream.WriteBigEndian(value.Length);
  stream.Write(data, 0, data.Length);
}

Saving the file

I covered the format of an ase file in the previous post, so I won’t cover that again either. In summary, you have a version header, a block count, then a number of blocks – of which a block can either be a group (start or end) or a color.

Saving the version header is rudimentry

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

After this, we write the number of blocks, then cycle each group and color in our document.

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

Writing a block is slightly complicated as you need to know – up front – the final size of all of the data belonging to that block. Originally I wrote the block to a temporary MemoryStream, then copied the length and the data into a real stream but that isn’t a very efficient approach, so now I just calculate the block size.

Writing Groups

If you recall from the previous article, a group is comprised of a least two blocks – one that starts the group (and includes the name), and one that finishes the group. There can also be any number of color blocks in between. Potentially you can have nested groups, but I haven’t coded for this yet – I need to experiment with ase files some more, at which point I’ll update these samples if need be.

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

Writing Colors

Writing a color block is fairly painless, at least for RGB colors. As with loading an ase file, I’m completely ignoring the existence of Lab, CMYK and Gray scale colors.

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

Caveats

When I originally tested this code, I added a simple compare function which compared the bytes of a source ase file with a version written by the new code. For two of the three samples I was using, this was fine, but for the third the files didn’t match. As this didn’t help me in any way diagnose the issue, I ended up writing a very basic (and inefficient) hex viewer, artfully highlighted using the same colors as the ase format description on sepla.net

Comparing a third party ASE file with the version created by the sample application

Comparing a third party ASE file with the version created by the sample application

This allowed me to easily view the files side by side and be able to break them down into their sections to see what was wrong. The example screenshot above shows an identical comparison.

Another compare of a third party ASE file with the version created by the sample application, showing the color data is the same, but the raw file differs

Another compare of a third party ASE file with the version created by the sample application, showing the color data is the same, but the raw file differs

With that third sample file, it was more complicated. In the first case, the file sizes were different – the hex viewer very clearly showed that the sample file has 3 extra null bytes at the end of the file, which my version doesn’t bother writing. I’m not entirely sure what these bytes are for, but I can’t imagine they are official as it’s an odd number.

The second issue was potentially more problematic. In the screenshot above, you can see all the orange values which are the float point representations of the RGB colors, and the last byte of each of these values does not match. However, the translated RGB values do match, so I guess it is a rounding/precision issue.

When I turn this into something more production ready, I will probably store the original floating point values and write them back, rather than loosing precision by converting them to integers (well, bytes really as the range is 0-255) and back again.

 

Download

AdobeSwatchExchangeLoader-v2.zip 10/20/2015 324 Kb

Reading Adobe Swatch Exchange (ASE) files using C#

Previously I wrote articles on how to read and writes files using the Photoshop Color Swatch file format. In this article, I’m now going to take a long overdue look at Adobe’s  Swatch Exchange file format and show how to read these files using C#.

An example of an ASE file with a single group containing 5 RGB colors

An example of an ASE file with a single group containing 5 RGB colors

Note

Unlike some of Adobe’s other specification, they don’t seem to have published an official specification for the ase format. For the purposes of this article, I’ve had to manually poke around in sample files using a HEX editor to undercover their structure.

And, as with my previous articles, the code doesn’t handle CMYK or Lab color spaces.

 

Structure of a Adobe Swatch Exchange file

ase files support the notion of groups, so you can have multiple groups containing colors. Judging by the files I have tested, you can also have a bunch of colors without a group at all. I’m uncertain if groups can be nested, so I have assumed they cannot be.

With that said, the structure is relatively straight forward, and helpfully includes data that means I can skip the bits that I have no idea at all what they are. The format is comprised of a basic version header, then a number of blocks. Each block includes a type, data length, the block name, and then additional data specific to the block type, and optional custom data specific to that particular block.

Blocks can either be a color, the start of a group, or the end of a group.

Color blocks include the color space, 1-4 floating point values that describe the color (3 for RGB and LAB, 4 for CMYK and 1 for Grayscale), and a type.

Finally, all blocks can carry custom data. I have no idea what this data is, but it doesn’t seem to be essential nor are you required to know what it is for in order to pull out the color information. Fortunately, as you know how large each block is, you can skip the remaining bytes from the block and move onto the next one. As there seems to be little difference between the purposes of aco and asefiles (the obvious one being that the former is just a list of colors while the latter supports grouping). I assume this data is metadata from the application that created the file, but this is just a conjecture.

The following table attempts to describe the layout.

Length Description
4 Signature
2 Major Version
2 Minor Version
4 Number of blocks
variable Block data
Length Description
2 Type
4 Block length
2 Name length
(name length) Name
Colour blocks only
Length Description
4 Colour space
12 (RGB, LAB), 16 (CMYK), 4 (Grayscale) Colour data. Every four bytes represents one floating point value
2 Colour type
All blocks
Length Description
variable (Block length – previously read data) Unknown

As with aco files, all the data in an ase file is stored in big-endian format and therefore needs to be reversed on Windows systems. Unlike the aco files where four values are present for each color even if not required by the appropriate color space, the ase format uses between one and four values, make it slightly more compact than aco.

 

Color Spaces

I mentioned above that each color has a description of what color space it belongs to. There appear to be four supported color spaces. Note that space names are 4 characters long in an ase file, shorter names are therefore padded with spaces.

  • RGB
  • LAB
  • CMYK
  • Gray

In my experiments, RGB was easy enough – just multiply the value read from the file by 255 to get the right value to use with .NET’s Color structure. I don’t know the other 3 types however – I need more samples!

 

Big-endian conversion

I covered the basics of reading shorts, ints, and strings in big-endian in my previous article on aco files so I won’t cover that here.

However, this time around I do need to read floats from the files too. While the BitConverter class has a ToSingle method that will convert a 4-byte array to a float, of course it is for little-endian.

I looked at the reference source for this method and saw it does a neat little trick – it converts the four bytes to an integer, then creates a float from that integer via pointers.

So, I used the same approach – read an int in big-endian, then convert it to a float. The only caveat is that you are using pointers, meaning unsafe code. By default you can’t use the unsafe keyword without enabling a special option in project properties. I use unsafe code quite frequently when working with image data and generally don’t have a problem, if you are unwilling to enable this option then you can always take the four bytes, reverse the, and then call BitConverter.ToSingle with the reversed array.

public static float ReadSingleBigEndian(this Stream stream)
{
  unsafe
  {
    int value;

    value = stream.ReadUInt32BigEndian();

    return *(float*)&value;
  }
}

Another slight difference between aco and ase files is that in ase files, strings are null terminated, and the name length includes that terminator. Of course, when reading the strings back out, we really don’t want that terminator to be included. So I added another helper method to deal with that.

public static string ReadStringBigEndian(this Stream stream)
{
  int length;
  string value;

  // string is null terminated, value saved in file includes the terminator

  length = stream.ReadUInt16BigEndian() - 1;
  value = stream.ReadStringBigEndian(length);
  stream.ReadUInt16BigEndian(); // read and discard the terminator

  return value;
}

 

Storage classes

In my previous examples on reading color data from files, I’ve kept it simple and returned arrays of colors, discarding incidental details such as names. This time, I’ve created a small set of helper classes. to preserve this information and to make it easier to serialize it.

internal abstract class Block
{
  public byte[] ExtraData { get; set; }
  public string Name { get; set; }
}

internal class ColorEntry : Block
{
  public int B { get; set; }
  public int G { get; set; }
  public int R { get; set; }
  public ColorType Type { get; set; }

  public Color ToColor()
  {
    return Color.FromArgb(this.R, this.G, this.B);
  }
}

internal class ColorEntryCollection : Collection<ColorEntry>
{ }

internal class ColorGroup : Block, IEnumerable<ColorEntry>
{
  public ColorGroup()
  {
    this.Colors = new ColorEntryCollection();
  }

  public ColorEntryCollection Colors { get; set; }

  public IEnumerator<ColorEntry> GetEnumerator()
  {
    return this.Colors.GetEnumerator();
  }

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

internal class ColorGroupCollection : Collection<ColorGroup>
{ }

internal class SwatchExchangeData
{
  public SwatchExchangeData()
  {
    this.Groups = new ColorGroupCollection();
    this.Colors = new ColorEntryCollection();
  }

  public ColorEntryCollection Colors { get; set; }
  public ColorGroupCollection Groups { get; set; }
}

That should be all we need, time to load some files!

 

Reading the file

To start with, we create a new ColorEntryCollection that will be used for global colors (i.e. color blocks that don’t appear within a group). To make things simple, I’m also creating a Stack to which I push this global collection. Later on, when I encounter a start group block, I’ll Push a new ColorEntryCollection to this stack, and when I encounter an end group block, I’ll Pop the value at the top of the stack. This way, when I encounter a color block, I can easily add it to the right collection without needing to explicitly keep track of the active group or lack thereof.

public void Load(string fileName)
{
  Stack<ColorEntryCollection> colors;
  ColorGroupCollection groups;
  ColorEntryCollection globalColors;

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

  using (Stream stream = File.OpenRead(fileName))
  {
    int blockCount;

    this.ReadAndValidateVersion(stream);

    blockCount = stream.ReadUInt32BigEndian();

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

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

After opening a Stream containing our file data, we need to check that the stream contains both ase data, and that the data is a version we can read. This is done by reading 8 bytes from the start of the data. The first four are ASCII characters which should match the string ASEFASEF, the next two are the major version and the final two the minor version.

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

  // get the signature (4 ascii characters)
  signature = stream.ReadAsciiString(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.");
  }
}

Assuming the data is valid, we read the number of blocks in the file, and enter a loop to process each block. For each block, first we read the type of the block, and then the length of the block’s data.

How we continue reading from the stream depends on the block type (more on that later), after which we work out how much data is left in the block, read it, and store it as raw bytes on the off-chance the consuming application can do something with it, or for saving back into the file.

This technique assumes that the source stream is seekable. If this is not the case, you’ll need to manually keep track of how many bytes you have read from the block to calculate the remaining custom data left to read.

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 can't use
  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;
    }
  }
}

Processing groups

If we have found a “start group” block, then we create a new ColorGroup object and read the group name. We also push the group’s ColorEntryCollection to the stack I mentioned earlier.

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 colour blocks
  // are read, they will be added to the correct collection
  colorStack.Push(block.Colors);

  return block;
}

For “end group” blocks, we don’t do any custom processing as I do not think there is any data associated with these. Instead, we just pop the last value from our color stack. (Of course, that means if there is a malformed ase file containing a group end without a group start, this procedure is going to crash sooner or later!)

Processing colors

When we hit a color block, we read the color’s name and the color mode.

Then, depending on the mode, we read between 1 and 4 float values which describe the color. As anything other than RGB processing is beyond the scope of this article, I’m throwing an exception for the LAB, CMYK and Gray color spaces.

For RGB colors, I take each value and multiply it by 255 to get a value suitable for use with .NET’s Color struct.

After reading the color data, there’s one official value left to read, which is the color type. This can either be Global (0), Spot (1) or Normal (2).

Finally, I construct a new ColorEntry obect containing the color information and add it to whatever ColorEntryCollection is on top of the stack.

private Block ReadColorBlock(Stream stream, Stack<ColorEntryCollection> colorStack)
{
  ColorEntry block;
  string colorMode;
  int r;
  int g;
  int 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.ReadAsciiString(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 = Convert.ToInt32(value1 * 255);
      g = Convert.ToInt32(value2 * 255);
      b = 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;
}

 

Done

 

An example of a group-less ASE file

An example of a group-less ASE file

The ase format is pretty simple to process, although the fact there is still data in these files with an unknown purpose could be a potential issue.

However, I have tested this code on a number of files downloaded from the internet and have been able to pull out all the color information, so I suspect the Swatch Buckler library will be getting ase support fairly soon!

 

Download

AdobeSwatchExchangeLoader-v2.zip 10/16/2015 152 Kb

Oil Painting Filter in C#

Converting an image to make it look like an oil painting is not only a nice effect, but is also an easy to implement algorithm. This article demonstrates how to create an oil painting effect from an image. The oil painting filter consists of two main components: color gradients and pixel color intensities. Result images express a lesser degree of detail and tend to appear to have smaller color ranges.

Implementation

The following steps are required for the algorithm:

  1. Iterate each pixel – Every pixel forming part of the source image should be iterated. When iterating a pixel determine the neighboring pixel values based on the specified filter size (or radius).
  2. Calculate color intensityDetermine the Color Intensity of each pixel being iterated and that of the neighboring pixels. The neighboring pixels included should extend to a range determined by the Filter Size specified. The calculated value should be reduced in order to match a value ranging from zero to the number of Intensity Levels specified.
  3. Determine maximum neighborhood color intensity – When calculating the color intensities of a pixel neighborhood determine the maximum intensity value. In addition, record the occurrence of each intensity level and sum each of the Red, Green and Blue pixel color component values equating to the same intensity level.
  4. Assign the result pixel – The value assigned to the corresponding pixel in the resulting image equates to the pixel color sum total, where those pixels expressed the same intensity level. The sum total should be averaged by dividing the color sum total by the intensity level occurrence

When calculating color intensity reduced to fit the number of levels specified the formula can be expressed as follows:

colorintensityalgorithm.png

The variables in the above formula can be described as follows:

  • i – Intensity: the calculated intensity value.
  • R – Red: The value of the pixel’s red color component.
  • G – Green:
  • B – Blue:
  • l – Number of intensity levels: the maximum number of intensity levels specified.

Result Image: Filter 15, Levels 30

Oil Painting - Filter 15, Levels 30

The sample source code defines OilPaintFilter method, an extension method targeting the Bitmap class. This method determines the maximum color intensity from a pixel’s neighbors.

public static Bitmap OilPaintFilter(this Bitmap sourceBitmap, int levels, int filterSize)
        {
            Bitmap image;
            ArgbColor[] originalData;
            ArgbColor[] resultData;
            Size size;

            image = sourceBitmap;
            size = image.Size;

            originalData = image.GetPixelsFrom32BitArgbImage();
            resultData = new ArgbColor[size.Width * size.Height];

            int[] intensityBin = new int[levels];
            int[] blueBin = new int[levels];
            int[] greenBin = new int[levels];
            int[] redBin = new int[levels];

            levels = levels - 1;

            int filterOffset = (filterSize - 1) / 2;
            int byteOffset = 0;
            int calcOffset = 0;
            int currentIntensity = 0;
            int maxIntensity = 0;
            int maxIndex = 0;

            double blue = 0;
            double green = 0;
            double red = 0;

            for (int offsetY = filterOffset; offsetY &lt; size.Height - filterOffset; offsetY++)
            {
                for (int offsetX = filterOffset; offsetX &lt; size.Width - filterOffset; offsetX++)
                {
                    ArgbColor result;

                    blue = green = red = 0;

                    currentIntensity = maxIntensity = maxIndex = 0;

                    intensityBin = new int[levels + 1];
                    blueBin = new int[levels + 1];
                    greenBin = new int[levels + 1];
                    redBin = new int[levels + 1];

                    byteOffset = offsetY * size.Width + offsetX;

                    for (int filterY = -filterOffset; filterY &lt;= filterOffset; filterY++)
                    {
                        for (int filterX = -filterOffset; filterX &lt;= filterOffset; filterX++)
                        {
                            calcOffset = byteOffset + (filterX) + (filterY * size.Width);

                            currentIntensity = (int)Math.Round(((double)(originalData[calcOffset].R +
                                                originalData[calcOffset].G + originalData[calcOffset].B) / 3.0 * (levels)) / 255.0);

                            intensityBin[currentIntensity] += 1;
                            blueBin[currentIntensity] += originalData[calcOffset].B;
                            greenBin[currentIntensity] += originalData[calcOffset].G;
                            redBin[currentIntensity] += originalData[calcOffset].R;

                            if (intensityBin[currentIntensity] &gt; maxIntensity)
                            {
                                maxIntensity = intensityBin[currentIntensity];
                                maxIndex = currentIntensity;
                            }
                        }
                    }

                    blue = blueBin[maxIndex] / maxIntensity;
                    green = greenBin[maxIndex] / maxIntensity;
                    red = redBin[maxIndex] / maxIntensity;

                    result.A = 255;
                    result.R = ClipByte(red);
                    result.G = ClipByte(green);
                    result.B = ClipByte(blue);

                    resultData[byteOffset] = result;
                }
            }

            return resultData.ToBitmap(size); ;
        }

Downloads

OilPaintingFilter.zip 13 Nov 2014 137 KB

Kuwahara Filter in C#

The Kuwahara filter is probably one of those things that you haven’t heard of much, if ever. It’s a noise reduction/blurring technique for images that preserves edges, similar to a median filter. It acts like a box blur, except we take that box around the center pixel and divide that into four smaller boxes (with some overlap since the number of items in the box is usually odd along the x and y axis). These smaller boxes are calculated in the same fashion as the box blur for the average value. While we’re doing that, we’re also performing another step: Finding the variance for the box. We want to know which box has the least amount of variance. The variance can be calculated in a number of ways but the easiest is simply finding the minimum and maximum values (for r, g and b) for a box and subtracting the min from the max. The bit of code below is based on my Visual Basic source used in RasterWave:

public static Bitmap KuwaharaBlur(Bitmap Image, int Size)
{
    System.Drawing.Bitmap TempBitmap = Image;
    System.Drawing.Bitmap NewBitmap = new System.Drawing.Bitmap(TempBitmap.Width, TempBitmap.Height);
    System.Drawing.Graphics NewGraphics = System.Drawing.Graphics.FromImage(NewBitmap);
    NewGraphics.DrawImage(TempBitmap, new System.Drawing.Rectangle(0, 0, TempBitmap.Width, TempBitmap.Height), new System.Drawing.Rectangle(0, 0, TempBitmap.Width, TempBitmap.Height), System.Drawing.GraphicsUnit.Pixel);
    NewGraphics.Dispose();
    Random TempRandom = new Random();
    int[] ApetureMinX = { -(Size / 2), 0, -(Size / 2), 0 };
    int[] ApetureMaxX = { 0, (Size / 2), 0, (Size / 2) };
    int[] ApetureMinY = { -(Size / 2), -(Size / 2), 0, 0 };
    int[] ApetureMaxY = { 0, 0, (Size / 2), (Size / 2) };
    for (int x = 0; x < NewBitmap.Width; ++x)
    {
        for (int y = 0; y < NewBitmap.Height; ++y)
        {
            int[] RValues = { 0, 0, 0, 0 };
            int[] GValues = { 0, 0, 0, 0 };
            int[] BValues = { 0, 0, 0, 0 };
            int[] NumPixels = { 0, 0, 0, 0 };
            int[] MaxRValue = { 0, 0, 0, 0 };
            int[] MaxGValue = { 0, 0, 0, 0 };
            int[] MaxBValue = { 0, 0, 0, 0 };
            int[] MinRValue = { 255, 255, 255, 255 };
            int[] MinGValue = { 255, 255, 255, 255 };
            int[] MinBValue = { 255, 255, 255, 255 };
            for (int i = 0; i < 4; ++i)
            {
                for (int x2 = ApetureMinX[i]; x2 < ApetureMaxX[i]; ++x2)
                {
                    int TempX = x + x2;
                    if (TempX >= 0 && TempX < NewBitmap.Width)
                    {
                        for (int y2 = ApetureMinY[i]; y2 < ApetureMaxY[i]; ++y2)
                        {
                            int TempY = y + y2;
                            if (TempY >= 0 && TempY < NewBitmap.Height)
                            {
                                Color TempColor = TempBitmap.GetPixel(TempX, TempY);
                                RValues[i] += TempColor.R;
                                GValues[i] += TempColor.G;
                                BValues[i] += TempColor.B;
                                if (TempColor.R > MaxRValue[i])
                                {
                                    MaxRValue[i] = TempColor.R;
                                }
                                else if (TempColor.R < MinRValue[i])
                                {
                                    MinRValue[i] = TempColor.R;
                                }
 
                                if (TempColor.G > MaxGValue[i])
                                {
                                    MaxGValue[i] = TempColor.G;
                                }
                                else if (TempColor.G < MinGValue[i])
                                {
                                    MinGValue[i] = TempColor.G;
                                }
 
                                if (TempColor.B > MaxBValue[i])
                                {
                                    MaxBValue[i] = TempColor.B;
                                }
                                else if (TempColor.B < MinBValue[i])
                                {
                                    MinBValue[i] = TempColor.B;
                                }
                                ++NumPixels[i];
                            }
                        }
                    }
                }
            }
            int j = 0;
            int MinDifference = 10000;
            for (int i = 0; i < 4; ++i)
            {
                int CurrentDifference = (MaxRValue[i] - MinRValue[i]) + (MaxGValue[i] - MinGValue[i]) + (MaxBValue[i] - MinBValue[i]);
                if (CurrentDifference < MinDifference && NumPixels[i] > 0)
                {
                    j = i;
                    MinDifference = CurrentDifference;
                }
            }
 
            Color MeanPixel = Color.FromArgb(RValues[j] / NumPixels[j],
                GValues[j] / NumPixels[j],
                BValues[j] / NumPixels[j]);
            NewBitmap.SetPixel(x, y, MeanPixel);
        }
    }
    return NewBitmap;
}

The code above takes in an image as well as the size that you want the aperture to be. In turn it gives you a smoothed image. This filter technique is quite different from other blur filters as it produces a lot more stylized look. Hopefully this helps you out in some way.

Writing Adobe Color Swatch (.aco) files using C#

Getting started

The previous article described how to read files in Adobe’s Swatch File format as used by Photoshop and other high-end photo editors. In this accompanying article, I’ll describe how to write such files. I’m not going to go over the structure again, so you haven’t already done so, please read the previous article for full details on the file structure and how to read it.

Writing big-endian values

All the data in an aco file is stored in big-endian format and therefore needs to be reversed on Windows systems before writing it back into the file.

We can use the following two methods to write a short or an int respectively into a stream as a series of bytes. Of course, if you just want functions to convert these into bytes you could use similar code, just remove the bit-shift.

private void WriteInt16(Stream stream, short value)
{
  stream.WriteByte((byte)(value >> 8));
  stream.WriteByte((byte)(value >> 0));
}
 
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));
}

As with the equivalent read functions, the >> 0 shift is unnecessary but it does clarify the code.

We also need to store color swatch names, so again we’ll make use of the Encoding.BigEndianUnicode property to convert a string into a series of bytes to write out.

private void WriteString(Stream stream, string value)
{
  stream.Write(Encoding.BigEndianUnicode.GetBytes(value), 0, value.Length * 2);
}

Writing the file

When writing the file, I’m going to follow the specification’s suggestion of writing a version 1 palette (for backwards compatibility), followed by a version 2 palette (for applications that support swatch names).

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

The core save routine follows. First, we write the version of format and then the number of colors in the palette.

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;

With that done, we loop through each color, calculate the four values that comprise the color data and then write that.

If it’s a version 2 file, we also write the swatch name. As these basic examples are just using the Color class, there’s no real flexibility in names, so we cheat – if it’s a “named” color, then we use the Color.Name property. Otherwise, we generate a Swatch name.

  foreach (Color color in palette)
  {
    short value1;
    short value2;
    short value3;
    short value4;
 
    swatchIndex++;
 
    switch (colorSpace)
    {
      // Calculate color space values here!
      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);
    }
  }
}

Converting color spaces

As previously mentioned, the specification states that each color is comprised of four values. Even if a particular color space doesn’t use all four (for example Grayscale just uses one), you still need to write the other values, typically as zero’s.

Although it’s a slight duplication, I’ll include the description table for color spaces to allow easy reference of the value types.

Id Description
0 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 RGBColordata structure. Pure red = 65535, 0, 0.
7 Lab. The first three values in the color data are lightness, a chrominance, and b chrominance. Lightness is a 16-bit value from 0…10000. Chrominance components are each 16-bit values from -12800…12700. Gray values are represented by chrominance components of 0. Pure white = 10000,0,0.
1 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 HSVColordata structure. Pure red = 0,65535, 65535.
8 Grayscale. The first value in the color data is the gray value, from 0…10000.
2 CMYK. The four values in the color data are cyan, magenta, yellow, and black. They are full unsigned 16-bit values. For example, pure cyan = 0,65535,65535,65535.

While supporting CMYK colors are beyond the scope of this article as they require color profiles, we can easily support RGB, HSL and Grayscale spaces.

RGB is the simplest as .NET colors are already in this format. The only thing we have to do is divide each channel by 255 as the specification uses the range 0-65535 rather than the typical 0-255.

Notice value4 is simply initialized to zero as this space only needs 3 of the 4 values.

case ColorSpace.Rgb:
  value1 = (short)(color.R * 256);
  value2 = (short)(color.G * 256);
  value3 = (short)(color.B * 256);
  value4 = 0;
  break;

We can also support HSL without too much trouble as the Color class already includes methods for extracting these values. Again, we need to do a little fiddling to change the number into the range used by the specification.

case ColorSpace.Hsb:
  value1 = (short)(color.GetHue() * 182.04);
  value2 = (short)(color.GetSaturation() * 655.35);
  value3 = (short)(color.GetBrightness() * 655.35);
  value4 = 0;
  break;

The last format we can easily support is grayscale. If the source color is already gray (i.e. the red, green and blue channels are all the same value), then we use that, otherwise we’ll average the 3 channels and use that as the value.

case ColorSpace.Grayscale:
  if (color.R == color.G &amp;&amp; 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;

Demo Application

The sample generates a random 255 color palette, then writes this to a temporary file using the specified color space. It then reads it back in, and displays both palettes side by side for comparison.

Downloads

PhotoshopColorSwatchWriter.zip 21 July 2014 78.7 KB

Reading Adobe Color Swatch (.aco) files using C#

Structure of a Photoshop color swatch file

The structure of the aco file is straightforward, helped by Adobe themselves publishing the specification which is something to appreciate. This article was created using the October 2013 edition of this specification.

According to the specification, there’s two versions of the format both of which are are fairly similar. The specification also implies that applications which support version 2 should write a version 1 palette first, which would admirably solve backwards compatibility problems. In practice this doesn’t seem to be the case, as some of the files I tested only had version 2 palettes in them.

The structure is simple. There’s a 2-byte version code, followed by 2-bytes describing the number of colors. Then, for each color, there are 10 further bytes, 2 each describing the color space and then four values to describe the color. Version two palettes also then follow this with a four byte integer describing the length of the name, then the bytes which make up said name.

Length Description
2 Version
2 Number of colors
count * 10 (+ 4 + variable (version 2 only))

Color data

Length Description
2 Color space
2 Color data value 1
2 Color data value 2
2 Color data value 3
2 Color data value 4

Version 2 only

Length Description
4 Length of name string in characters
length * 2 Unicode code characters, two bytes per character

All the data in an aco file is stored in big-endian format and therefore needs to be reversed on Windows systems.

Most color spaces only use three of the four available values, but regardless of how many are actually used, all must be specified.

Color Spaces

I mentioned above that each color has a description of what color space it belongs to. The specification defines the following color spaces:

Id Description
0 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 RGBColordata structure. Pure red = 65535, 0, 0.
1 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 HSVColordata structure. Pure red = 0,65535, 65535.
2 CMYK.
The four values in the color data are cyan, magenta, yellow, and black. They are full unsigned 16-bit values.
For example, pure cyan = 0,65535,65535,65535.
7 Lab.
The first three values in the color data are lightness, a chrominance, and b chrominance.
Lightness is a 16-bit value from 0…10000. Chrominance components are each 16-bit values from -12800…12700. Gray values are represented by chrominance components of 0. Pure white = 10000,0,0.
8 Grayscale.
The first value in the color data is the gray value, from 0…10000.

To avoid complicating matters, this article will concentrate on RGB and Grayscale color spaces, although I’ll include the basics of HSV too for if you have a conversion class kicking around.

Reading short/int data types from bytes

As I mentioned above, the values in this file format are all big-endian. As Windows uses little-endian, we need to do some bit shifting when we read each byte comprising either a short (Int16) or an int (Int32), using the following helpers:

/// <summary>
/// Reads a 16bit unsigned integer in big-endian format.
/// </summary>
/// <param name="stream">The stream to read the data from.
/// <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.
/// <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);
}

The << 0 bit-shift in the above methods is technically unnecessary and can be removed. However, I find it makes the intent of the code clearer.

Reading strings

For version 2 files, we need to read a string, which is comprised of two bytes per character. Fortunately for us, the .NET Framework includes a BigEndianUnicode (MSDN) class that we can use to convert a byte array to a string. As this class does the endian conversion for us, we don’t need to do anything special when reading the bytes.

/// <summary>
/// Reads a unicode string of the specified length.
/// </summary>
/// <param name="stream">The stream to read the data from.
/// <param name="length">The number of characters in the string.
/// <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);
}

Reading the file

We start off by reading the file version so we know how to process the rest of the file, or at least the first part of it. If we don’t have a version 1 or version 2 file, then we simply abort.

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.");
 
  colorPalette = this.ReadSwatches(stream, version);
  if (version == FileVersion.Version1)
  {
    version = (FileVersion)this.ReadInt16(stream);
    if (version == FileVersion.Version2)
      colorPalette = this.ReadSwatches(stream, version);
  }
}

In the above example, if a file has both versions, then I read them both (assuming the file contains version 1 followed by version 2). However, there’s no point in doing this if you aren’t going to do anything with the swatch name. For example, this demonstration program converts all the values into the standard .NET Color structure – which doesn’t allow you to set the Name property. In this scenario, clearly it’s a waste of time reading the version 2 data if you’ve just read the data from version 1. However, if you are storing the data in an object that supports the name, then it’s probably a good idea to discard the previously read data and re-read the version 2 data.

Reading color data

As the two documented file formats are almost identical, we can use the same code to handle reading the data, and then perform a little bit extra for the newer file format. The core of the code which reads the color data looks like this.

// 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));
 
  // then the four values which comprise each color
  value1 = this.ReadInt16(stream);
  value2 = this.ReadInt16(stream);
  value3 = this.ReadInt16(stream);
  value4 = this.ReadInt16(stream);
 
  // and finally, the name of the swatch (version2 only)
  if (version == FileVersion.Version2)
  {
    int length;
    string name;
 
    length = ReadInt32(stream);
    name = this.ReadString(stream, length);
  }
}

Translating the color spaces

Once we’ve read the color space and the four values of the color data, we need to process it.

The first space, RGB, is simple enough. The Adobe format is using the range 0-65535, so we just need to convert that to the standard 0-255 range:

switch (colorSpace)
{
  case ColorSpace.Rgb:
    int red;
    int green;
    int blue;
 
    red = value1 / 256; // 0-255
    green = value2 / 256; // 0-255
    blue = value3 / 256; // 0-255
 
    results.Add(Color.FromArgb(red, green, blue));
    break;

Next is HSL. How you process that depends on the class you are using, and the range of values it accepts.

case ColorSpace.Hsb:
  double hue;
  double saturation;
  double brightness;
 
  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;

The last color space we can easily support is gray scale.

case AdobePhotoshopColorSwatchColorSpace.Grayscale:
 
  int gray;
 
  // Grayscale.
  // The first value in the color data is the gray value, from 0...10000.
  gray = (int)(value1 / 39.0625);
 
  results.Add(Color.FromArgb(gray, gray, gray));
  break;

Files using the Lab or CMYK spaces will throw an exception as these are beyond the scope of this example.

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

Although none of the sample files I tested mixed color spaces, they were either all RGB, all Lab or all CMYK, the specification suggests that it’s at least possible. In this case, throwing an exception might not be the right idea as it could be possible to load other colors. Therefore it may be a better idea to just ignore such errors to allow any valid data to be read.

Conclusion

As you can see, reading Photoshop color swatches was quite an easy process.

You can download a fully working sample from the link below, and my next article will reverse the process to allow you to write your own aco files.

Downloads

PhotoshopColorSwatchLoader.zip 15 July 2014 76.6 KB