Cyotek Development Bloghttps://devblog.cyotek.com/tag/adobe/atom.xml2015-10-21T20:02:41ZWriting Adobe Swatch Exchange (ase) files using C#urn:uuid:c5d37d48-c255-4429-9908-24737ace603c2015-10-21T20:02:41Z2015-10-21T20:02:41Z<p>In my last post, I <a href="/post/reading-adobe-swatch-exchange-ase-files-using-csharp">described</a> how to read Adobe Swatch
Exchange files using C#. Now I'm going to update that sample
program to save <code>ase</code> files as well as load them.</p>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/ase-writer-1a.png" class="gallery" title="An example of a multi-group ASE file created by the sample application" ><img src="https://images.cyotek.com/image/thumbnail/devblog/ase-writer-1a.png" alt="An example of a multi-group ASE file created by the sample application" decoding="async" loading="lazy" /></a><figcaption>An example of a multi-group ASE file created by the sample application</figcaption></figure><h2 id="writing-big-endian-values">Writing big endian values</h2>
<p>I covered the basics of writing big-endian values in my original
post on <a href="/post/writing-photoshop-color-swatch-aco-files-using-csharp">writing Photoshop aco files</a>, so I'll not cover that
again but only mention the new bits.</p>
<p>Firstly, we now need to store float values. I mentioned the
trick that <code>BitConverter.ToSingle</code> does where it converts a
<code>int</code> to a pointer, and then the pointer to a <code>float</code>. I'm going
to do exactly the reverse in order to write the float to a
stream - convert the <code>float</code> to a pointer, then convert it to an
<code>int</code>, then write the bytes of the <code>int</code>.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> WriteBigEndian<span class="symbol">(</span><span class="keyword">this</span> Stream stream<span class="symbol">,</span> <span class="keyword">float</span> value<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">unsafe</span>
 <span class="symbol">{</span>
 stream<span class="symbol">.</span>WriteBigEndian<span class="symbol">(</span><span class="symbol">*</span><span class="symbol">(</span><span class="keyword">int</span><span class="symbol">*</span><span class="symbol">)</span><span class="symbol">&amp;</span>value<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>We also need to store unsigned 2-byte integers, so we have
another extension for that.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> WriteBigEndian<span class="symbol">(</span><span class="keyword">this</span> Stream stream<span class="symbol">,</span> <span class="keyword">ushort</span> value<span class="symbol">)</span>
<span class="symbol">{</span>
 stream<span class="symbol">.</span>WriteByte<span class="symbol">(</span><span class="symbol">(</span><span class="keyword">byte</span><span class="symbol">)</span><span class="symbol">(</span>value <span class="symbol">&gt;&gt;</span> <span class="number">8</span><span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>
 stream<span class="symbol">.</span>WriteByte<span class="symbol">(</span><span class="symbol">(</span><span class="keyword">byte</span><span class="symbol">)</span><span class="symbol">(</span>value <span class="symbol">&gt;&gt;</span> <span class="number">0</span><span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>Finally, lets not forget our length prefixed strings!</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> WriteBigEndian<span class="symbol">(</span><span class="keyword">this</span> Stream stream<span class="symbol">,</span> <span class="keyword">string</span> value<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">byte</span><span class="symbol">[</span><span class="symbol">]</span> data<span class="symbol">;</span>

 data <span class="symbol">=</span> Encoding<span class="symbol">.</span>BigEndianUnicode<span class="symbol">.</span>GetBytes<span class="symbol">(</span>value<span class="symbol">)</span><span class="symbol">;</span>

 stream<span class="symbol">.</span>WriteBigEndian<span class="symbol">(</span>value<span class="symbol">.</span>Length<span class="symbol">)</span><span class="symbol">;</span>
 stream<span class="symbol">.</span>Write<span class="symbol">(</span>data<span class="symbol">,</span> <span class="number">0</span><span class="symbol">,</span> data<span class="symbol">.</span>Length<span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<h2 id="saving-the-file">Saving the file</h2>
<p>I covered the format of an <code>ase</code> 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 colour.</p>
<p>Saving the version header is rudimentary</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">private</span> <span class="keyword">void</span> WriteVersionHeader<span class="symbol">(</span>Stream stream<span class="symbol">)</span>
<span class="symbol">{</span>
 stream<span class="symbol">.</span>Write<span class="symbol">(</span><span class="string">&quot;ASEF&quot;</span><span class="symbol">)</span><span class="symbol">;</span>
 stream<span class="symbol">.</span>WriteBigEndian<span class="symbol">(</span><span class="symbol">(</span><span class="keyword">ushort</span><span class="symbol">)</span><span class="number">1</span><span class="symbol">)</span><span class="symbol">;</span>
 stream<span class="symbol">.</span>WriteBigEndian<span class="symbol">(</span><span class="symbol">(</span><span class="keyword">ushort</span><span class="symbol">)</span><span class="number">0</span><span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>

</pre>
</figure>
<p>After this, we write the number of blocks, then cycle each group
and colour in our document.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">private</span> <span class="keyword">void</span> WriteBlocks<span class="symbol">(</span>Stream stream<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">int</span> blockCount<span class="symbol">;</span>

 blockCount <span class="symbol">=</span> <span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>Groups<span class="symbol">.</span>Count <span class="symbol">*</span> <span class="number">2</span><span class="symbol">)</span> <span class="symbol">+</span> <span class="keyword">this</span><span class="symbol">.</span>Colors<span class="symbol">.</span>Count <span class="symbol">+</span> <span class="keyword">this</span><span class="symbol">.</span>Groups<span class="symbol">.</span>Sum<span class="symbol">(</span><span class="keyword">group</span> <span class="symbol">=&gt;</span> <span class="keyword">group</span><span class="symbol">.</span>Colors<span class="symbol">.</span>Count<span class="symbol">)</span><span class="symbol">;</span>

 stream<span class="symbol">.</span>WriteBigEndian<span class="symbol">(</span>blockCount<span class="symbol">)</span><span class="symbol">;</span>

 <span class="comment">// write the global colors first</span>
 <span class="comment">// not sure if global colors + groups is a supported combination however</span>
 <span class="keyword">foreach</span> <span class="symbol">(</span>ColorEntry color <span class="keyword">in</span> <span class="keyword">this</span><span class="symbol">.</span>Colors<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>WriteBlock<span class="symbol">(</span>stream<span class="symbol">,</span> color<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="comment">// now write the groups</span>
 <span class="keyword">foreach</span> <span class="symbol">(</span>ColorGroup <span class="keyword">group</span> <span class="keyword">in</span> <span class="keyword">this</span><span class="symbol">.</span>Groups<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>WriteBlock<span class="symbol">(</span>stream<span class="symbol">,</span> <span class="keyword">group</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>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
<code>MemoryStream</code>, then copied the length and the data into the
real stream but that isn't a very efficient approach, so now I
just calculate the block size.</p>
<h3 id="writing-groups">Writing Groups</h3>
<p>If you recall from the previous article, a group is comprised of
at 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 colour blocks in between. Potentially you can have
nested groups, but I haven't coded for this - I need to grab
myself a Creative Cloud subscription and experiment with <code>ase</code>
files, at which point I'll update these samples if need be.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">private</span> <span class="keyword">int</span> GetBlockLength<span class="symbol">(</span>Block block<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">int</span> blockLength<span class="symbol">;</span>

 <span class="comment">// name data (2 bytes per character + null terminator, plus 2 bytes to describe that first number )</span>
 blockLength <span class="symbol">=</span> <span class="number">2</span> <span class="symbol">+</span> <span class="symbol">(</span><span class="symbol">(</span><span class="symbol">(</span>block<span class="symbol">.</span>Name <span class="symbol">??</span> <span class="keyword">string</span><span class="symbol">.</span>Empty<span class="symbol">)</span><span class="symbol">.</span>Length <span class="symbol">+</span> <span class="number">1</span><span class="symbol">)</span> <span class="symbol">*</span> <span class="number">2</span><span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>block<span class="symbol">.</span>ExtraData <span class="symbol">!=</span> <span class="keyword">null</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 blockLength <span class="symbol">+=</span> block<span class="symbol">.</span>ExtraData<span class="symbol">.</span>Length<span class="symbol">;</span> <span class="comment">// data we can&#39;t process but keep anyway</span>
 <span class="symbol">}</span>

 <span class="keyword">return</span> blockLength<span class="symbol">;</span>
<span class="symbol">}</span>

<span class="keyword">private</span> <span class="keyword">void</span> WriteBlock<span class="symbol">(</span>Stream stream<span class="symbol">,</span> ColorGroup block<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">int</span> blockLength<span class="symbol">;</span>

 blockLength <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>GetBlockLength<span class="symbol">(</span>block<span class="symbol">)</span><span class="symbol">;</span>

 <span class="comment">// write the start group block</span>
 stream<span class="symbol">.</span>WriteBigEndian<span class="symbol">(</span><span class="symbol">(</span><span class="keyword">ushort</span><span class="symbol">)</span>BlockType<span class="symbol">.</span>GroupStart<span class="symbol">)</span><span class="symbol">;</span>
 stream<span class="symbol">.</span>WriteBigEndian<span class="symbol">(</span>blockLength<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>WriteNullTerminatedString<span class="symbol">(</span>stream<span class="symbol">,</span> block<span class="symbol">.</span>Name<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>WriteExtraData<span class="symbol">(</span>stream<span class="symbol">,</span> block<span class="symbol">.</span>ExtraData<span class="symbol">)</span><span class="symbol">;</span>

 <span class="comment">// write the colors in the group</span>
 <span class="keyword">foreach</span> <span class="symbol">(</span>ColorEntry color <span class="keyword">in</span> block<span class="symbol">.</span>Colors<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>WriteBlock<span class="symbol">(</span>stream<span class="symbol">,</span> color<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="comment">// and write the end group block</span>
 stream<span class="symbol">.</span>WriteBigEndian<span class="symbol">(</span><span class="symbol">(</span><span class="keyword">ushort</span><span class="symbol">)</span>BlockType<span class="symbol">.</span>GroupEnd<span class="symbol">)</span><span class="symbol">;</span>
 stream<span class="symbol">.</span>WriteBigEndian<span class="symbol">(</span><span class="number">0</span><span class="symbol">)</span><span class="symbol">;</span> <span class="comment">// there isn&#39;t any data, but we still need to specify that</span>
<span class="symbol">}</span>
</pre>
</figure>
<h3 id="writing-colours">Writing Colours</h3>
<p>Writing a colour block is fairly painless, at least for RGB
colours. As with loading an <code>ase</code> file, I'm completely ignoring
the existence of Lab, CMYK and Gray scale colours.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">private</span> <span class="keyword">int</span> GetBlockLength<span class="symbol">(</span>ColorEntry block<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">int</span> blockLength<span class="symbol">;</span>

 blockLength <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>GetBlockLength<span class="symbol">(</span><span class="symbol">(</span>Block<span class="symbol">)</span>block<span class="symbol">)</span><span class="symbol">;</span>

 blockLength <span class="symbol">+=</span> <span class="number">6</span><span class="symbol">;</span> <span class="comment">// 4 bytes for the color space and 2 bytes for the color type</span>

 <span class="comment">// TODO: Include support for other color spaces</span>

 blockLength <span class="symbol">+=</span> <span class="number">12</span><span class="symbol">;</span> <span class="comment">// length of RGB data (3 * 4 bytes)</span>

 <span class="keyword">return</span> blockLength<span class="symbol">;</span>
<span class="symbol">}</span>

<span class="keyword">private</span> <span class="keyword">void</span> WriteBlock<span class="symbol">(</span>Stream stream<span class="symbol">,</span> ColorEntry block<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">int</span> blockLength<span class="symbol">;</span>

 blockLength <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>GetBlockLength<span class="symbol">(</span>block<span class="symbol">)</span><span class="symbol">;</span>

 stream<span class="symbol">.</span>WriteBigEndian<span class="symbol">(</span><span class="symbol">(</span><span class="keyword">ushort</span><span class="symbol">)</span>BlockType<span class="symbol">.</span>Color<span class="symbol">)</span><span class="symbol">;</span>
 stream<span class="symbol">.</span>WriteBigEndian<span class="symbol">(</span>blockLength<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">this</span><span class="symbol">.</span>WriteNullTerminatedString<span class="symbol">(</span>stream<span class="symbol">,</span> block<span class="symbol">.</span>Name<span class="symbol">)</span><span class="symbol">;</span>

 stream<span class="symbol">.</span>Write<span class="symbol">(</span><span class="string">&quot;RGB &quot;</span><span class="symbol">)</span><span class="symbol">;</span>

 stream<span class="symbol">.</span>WriteBigEndian<span class="symbol">(</span><span class="symbol">(</span><span class="keyword">float</span><span class="symbol">)</span><span class="symbol">(</span>block<span class="symbol">.</span>R <span class="symbol">/</span> <span class="number">255.0</span><span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>
 stream<span class="symbol">.</span>WriteBigEndian<span class="symbol">(</span><span class="symbol">(</span><span class="keyword">float</span><span class="symbol">)</span><span class="symbol">(</span>block<span class="symbol">.</span>G <span class="symbol">/</span> <span class="number">255.0</span><span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>
 stream<span class="symbol">.</span>WriteBigEndian<span class="symbol">(</span><span class="symbol">(</span><span class="keyword">float</span><span class="symbol">)</span><span class="symbol">(</span>block<span class="symbol">.</span>B <span class="symbol">/</span> <span class="number">255.0</span><span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>

 stream<span class="symbol">.</span>WriteBigEndian<span class="symbol">(</span><span class="symbol">(</span><span class="keyword">ushort</span><span class="symbol">)</span>block<span class="symbol">.</span>Type<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">this</span><span class="symbol">.</span>WriteExtraData<span class="symbol">(</span>stream<span class="symbol">,</span> block<span class="symbol">.</span>ExtraData<span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<h2 id="caveats-or-why-this-took-longer-than-it-should-have-done">Caveats, or why this took longer than it should have done</h2>
<p>When I originally tested this code, I added a simple compare
function which compared the bytes of a source <code>ase</code> 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 colours as the <code>ase</code> format
description on <a href="http://www.selapa.net/swatches/colors/fileformats.php#adobe_ase" rel="external nofollow noopener">sepla.net</a>.</p>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/ase-writer-1b.png" class="gallery" title="Comparing a third party ASE file with the version created by the sample application" ><img src="https://images.cyotek.com/image/thumbnail/devblog/ase-writer-1b.png" alt="Comparing a third party ASE file with the version created by the sample application" decoding="async" loading="lazy" /></a><figcaption>Comparing a third party ASE file with the version created by the sample application</figcaption></figure>
<p>This allowed me to easily view the files side by side and be
able to break the files down into their sections and see what
was wrong. The example screenshot above shows an identical
comparison.</p>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/ase-writer-1c.png" class="gallery" title="Another compare of a third party ASE file with the version created by the sample application, showing the colour data is the same, but the raw file differs" ><img src="https://images.cyotek.com/image/thumbnail/devblog/ase-writer-1c.png" alt="Another compare of a third party ASE file with the version created by the sample application, showing the colour data is the same, but the raw file differs" decoding="async" loading="lazy" /></a><figcaption>Another compare of a third party ASE file with the version created by the sample application, showing the colour data is the same, but the raw file differs</figcaption></figure>
<p>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.</p>
<p>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 colours, and the last
byte of each of these values does not match. However, the
translated RGB values <strong>do</strong> match, so I guess it is a rounding
/ precision issue.</p>
<p>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.</p>
<h2 id="on-with-the-show">On with the show</h2>
<p>The updated demonstration application is available for download
below, including new sample files generated directly by the
program.</p>
<h2 id="update-history">Update History</h2>
<ul>
<li>2015-10-21 - First published</li>
<li>2020-11-21 - Updated formatting</li>
</ul>

<p><small>
All content <a href="https://devblog.cyotek.com/copyright-and-trademarks">Copyright (c) by Cyotek Ltd</a> or its respective writers. Permission to reproduce news and web log entries and other RSS feed content in unmodified form without notice is granted provided they are not used to endorse or promote any products or opinions (other than what was expressed by the author) and without taking them out of context. Written permission from the copyright owner must be obtained for everything else.<br />Original URL of this content is https://devblog.cyotek.com/post/writing-adobe-swatch-exchange-ase-files-using-csharp .
</small></p>Richard Mosshttps://www.cyotek.com/richard.moss@cyotek.comReading Adobe Swatch Exchange (ase) files using C#urn:uuid:24615ad2-52ae-4bc9-9526-582fa6c161922015-10-16T17:14:07Z2015-10-16T17:14:07Z<p>Previously I wrote how to <a href="/post/reading-photoshop-color-swatch-aco-files-using-csharp">read</a> and <a href="/post/writing-photoshop-color-swatch-aco-files-using-csharp">write</a> files using
the Photoshop Color Swatch file format. In this article
mini-series, I'm now going to take a belated look at Adobe's
Swatch Exchange file format and show how to read and write these
files using C#. This first article covers reading an existing
<code>ase</code> file.</p>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/ase-loader-1a.png" class="gallery" title="An example of an ASE file with a single group containing 5 RGB colours" ><img src="https://images.cyotek.com/image/thumbnail/devblog/ase-loader-1a.png" alt="An example of an ASE file with a single group containing 5 RGB colours" decoding="async" loading="lazy" /></a><figcaption>An example of an ASE file with a single group containing 5 RGB colours</figcaption></figure><h2 id="caveat-emptor">Caveat Emptor</h2>
<p>Unlike some of Adobe's other specifications, they don't seem to
have published an official specification for the <code>ase</code> format
themselves. For the purposes of this article, I've been using
unofficial details available from <a href="http://www.selapa.net/swatches/colors/fileformats.php#adobe_ase" rel="external nofollow noopener">Olivier Berten</a> and
<a href="http://mh-nexus.de/en/" rel="external nofollow noopener">HxD</a> to poke around in sample files I have downloaded.</p>
<p>And, as with my previous articles, the code I'm about to present
doesn't handle CMYK or Lab colour spaces. It's also received a
very limited amount of testing.</p>
<h2 id="structure-of-a-adobe-swatch-exchange-file">Structure of a Adobe Swatch Exchange file</h2>
<p><code>ase</code> files support the notion of groups, so you can have
multiple groups containing colours. Judging from the files I
have tested, you can also just have a bunch of colours without a
group at all. I'm uncertain if groups can be nested, so I have
assumed they cannot be.</p>
<p>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 comprises 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 optionally
custom data specific to that particular block.</p>
<p>Blocks can either be a colour, the start of a group, or the end
of a group.</p>
<p>Colour blocks include the colour space, 1-4 floating point
values that describe the colour (3 for RGB and LAB, 4 for CMYK
and 1 for grayscale), and a type.</p>
<p>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 colour
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 <code>aco</code> and <code>ase</code> files (the obvious one being that
the former is just a list of colours while the latter supports
grouping) I assume this data is meta data from the application
that created the <code>ase</code> file, but it is all supposition.</p>
<p>The following table attempts to describe the layout, although I
actually found the highlighted hex grid displayed at
<a href="http://www.selapa.net/swatches/colors/fileformats.php#adobe_ase" rel="external nofollow noopener">selapa.net</a> to potentially be easier to read.</p>
<table>
 <thead>
 <tr>
 <th>Length
 </th>
 <th>Description
 </th>
 </tr>
 </thead>
 <tbody>
 <tr>
 <td>4</td>
 <td>Signature</td>
 </tr>
 <tr>
 <td>2</td>
 <td>Major Version</td>
 </tr>
 <tr>
 <td>2</td>
 <td>Minor Version</td>
 </tr>
 <tr>
 <td>4</td>
 <td>Number of blocks</td>
 </tr>
 <tr>
 <td rowspan="3">variable</td>
 <td>
 <p> Block data</p>
 <table>
 <thead>
 <tr>
 <th>Length
 </th>
 <th>Description
 </th>
 </tr>
 </thead>
 <tbody>
 <tr>
 <td>2</td>
 <td>Type</td>
 </tr>
 <tr>
 <td>4</td>
 <td>Block length</td>
 </tr>
 <tr>
 <td>2</td>
 <td>Name length</td>
 </tr>
 <tr>
 <td>(name length)</td>
 <td>Name</td>
 </tr>
 </tbody>
 </table>
 </td>
 </tr>
 <tr>
 <td>
 <p><em>Colour blocks only</em></p>
 <table>
 <thead>
 <tr>
 <th>Length
 </th>
 <th>Description
 </th>
 </tr>
 </thead>
 <tbody>
 <tr>
 <td>4</td>
 <td>Colour space</td>
 </tr>
 <tr>
 <td>12 (RGB, LAB), 16 (CMYK), 4 (Grayscale)</td>
 <td>Colour data. Every four bytes represents one floating
 point value</td>
 </tr>
 <tr>
 <td>2</td>
 <td>Colour type</td>
 </tr>
 </tbody>
 </table>
 </td>
 </tr>
 <tr>
 <td>
 <p><em>All blocks</em></p>
 <table>
 <thead>
 <tr>
 <th>Length
 </th>
 <th>Description
 </th>
 </tr>
 </thead>
 <tbody>
 <tr>
 <td>variable (Block length - previously read data)</td>
 <td>Unknown</td>
 </tr>
 </tbody>
 </table>
 </td>
 </tr>
 </tbody>
</table>
<p>As with <code>aco</code> files, all the data in an <code>ase</code> file is stored in
<a href="http://en.wikipedia.org/wiki/Endianness" rel="external nofollow noopener">big-endian</a> format and therefore needs to be reversed on
Windows systems. Unlike the <code>aco</code> files where four values are
present for each colour even if not required by the appropriate
colour space, the <code>ase</code> format uses between one and four values,
making it slightly more compact that <code>aso</code>.</p>
<h2 id="colour-spaces">Colour Spaces</h2>
<p>I mentioned above that each colour has a description of what
colour space it belongs to. There appear to be four supported
colour spaces. Note that space names are 4 characters long in an
<code>ase</code> file, shorter names are therefore padded with spaces.</p>
<ul>
<li>RGB</li>
<li>LAB</li>
<li>CMYK</li>
<li>Gray</li>
</ul>
<p>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 <code>Color</code> structure. I have no idea on the other 3 types
however - I need more samples!</p>
<h2 id="big-endian-conversion">Big-endian conversion</h2>
<p>I covered the basics of reading shorts, ints, and strings in
big-endian format in my <a href="/post/reading-photoshop-color-swatch-aco-files-using-csharp">previous article</a> on <code>aco</code> files so
I won't cover that here.</p>
<p>However, this time around I do need to read floats from the
files too. While the <code>BitConverter</code> class has a <code>ToSingle</code>
method that will convert a 4-byte array to a float, of course it
is for little-endian.</p>
<p>I looked at the <a href="http://referencesource.microsoft.com/#mscorlib/system/bitconverter.cs#7d2958fc09cde954" rel="external nofollow noopener">reference source</a> for this method and saw it
does a really neat trick - it converts the four bytes into an
integer, then creates a float from that integer via pointers.</p>
<p>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
<code>unsafe</code> keyword without enabling a special option in project
properties. I use unsafe code quite frequently for 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 them, and then call <code>BitConverter.ToSingle</code>
with the reversed array.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">float</span> ReadSingleBigEndian<span class="symbol">(</span><span class="keyword">this</span> Stream stream<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">unsafe</span>
 <span class="symbol">{</span>
 <span class="keyword">int</span> value<span class="symbol">;</span>

 value <span class="symbol">=</span> stream<span class="symbol">.</span>ReadUInt<span class="number">32</span>BigEndian<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">return</span> <span class="symbol">*</span><span class="symbol">(</span><span class="keyword">float</span><span class="symbol">*</span><span class="symbol">)</span><span class="symbol">&amp;</span>value<span class="symbol">;</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>Another slight difference between <code>aco</code> and <code>ase</code> files is that
in <code>ase</code> 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.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">string</span> ReadStringBigEndian<span class="symbol">(</span><span class="keyword">this</span> Stream stream<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">int</span> length<span class="symbol">;</span>
 <span class="keyword">string</span> value<span class="symbol">;</span>

 <span class="comment">// string is null terminated, value saved in file includes the terminator</span>

 length <span class="symbol">=</span> stream<span class="symbol">.</span>ReadUInt<span class="number">16</span>BigEndian<span class="symbol">(</span><span class="symbol">)</span> <span class="symbol">-</span> <span class="number">1</span><span class="symbol">;</span>
 value <span class="symbol">=</span> stream<span class="symbol">.</span>ReadStringBigEndian<span class="symbol">(</span>length<span class="symbol">)</span><span class="symbol">;</span>
 stream<span class="symbol">.</span>ReadUInt<span class="number">16</span>BigEndian<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span> <span class="comment">// read and discard the terminator</span>

 <span class="keyword">return</span> value<span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<h2 id="storage-classes">Storage classes</h2>
<p>In my previous examples on reading colour data from files, I've
kept it simple and returned arrays of colours, 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.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">internal</span> <span class="keyword">abstract</span> <span class="keyword">class</span> Block
<span class="symbol">{</span>
 <span class="keyword">public</span> <span class="keyword">byte</span><span class="symbol">[</span><span class="symbol">]</span> ExtraData <span class="symbol">{</span> <span class="keyword">get</span><span class="symbol">;</span> <span class="keyword">set</span><span class="symbol">;</span> <span class="symbol">}</span>
 <span class="keyword">public</span> <span class="keyword">string</span> Name <span class="symbol">{</span> <span class="keyword">get</span><span class="symbol">;</span> <span class="keyword">set</span><span class="symbol">;</span> <span class="symbol">}</span>
<span class="symbol">}</span>

<span class="keyword">internal</span> <span class="keyword">class</span> ColorEntry <span class="symbol">:</span> Block
<span class="symbol">{</span>
 <span class="keyword">public</span> <span class="keyword">int</span> B <span class="symbol">{</span> <span class="keyword">get</span><span class="symbol">;</span> <span class="keyword">set</span><span class="symbol">;</span> <span class="symbol">}</span>
 <span class="keyword">public</span> <span class="keyword">int</span> G <span class="symbol">{</span> <span class="keyword">get</span><span class="symbol">;</span> <span class="keyword">set</span><span class="symbol">;</span> <span class="symbol">}</span>
 <span class="keyword">public</span> <span class="keyword">int</span> R <span class="symbol">{</span> <span class="keyword">get</span><span class="symbol">;</span> <span class="keyword">set</span><span class="symbol">;</span> <span class="symbol">}</span>
 <span class="keyword">public</span> ColorType Type <span class="symbol">{</span> <span class="keyword">get</span><span class="symbol">;</span> <span class="keyword">set</span><span class="symbol">;</span> <span class="symbol">}</span>

 <span class="keyword">public</span> Color ToColor<span class="symbol">(</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">return</span> Color<span class="symbol">.</span>FromArgb<span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>R<span class="symbol">,</span> <span class="keyword">this</span><span class="symbol">.</span>G<span class="symbol">,</span> <span class="keyword">this</span><span class="symbol">.</span>B<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>

<span class="keyword">internal</span> <span class="keyword">class</span> ColorEntryCollection <span class="symbol">:</span> Collection<span class="symbol">&lt;</span>ColorEntry<span class="symbol">&gt;</span>
<span class="symbol">{</span> <span class="symbol">}</span>

<span class="keyword">internal</span> <span class="keyword">class</span> ColorGroup <span class="symbol">:</span> Block<span class="symbol">,</span> IEnumerable<span class="symbol">&lt;</span>ColorEntry<span class="symbol">&gt;</span>
<span class="symbol">{</span>
 <span class="keyword">public</span> ColorGroup<span class="symbol">(</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>Colors <span class="symbol">=</span> <span class="keyword">new</span> ColorEntryCollection<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">public</span> ColorEntryCollection Colors <span class="symbol">{</span> <span class="keyword">get</span><span class="symbol">;</span> <span class="keyword">set</span><span class="symbol">;</span> <span class="symbol">}</span>

 <span class="keyword">public</span> IEnumerator<span class="symbol">&lt;</span>ColorEntry<span class="symbol">&gt;</span> GetEnumerator<span class="symbol">(</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">return</span> <span class="keyword">this</span><span class="symbol">.</span>Colors<span class="symbol">.</span>GetEnumerator<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 IEnumerator IEnumerable<span class="symbol">.</span>GetEnumerator<span class="symbol">(</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">return</span> <span class="keyword">this</span><span class="symbol">.</span>GetEnumerator<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>

<span class="keyword">internal</span> <span class="keyword">class</span> ColorGroupCollection <span class="symbol">:</span> Collection<span class="symbol">&lt;</span>ColorGroup<span class="symbol">&gt;</span>
<span class="symbol">{</span> <span class="symbol">}</span>

<span class="keyword">internal</span> <span class="keyword">class</span> SwatchExchangeData
<span class="symbol">{</span>
 <span class="keyword">public</span> SwatchExchangeData<span class="symbol">(</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>Groups <span class="symbol">=</span> <span class="keyword">new</span> ColorGroupCollection<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>Colors <span class="symbol">=</span> <span class="keyword">new</span> ColorEntryCollection<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">public</span> ColorEntryCollection Colors <span class="symbol">{</span> <span class="keyword">get</span><span class="symbol">;</span> <span class="keyword">set</span><span class="symbol">;</span> <span class="symbol">}</span>
 <span class="keyword">public</span> ColorGroupCollection Groups <span class="symbol">{</span> <span class="keyword">get</span><span class="symbol">;</span> <span class="keyword">set</span><span class="symbol">;</span> <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>That should be all we need, time to load some files!</p>
<h2 id="reading-the-file">Reading the file</h2>
<p>To start with, we create a new <code>ColorEntryCollection</code> that will
be used for global colours (i.e. colour blocks that don't appear
within a group). To make things simple, I'm also creating a
<code>Stack&lt;ColorEntryCollection&gt;</code> to which I push this global
collection. Later on, when I encounter a start group block, I'll
<code>Push</code> a new <code>ColorEntryCollection</code> to this stack, and when I
encounter an end group block, I'll <code>Pop</code> the value at the top of
the stack. This way, when I encounter a colour block, I can
easily add it to the right collection without needing to
explicitly keep track of the active group or lack thereof.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">public</span> <span class="keyword">void</span> Load<span class="symbol">(</span><span class="keyword">string</span> fileName<span class="symbol">)</span>
<span class="symbol">{</span>
 Stack<span class="symbol">&lt;</span>ColorEntryCollection<span class="symbol">&gt;</span> colors<span class="symbol">;</span>
 ColorGroupCollection groups<span class="symbol">;</span>
 ColorEntryCollection globalColors<span class="symbol">;</span>

 groups <span class="symbol">=</span> <span class="keyword">new</span> ColorGroupCollection<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 globalColors <span class="symbol">=</span> <span class="keyword">new</span> ColorEntryCollection<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 colors <span class="symbol">=</span> <span class="keyword">new</span> Stack<span class="symbol">&lt;</span>ColorEntryCollection<span class="symbol">&gt;</span><span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>

 <span class="comment">// add the global collection to the bottom of the stack to handle color blocks outside of a group</span>
 colors<span class="symbol">.</span>Push<span class="symbol">(</span>globalColors<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">using</span> <span class="symbol">(</span>Stream stream <span class="symbol">=</span> File<span class="symbol">.</span>OpenRead<span class="symbol">(</span>fileName<span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">int</span> blockCount<span class="symbol">;</span>

 <span class="keyword">this</span><span class="symbol">.</span>ReadAndValidateVersion<span class="symbol">(</span>stream<span class="symbol">)</span><span class="symbol">;</span>

 blockCount <span class="symbol">=</span> stream<span class="symbol">.</span>ReadUInt<span class="number">32</span>BigEndian<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">for</span> <span class="symbol">(</span><span class="keyword">int</span> i <span class="symbol">=</span> <span class="number">0</span><span class="symbol">;</span> i <span class="symbol">&lt;</span> blockCount<span class="symbol">;</span> i<span class="symbol">++</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>ReadBlock<span class="symbol">(</span>stream<span class="symbol">,</span> groups<span class="symbol">,</span> colors<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>

 <span class="keyword">this</span><span class="symbol">.</span>Groups <span class="symbol">=</span> groups<span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>Colors <span class="symbol">=</span> globalColors<span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>After opening a <code>Stream</code> containing our file data, we need to
check that the stream contains both <code>ase</code> 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 <code>ASEF</code>, the next two are the major
version and the final two the minor version.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">private</span> <span class="keyword">void</span> ReadAndValidateVersion<span class="symbol">(</span>Stream stream<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">string</span> signature<span class="symbol">;</span>
 <span class="keyword">int</span> majorVersion<span class="symbol">;</span>
 <span class="keyword">int</span> minorVersion<span class="symbol">;</span>

 <span class="comment">// get the signature (4 ascii characters)</span>
 signature <span class="symbol">=</span> stream<span class="symbol">.</span>ReadAsciiString<span class="symbol">(</span><span class="number">4</span><span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>signature <span class="symbol">!=</span> <span class="string">&quot;ASEF&quot;</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">throw</span> <span class="keyword">new</span> InvalidDataException<span class="symbol">(</span><span class="string">&quot;Invalid file format.&quot;</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="comment">// read the version</span>
 majorVersion <span class="symbol">=</span> stream<span class="symbol">.</span>ReadUInt<span class="number">16</span>BigEndian<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 minorVersion <span class="symbol">=</span> stream<span class="symbol">.</span>ReadUInt<span class="number">16</span>BigEndian<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>majorVersion <span class="symbol">!=</span> <span class="number">1</span> <span class="symbol">&amp;&amp;</span> minorVersion <span class="symbol">!=</span> <span class="number">0</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">throw</span> <span class="keyword">new</span> InvalidDataException<span class="symbol">(</span><span class="string">&quot;Invalid version information.&quot;</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>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.</p>
<p>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.</p>
<blockquote>
<p>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.</p>
</blockquote>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">private</span> <span class="keyword">void</span> ReadBlock<span class="symbol">(</span>Stream stream<span class="symbol">,</span> ColorGroupCollection groups<span class="symbol">,</span> Stack<span class="symbol">&lt;</span>ColorEntryCollection<span class="symbol">&gt;</span> colorStack<span class="symbol">)</span>
<span class="symbol">{</span>
 BlockType blockType<span class="symbol">;</span>
 <span class="keyword">int</span> blockLength<span class="symbol">;</span>
 <span class="keyword">int</span> offset<span class="symbol">;</span>
 <span class="keyword">int</span> dataLength<span class="symbol">;</span>
 Block block<span class="symbol">;</span>

 blockType <span class="symbol">=</span> <span class="symbol">(</span>BlockType<span class="symbol">)</span>stream<span class="symbol">.</span>ReadUInt<span class="number">16</span>BigEndian<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 blockLength <span class="symbol">=</span> stream<span class="symbol">.</span>ReadUInt<span class="number">32</span>BigEndian<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>

 <span class="comment">// store the current position of the stream, so we can calculate the offset</span>
 <span class="comment">// from bytes read to the block length in order to skip the bits we can&#39;t use</span>
 offset <span class="symbol">=</span> <span class="symbol">(</span><span class="keyword">int</span><span class="symbol">)</span>stream<span class="symbol">.</span>Position<span class="symbol">;</span>

 <span class="comment">// process the actual block</span>
 <span class="keyword">switch</span> <span class="symbol">(</span>blockType<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">case</span> BlockType<span class="symbol">.</span>Color<span class="symbol">:</span>
 block <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>ReadColorBlock<span class="symbol">(</span>stream<span class="symbol">,</span> colorStack<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">break</span><span class="symbol">;</span>
 <span class="keyword">case</span> BlockType<span class="symbol">.</span>GroupStart<span class="symbol">:</span>
 block <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>ReadGroupBlock<span class="symbol">(</span>stream<span class="symbol">,</span> groups<span class="symbol">,</span> colorStack<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">break</span><span class="symbol">;</span>
 <span class="keyword">case</span> BlockType<span class="symbol">.</span>GroupEnd<span class="symbol">:</span>
 block <span class="symbol">=</span> <span class="keyword">null</span><span class="symbol">;</span>
 colorStack<span class="symbol">.</span>Pop<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">break</span><span class="symbol">;</span>
 <span class="keyword">default</span><span class="symbol">:</span>
 <span class="keyword">throw</span> <span class="keyword">new</span> InvalidDataException<span class="symbol">(</span><span class="symbol">$</span><span class="string">&quot;Unsupported block type &#39;{blockType}&#39;.&quot;</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="comment">// load in any custom data and attach it to the</span>
 <span class="comment">// current block (if available) as raw byte data</span>
 dataLength <span class="symbol">=</span> blockLength <span class="symbol">-</span> <span class="symbol">(</span><span class="keyword">int</span><span class="symbol">)</span><span class="symbol">(</span>stream<span class="symbol">.</span>Position <span class="symbol">-</span> offset<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>dataLength <span class="symbol">&gt;</span> <span class="number">0</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">byte</span><span class="symbol">[</span><span class="symbol">]</span> extraData<span class="symbol">;</span>

 extraData <span class="symbol">=</span> <span class="keyword">new</span> <span class="keyword">byte</span><span class="symbol">[</span>dataLength<span class="symbol">]</span><span class="symbol">;</span>
 stream<span class="symbol">.</span>Read<span class="symbol">(</span>extraData<span class="symbol">,</span> <span class="number">0</span><span class="symbol">,</span> dataLength<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>block <span class="symbol">!=</span> <span class="keyword">null</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 block<span class="symbol">.</span>ExtraData <span class="symbol">=</span> extraData<span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<h3 id="processing-groups">Processing groups</h3>
<p>If we have found a &quot;start group&quot; block, then we create a new
<code>ColorGroup</code> object and read the group name. We also push the
group's <code>ColorEntryCollection</code> to the stack I mentioned earlier.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">private</span> Block ReadGroupBlock<span class="symbol">(</span>Stream stream<span class="symbol">,</span> ColorGroupCollection groups<span class="symbol">,</span> Stack<span class="symbol">&lt;</span>ColorEntryCollection<span class="symbol">&gt;</span> colorStack<span class="symbol">)</span>
<span class="symbol">{</span>
 ColorGroup block<span class="symbol">;</span>
 <span class="keyword">string</span> name<span class="symbol">;</span>

 <span class="comment">// read the name of the group</span>
 name <span class="symbol">=</span> stream<span class="symbol">.</span>ReadStringBigEndian<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>

 <span class="comment">// create the group and add it to the results set</span>
 block <span class="symbol">=</span> <span class="keyword">new</span> ColorGroup
 <span class="symbol">{</span>
 Name <span class="symbol">=</span> name
 <span class="symbol">}</span><span class="symbol">;</span>

 groups<span class="symbol">.</span>Add<span class="symbol">(</span>block<span class="symbol">)</span><span class="symbol">;</span>

 <span class="comment">// add the group color collection to the stack, so when subsequent colour blocks</span>
 <span class="comment">// are read, they will be added to the correct collection</span>
 colorStack<span class="symbol">.</span>Push<span class="symbol">(</span>block<span class="symbol">.</span>Colors<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">return</span> block<span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>For &quot;end group&quot; 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 colour stack. (Of course,
that means if there is a malformed <code>ase</code> file containing a group
end without a group start, this procedure is going to crash
sooner or later!</p>
<h3 id="processing-colours">Processing colours</h3>
<p>When we hit a colour block, we read the colour's name and the
colour mode.</p>
<p>Then, depending on the mode, we read between 1 and 4 float
values which describe the colour. As anything other than RGB
processing is beyond the scope of this article, I'm throwing an
exception for the LAB, CMYK and Gray colour spaces.</p>
<p>For RGB colours, I take each value and multiple it by 255 to get
a value suitable for use with the .NET <code>Color</code> struct.</p>
<p>After reading the colour data, there's one official value left
to read, which is the colour type. This can either be Global
(0), Spot (1) or Normal (2).</p>
<p>Finally, I construct a new <code>ColorEntry</code> object containing the
colour information and add it to whatever <code>ColorEntryCollection</code>
is on the top of the stack.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">private</span> Block ReadColorBlock<span class="symbol">(</span>Stream stream<span class="symbol">,</span> Stack<span class="symbol">&lt;</span>ColorEntryCollection<span class="symbol">&gt;</span> colorStack<span class="symbol">)</span>
<span class="symbol">{</span>
 ColorEntry block<span class="symbol">;</span>
 <span class="keyword">string</span> colorMode<span class="symbol">;</span>
 <span class="keyword">int</span> r<span class="symbol">;</span>
 <span class="keyword">int</span> g<span class="symbol">;</span>
 <span class="keyword">int</span> b<span class="symbol">;</span>
 ColorType colorType<span class="symbol">;</span>
 <span class="keyword">string</span> name<span class="symbol">;</span>
 ColorEntryCollection colors<span class="symbol">;</span>

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

 <span class="comment">// get the mode of the color, which is stored</span>
 <span class="comment">// as four ASCII characters</span>
 colorMode <span class="symbol">=</span> stream<span class="symbol">.</span>ReadAsciiString<span class="symbol">(</span><span class="number">4</span><span class="symbol">)</span><span class="symbol">;</span>

 <span class="comment">// read the color data</span>
 <span class="comment">// how much data we need to read depends on the</span>
 <span class="comment">// color mode we previously read</span>
 <span class="keyword">switch</span> <span class="symbol">(</span>colorMode<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">case</span> <span class="string">&quot;RGB &quot;</span><span class="symbol">:</span>
 <span class="comment">// RGB is comprised of three floating point values ranging from 0-1.0</span>
 <span class="keyword">float</span> value<span class="number">1</span><span class="symbol">;</span>
 <span class="keyword">float</span> value<span class="number">2</span><span class="symbol">;</span>
 <span class="keyword">float</span> value<span class="number">3</span><span class="symbol">;</span>
 value<span class="number">1</span> <span class="symbol">=</span> stream<span class="symbol">.</span>ReadSingleBigEndian<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 value<span class="number">2</span> <span class="symbol">=</span> stream<span class="symbol">.</span>ReadSingleBigEndian<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 value<span class="number">3</span> <span class="symbol">=</span> stream<span class="symbol">.</span>ReadSingleBigEndian<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 r <span class="symbol">=</span> Convert<span class="symbol">.</span>ToInt<span class="number">32</span><span class="symbol">(</span>value<span class="number">1</span> <span class="symbol">*</span> <span class="number">255</span><span class="symbol">)</span><span class="symbol">;</span>
 g <span class="symbol">=</span> Convert<span class="symbol">.</span>ToInt<span class="number">32</span><span class="symbol">(</span>value<span class="number">2</span> <span class="symbol">*</span> <span class="number">255</span><span class="symbol">)</span><span class="symbol">;</span>
 b <span class="symbol">=</span> Convert<span class="symbol">.</span>ToInt<span class="number">32</span><span class="symbol">(</span>value<span class="number">3</span> <span class="symbol">*</span> <span class="number">255</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">break</span><span class="symbol">;</span>
 <span class="keyword">case</span> <span class="string">&quot;CMYK&quot;</span><span class="symbol">:</span>
 <span class="comment">// CMYK is comprised of four floating point values</span>
 <span class="keyword">throw</span> <span class="keyword">new</span> InvalidDataException<span class="symbol">(</span><span class="symbol">$</span><span class="string">&quot;Unsupported color mode &#39;{colorMode}&#39;.&quot;</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">case</span> <span class="string">&quot;LAB &quot;</span><span class="symbol">:</span>
 <span class="comment">// LAB is comprised of three floating point values</span>
 <span class="keyword">throw</span> <span class="keyword">new</span> InvalidDataException<span class="symbol">(</span><span class="symbol">$</span><span class="string">&quot;Unsupported color mode &#39;{colorMode}&#39;.&quot;</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">case</span> <span class="string">&quot;Gray&quot;</span><span class="symbol">:</span>
 <span class="comment">// Grayscale is comprised of a single floating point value</span>
 <span class="keyword">throw</span> <span class="keyword">new</span> InvalidDataException<span class="symbol">(</span><span class="symbol">$</span><span class="string">&quot;Unsupported color mode &#39;{colorMode}&#39;.&quot;</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">default</span><span class="symbol">:</span>
 <span class="keyword">throw</span> <span class="keyword">new</span> InvalidDataException<span class="symbol">(</span><span class="symbol">$</span><span class="string">&quot;Unsupported color mode &#39;{colorMode}&#39;.&quot;</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="comment">// the final &quot;official&quot; piece of data is a color type</span>
 colorType <span class="symbol">=</span> <span class="symbol">(</span>ColorType<span class="symbol">)</span>stream<span class="symbol">.</span>ReadUInt<span class="number">16</span>BigEndian<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>

 block <span class="symbol">=</span> <span class="keyword">new</span> ColorEntry
 <span class="symbol">{</span>
 R <span class="symbol">=</span> r<span class="symbol">,</span>
 G <span class="symbol">=</span> g<span class="symbol">,</span>
 B <span class="symbol">=</span> b<span class="symbol">,</span>
 Name <span class="symbol">=</span> name<span class="symbol">,</span>
 Type <span class="symbol">=</span> colorType
 <span class="symbol">}</span><span class="symbol">;</span>

 colors <span class="symbol">=</span> colorStack<span class="symbol">.</span>Peek<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 colors<span class="symbol">.</span>Add<span class="symbol">(</span>block<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">return</span> block<span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<h2 id="and-done">And done</h2>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/ase-loader-1b.png" class="gallery" title="An example of a group-less ASE file" ><img src="https://images.cyotek.com/image/thumbnail/devblog/ase-loader-1b.png" alt="An example of a group-less ASE file" decoding="async" loading="lazy" /></a><figcaption>An example of a group-less ASE file</figcaption></figure>
<p>The <code>ase</code> 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. Unfortunately, I don't have a recent
version of PhotoShop to actually generate some of these files to
investigate further (and to test if groups can be nested so I
can adapt this code accordingly).</p>
<p>However, I have tested this code on a number of files downloaded
from the internet and have been able to pull out all the colour
information, so I suspect the <a href="https://cyotek.com/cyotek-palette-editor">Color Palette Editor</a> and
<a href="https://github.com/cyotek/Cyotek.Windows.Forms.ColorPicker" rel="external nofollow noopener">Color Picker Controls</a> will be getting <code>ase</code> support fairly
soon!</p>

<p><small>
All content <a href="https://devblog.cyotek.com/copyright-and-trademarks">Copyright (c) by Cyotek Ltd</a> or its respective writers. Permission to reproduce news and web log entries and other RSS feed content in unmodified form without notice is granted provided they are not used to endorse or promote any products or opinions (other than what was expressed by the author) and without taking them out of context. Written permission from the copyright owner must be obtained for everything else.<br />Original URL of this content is https://devblog.cyotek.com/post/reading-adobe-swatch-exchange-ase-files-using-csharp .
</small></p>Richard Mosshttps://www.cyotek.com/richard.moss@cyotek.comWriting Photoshop Color Swatch (aco) files using C#urn:uuid:185a2dfb-dc4d-479e-b09c-1289092e44142014-01-28T18:25:09Z2014-01-28T18:25:09Z<p>The <a href="/post/reading-photoshop-color-swatch-aco-files-using-csharp">previous article</a> in this mini-series described how to
read files in Abode's Colour File format as used by applications
such as Photoshop and other drawing programs.</p>
<p>In this second article, I'll describe how to write such files.</p>
<div class="article-gallery">
<a href="https://images.cyotek.com/image/devblog/savephotoshopcolorswatch1.png" class="gallery" title="RGB - " ><img src="https://images.cyotek.com/image/thumbnail/devblog/savephotoshopcolorswatch1.png" alt="RGB" decoding="async" loading="lazy" /></a><a href="https://images.cyotek.com/image/devblog/savephotoshopcolorswatch2.png" class="gallery" title="HSL - " ><img src="https://images.cyotek.com/image/thumbnail/devblog/savephotoshopcolorswatch2.png" alt="HSL" decoding="async" loading="lazy" /></a><a href="https://images.cyotek.com/image/devblog/savephotoshopcolorswatch3.png" class="gallery" title="Grayscale - " ><img src="https://images.cyotek.com/image/thumbnail/devblog/savephotoshopcolorswatch3.png" alt="Grayscale" decoding="async" loading="lazy" /></a></div>
<h2 id="getting-started">Getting started</h2>
<p>I'm not going to go over the structure again, so if you haven't
already done so, please read the previous article <a href="/post/reading-photoshop-color-swatch-aco-files-using-csharp">Reading
Photoshop Color Swatch (aco) files using C#</a> for full details
on the file structure and how to read it.</p>
<h2 id="writing-big-endian-values">Writing big-endian values</h2>
<p>All the data in an <code>aco</code> file is stored in <a href="http://en.wikipedia.org/wiki/Endianness" rel="external nofollow noopener">big-endian</a>
format and therefore needs to be reversed on Windows systems
before writing it back into the file.</p>
<p>We can use the following two methods to write a <code>short</code> or a
<code>int</code> 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.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">private</span> <span class="keyword">void</span> WriteInt<span class="number">16</span><span class="symbol">(</span>Stream stream<span class="symbol">,</span> <span class="keyword">short</span> value<span class="symbol">)</span>
<span class="symbol">{</span>
 stream<span class="symbol">.</span>WriteByte<span class="symbol">(</span><span class="symbol">(</span><span class="keyword">byte</span><span class="symbol">)</span><span class="symbol">(</span>value <span class="symbol">&gt;&gt;</span> <span class="number">8</span><span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>
 stream<span class="symbol">.</span>WriteByte<span class="symbol">(</span><span class="symbol">(</span><span class="keyword">byte</span><span class="symbol">)</span><span class="symbol">(</span>value <span class="symbol">&gt;&gt;</span> <span class="number">0</span><span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>

<span class="keyword">private</span> <span class="keyword">void</span> WriteInt<span class="number">32</span><span class="symbol">(</span>Stream stream<span class="symbol">,</span> <span class="keyword">int</span> value<span class="symbol">)</span>
<span class="symbol">{</span>
 stream<span class="symbol">.</span>WriteByte<span class="symbol">(</span><span class="symbol">(</span><span class="keyword">byte</span><span class="symbol">)</span><span class="symbol">(</span><span class="symbol">(</span>value <span class="symbol">&amp;</span> <span class="number">0xFF000000</span><span class="symbol">)</span> <span class="symbol">&gt;&gt;</span> <span class="number">24</span><span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>
 stream<span class="symbol">.</span>WriteByte<span class="symbol">(</span><span class="symbol">(</span><span class="keyword">byte</span><span class="symbol">)</span><span class="symbol">(</span><span class="symbol">(</span>value <span class="symbol">&amp;</span> <span class="number">0x00FF0000</span><span class="symbol">)</span> <span class="symbol">&gt;&gt;</span> <span class="number">16</span><span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>
 stream<span class="symbol">.</span>WriteByte<span class="symbol">(</span><span class="symbol">(</span><span class="keyword">byte</span><span class="symbol">)</span><span class="symbol">(</span><span class="symbol">(</span>value <span class="symbol">&amp;</span> <span class="number">0x0000FF00</span><span class="symbol">)</span> <span class="symbol">&gt;&gt;</span> <span class="number">8</span><span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>
 stream<span class="symbol">.</span>WriteByte<span class="symbol">(</span><span class="symbol">(</span><span class="keyword">byte</span><span class="symbol">)</span><span class="symbol">(</span><span class="symbol">(</span>value <span class="symbol">&amp;</span> <span class="number">0x000000FF</span><span class="symbol">)</span> <span class="symbol">&gt;&gt;</span> <span class="number">0</span><span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>As with the equivalent read functions, the <code>&gt;&gt; 0</code> shift is
unnecessary but it does clarify the code.</p>
<p>We also need to store colour swatch names, so again we'll make
use of the <code>Encoding.BigEndianUnicode</code> property to convert a
string into a series of bytes to write out.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">private</span> <span class="keyword">void</span> WriteString<span class="symbol">(</span>Stream stream<span class="symbol">,</span> <span class="keyword">string</span> value<span class="symbol">)</span>
<span class="symbol">{</span>
 stream<span class="symbol">.</span>Write<span class="symbol">(</span>Encoding<span class="symbol">.</span>BigEndianUnicode<span class="symbol">.</span>GetBytes<span class="symbol">(</span>value<span class="symbol">)</span><span class="symbol">,</span> <span class="number">0</span><span class="symbol">,</span> value<span class="symbol">.</span>Length <span class="symbol">*</span> <span class="number">2</span><span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<h2 id="writing-the-file">Writing the file</h2>
<p>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).</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">using</span> <span class="symbol">(</span>Stream stream <span class="symbol">=</span> File<span class="symbol">.</span>Create<span class="symbol">(</span>fileName<span class="symbol">)</span><span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">this</span><span class="symbol">.</span>WritePalette<span class="symbol">(</span>stream<span class="symbol">,</span> palette<span class="symbol">,</span> FileVersion<span class="symbol">.</span>Version<span class="number">1</span><span class="symbol">,</span> ColorSpace<span class="symbol">.</span>Rgb<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>WritePalette<span class="symbol">(</span>stream<span class="symbol">,</span> palette<span class="symbol">,</span> FileVersion<span class="symbol">.</span>Version<span class="number">2</span><span class="symbol">,</span> ColorSpace<span class="symbol">.</span>Rgb<span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>The core save routine follows. First, we write the version of
format and then the number of colours in the palette.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">private</span> <span class="keyword">void</span> WritePalette<span class="symbol">(</span>Stream stream<span class="symbol">,</span> ICollection<span class="symbol">&lt;</span>Color<span class="symbol">&gt;</span> palette<span class="symbol">,</span> FileVersion version<span class="symbol">,</span> ColorSpace colorSpace<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">int</span> swatchIndex<span class="symbol">;</span>

 <span class="keyword">this</span><span class="symbol">.</span>WriteInt<span class="number">16</span><span class="symbol">(</span>stream<span class="symbol">,</span> <span class="symbol">(</span><span class="keyword">short</span><span class="symbol">)</span>version<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>WriteInt<span class="number">16</span><span class="symbol">(</span>stream<span class="symbol">,</span> <span class="symbol">(</span><span class="keyword">short</span><span class="symbol">)</span>palette<span class="symbol">.</span>Count<span class="symbol">)</span><span class="symbol">;</span>

 swatchIndex <span class="symbol">=</span> <span class="number">0</span><span class="symbol">;</span>
</pre>
</figure>
<p>With that done, we loop through each colour, calculate the four
values that comprise the colour data and then write that.</p>
<p>If it's a version 2 file, we also write the swatch name. As
these basic examples are just using the <code>Color</code> class, there's
no real flexibility in names, so we cheat - if it's a &quot;named&quot;
colour, then we use the <code>Color.Name</code> property. Otherwise, we
generate a <code>Swatch &lt;index&gt;</code> name.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
 <span class="keyword">foreach</span> <span class="symbol">(</span>Color color <span class="keyword">in</span> palette<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">short</span> value<span class="number">1</span><span class="symbol">;</span>
 <span class="keyword">short</span> value<span class="number">2</span><span class="symbol">;</span>
 <span class="keyword">short</span> value<span class="number">3</span><span class="symbol">;</span>
 <span class="keyword">short</span> value<span class="number">4</span><span class="symbol">;</span>

 swatchIndex<span class="symbol">++</span><span class="symbol">;</span>

 <span class="keyword">switch</span> <span class="symbol">(</span>colorSpace<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="comment">// Calculate color space values here!</span>
 <span class="keyword">default</span><span class="symbol">:</span>
 <span class="keyword">throw</span> <span class="keyword">new</span> InvalidOperationException<span class="symbol">(</span><span class="string">&quot;Color space not supported.&quot;</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">this</span><span class="symbol">.</span>WriteInt<span class="number">16</span><span class="symbol">(</span>stream<span class="symbol">,</span> <span class="symbol">(</span><span class="keyword">short</span><span class="symbol">)</span>colorSpace<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>WriteInt<span class="number">16</span><span class="symbol">(</span>stream<span class="symbol">,</span> value<span class="number">1</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>WriteInt<span class="number">16</span><span class="symbol">(</span>stream<span class="symbol">,</span> value<span class="number">2</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>WriteInt<span class="number">16</span><span class="symbol">(</span>stream<span class="symbol">,</span> value<span class="number">3</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>WriteInt<span class="number">16</span><span class="symbol">(</span>stream<span class="symbol">,</span> value<span class="number">4</span><span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>version <span class="symbol">==</span> FileVersion<span class="symbol">.</span>Version<span class="number">2</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">string</span> name<span class="symbol">;</span>

 name <span class="symbol">=</span> color<span class="symbol">.</span>IsNamedColor <span class="symbol">?</span> color<span class="symbol">.</span>Name <span class="symbol">:</span> <span class="keyword">string</span><span class="symbol">.</span>Format<span class="symbol">(</span><span class="string">&quot;Swatch {0}&quot;</span><span class="symbol">,</span> swatchIndex<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">this</span><span class="symbol">.</span>WriteInt<span class="number">32</span><span class="symbol">(</span>stream<span class="symbol">,</span> name<span class="symbol">.</span>Length<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">this</span><span class="symbol">.</span>WriteString<span class="symbol">(</span>stream<span class="symbol">,</span> name<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<h2 id="converting-colour-spaces">Converting colour spaces</h2>
<p>As previously mentioned, the <a href="http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577411_pgfId-1055819" rel="external nofollow noopener">specification</a> states that each
colour is comprised of four values. Even if a particular colour
space doesn't use all four (for example <code>Grayscale</code> just uses
one, you still need to write the other values, typically as
zero's.</p>
<p>Although it's a slight duplication, I'll include the description
table for colour spaces to allow easy reference of the value
types.</p>
<table>
<thead>
<tr>
<th style="text-align: right;">Id</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: right;">0</td>
<td>RGB. The first three values in the colour data are red, green, and blue. They are full unsigned 16-bit values as in Apple's RGBColordata structure. Pure red = 65535, 0, 0.</td>
</tr>
<tr>
<td style="text-align: right;">1</td>
<td>HSB. The first three values in the colour data are hue, saturation, and brightness. They are full unsigned 16-bit values as in Apple's HSVColordata structure. Pure red = 0,65535, 65535.</td>
</tr>
<tr>
<td style="text-align: right;">2</td>
<td>CMYK. The four values in the colour data are cyan, magenta, yellow, and black. They are full unsigned 16-bit values. For example, pure cyan = 0,65535,65535,65535.</td>
</tr>
<tr>
<td style="text-align: right;">7</td>
<td>Lab. The first three values in the colour 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.</td>
</tr>
<tr>
<td style="text-align: right;">8</td>
<td>Grayscale. The first value in the colour data is the gray value, from 0...10000.</td>
</tr>
</tbody>
</table>
<p>While supporting <code>CMYK</code> colours are beyond the scope of this
article as they require colour profiles, and I haven't the
foggiest on the <code>Lab</code> space, we can easily support <code>RGB</code>, <code>HSL</code>
and <code>Grayscale</code> spaces.</p>
<p>RGB is the simplest as .NET colours are already in this format.
The only thing we have to do is multiple each channel by 256 as
the specification uses the range 0-65535 rather than the typical
0-255.</p>
<p>Notice <code>value4</code> is simply initialized to zero as this space only
needs 3 of the 4 values.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
 <span class="keyword">case</span> ColorSpace<span class="symbol">.</span>Rgb<span class="symbol">:</span>
 value<span class="number">1</span> <span class="symbol">=</span> <span class="symbol">(</span><span class="keyword">short</span><span class="symbol">)</span><span class="symbol">(</span>color<span class="symbol">.</span>R <span class="symbol">*</span> <span class="number">256</span><span class="symbol">)</span><span class="symbol">;</span>
 value<span class="number">2</span> <span class="symbol">=</span> <span class="symbol">(</span><span class="keyword">short</span><span class="symbol">)</span><span class="symbol">(</span>color<span class="symbol">.</span>G <span class="symbol">*</span> <span class="number">256</span><span class="symbol">)</span><span class="symbol">;</span>
 value<span class="number">3</span> <span class="symbol">=</span> <span class="symbol">(</span><span class="keyword">short</span><span class="symbol">)</span><span class="symbol">(</span>color<span class="symbol">.</span>B <span class="symbol">*</span> <span class="number">256</span><span class="symbol">)</span><span class="symbol">;</span>
 value<span class="number">4</span> <span class="symbol">=</span> <span class="number">0</span><span class="symbol">;</span>
 <span class="keyword">break</span><span class="symbol">;</span>
</pre>
</figure>
<p>We can also support HSL without too much trouble as the <code>Color</code>
class already includes methods for extracting these values.
Again, we need to do a little fiddling to change the numbers
into the range used by the specification.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
 <span class="keyword">case</span> ColorSpace<span class="symbol">.</span>Hsb<span class="symbol">:</span>
 value<span class="number">1</span> <span class="symbol">=</span> <span class="symbol">(</span><span class="keyword">short</span><span class="symbol">)</span><span class="symbol">(</span>color<span class="symbol">.</span>GetHue<span class="symbol">(</span><span class="symbol">)</span> <span class="symbol">*</span> <span class="number">182.04</span><span class="symbol">)</span><span class="symbol">;</span>
 value<span class="number">2</span> <span class="symbol">=</span> <span class="symbol">(</span><span class="keyword">short</span><span class="symbol">)</span><span class="symbol">(</span>color<span class="symbol">.</span>GetSaturation<span class="symbol">(</span><span class="symbol">)</span> <span class="symbol">*</span> <span class="number">655.35</span><span class="symbol">)</span><span class="symbol">;</span>
 value<span class="number">3</span> <span class="symbol">=</span> <span class="symbol">(</span><span class="keyword">short</span><span class="symbol">)</span><span class="symbol">(</span>color<span class="symbol">.</span>GetBrightness<span class="symbol">(</span><span class="symbol">)</span> <span class="symbol">*</span> <span class="number">655.35</span><span class="symbol">)</span><span class="symbol">;</span>
 value<span class="number">4</span> <span class="symbol">=</span> <span class="number">0</span><span class="symbol">;</span>
 <span class="keyword">break</span><span class="symbol">;</span>
</pre>
</figure>
<p>The last format we can easily support is grayscale. If the
source colour is already grey (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.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
 <span class="keyword">case</span> ColorSpace<span class="symbol">.</span>Grayscale<span class="symbol">:</span>
 <span class="keyword">if</span> <span class="symbol">(</span>color<span class="symbol">.</span>R <span class="symbol">==</span> color<span class="symbol">.</span>G <span class="symbol">&amp;&amp;</span> color<span class="symbol">.</span>R <span class="symbol">==</span> color<span class="symbol">.</span>B<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="comment">// already grayscale</span>
 value<span class="number">1</span> <span class="symbol">=</span> <span class="symbol">(</span><span class="keyword">short</span><span class="symbol">)</span><span class="symbol">(</span>color<span class="symbol">.</span>R <span class="symbol">*</span> <span class="number">39.0625</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="keyword">else</span>
 <span class="symbol">{</span>
 <span class="comment">// color is not grayscale, convert</span>
 value<span class="number">1</span> <span class="symbol">=</span> <span class="symbol">(</span><span class="keyword">short</span><span class="symbol">)</span><span class="symbol">(</span><span class="symbol">(</span><span class="symbol">(</span>color<span class="symbol">.</span>R <span class="symbol">+</span> color<span class="symbol">.</span>G <span class="symbol">+</span> color<span class="symbol">.</span>B<span class="symbol">)</span> <span class="symbol">/</span> <span class="number">3.0</span><span class="symbol">)</span> <span class="symbol">*</span> <span class="number">39.0625</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 value<span class="number">2</span> <span class="symbol">=</span> <span class="number">0</span><span class="symbol">;</span>
 value<span class="number">3</span> <span class="symbol">=</span> <span class="number">0</span><span class="symbol">;</span>
 value<span class="number">4</span> <span class="symbol">=</span> <span class="number">0</span><span class="symbol">;</span>
 <span class="keyword">break</span><span class="symbol">;</span>
</pre>
</figure>
<h2 id="demo-application">Demo Application</h2>
<p>The usual sample application is available from the links at the
end of this article. The sample generates a random 256 colour
palette, then writes this to a temporary file using the
specified colour space. It then reads it back in, and displays
both palettes side by side for comparison.</p>
<h2 id="update-history">Update History</h2>
<ul>
<li>2009-08-10 - First published</li>
<li>2020-11-21 - Updated formatting</li>
</ul>

<p><small>
All content <a href="https://devblog.cyotek.com/copyright-and-trademarks">Copyright (c) by Cyotek Ltd</a> or its respective writers. Permission to reproduce news and web log entries and other RSS feed content in unmodified form without notice is granted provided they are not used to endorse or promote any products or opinions (other than what was expressed by the author) and without taking them out of context. Written permission from the copyright owner must be obtained for everything else.<br />Original URL of this content is https://devblog.cyotek.com/post/writing-photoshop-color-swatch-aco-files-using-csharp .
</small></p>Richard Mosshttps://www.cyotek.com/richard.moss@cyotek.comReading Photoshop Color Swatch (aco) files using C#urn:uuid:a613a036-56f1-4668-bbe2-c782192200a92014-01-22T21:27:55Z2014-01-22T21:27:55Z<p>In a <a href="/post/loading-the-color-palette-from-a-bbm-lbm-image-file-using-csharp">previous article</a> I described how to read the colour
map from a DeluxePaint LBM/BBM file. In the next pair of
articles, I'm going to describe how to load and save colour
swatch files used by Photoshop (those with the <code>.aco</code>
extension).</p>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/loadphotoshopcolorswatch.png" class="gallery" title="A sample application showing loading of colour swatches from Photoshop colour swatch files" ><img src="https://images.cyotek.com/image/thumbnail/devblog/loadphotoshopcolorswatch.png" alt="A sample application showing loading of colour swatches from Photoshop colour swatch files" decoding="async" loading="lazy" /></a><figcaption>A sample application showing loading of colour swatches from Photoshop colour swatch files</figcaption></figure><h2 id="caveat-emptor">Caveat Emptor</h2>
<p>As usual, I'll start with a warning. I have a very limited set
of sample files to test with, so it may be that there's an error
in this code which means it can't handle all files. Certainly it
can't handle all colour spaces (more on that later). However,
I've tested it on a number of files download from the internet
without problems.</p>
<h2 id="structure-of-a-photoshop-colour-swatch-file">Structure of a Photoshop colour swatch file</h2>
<p>The structure of the <code>aco</code> file is straightforward, helped by
Adobe themselves publishing the <a href="http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577411_pgfId-1055819" rel="external nofollow noopener">specification</a> which is
something to appreciate. This article was created using the
October 2013 edition of this specification.</p>
<p>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.</p>
<p>The structure is simple. There's a 2-byte version code, followed
by 2-bytes describing the number of colours. Then, for each
colour, there are 10 further bytes, 2 each describing the colour
space and then four values to describe the colour. 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.</p>
<table class="table table-condensed table-bordered">
 <thead>
 <tr>
 <th>Length
 </th>
 <th>Description
 </th>
 </tr>
 </thead>
 <tbody>
 <tr>
 <td>2</td>
 <td>Version</td>
 </tr>
 <tr>
 <td>2</td>
 <td>Number of colours</td>
 </tr>
 <tr>
 <td rowspan="2">count * 10 <em>(+ 4 + variable (version 2 only))</em>
 </td>
 <td>
 <p> Colour data</p>
 <table class="table table-condensed table-bordered">
 <thead>
 <tr>
 <th>Length
 </th>
 <th>Description
 </th>
 </tr>
 </thead>
 <tbody>
 <tr>
 <td>2</td>
 <td>Colour space</td>
 </tr>
 <tr>
 <td>2</td>
 <td>Colour data value 1</td>
 </tr>
 <tr>
 <td>2</td>
 <td>Colour data value 2</td>
 </tr>
 <tr>
 <td>2</td>
 <td>Colour data value 3</td>
 </tr>
 <tr>
 <td>2</td>
 <td>Colour data value 4</td>
 </tr>
 </tbody>
 </table>
 </td>
 </tr>
 <tr>
 <td>
 <p><em>Version 2 only</em></p>
 <table class="table table-condensed table-bordered">
 <thead>
 <tr>
 <th>Length
 </th>
 <th>Description
 </th>
 </tr>
 </thead>
 <tbody>
 <tr>
 <td>4</td>
 <td>Length of name string in characters</td>
 </tr>
 <tr>
 <td>length * 2</td>
 <td>Unicode code characters, two bytes per character</td>
 </tr>
 </tbody>
 </table>
 </td>
 </tr>
 </tbody>
</table>
<p>All the data in an <code>aco</code> file is stored in <a href="http://en.wikipedia.org/wiki/Endianness" rel="external nofollow noopener">big-endian</a>
format and therefore needs to be reversed on Windows systems.</p>
<p>Most colour spaces only use three of the four available values,
but regardless of how many are actually used, all must be
specified.</p>
<h2 id="colour-spaces">Colour Spaces</h2>
<p>I mentioned above that each colour has a description of what
colour space it belongs to. The specification defines the
following colour spaces:</p>
<table>
<thead>
<tr>
<th style="text-align: right;">Id</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: right;">0</td>
<td>RGB. The first three values in the colour data are red, green, and blue. They are full unsigned 16-bit values as in Apple's RGBColordata structure. Pure red = 65535, 0, 0.</td>
</tr>
<tr>
<td style="text-align: right;">1</td>
<td>HSB. The first three values in the colour data are hue, saturation, and brightness. They are full unsigned 16-bit values as in Apple's HSVColordata structure. Pure red = 0,65535, 65535.</td>
</tr>
<tr>
<td style="text-align: right;">2</td>
<td>CMYK. The four values in the colour data are cyan, magenta, yellow, and black. They are full unsigned 16-bit values. For example, pure cyan = 0,65535,65535,65535.</td>
</tr>
<tr>
<td style="text-align: right;">7</td>
<td>Lab. The first three values in the colour 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.</td>
</tr>
<tr>
<td style="text-align: right;">8</td>
<td>Grayscale. The first value in the colour data is the gray value, from 0...10000.</td>
</tr>
</tbody>
</table>
<p>To avoid complicating matters, this article will concentrate on
<code>RGB</code> and <code>Grayscale</code> colour spaces, although I'll include the
basics of <code>HSV</code> too for if you have a conversion class kicking
around.</p>
<h2 id="reading-shortint-data-types-from-bytes">Reading short/int data types from bytes</h2>
<p>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 <code>short</code>
(<code>Int16</code>) or an <code>int</code> (<code>Int32</code>), using the following helpers:</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="selector-tag">///</span> <span class="selector-tag">&lt;summary&gt;</span>
<span class="selector-tag">///</span><span class="comment"> Reads a 16bit unsigned integer in big-endian format.</span>
<span class="selector-tag">///</span> <span class="selector-tag">&lt;/summary&gt;</span>
<span class="selector-tag">///</span> <span class="selector-tag">&lt;param name=&quot;stream&quot;&gt;</span><span class="comment">The stream to read the data from.&lt;/param&gt;</span>
<span class="selector-tag">///</span> <span class="selector-tag">&lt;returns&gt;</span><span class="comment">The unsigned 16bit integer cast to an &lt;c&gt;Int32&lt;/c&gt;.&lt;/returns&gt;</span>
<span class="keyword">private</span> <span class="keyword">int</span> ReadInt<span class="number">16</span><span class="symbol">(</span>Stream stream<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">return</span> <span class="symbol">(</span>stream<span class="symbol">.</span>ReadByte<span class="symbol">(</span><span class="symbol">)</span> <span class="symbol">&lt;&lt;</span> <span class="number">8</span><span class="symbol">)</span> <span class="symbol">|</span> <span class="symbol">(</span>stream<span class="symbol">.</span>ReadByte<span class="symbol">(</span><span class="symbol">)</span> <span class="symbol">&lt;&lt;</span> <span class="number">0</span><span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>

<span class="selector-tag">///</span> <span class="selector-tag">&lt;summary&gt;</span>
<span class="selector-tag">///</span><span class="comment"> Reads a 32bit unsigned integer in big-endian format.</span>
<span class="selector-tag">///</span> <span class="selector-tag">&lt;/summary&gt;</span>
<span class="selector-tag">///</span> <span class="selector-tag">&lt;param name=&quot;stream&quot;&gt;</span><span class="comment">The stream to read the data from.&lt;/param&gt;</span>
<span class="selector-tag">///</span> <span class="selector-tag">&lt;returns&gt;</span><span class="comment">The unsigned 32bit integer cast to an &lt;c&gt;Int32&lt;/c&gt;.&lt;/returns&gt;</span>
<span class="keyword">private</span> <span class="keyword">int</span> ReadInt<span class="number">32</span><span class="symbol">(</span>Stream stream<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">return</span> <span class="symbol">(</span><span class="symbol">(</span><span class="keyword">byte</span><span class="symbol">)</span>stream<span class="symbol">.</span>ReadByte<span class="symbol">(</span><span class="symbol">)</span> <span class="symbol">&lt;&lt;</span> <span class="number">24</span><span class="symbol">)</span> <span class="symbol">|</span> <span class="symbol">(</span><span class="symbol">(</span><span class="keyword">byte</span><span class="symbol">)</span>stream<span class="symbol">.</span>ReadByte<span class="symbol">(</span><span class="symbol">)</span> <span class="symbol">&lt;&lt;</span> <span class="number">16</span><span class="symbol">)</span> <span class="symbol">|</span> <span class="symbol">(</span><span class="symbol">(</span><span class="keyword">byte</span><span class="symbol">)</span>stream<span class="symbol">.</span>ReadByte<span class="symbol">(</span><span class="symbol">)</span> <span class="symbol">&lt;&lt;</span> <span class="number">8</span><span class="symbol">)</span> <span class="symbol">|</span> <span class="symbol">(</span><span class="symbol">(</span><span class="keyword">byte</span><span class="symbol">)</span>stream<span class="symbol">.</span>ReadByte<span class="symbol">(</span><span class="symbol">)</span> <span class="symbol">&lt;&lt;</span> <span class="number">0</span><span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<blockquote>
<p>The <code>&lt;&lt; 0</code> bit-shift in the above methods is technically
unnecessary and can be removed. However, I find it makes the
intent of the code clearer.</p>
</blockquote>
<h2 id="reading-strings">Reading strings</h2>
<p>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 <code>BigEndianUnicode</code> (<a href="http://msdn.microsoft.com/en-us/library/vstudio/system.text.encoding.bigendianunicode(v=vs.90).aspx" rel="external nofollow noopener">MSDN</a>) 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.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="selector-tag">///</span> <span class="selector-tag">&lt;summary&gt;</span>
<span class="selector-tag">///</span><span class="comment"> Reads a unicode string of the specified length.</span>
<span class="selector-tag">///</span> <span class="selector-tag">&lt;/summary&gt;</span>
<span class="selector-tag">///</span> <span class="selector-tag">&lt;param name=&quot;stream&quot;&gt;</span><span class="comment">The stream to read the data from.&lt;/param&gt;</span>
<span class="selector-tag">///</span> <span class="selector-tag">&lt;param name=&quot;length&quot;&gt;</span><span class="comment">The number of characters in the string.&lt;/param&gt;</span>
<span class="selector-tag">///</span> <span class="selector-tag">&lt;returns&gt;</span><span class="comment">The string read from the stream.&lt;/returns&gt;</span>
<span class="keyword">private</span> <span class="keyword">string</span> ReadString<span class="symbol">(</span>Stream stream<span class="symbol">,</span> <span class="keyword">int</span> length<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">byte</span><span class="symbol">[</span><span class="symbol">]</span> buffer<span class="symbol">;</span>

 buffer <span class="symbol">=</span> <span class="keyword">new</span> <span class="keyword">byte</span><span class="symbol">[</span>length <span class="symbol">*</span> <span class="number">2</span><span class="symbol">]</span><span class="symbol">;</span>

 stream<span class="symbol">.</span>Read<span class="symbol">(</span>buffer<span class="symbol">,</span> <span class="number">0</span><span class="symbol">,</span> buffer<span class="symbol">.</span>Length<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">return</span> Encoding<span class="symbol">.</span>BigEndianUnicode<span class="symbol">.</span>GetString<span class="symbol">(</span>buffer<span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<h2 id="reading-the-file">Reading the file</h2>
<p>With the preliminaries done with, lets read the file!</p>
<p>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.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">using</span> <span class="symbol">(</span>Stream stream <span class="symbol">=</span> File<span class="symbol">.</span>OpenRead<span class="symbol">(</span>fileName<span class="symbol">)</span><span class="symbol">)</span>
<span class="symbol">{</span>
 FileVersion version<span class="symbol">;</span>

 <span class="comment">// read the version, which occupies two bytes</span>
 version <span class="symbol">=</span> <span class="symbol">(</span>FileVersion<span class="symbol">)</span><span class="keyword">this</span><span class="symbol">.</span>ReadInt<span class="number">16</span><span class="symbol">(</span>stream<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>version <span class="symbol">!=</span> FileVersion<span class="symbol">.</span>Version<span class="number">1</span> <span class="symbol">&amp;&amp;</span> version <span class="symbol">!=</span> FileVersion<span class="symbol">.</span>Version<span class="number">2</span><span class="symbol">)</span>
 <span class="keyword">throw</span> <span class="keyword">new</span> InvalidDataException<span class="symbol">(</span><span class="string">&quot;Invalid version information.&quot;</span><span class="symbol">)</span><span class="symbol">;</span>

 colorPalette <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>ReadSwatches<span class="symbol">(</span>stream<span class="symbol">,</span> version<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">if</span> <span class="symbol">(</span>version <span class="symbol">==</span> FileVersion<span class="symbol">.</span>Version<span class="number">1</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 version <span class="symbol">=</span> <span class="symbol">(</span>FileVersion<span class="symbol">)</span><span class="keyword">this</span><span class="symbol">.</span>ReadInt<span class="number">16</span><span class="symbol">(</span>stream<span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">if</span> <span class="symbol">(</span>version <span class="symbol">==</span> FileVersion<span class="symbol">.</span>Version<span class="number">2</span><span class="symbol">)</span>
 colorPalette <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>ReadSwatches<span class="symbol">(</span>stream<span class="symbol">,</span> version<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>In the above example, if a file has <em>both</em> 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 <code>Color</code> structure - which doesn't allow you to
set the <code>Name</code> 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.</p>
<h2 id="reading-colour-data">Reading colour data</h2>
<p>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 colour data looks like this.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="comment">// read the number of colors, which also occupies two bytes</span>
colorCount <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>ReadInt<span class="number">16</span><span class="symbol">(</span>stream<span class="symbol">)</span><span class="symbol">;</span>

<span class="keyword">for</span> <span class="symbol">(</span><span class="keyword">int</span> i <span class="symbol">=</span> <span class="number">0</span><span class="symbol">;</span> i <span class="symbol">&lt;</span> colorCount<span class="symbol">;</span> i<span class="symbol">++</span><span class="symbol">)</span>
<span class="symbol">{</span>
 ColorSpace colorSpace<span class="symbol">;</span>
 <span class="keyword">int</span> value<span class="number">1</span><span class="symbol">;</span>
 <span class="keyword">int</span> value<span class="number">2</span><span class="symbol">;</span>
 <span class="keyword">int</span> value<span class="number">3</span><span class="symbol">;</span>
 <span class="keyword">int</span> value<span class="number">4</span><span class="symbol">;</span>

 <span class="comment">// again, two bytes for the color space</span>
 colorSpace <span class="symbol">=</span> <span class="symbol">(</span>ColorSpace<span class="symbol">)</span><span class="symbol">(</span><span class="keyword">this</span><span class="symbol">.</span>ReadInt<span class="number">16</span><span class="symbol">(</span>stream<span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>

 <span class="comment">// then the four values which comprise each color</span>
 value<span class="number">1</span> <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>ReadInt<span class="number">16</span><span class="symbol">(</span>stream<span class="symbol">)</span><span class="symbol">;</span>
 value<span class="number">2</span> <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>ReadInt<span class="number">16</span><span class="symbol">(</span>stream<span class="symbol">)</span><span class="symbol">;</span>
 value<span class="number">3</span> <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>ReadInt<span class="number">16</span><span class="symbol">(</span>stream<span class="symbol">)</span><span class="symbol">;</span>
 value<span class="number">4</span> <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>ReadInt<span class="number">16</span><span class="symbol">(</span>stream<span class="symbol">)</span><span class="symbol">;</span>

 <span class="comment">// and finally, the name of the swatch (version2 only)</span>
 <span class="keyword">if</span> <span class="symbol">(</span>version <span class="symbol">==</span> FileVersion<span class="symbol">.</span>Version<span class="number">2</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">int</span> length<span class="symbol">;</span>
 <span class="keyword">string</span> name<span class="symbol">;</span>

 length <span class="symbol">=</span> ReadInt<span class="number">32</span><span class="symbol">(</span>stream<span class="symbol">)</span><span class="symbol">;</span>
 name <span class="symbol">=</span> <span class="keyword">this</span><span class="symbol">.</span>ReadString<span class="symbol">(</span>stream<span class="symbol">,</span> length<span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<h2 id="translating-the-colour-spaces">Translating the colour spaces</h2>
<p>Once we've read the colour space and the four values of the
colour data, we need to process it.</p>
<p>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:</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">switch</span> <span class="symbol">(</span>colorSpace<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">case</span> ColorSpace<span class="symbol">.</span>Rgb<span class="symbol">:</span>
 <span class="keyword">int</span> red<span class="symbol">;</span>
 <span class="keyword">int</span> green<span class="symbol">;</span>
 <span class="keyword">int</span> blue<span class="symbol">;</span>

 red <span class="symbol">=</span> value<span class="number">1</span> <span class="symbol">/</span> <span class="number">256</span><span class="symbol">;</span> <span class="comment">// 0-255</span>
 green <span class="symbol">=</span> value<span class="number">2</span> <span class="symbol">/</span> <span class="number">256</span><span class="symbol">;</span> <span class="comment">// 0-255</span>
 blue <span class="symbol">=</span> value<span class="number">3</span> <span class="symbol">/</span> <span class="number">256</span><span class="symbol">;</span> <span class="comment">// 0-255</span>

 results<span class="symbol">.</span>Add<span class="symbol">(</span>Color<span class="symbol">.</span>FromArgb<span class="symbol">(</span>red<span class="symbol">,</span> green<span class="symbol">,</span> blue<span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">break</span><span class="symbol">;</span>
</pre>
</figure>
<p>Next is HSL. How you process that depends on the class you are
using, and the range of values it accepts.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">case</span> ColorSpace<span class="symbol">.</span>Hsb<span class="symbol">:</span>
 <span class="keyword">double</span> hue<span class="symbol">;</span>
 <span class="keyword">double</span> saturation<span class="symbol">;</span>
 <span class="keyword">double</span> brightness<span class="symbol">;</span>

 hue <span class="symbol">=</span> value<span class="number">1</span> <span class="symbol">/</span> <span class="number">182.04</span><span class="symbol">;</span> <span class="comment">// 0-359</span>
 saturation <span class="symbol">=</span> value<span class="number">2</span> <span class="symbol">/</span> <span class="number">655.35</span><span class="symbol">;</span> <span class="comment">// 0-1</span>
 brightness <span class="symbol">=</span> value<span class="number">3</span> <span class="symbol">/</span> <span class="number">655.35</span><span class="symbol">;</span> <span class="comment">// 0-1</span>

 results<span class="symbol">.</span>Add<span class="symbol">(</span><span class="keyword">new</span> HslColor<span class="symbol">(</span>hue<span class="symbol">,</span> saturation<span class="symbol">,</span> brightness<span class="symbol">)</span><span class="symbol">.</span>ToRgbColor<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">break</span><span class="symbol">;</span>
</pre>
</figure>
<p>The last colour space we can easily support is gray scale.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">case</span> AdobePhotoshopColorSwatchColorSpace<span class="symbol">.</span>Grayscale<span class="symbol">:</span>

 <span class="keyword">int</span> gray<span class="symbol">;</span>

 <span class="comment">// Grayscale.</span>
 <span class="comment">// The first value in the color data is the gray value, from 0...10000.</span>
 gray <span class="symbol">=</span> <span class="symbol">(</span><span class="keyword">int</span><span class="symbol">)</span><span class="symbol">(</span>value<span class="number">1</span> <span class="symbol">/</span> <span class="number">39.0625</span><span class="symbol">)</span><span class="symbol">;</span>

 results<span class="symbol">.</span>Add<span class="symbol">(</span>Color<span class="symbol">.</span>FromArgb<span class="symbol">(</span>gray<span class="symbol">,</span> gray<span class="symbol">,</span> gray<span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">break</span><span class="symbol">;</span>
</pre>
</figure>
<p>Files using the Lab or CMYK spaces will throw an exception as
these are beyond the scope of this example.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
 <span class="keyword">default</span><span class="symbol">:</span>
 <span class="keyword">throw</span> <span class="keyword">new</span> InvalidDataException<span class="symbol">(</span><span class="keyword">string</span><span class="symbol">.</span>Format<span class="symbol">(</span><span class="string">&quot;Color space &#39;{0}&#39; not supported.&quot;</span><span class="symbol">,</span> colorSpace<span class="symbol">)</span><span class="symbol">)</span><span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>Although none of the sample files I tested mixed colour 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 colours. Therefore it may be a better idea to just
ignore such errors to allow any valid data to be read.</p>
<h2 id="wrapping-up">Wrapping up</h2>
<p>As with <a href="/post/loading-the-color-palette-from-a-bbm-lbm-image-file-using-csharp">reading LBM colour maps</a>, reading the Photoshop
colour swatches was also quite an easy process.</p>
<p>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 <code>aco</code> files.</p>
<h2 id="update-history">Update History</h2>
<ul>
<li>2014-01-22 - First published</li>
<li>2020-11-21 - Updated formatting</li>
</ul>

<p><small>
All content <a href="https://devblog.cyotek.com/copyright-and-trademarks">Copyright (c) by Cyotek Ltd</a> or its respective writers. Permission to reproduce news and web log entries and other RSS feed content in unmodified form without notice is granted provided they are not used to endorse or promote any products or opinions (other than what was expressed by the author) and without taking them out of context. Written permission from the copyright owner must be obtained for everything else.<br />Original URL of this content is https://devblog.cyotek.com/post/reading-photoshop-color-swatch-aco-files-using-csharp .
</small></p>Richard Mosshttps://www.cyotek.com/richard.moss@cyotek.com