Cyotek Development Bloghttps://devblog.cyotek.com/tag/algorithm/atom.xml2017-02-27T06:37:43ZFinding nearest colors using Euclidean distanceurn:uuid:973d6cfd-bf49-4bb6-921e-9d5bb2138cb22017-02-27T06:37:43Z2017-01-06T08:48:24Z<p>I've recently been updating our series on <a href="/tag/dither">dithering</a> to
include ordered dithering. However, in order to fully
demonstrate this I also updated the sample to include basic
color quantizing with a fixed palette.</p>
<p>While color reduction and dithering are related, I didn't want
to cover both topics in a single blog post, so here we are with
a first post on finding the nearest color via Euclidean
distance, and I'll follow up in another post on ordered
dithering.</p>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/colordistance-1a.png" class="gallery" title="A demo showing the distance between two colors, and mapping those colors to the nearest color in a fixed palette" ><img src="https://images.cyotek.com/image/thumbnail/devblog/colordistance-1a.png" alt="A demo showing the distance between two colors, and mapping those colors to the nearest color in a fixed palette" decoding="async" loading="lazy" /></a><figcaption>A demo showing the distance between two colors, and mapping those colors to the nearest color in a fixed palette</figcaption></figure><h2 id="getting-the-distance-between-two-colors">Getting the distance between two colors</h2>
<p>Getting the distance between two colors is a matter of
multiplying the difference of each channel between the two
colors and then adding it all together, or if you want a
formula, <a href="https://en.wikipedia.org/wiki/Euclidean_distance" rel="external nofollow noopener">Wikipedia</a> obliges handily</p>
<p><img src="https://images.cyotek.com/image/devblog/d1d13a40a7b203b455ae6d4be8b3cce898bda625.svg" decoding="async" loading="lazy" alt="Three-dimensional Euclidean space formula" /></p>
<p>In C# terms, that translates to a helper function similar to the
below</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">int</span> GetDistance<span class="symbol">(</span>Color current<span class="symbol">,</span> Color match<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">int</span> redDifference<span class="symbol">;</span>
 <span class="keyword">int</span> greenDifference<span class="symbol">;</span>
 <span class="keyword">int</span> blueDifference<span class="symbol">;</span>

 redDifference <span class="symbol">=</span> current<span class="symbol">.</span>R <span class="symbol">-</span> match<span class="symbol">.</span>R<span class="symbol">;</span>
 greenDifference <span class="symbol">=</span> current<span class="symbol">.</span>G <span class="symbol">-</span> match<span class="symbol">.</span>G<span class="symbol">;</span>
 blueDifference <span class="symbol">=</span> current<span class="symbol">.</span>B <span class="symbol">-</span> match<span class="symbol">.</span>B<span class="symbol">;</span>

 <span class="keyword">return</span> redDifference <span class="symbol">*</span> redDifference <span class="symbol">+</span> greenDifference <span class="symbol">*</span> greenDifference <span class="symbol">+</span> blueDifference <span class="symbol">*</span> blueDifference<span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>Note that the distance is the same between two colours no matter
which way around you call <code>GetDistance</code> with them.</p>
<h2 id="finding-the-nearest-color">Finding the nearest color</h2>
<p>With the ability to identify the distance between two colours,
it is now a trivial matter to scan a fixed array of colors
looking for the closest match. The closest match is merely the
color with the lowest distance. A distance of zero means the
colors are a direct match.</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">int</span> FindNearestColor<span class="symbol">(</span>Color<span class="symbol">[</span><span class="symbol">]</span> map<span class="symbol">,</span> Color current<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">int</span> shortestDistance<span class="symbol">;</span>
 <span class="keyword">int</span> index<span class="symbol">;</span>

 index <span class="symbol">=</span> <span class="symbol">-</span><span class="number">1</span><span class="symbol">;</span>
 shortestDistance <span class="symbol">=</span> <span class="keyword">int</span><span class="symbol">.</span>MaxValue<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> map<span class="symbol">.</span>Length<span class="symbol">;</span> i<span class="symbol">++</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 Color match<span class="symbol">;</span>
 <span class="keyword">int</span> distance<span class="symbol">;</span>

 match <span class="symbol">=</span> map<span class="symbol">[</span>i<span class="symbol">]</span><span class="symbol">;</span>
 distance <span class="symbol">=</span> GetDistance<span class="symbol">(</span>current<span class="symbol">,</span> match<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>distance <span class="symbol">&lt;</span> shortestDistance<span class="symbol">)</span>
 <span class="symbol">{</span>
 index <span class="symbol">=</span> i<span class="symbol">;</span>
 shortestDistance <span class="symbol">=</span> distance<span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>

 <span class="keyword">return</span> index<span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<h2 id="optimizing-finding-the-match">Optimizing finding the match</h2>
<p>While the initial code is simple, using it practically isn't. In
the demonstration program attached to this post, the
<code>FindNearestColor</code> is only called once and so you probably won't
notice any performance impact. However, if you are performing
many searches (for example to reduce the colors in an image),
then you may find the code quite slow. In this case, you
probably want to look at caching the value of <code>FindNearestColor</code>
along with the source color, so that future calls just look in
the cache rather than performing a full scan (a normal
<code>Dictionary&lt;Color, int&gt;</code> worked fine in my limited testing). Of
course the more colours in the map, the slower it will be as
well.</p>
<p>While I haven't tried this yet, using an ordered palette may
allow the use of linear searching. When combined with a cached
lookup, that ought to be enough for most scenarios.</p>
<h2 id="what-about-the-alpha-channel">What about the Alpha channel?</h2>
<p>For my purposes I don't need to consider the alpha value of a
color. However, if you do want to use it, then adjust
<code>GetDistance</code> to include the channel, and it will work just
fine.</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">int</span> GetDistance<span class="symbol">(</span>Color current<span class="symbol">,</span> Color match<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">int</span> redDifference<span class="symbol">;</span>
 <span class="keyword">int</span> greenDifference<span class="symbol">;</span>
 <span class="keyword">int</span> blueDifference<span class="symbol">;</span>
 <span class="keyword">int</span> alphaDifference<span class="symbol">;</span>

 alphaDifference <span class="symbol">=</span> current<span class="symbol">.</span>A <span class="symbol">-</span> match<span class="symbol">.</span>A<span class="symbol">;</span>
 redDifference <span class="symbol">=</span> current<span class="symbol">.</span>R <span class="symbol">-</span> match<span class="symbol">.</span>R<span class="symbol">;</span>
 greenDifference <span class="symbol">=</span> current<span class="symbol">.</span>G <span class="symbol">-</span> match<span class="symbol">.</span>G<span class="symbol">;</span>
 blueDifference <span class="symbol">=</span> current<span class="symbol">.</span>B <span class="symbol">-</span> match<span class="symbol">.</span>B<span class="symbol">;</span>

 <span class="keyword">return</span> alphaDifference <span class="symbol">*</span> alphaDifference <span class="symbol">+</span> redDifference <span class="symbol">*</span> redDifference <span class="symbol">+</span> greenDifference <span class="symbol">*</span> greenDifference <span class="symbol">+</span> blueDifference <span class="symbol">*</span> blueDifference<span class="symbol">;</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>The images below were obtained by setting the value of the box
on the left to <code>0, 0, 220, 0</code>, and the right <code>255, 0, 220, 0</code> -
same RGB, just different alpha.</p>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/colordistance-1b.png" class="gallery" title="Distance from the same color with different alpha" ><img src="https://images.cyotek.com/image/thumbnail/devblog/colordistance-1b.png" alt="Distance from the same color with different alpha" decoding="async" loading="lazy" /></a><figcaption>Distance from the same color with different alpha</figcaption></figure><h2 id="update-history">Update History</h2>
<ul>
<li>2017-01-06 - 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/finding-nearest-colors-using-euclidean-distance .
</small></p>Richard Mosshttps://www.cyotek.com/richard.moss@cyotek.comEven more algorithms for dithering images using C#urn:uuid:13fbfc7c-65f9-47a2-99fb-2a23bb07e05b2015-06-13T10:55:53Z2015-06-13T10:55:53Z<p>Although I should really be working on adding the dithering
algorithms into <a href="https://cyotek.com/cyotek-gif-animator">Gif Animator</a>, I thought it would be useful
to expand the repertoire of algorithms available for use with it
and the other projects I'm working on.</p>
<h2 id="adding-a-general-purpose-base-class">Adding a general purpose base class</h2>
<p>I decided to re-factor the class I created for the <a href="/post/dithering-an-image-using-the-burkes-algorithm-in-csharp">Burkes</a>
algorithm to make it suitable for adding other error diffusion
filters with a minimal amount of code.</p>
<p>First, I added a new abstract class, <code>ErrorDiffusionDithering</code>.
The constructor of this class requires you to pass in the matrix
used to disperse the error to neighbouring pixels, the divisor,
and whether or not to use bit shifting. The reason for the last
parameter is the <a href="/post/dithering-an-image-using-the-floyd-steinberg-algorithm-in-csharp">Floyd-Steinberg</a> and Burkes algorithms
covered in my earlier posts had divisors that were powers of
two, and so could therefore be bit shifted for faster division.
Not all algorithms use a power of two divisor though and so we
need to be flexible.</p>
<p>The constructor then stores the matrix, and pre-calculates a
couple of other values to avoid repeating these each time the
<code>Diffuse</code> method is called.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">protected</span> ErrorDiffusionDithering<span class="symbol">(</span><span class="keyword">byte</span><span class="symbol">[</span><span class="symbol">,</span><span class="symbol">]</span> matrix<span class="symbol">,</span> <span class="keyword">byte</span> divisor<span class="symbol">,</span> <span class="keyword">bool</span> useShifting<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span>matrix <span class="symbol">==</span> <span class="keyword">null</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">throw</span> <span class="keyword">new</span> ArgumentNullException<span class="symbol">(</span><span class="string">&quot;matrix&quot;</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="keyword">if</span> <span class="symbol">(</span>matrix<span class="symbol">.</span>Length <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> ArgumentException<span class="symbol">(</span><span class="string">&quot;Matrix is empty.&quot;</span><span class="symbol">,</span> <span class="string">&quot;matrix&quot;</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="symbol">}</span>

 _matrix <span class="symbol">=</span> matrix<span class="symbol">;</span>
 _matrixWidth <span class="symbol">=</span> <span class="symbol">(</span><span class="keyword">byte</span><span class="symbol">)</span><span class="symbol">(</span>matrix<span class="symbol">.</span>GetUpperBound<span class="symbol">(</span><span class="number">1</span><span class="symbol">)</span> <span class="symbol">+</span> <span class="number">1</span><span class="symbol">)</span><span class="symbol">;</span>
 _matrixHeight <span class="symbol">=</span> <span class="symbol">(</span><span class="keyword">byte</span><span class="symbol">)</span><span class="symbol">(</span>matrix<span class="symbol">.</span>GetUpperBound<span class="symbol">(</span><span class="number">0</span><span class="symbol">)</span> <span class="symbol">+</span> <span class="number">1</span><span class="symbol">)</span><span class="symbol">;</span>
 _divisor <span class="symbol">=</span> divisor<span class="symbol">;</span>
 _useShifting <span class="symbol">=</span> useShifting<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> _matrixWidth<span class="symbol">;</span> i<span class="symbol">++</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span>matrix<span class="symbol">[</span><span class="number">0</span><span class="symbol">,</span> i<span class="symbol">]</span> <span class="symbol">!=</span> <span class="number">0</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 _startingOffset <span class="symbol">=</span> <span class="symbol">(</span><span class="keyword">byte</span><span class="symbol">)</span><span class="symbol">(</span>i <span class="symbol">-</span> <span class="number">1</span><span class="symbol">)</span><span class="symbol">;</span>
 <span class="keyword">break</span><span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>The actual dithering implementation is unchanged from original
matrix handling code, with the exception of supporting bit
shifting or integer division, and not having to work out the
current pixel in the matrix, width or height.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">void</span> IErrorDiffusion<span class="symbol">.</span>Diffuse<span class="symbol">(</span>ArgbColor<span class="symbol">[</span><span class="symbol">]</span> data<span class="symbol">,</span> ArgbColor original<span class="symbol">,</span> ArgbColor transformed<span class="symbol">,</span> <span class="keyword">int</span> x<span class="symbol">,</span> <span class="keyword">int</span> y<span class="symbol">,</span> <span class="keyword">int</span> width<span class="symbol">,</span> <span class="keyword">int</span> height<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">int</span> redError<span class="symbol">;</span>
 <span class="keyword">int</span> blueError<span class="symbol">;</span>
 <span class="keyword">int</span> greenError<span class="symbol">;</span>

 redError <span class="symbol">=</span> original<span class="symbol">.</span>R <span class="symbol">-</span> transformed<span class="symbol">.</span>R<span class="symbol">;</span>
 blueError <span class="symbol">=</span> original<span class="symbol">.</span>G <span class="symbol">-</span> transformed<span class="symbol">.</span>G<span class="symbol">;</span>
 greenError <span class="symbol">=</span> original<span class="symbol">.</span>B <span class="symbol">-</span> transformed<span class="symbol">.</span>B<span class="symbol">;</span>

 <span class="keyword">for</span> <span class="symbol">(</span><span class="keyword">int</span> row <span class="symbol">=</span> <span class="number">0</span><span class="symbol">;</span> row <span class="symbol">&lt;</span> _matrixHeight<span class="symbol">;</span> row<span class="symbol">++</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">int</span> offsetY<span class="symbol">;</span>

 offsetY <span class="symbol">=</span> y <span class="symbol">+</span> row<span class="symbol">;</span>

 <span class="keyword">for</span> <span class="symbol">(</span><span class="keyword">int</span> col <span class="symbol">=</span> <span class="number">0</span><span class="symbol">;</span> col <span class="symbol">&lt;</span> _matrixWidth<span class="symbol">;</span> col<span class="symbol">++</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">int</span> coefficient<span class="symbol">;</span>
 <span class="keyword">int</span> offsetX<span class="symbol">;</span>

 coefficient <span class="symbol">=</span> _matrix<span class="symbol">[</span>row<span class="symbol">,</span> col<span class="symbol">]</span><span class="symbol">;</span>
 offsetX <span class="symbol">=</span> x <span class="symbol">+</span> <span class="symbol">(</span>col <span class="symbol">-</span> _startingOffset<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>coefficient <span class="symbol">!=</span> <span class="number">0</span> <span class="symbol">&amp;&amp;</span> offsetX <span class="symbol">&gt;</span> <span class="number">0</span> <span class="symbol">&amp;&amp;</span> offsetX <span class="symbol">&lt;</span> width <span class="symbol">&amp;&amp;</span> offsetY <span class="symbol">&gt;</span> <span class="number">0</span> <span class="symbol">&amp;&amp;</span> offsetY <span class="symbol">&lt;</span> height<span class="symbol">)</span>
 <span class="symbol">{</span>
 ArgbColor offsetPixel<span class="symbol">;</span>
 <span class="keyword">int</span> offsetIndex<span class="symbol">;</span>
 <span class="keyword">int</span> newR<span class="symbol">;</span>
 <span class="keyword">int</span> newG<span class="symbol">;</span>
 <span class="keyword">int</span> newB<span class="symbol">;</span>

 offsetIndex <span class="symbol">=</span> offsetY <span class="symbol">*</span> width <span class="symbol">+</span> offsetX<span class="symbol">;</span>
 offsetPixel <span class="symbol">=</span> data<span class="symbol">[</span>offsetIndex<span class="symbol">]</span><span class="symbol">;</span>

 <span class="comment">// if the UseShifting property is set, then bit shift the values by the specified</span>
 <span class="comment">// divisor as this is faster than integer division. Otherwise, use integer division</span>

 <span class="keyword">if</span> <span class="symbol">(</span>_useShifting<span class="symbol">)</span>
 <span class="symbol">{</span>
 newR <span class="symbol">=</span> <span class="symbol">(</span>redError <span class="symbol">*</span> coefficient<span class="symbol">)</span> <span class="symbol">&gt;&gt;</span> _divisor<span class="symbol">;</span>
 newG <span class="symbol">=</span> <span class="symbol">(</span>greenError <span class="symbol">*</span> coefficient<span class="symbol">)</span> <span class="symbol">&gt;&gt;</span> _divisor<span class="symbol">;</span>
 newB <span class="symbol">=</span> <span class="symbol">(</span>blueError <span class="symbol">*</span> coefficient<span class="symbol">)</span> <span class="symbol">&gt;&gt;</span> _divisor<span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="keyword">else</span>
 <span class="symbol">{</span>
 newR <span class="symbol">=</span> <span class="symbol">(</span>redError <span class="symbol">*</span> coefficient<span class="symbol">)</span> <span class="symbol">/</span> _divisor<span class="symbol">;</span>
 newG <span class="symbol">=</span> <span class="symbol">(</span>greenError <span class="symbol">*</span> coefficient<span class="symbol">)</span> <span class="symbol">/</span> _divisor<span class="symbol">;</span>
 newB <span class="symbol">=</span> <span class="symbol">(</span>blueError <span class="symbol">*</span> coefficient<span class="symbol">)</span> <span class="symbol">/</span> _divisor<span class="symbol">;</span>
 <span class="symbol">}</span>

 offsetPixel<span class="symbol">.</span>R <span class="symbol">=</span> <span class="symbol">(</span>offsetPixel<span class="symbol">.</span>R <span class="symbol">+</span> newR<span class="symbol">)</span><span class="symbol">.</span>ToByte<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 offsetPixel<span class="symbol">.</span>G <span class="symbol">=</span> <span class="symbol">(</span>offsetPixel<span class="symbol">.</span>G <span class="symbol">+</span> newG<span class="symbol">)</span><span class="symbol">.</span>ToByte<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 offsetPixel<span class="symbol">.</span>B <span class="symbol">=</span> <span class="symbol">(</span>offsetPixel<span class="symbol">.</span>B <span class="symbol">+</span> newB<span class="symbol">)</span><span class="symbol">.</span>ToByte<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>

 data<span class="symbol">[</span>offsetIndex<span class="symbol">]</span> <span class="symbol">=</span> offsetPixel<span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<h2 id="burkes-dithering-redux">Burkes Dithering, redux</h2>
<p>The <code>BurkesDithering</code> class now looks like this</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">public</span> <span class="keyword">sealed</span> <span class="keyword">class</span> BurksDithering <span class="symbol">:</span> ErrorDiffusionDithering
<span class="symbol">{</span>
 <span class="keyword">public</span> BurksDithering<span class="symbol">(</span><span class="symbol">)</span>
 <span class="symbol">:</span> <span class="keyword">base</span><span class="symbol">(</span><span class="keyword">new</span> <span class="keyword">byte</span><span class="symbol">[</span><span class="symbol">,</span><span class="symbol">]</span>
 <span class="symbol">{</span>
 <span class="symbol">{</span>
 <span class="number">0</span><span class="symbol">,</span> <span class="number">0</span><span class="symbol">,</span> <span class="number">0</span><span class="symbol">,</span> <span class="number">8</span><span class="symbol">,</span> <span class="number">4</span>
 <span class="symbol">}</span><span class="symbol">,</span>
 <span class="symbol">{</span>
 <span class="number">2</span><span class="symbol">,</span> <span class="number">4</span><span class="symbol">,</span> <span class="number">8</span><span class="symbol">,</span> <span class="number">4</span><span class="symbol">,</span> <span class="number">2</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span><span class="symbol">,</span> <span class="number">5</span><span class="symbol">,</span> <span class="keyword">true</span><span class="symbol">)</span>
 <span class="symbol">{</span> <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>No code, just the matrix and the bit shifted divisor of 5, which
will divide each result by 32. Nice!</p>
<h2 id="more-algorithms">More Algorithms</h2>
<p>As well as opening the door to allowing a user to define a
custom dither matrix, it also makes it trivial to implement a
number of other common error diffusion matrixes. The <a href="https://github.com/cyotek/Dithering" rel="external nofollow noopener">GitHub
Repository</a> now offers the
following algorithms</p>
<ul>
<li>Atkinson</li>
<li>Burkes</li>
<li>Floyd-Steinberg</li>
<li>Jarvis, Judice &amp; Ninke</li>
<li>Sierra</li>
<li>Two Row Sierra</li>
<li>Sierra Light</li>
<li>Stucki</li>
</ul>
<p>Which is a fairly nice array.</p>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/dithering-atkinson.png" class="gallery" title="An example of Atkinson dithering" ><img src="https://images.cyotek.com/image/thumbnail/devblog/dithering-atkinson.png" alt="An example of Atkinson dithering" decoding="async" loading="lazy" /></a><figcaption>An example of Atkinson dithering</figcaption></figure><figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">public</span> <span class="keyword">sealed</span> <span class="keyword">class</span> AtkinsonDithering <span class="symbol">:</span> ErrorDiffusionDithering
<span class="symbol">{</span>
 <span class="keyword">public</span> AtkinsonDithering<span class="symbol">(</span><span class="symbol">)</span>
 <span class="symbol">:</span> <span class="keyword">base</span><span class="symbol">(</span><span class="keyword">new</span> <span class="keyword">byte</span><span class="symbol">[</span><span class="symbol">,</span><span class="symbol">]</span>
 <span class="symbol">{</span>
 <span class="symbol">{</span>
 <span class="number">0</span><span class="symbol">,</span> <span class="number">0</span><span class="symbol">,</span> <span class="number">1</span><span class="symbol">,</span> <span class="number">1</span>
 <span class="symbol">}</span><span class="symbol">,</span>
 <span class="symbol">{</span>
 <span class="number">1</span><span class="symbol">,</span> <span class="number">1</span><span class="symbol">,</span> <span class="number">1</span><span class="symbol">,</span> <span class="number">0</span>
 <span class="symbol">}</span><span class="symbol">,</span>
 <span class="symbol">{</span>
 <span class="number">0</span><span class="symbol">,</span> <span class="number">1</span><span class="symbol">,</span> <span class="number">0</span><span class="symbol">,</span> <span class="number">0</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span><span class="symbol">,</span> <span class="number">3</span><span class="symbol">,</span> <span class="keyword">true</span><span class="symbol">)</span>
 <span class="symbol">{</span> <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<h2 id="random-dithering">Random Dithering</h2>
<p>There's a rather old (in internet terms anyway!) text file
floating around named <a href="http://www.efg2.com/Lab/Library/ImageProcessing/DHALF.TXT" rel="external nofollow noopener">DHALF.TXT</a> (based in turn on an even
older document named <code>DITHER.TXT</code>) that has a ton of useful
information on dithering, and with the exception of the
Altkinson algorithm (I took that from <a href="http://www.tannerhelland.com/4660/dithering-eleven-algorithms-source-code/#attachment_4677" rel="external nofollow noopener">here</a> is where I have
pulled all the error weights and divisors from.</p>
<p>One of the sections in this document dealt with random
dithering. Although I didn't think I would ever use it myself, I
thought I'd add an implementation of it anyway to see what it's
like.</p>
<p>Unlike the error diffusion methods, random dithering affects
only a single pixel at a time, and does not consider or modify
its neighbours. You also have a modicum of control over it too,
if you can control the initial seed of the random number
generator.</p>
<p>The <code>DHALF.TXT</code> text sums it up succinctly: For each dot in our
grayscale image, we generate a random number in the range 0 -
255: if the random number is greater than the image value at
that dot, the display device plots the dot white; otherwise, it
plots it black. That's it.</p>
<p>And here's our implementation (ignoring the fact that it isn't
error diffusion and all of a sudden our <code>IErrorDiffusion</code>
interface is named wrong!)</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">void</span> IErrorDiffusion<span class="symbol">.</span>Diffuse<span class="symbol">(</span>ArgbColor<span class="symbol">[</span><span class="symbol">]</span> data<span class="symbol">,</span> ArgbColor original<span class="symbol">,</span> ArgbColor transformed<span class="symbol">,</span> <span class="keyword">int</span> x<span class="symbol">,</span> <span class="keyword">int</span> y<span class="symbol">,</span> <span class="keyword">int</span> width<span class="symbol">,</span> <span class="keyword">int</span> height<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">byte</span> gray<span class="symbol">;</span>

 gray <span class="symbol">=</span> <span class="symbol">(</span><span class="keyword">byte</span><span class="symbol">)</span><span class="symbol">(</span><span class="number">0.299</span> <span class="symbol">*</span> original<span class="symbol">.</span>R <span class="symbol">+</span> <span class="number">0.587</span> <span class="symbol">*</span> original<span class="symbol">.</span>G <span class="symbol">+</span> <span class="number">0.114</span> <span class="symbol">*</span> original<span class="symbol">.</span>B<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>gray <span class="symbol">&gt;</span> _random<span class="symbol">.</span>Next<span class="symbol">(</span><span class="number">0</span><span class="symbol">,</span> <span class="number">255</span><span class="symbol">)</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 data<span class="symbol">[</span>y <span class="symbol">*</span> width <span class="symbol">+</span> x<span class="symbol">]</span> <span class="symbol">=</span> _white<span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="keyword">else</span>
 <span class="symbol">{</span>
 data<span class="symbol">[</span>y <span class="symbol">*</span> width <span class="symbol">+</span> x<span class="symbol">]</span> <span class="symbol">=</span> _black<span class="symbol">;</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>(Although I reversed black and white from the original
description as otherwise it looked completely wrong)</p>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/dithering-random.png" class="gallery" title="Random dithering - it doesn't actually look too bad" ><img src="https://images.cyotek.com/image/thumbnail/devblog/dithering-random.png" alt="Random dithering - it doesn't actually look too bad" decoding="async" loading="lazy" /></a><figcaption>Random dithering - it doesn't actually look too bad</figcaption></figure><figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/dithering-random-color.png" class="gallery" title="Another example of random dithering, this time using colour" ><img src="https://images.cyotek.com/image/thumbnail/devblog/dithering-random-color.png" alt="Another example of random dithering, this time using colour" decoding="async" loading="lazy" /></a><figcaption>Another example of random dithering, this time using colour</figcaption></figure>
<p>I was surprised to see it actually doesn't look that bad.</p>
<h2 id="continuation">Continuation</h2>
<p>I've almost got a full house of useful dithering algorithms now.
About the only thing left for me to do is to implement a ordered
Bayer dithering as I really like the look of this type, and
reminds me of games and computers of yesteryear. So there's
still at least one more article to follow in this series!</p>
<p>The updated source code with all these algorithms is available
from the <a href="https://github.com/cyotek/Dithering" rel="external nofollow noopener">GitHub repository</a>.</p>
<h2 id="update-history">Update History</h2>
<ul>
<li>2015-06-13 - 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/even-more-algorithms-for-dithering-images-using-csharp .
</small></p>Richard Mosshttps://www.cyotek.com/richard.moss@cyotek.comDithering an image using the Burkes algorithm in C#urn:uuid:119ddf2c-d60c-459f-9438-743b434a8c752015-06-06T11:02:11Z2015-06-06T11:02:11Z<p>In my <a href="/post/dithering-an-image-using-the-floyd-steinberg-algorithm-in-csharp">previous post</a>, I described how to dither an image in
C# using the Floyd‑Steinberg algorithm. Continuing this theme,
this post will cover the Burkes algorithm.</p>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/dithering-threshold.png" class="gallery" title="An example of 1bit conversion via a threshold" ><img src="https://images.cyotek.com/image/thumbnail/devblog/dithering-threshold.png" alt="An example of 1bit conversion via a threshold" decoding="async" loading="lazy" /></a><figcaption>An example of 1bit conversion via a threshold</figcaption></figure>
<p>I will be using the same demonstration application as from the
previous post, so I won't go over how this works again.</p>
<h2 id="burkes-dithering">Burkes dithering</h2>
<p>As with Floyd‑Steinberg, the Burkes algorithm is an error
diffusion algorithm, which is to say for each pixel an &quot;<em>error</em>&quot;
is generated and then distributed to pixels around the source.
Unlike Floyd‑Steinberg however (which modifies 4 surrounding
pixels), Burkes modifies 7 pixels.</p>
<blockquote>
<p>Burkes is actually a modified version of the Stucki algorithm,
which in turn is an evolution of the Jarvis algorithms.</p>
</blockquote>
<p>The diagram below shows the distribution of the error
coefficients.</p>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/dithering-burkes-diagram.png" class="gallery" title="How the error of the current pixel is diffused to its neighbours" ><img src="https://images.cyotek.com/image/devblog/dithering-burkes-diagram.png" alt="How the error of the current pixel is diffused to its neighbours" decoding="async" loading="lazy" /></a><figcaption>How the error of the current pixel is diffused to its neighbours</figcaption></figure>
<ul>
<li>8 for the pixel to the right of the current pixel</li>
<li>4 for the second pixel to the right</li>
<li>2 for the pixel below and two to the left</li>
<li>4 for the pixel below and to the left</li>
<li>8 for the pixel below</li>
<li>4 for the pixel below and to the right</li>
<li>2 for the pixel below and two to the right</li>
</ul>
<p>Unlike Floyd‑Steinberg, the error result in this algorithm is
divided by 32. But as that's still a power of two, once again we
can use bit shifting to perform the division.</p>
<p>Due to the additional calculations I would assume that this
algorithm will be slightly slower than Floyd‑Steinberg, but as
of yet I haven't ran any form of benchmarks to test this.</p>
<h2 id="applying-the-algorithm">Applying the algorithm</h2>
<p>In my Floyd‑Steinberg example, I replicated the calculations
four times for each pixel. As there are now seven sets of
calculations with Burkes, I decided to store the coefficients in
a 2D array mimicing the diagram above, then iterating this. I'm
not entirely convinced this is the best approach, but it does
seem to be working.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">readonly</span> <span class="keyword">byte</span><span class="symbol">[</span><span class="symbol">,</span><span class="symbol">]</span> _matrix <span class="symbol">=</span>
<span class="symbol">{</span>
 <span class="symbol">{</span>
 <span class="number">0</span><span class="symbol">,</span> <span class="number">0</span><span class="symbol">,</span> <span class="number">0</span><span class="symbol">,</span> <span class="number">8</span><span class="symbol">,</span> <span class="number">4</span>
 <span class="symbol">}</span><span class="symbol">,</span>
 <span class="symbol">{</span>
 <span class="number">2</span><span class="symbol">,</span> <span class="number">4</span><span class="symbol">,</span> <span class="number">8</span><span class="symbol">,</span> <span class="number">4</span><span class="symbol">,</span> <span class="number">2</span>
 <span class="symbol">}</span>
<span class="symbol">}</span><span class="symbol">;</span>

<span class="keyword">private</span> <span class="keyword">const</span> <span class="keyword">int</span> _matrixHeight <span class="symbol">=</span> <span class="number">2</span><span class="symbol">;</span>

<span class="keyword">private</span> <span class="keyword">const</span> <span class="keyword">int</span> _matrixStartX <span class="symbol">=</span> <span class="number">2</span><span class="symbol">;</span>

<span class="keyword">private</span> <span class="keyword">const</span> <span class="keyword">int</span> _matrixWidth <span class="symbol">=</span> <span class="number">5</span><span class="symbol">;</span>
</pre>
</figure>
<p>This sets up the matrix as a static that is only created once.
I've also added some constants to control the offsets as I can't
create an array with a non-zero lower bound. This does smell a
bit so I'll be revisiting this!</p>
<p>Below is the code to dither a single pixel. Remember that the
demonstration program uses a 1D array of <code>ArgbColor</code> structs to
make it easy to read and understand, but you could equally use
direct pointer manipulation on a bitmap's bits, with lots of
extra code to handle different colour depths.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">int</span> redError<span class="symbol">;</span>
<span class="keyword">int</span> blueError<span class="symbol">;</span>
<span class="keyword">int</span> greenError<span class="symbol">;</span>

redError <span class="symbol">=</span> originalPixel<span class="symbol">.</span>R <span class="symbol">-</span> transformedPixel<span class="symbol">.</span>R<span class="symbol">;</span>
blueError <span class="symbol">=</span> originalPixel<span class="symbol">.</span>G <span class="symbol">-</span> transformedPixel<span class="symbol">.</span>G<span class="symbol">;</span>
greenError <span class="symbol">=</span> originalPixel<span class="symbol">.</span>B <span class="symbol">-</span> transformedPixel<span class="symbol">.</span>B<span class="symbol">;</span>

<span class="keyword">for</span> <span class="symbol">(</span><span class="keyword">int</span> row <span class="symbol">=</span> <span class="number">0</span><span class="symbol">;</span> row <span class="symbol">&lt;</span> _matrixHeight<span class="symbol">;</span> row<span class="symbol">++</span><span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">int</span> offsetY<span class="symbol">;</span>

 offsetY <span class="symbol">=</span> y <span class="symbol">+</span> row<span class="symbol">;</span>

 <span class="keyword">for</span> <span class="symbol">(</span><span class="keyword">int</span> col <span class="symbol">=</span> <span class="number">0</span><span class="symbol">;</span> col <span class="symbol">&lt;</span> _matrixWidth<span class="symbol">;</span> col<span class="symbol">++</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="keyword">int</span> coefficient<span class="symbol">;</span>
 <span class="keyword">int</span> offsetX<span class="symbol">;</span>

 coefficient <span class="symbol">=</span> _matrix<span class="symbol">[</span>row<span class="symbol">,</span> col<span class="symbol">]</span><span class="symbol">;</span>
 offsetX <span class="symbol">=</span> x <span class="symbol">+</span> <span class="symbol">(</span>col <span class="symbol">-</span> _matrixStartX<span class="symbol">)</span><span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>coefficient <span class="symbol">!=</span> <span class="number">0</span> <span class="symbol">&amp;&amp;</span> offsetX <span class="symbol">&gt;</span> <span class="number">0</span> <span class="symbol">&amp;&amp;</span> offsetX <span class="symbol">&lt;</span> width <span class="symbol">&amp;&amp;</span> offsetY <span class="symbol">&gt;</span> <span class="number">0</span> <span class="symbol">&amp;&amp;</span> offsetY <span class="symbol">&lt;</span> height<span class="symbol">)</span>
 <span class="symbol">{</span>
 ArgbColor offsetPixel<span class="symbol">;</span>
 <span class="keyword">int</span> offsetIndex<span class="symbol">;</span>

 offsetIndex <span class="symbol">=</span> offsetY <span class="symbol">*</span> width <span class="symbol">+</span> offsetX<span class="symbol">;</span>
 offsetPixel <span class="symbol">=</span> original<span class="symbol">[</span>offsetIndex<span class="symbol">]</span><span class="symbol">;</span>
 offsetPixel<span class="symbol">.</span>R <span class="symbol">=</span> <span class="symbol">(</span>offsetPixel<span class="symbol">.</span>R <span class="symbol">+</span> <span class="symbol">(</span><span class="symbol">(</span>redError <span class="symbol">*</span> coefficient<span class="symbol">)</span> <span class="symbol">&gt;&gt;</span> <span class="number">5</span><span class="symbol">)</span><span class="symbol">)</span><span class="symbol">.</span>ToByte<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 offsetPixel<span class="symbol">.</span>G <span class="symbol">=</span> <span class="symbol">(</span>offsetPixel<span class="symbol">.</span>G <span class="symbol">+</span> <span class="symbol">(</span><span class="symbol">(</span>greenError <span class="symbol">*</span> coefficient<span class="symbol">)</span> <span class="symbol">&gt;&gt;</span> <span class="number">5</span><span class="symbol">)</span><span class="symbol">)</span><span class="symbol">.</span>ToByte<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 offsetPixel<span class="symbol">.</span>B <span class="symbol">=</span> <span class="symbol">(</span>offsetPixel<span class="symbol">.</span>B <span class="symbol">+</span> <span class="symbol">(</span><span class="symbol">(</span>blueError <span class="symbol">*</span> coefficient<span class="symbol">)</span> <span class="symbol">&gt;&gt;</span> <span class="number">5</span><span class="symbol">)</span><span class="symbol">)</span><span class="symbol">.</span>ToByte<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 original<span class="symbol">[</span>offsetIndex<span class="symbol">]</span> <span class="symbol">=</span> offsetPixel<span class="symbol">;</span>
 <span class="symbol">}</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>Due to the loop this code is now shorter than the
Floyd‑Steinberg version. It's also less readable due the
coefficients being stored in a 2D matrix. Of course, the
algorithm is fixed and won't change so perhaps that's not an
issue, but if performance really was a concern you can unroll
the loop and duplicate all that code. I'll stick with the loop!</p>
<h2 id="final-output">Final Output</h2>
<p>The image below shows our sample image dithered using the Burkes
algorithm. It's very similar to the output created via
Floyd–Steinberg, albeit darker.</p>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/dithering-burkes.png" class="gallery" title="The final result - a bitmap transformed with Burkes dithering" ><img src="https://images.cyotek.com/image/thumbnail/devblog/dithering-burkes.png" alt="The final result - a bitmap transformed with Burkes dithering" decoding="async" loading="lazy" /></a><figcaption>The final result - a bitmap transformed with Burkes dithering</figcaption></figure>
<p>Again, by changing the threshold at which colours are converted
to black or white, we can affect the output of the dithering
even if the conversion is to solid black.</p>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/dithering-burkes-extreme.png" class="gallery" title="The non-dithered version of this image is solid black" ><img src="https://images.cyotek.com/image/thumbnail/devblog/dithering-burkes-extreme.png" alt="The non-dithered version of this image is solid black" decoding="async" loading="lazy" /></a><figcaption>The non-dithered version of this image is solid black</figcaption></figure><h2 id="source-code">Source Code</h2>
<p>The latest source code for this demonstration (which will be
extended over time to include additional algorithms) can be
found at our <a href="https://github.com/cyotek/Dithering" rel="external nofollow noopener">GitHib page</a>.</p>
<p>The source code from the time this article was created is
available from the link below, however may not be fully up to
date.</p>
<h2 id="update-history">Update History</h2>
<ul>
<li>2015-06-06 - 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/dithering-an-image-using-the-burkes-algorithm-in-csharp .
</small></p>Richard Mosshttps://www.cyotek.com/richard.moss@cyotek.comDithering an image using the Floyd‑Steinberg algorithm in C#urn:uuid:24c8a964-e1ca-424d-9200-e58642d4d0982015-06-06T11:01:41Z2015-06-06T11:01:41Z<p>In my <a href="/post/an-introduction-to-dithering-images">previous introductory post</a>, I briefly described the
concept of dithering an image. In this article, I will describe
how to dither an image in C# using the Floyd–Steinberg
algorithm.</p>
<h2 id="the-demo-application">The Demo Application</h2>
<p>For this series of articles, I'll be using the same demo
application, the source of which can be found on <a href="https://github.com/cyotek/Dithering" rel="external nofollow noopener">GitHib</a>.
There's a few things about the demo I wish to cover before I get
onto the actual topic of dithering.</p>
<p>Algorithms can be a tricky thing to learn about, and so I don't
want the demo to be horribly complicated by including a
additional complex code unrelated to dithering. At the same
time, bitmap operations are expensive, so there is already some
advanced code present.</p>
<p>As I mentioned in my introduction, dithering is part of a
process. For this demo, the process will be converting a 32bit
image into a 1bit image as this is the simplest conversion I can
stick in a demo. <strong>This does not mean that the dithering
techniques can only be used to convert an image to black and
white, it is simply to make the demo easier to understand</strong>.</p>
<p>I have however broken this rule when it comes to the actual
image processing. The .NET <code>Bitmap</code> object offers <code>SetPixel</code> and
<code>GetPixel</code> methods. You should try and avoid using these as they
will utterly destroy the performance of whatever it is you are
trying to do. The best way of accessing pixel data is to access
it directly using <code>Bitmap.LockBits</code>, pointer manipulation, then
<code>Bitmap.UnlockBits</code>. In this demo, I use this approach to create
a custom array of colours, and while it is very fast, if you
want better performance it is probably better to manipulate
individual bytes via pointers. However, this requires much more
complex code to account for different colour depths and is well
beyond the scope of this demo.</p>
<blockquote>
<p>I did a version of the demo program using <code>SetPixel</code> and
<code>GetPixel</code>. Saying it was slow was an understatement. Just
pretend these methods don't exist!</p>
</blockquote>
<h2 id="converting-a-colour-to-black-or-white">Converting a colour to black or white</h2>
<p>In order to convert the image to 2 colours, I scan each pixel
and convert it to grayscale. If the grayscale value is around
50% (127 in .NET's 0 - 255 range), then the transformed pixel
will be black, otherwise it will be white.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
<span class="keyword">byte</span> gray<span class="symbol">;</span>

gray <span class="symbol">=</span> <span class="symbol">(</span><span class="keyword">byte</span><span class="symbol">)</span><span class="symbol">(</span><span class="number">0.299</span> <span class="symbol">*</span> pixel<span class="symbol">.</span>R <span class="symbol">+</span> <span class="number">0.587</span> <span class="symbol">*</span> pixel<span class="symbol">.</span>G <span class="symbol">+</span> <span class="number">0.114</span> <span class="symbol">*</span> pixel<span class="symbol">.</span>B<span class="symbol">)</span><span class="symbol">;</span>

<span class="keyword">return</span> gray <span class="symbol">&lt;</span> <span class="number">128</span> <span class="symbol">?</span> <span class="keyword">new</span> ArgbColor<span class="symbol">(</span>pixel<span class="symbol">.</span>A<span class="symbol">,</span> <span class="number">0</span><span class="symbol">,</span> <span class="number">0</span><span class="symbol">,</span> <span class="number">0</span><span class="symbol">)</span> <span class="symbol">:</span> <span class="keyword">new</span> ArgbColor<span class="symbol">(</span>pixel<span class="symbol">.</span>A<span class="symbol">,</span> <span class="number">255</span><span class="symbol">,</span> <span class="number">255</span><span class="symbol">,</span> <span class="number">255</span><span class="symbol">)</span><span class="symbol">;</span>
</pre>
</figure>
<p>This actually creates quite a nice result from our demonstration
image, but results will vary depending on the image.</p>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/dithering-threshold.png" class="gallery" title="An example of 1bit conversion via a threshold" ><img src="https://images.cyotek.com/image/thumbnail/devblog/dithering-threshold.png" alt="An example of 1bit conversion via a threshold" decoding="async" loading="lazy" /></a><figcaption>An example of 1bit conversion via a threshold</figcaption></figure><h2 id="floydsteinberg-dithering">Floyd‑Steinberg dithering</h2>
<p>The Floyd‑Steinberg algorithm is an error diffusion algorithm,
meaning for each pixel an &quot;<em>error</em>&quot; is generated and then
distributed to four pixels around the surrounding the current
pixel. Each of the four offset pixels has a different weight -
the error is multiplied by the weight, divided by 16 and then
added to the existing value of the offset pixel.</p>
<p>As a picture is definitely worth a thousand words, the diagram
below shows the weights.</p>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/dithering-floyd-steinberg-diagram.png" class="gallery" title="How the error of the current pixel is diffused to its neighbours" ><img src="https://images.cyotek.com/image/devblog/dithering-floyd-steinberg-diagram.png" alt="How the error of the current pixel is diffused to its neighbours" decoding="async" loading="lazy" /></a><figcaption>How the error of the current pixel is diffused to its neighbours</figcaption></figure>
<ul>
<li>7 for the pixel to the right of the current pixel</li>
<li>3 for the pixel below and to the left</li>
<li>5 for the pixel below</li>
<li>1 for the pixel below and to the right</li>
</ul>
<h2 id="calculating-the-error">Calculating the error</h2>
<p>The error calculation in our demonstration program is simple,
although in actuality it's 3 errors, one for the red, green and
blue channels. All we are doing is taking the difference between
the channels transformed value from the original value.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
redError <span class="symbol">=</span> originalPixel<span class="symbol">.</span>R <span class="symbol">-</span> transformedPixel<span class="symbol">.</span>R<span class="symbol">;</span>
blueError <span class="symbol">=</span> originalPixel<span class="symbol">.</span>G <span class="symbol">-</span> transformedPixel<span class="symbol">.</span>G<span class="symbol">;</span>
greenError <span class="symbol">=</span> originalPixel<span class="symbol">.</span>B <span class="symbol">-</span> transformedPixel<span class="symbol">.</span>B<span class="symbol">;</span>
</pre>
</figure>
<h2 id="applying-the-error">Applying the error</h2>
<p>Once we have our error, it's just a case of getting each
neighbouring pixels to adjust, and applying each error the
appropriate channel. The <code>ToByte</code> extension method in the
snippet below simply converts the calculated integer to a byte,
while ensuring it is in the 0-255 range.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
offsetPixel<span class="symbol">.</span>R <span class="symbol">=</span> <span class="symbol">(</span>offsetPixel<span class="symbol">.</span>R <span class="symbol">+</span> <span class="symbol">(</span><span class="symbol">(</span>redError <span class="symbol">*</span> <span class="number">7</span><span class="symbol">)</span> <span class="symbol">&gt;&gt;</span> <span class="number">4</span><span class="symbol">)</span><span class="symbol">)</span><span class="symbol">.</span>ToByte<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
offsetPixel<span class="symbol">.</span>G <span class="symbol">=</span> <span class="symbol">(</span>offsetPixel<span class="symbol">.</span>G <span class="symbol">+</span> <span class="symbol">(</span><span class="symbol">(</span>greenError <span class="symbol">*</span> <span class="number">7</span><span class="symbol">)</span> <span class="symbol">&gt;&gt;</span> <span class="number">4</span><span class="symbol">)</span><span class="symbol">)</span><span class="symbol">.</span>ToByte<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
offsetPixel<span class="symbol">.</span>B <span class="symbol">=</span> <span class="symbol">(</span>offsetPixel<span class="symbol">.</span>B <span class="symbol">+</span> <span class="symbol">(</span><span class="symbol">(</span>blueError <span class="symbol">*</span> <span class="number">7</span><span class="symbol">)</span> <span class="symbol">&gt;&gt;</span> <span class="number">4</span><span class="symbol">)</span><span class="symbol">)</span><span class="symbol">.</span>ToByte<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
</pre>
</figure>
<blockquote>
<h3 id="bit-shifting-for-division">Bit shifting for division</h3>
<p>As 16 is a power of two, it means we can use bit shifting to
do the division. While this may be slightly less readable if
you aren't hugely familiar with it, it ought to be faster. I
did a quick benchmark test using a sample of 1 million, 10
million and then 100 million random numbers. Using bit
shifting to divide each sample by 16 took roughly two thirds
of the time it took to do the same sets with integer division.
This is probably a useful thing to know when performing
thousands of operations processing an image.</p>
</blockquote>
<h2 id="dithering-a-single-pixel">Dithering a single pixel</h2>
<p>Here's the code used by the demonstration program to dither a
single source pixel - the <code>ArbColor</code> data representing each
pixel is stored in a one-dimensional array using <a href="/post/converting-2d-arrays-to-1d-and-accessing-as-either-2d-or-1d">row-major
order</a>.</p>
<figure class="lang-csharp highlight"><figcaption><span>csharp</span></figcaption><pre class="code">
ArgbColor offsetPixel<span class="symbol">;</span>
<span class="keyword">int</span> redError<span class="symbol">;</span>
<span class="keyword">int</span> blueError<span class="symbol">;</span>
<span class="keyword">int</span> greenError<span class="symbol">;</span>
<span class="keyword">int</span> offsetIndex<span class="symbol">;</span>
<span class="keyword">int</span> index<span class="symbol">;</span>

index <span class="symbol">=</span> y <span class="symbol">*</span> width <span class="symbol">+</span> x<span class="symbol">;</span>
redError <span class="symbol">=</span> originalPixel<span class="symbol">.</span>R <span class="symbol">-</span> transformedPixel<span class="symbol">.</span>R<span class="symbol">;</span>
blueError <span class="symbol">=</span> originalPixel<span class="symbol">.</span>G <span class="symbol">-</span> transformedPixel<span class="symbol">.</span>G<span class="symbol">;</span>
greenError <span class="symbol">=</span> originalPixel<span class="symbol">.</span>B <span class="symbol">-</span> transformedPixel<span class="symbol">.</span>B<span class="symbol">;</span>

<span class="keyword">if</span> <span class="symbol">(</span>x <span class="symbol">+</span> <span class="number">1</span> <span class="symbol">&lt;</span> width<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="comment">// right</span>
 offsetIndex <span class="symbol">=</span> index <span class="symbol">+</span> <span class="number">1</span><span class="symbol">;</span>
 offsetPixel <span class="symbol">=</span> original<span class="symbol">[</span>offsetIndex<span class="symbol">]</span><span class="symbol">;</span>
 offsetPixel<span class="symbol">.</span>R <span class="symbol">=</span> <span class="symbol">(</span>offsetPixel<span class="symbol">.</span>R <span class="symbol">+</span> <span class="symbol">(</span><span class="symbol">(</span>redError <span class="symbol">*</span> <span class="number">7</span><span class="symbol">)</span> <span class="symbol">&gt;&gt;</span> <span class="number">4</span><span class="symbol">)</span><span class="symbol">)</span><span class="symbol">.</span>ToByte<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 offsetPixel<span class="symbol">.</span>G <span class="symbol">=</span> <span class="symbol">(</span>offsetPixel<span class="symbol">.</span>G <span class="symbol">+</span> <span class="symbol">(</span><span class="symbol">(</span>greenError <span class="symbol">*</span> <span class="number">7</span><span class="symbol">)</span> <span class="symbol">&gt;&gt;</span> <span class="number">4</span><span class="symbol">)</span><span class="symbol">)</span><span class="symbol">.</span>ToByte<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 offsetPixel<span class="symbol">.</span>B <span class="symbol">=</span> <span class="symbol">(</span>offsetPixel<span class="symbol">.</span>B <span class="symbol">+</span> <span class="symbol">(</span><span class="symbol">(</span>blueError <span class="symbol">*</span> <span class="number">7</span><span class="symbol">)</span> <span class="symbol">&gt;&gt;</span> <span class="number">4</span><span class="symbol">)</span><span class="symbol">)</span><span class="symbol">.</span>ToByte<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 original<span class="symbol">[</span>offsetIndex<span class="symbol">]</span> <span class="symbol">=</span> offsetPixel<span class="symbol">;</span>
<span class="symbol">}</span>

<span class="keyword">if</span> <span class="symbol">(</span>y <span class="symbol">+</span> <span class="number">1</span> <span class="symbol">&lt;</span> height<span class="symbol">)</span>
<span class="symbol">{</span>
 <span class="keyword">if</span> <span class="symbol">(</span>x <span class="symbol">-</span> <span class="number">1</span> <span class="symbol">&gt;</span> <span class="number">0</span><span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="comment">// left and down</span>
 offsetIndex <span class="symbol">=</span> index <span class="symbol">+</span> width <span class="symbol">-</span> <span class="number">1</span><span class="symbol">;</span>
 offsetPixel <span class="symbol">=</span> original<span class="symbol">[</span>offsetIndex<span class="symbol">]</span><span class="symbol">;</span>
 offsetPixel<span class="symbol">.</span>R <span class="symbol">=</span> <span class="symbol">(</span>offsetPixel<span class="symbol">.</span>R <span class="symbol">+</span> <span class="symbol">(</span><span class="symbol">(</span>redError <span class="symbol">*</span> <span class="number">3</span><span class="symbol">)</span> <span class="symbol">&gt;&gt;</span> <span class="number">4</span><span class="symbol">)</span><span class="symbol">)</span><span class="symbol">.</span>ToByte<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 offsetPixel<span class="symbol">.</span>G <span class="symbol">=</span> <span class="symbol">(</span>offsetPixel<span class="symbol">.</span>G <span class="symbol">+</span> <span class="symbol">(</span><span class="symbol">(</span>greenError <span class="symbol">*</span> <span class="number">3</span><span class="symbol">)</span> <span class="symbol">&gt;&gt;</span> <span class="number">4</span><span class="symbol">)</span><span class="symbol">)</span><span class="symbol">.</span>ToByte<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 offsetPixel<span class="symbol">.</span>B <span class="symbol">=</span> <span class="symbol">(</span>offsetPixel<span class="symbol">.</span>B <span class="symbol">+</span> <span class="symbol">(</span><span class="symbol">(</span>blueError <span class="symbol">*</span> <span class="number">3</span><span class="symbol">)</span> <span class="symbol">&gt;&gt;</span> <span class="number">4</span><span class="symbol">)</span><span class="symbol">)</span><span class="symbol">.</span>ToByte<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 original<span class="symbol">[</span>offsetIndex<span class="symbol">]</span> <span class="symbol">=</span> offsetPixel<span class="symbol">;</span>
 <span class="symbol">}</span>

 <span class="comment">// down</span>
 offsetIndex <span class="symbol">=</span> index <span class="symbol">+</span> width<span class="symbol">;</span>
 offsetPixel <span class="symbol">=</span> original<span class="symbol">[</span>offsetIndex<span class="symbol">]</span><span class="symbol">;</span>
 offsetPixel<span class="symbol">.</span>R <span class="symbol">=</span> <span class="symbol">(</span>offsetPixel<span class="symbol">.</span>R <span class="symbol">+</span> <span class="symbol">(</span><span class="symbol">(</span>redError <span class="symbol">*</span> <span class="number">5</span><span class="symbol">)</span> <span class="symbol">&gt;&gt;</span> <span class="number">4</span><span class="symbol">)</span><span class="symbol">)</span><span class="symbol">.</span>ToByte<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 offsetPixel<span class="symbol">.</span>G <span class="symbol">=</span> <span class="symbol">(</span>offsetPixel<span class="symbol">.</span>G <span class="symbol">+</span> <span class="symbol">(</span><span class="symbol">(</span>greenError <span class="symbol">*</span> <span class="number">5</span><span class="symbol">)</span> <span class="symbol">&gt;&gt;</span> <span class="number">4</span><span class="symbol">)</span><span class="symbol">)</span><span class="symbol">.</span>ToByte<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 offsetPixel<span class="symbol">.</span>B <span class="symbol">=</span> <span class="symbol">(</span>offsetPixel<span class="symbol">.</span>B <span class="symbol">+</span> <span class="symbol">(</span><span class="symbol">(</span>blueError <span class="symbol">*</span> <span class="number">5</span><span class="symbol">)</span> <span class="symbol">&gt;&gt;</span> <span class="number">4</span><span class="symbol">)</span><span class="symbol">)</span><span class="symbol">.</span>ToByte<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 original<span class="symbol">[</span>offsetIndex<span class="symbol">]</span> <span class="symbol">=</span> offsetPixel<span class="symbol">;</span>

 <span class="keyword">if</span> <span class="symbol">(</span>x <span class="symbol">+</span> <span class="number">1</span> <span class="symbol">&lt;</span> width<span class="symbol">)</span>
 <span class="symbol">{</span>
 <span class="comment">// right and down</span>
 offsetIndex <span class="symbol">=</span> index <span class="symbol">+</span> width <span class="symbol">+</span> <span class="number">1</span><span class="symbol">;</span>
 offsetPixel <span class="symbol">=</span> original<span class="symbol">[</span>offsetIndex<span class="symbol">]</span><span class="symbol">;</span>
 offsetPixel<span class="symbol">.</span>R <span class="symbol">=</span> <span class="symbol">(</span>offsetPixel<span class="symbol">.</span>R <span class="symbol">+</span> <span class="symbol">(</span><span class="symbol">(</span>redError <span class="symbol">*</span> <span class="number">1</span><span class="symbol">)</span> <span class="symbol">&gt;&gt;</span> <span class="number">4</span><span class="symbol">)</span><span class="symbol">)</span><span class="symbol">.</span>ToByte<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 offsetPixel<span class="symbol">.</span>G <span class="symbol">=</span> <span class="symbol">(</span>offsetPixel<span class="symbol">.</span>G <span class="symbol">+</span> <span class="symbol">(</span><span class="symbol">(</span>greenError <span class="symbol">*</span> <span class="number">1</span><span class="symbol">)</span> <span class="symbol">&gt;&gt;</span> <span class="number">4</span><span class="symbol">)</span><span class="symbol">)</span><span class="symbol">.</span>ToByte<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 offsetPixel<span class="symbol">.</span>B <span class="symbol">=</span> <span class="symbol">(</span>offsetPixel<span class="symbol">.</span>B <span class="symbol">+</span> <span class="symbol">(</span><span class="symbol">(</span>blueError <span class="symbol">*</span> <span class="number">1</span><span class="symbol">)</span> <span class="symbol">&gt;&gt;</span> <span class="number">4</span><span class="symbol">)</span><span class="symbol">)</span><span class="symbol">.</span>ToByte<span class="symbol">(</span><span class="symbol">)</span><span class="symbol">;</span>
 original<span class="symbol">[</span>offsetIndex<span class="symbol">]</span> <span class="symbol">=</span> offsetPixel<span class="symbol">;</span>
 <span class="symbol">}</span>
<span class="symbol">}</span>
</pre>
</figure>
<p>Much of the code is duplicated, with a different co-efficient
for the multiplication, and (importantly!) guards to skip pixels
when the current pixel is either the first or last pixel in the
row, or is within the final row.</p>
<h2 id="and-the-result">And the result?</h2>
<p>The image below shows our sample image dithered using the
Floyd–Steinberg algorithm. It doesn't look too bad!</p>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/dithering-floyd-steinberg.png" class="gallery" title="The final result - a bitmap transformed with Floyd–Steinberg dithering" ><img src="https://images.cyotek.com/image/thumbnail/devblog/dithering-floyd-steinberg.png" alt="The final result - a bitmap transformed with Floyd–Steinberg dithering" decoding="async" loading="lazy" /></a><figcaption>The final result - a bitmap transformed with Floyd–Steinberg dithering</figcaption></figure>
<p>By changing the threshold at which colours are converted to
black or white, we can affect the output of the dithering even
if the conversion is to solid black.</p>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/dithering-floyd-steinberg-extreme.png" class="gallery" title="A slightly more extreme black and white conversion still dithers fairly well" ><img src="https://images.cyotek.com/image/thumbnail/devblog/dithering-floyd-steinberg-extreme.png" alt="A slightly more extreme black and white conversion still dithers fairly well" decoding="async" loading="lazy" /></a><figcaption>A slightly more extreme black and white conversion still dithers fairly well</figcaption></figure>
<p><em>(Note: The thumbnail hasn't resized well, the actual size
version looks better)</em></p>
<h2 id="source-code">Source Code</h2>
<p>The latest source code for this demonstration (which will be
extended over time to include additional algorithms) can be
found at our <a href="https://github.com/cyotek/Dithering" rel="external nofollow noopener">GitHib page</a>.</p>
<p>The source code from the time this article was created is
available from the link below, however may not be fully up to
date.</p>
<h2 id="update-history">Update History</h2>
<ul>
<li>2015-06-06 - 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/dithering-an-image-using-the-floyd-steinberg-algorithm-in-csharp .
</small></p>Richard Mosshttps://www.cyotek.com/richard.moss@cyotek.comAn introduction to dithering imagesurn:uuid:d1a52a83-2650-49b0-a59d-3a30c327f9602015-06-06T11:00:01Z2015-06-06T11:00:01Z<p>When you reduce the number of colours in an image, it's often
hard to get a 1:1 match, and so typically you can expect to see
banding in an image - areas of unbroken solid colours where once
multiple similar colours were present. Such banding can often
ruin the look of the image, however by using dithering
algorithms you can reduce such banding and greatly improve the
appearance of the reduced image.</p>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/dithering-original.png" class="gallery" title="The sample image our demonstration program will be using, a picture of the Tower of London" ><img src="https://images.cyotek.com/image/thumbnail/devblog/dithering-original.png" alt="The sample image our demonstration program will be using, a picture of the Tower of London" decoding="async" loading="lazy" /></a><figcaption>The sample image our demonstration program will be using, a picture of the Tower of London</figcaption></figure>
<p>Here we see a nice view of the Tower of London (Image Credit:
<a href="http://www.publicdomainpictures.net/view-image.php?image=18490" rel="external nofollow noopener">Vera Kratochvil</a>). Lets say we wanted to reduce the number
of colours in this image to 256 using the <a href="http://en.wikipedia.org/wiki/Web_colors" rel="external nofollow noopener">web safe colour
palette</a>.</p>
<p>If we simply reduce the colour depth by matching the nearest
colour in the old palette to one in the new, then we'll get
something similar to the image below. As is quite evident, the
skyline has been badly effected by banding.</p>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/dithering-banding.png" class="gallery" title="Not exactly the best representation of the original image" ><img src="https://images.cyotek.com/image/thumbnail/devblog/dithering-banding.png" alt="Not exactly the best representation of the original image" decoding="async" loading="lazy" /></a><figcaption>Not exactly the best representation of the original image</figcaption></figure>
<p>However, by applying a technique known as dithering, we can
still reduce the colour depth using exactly the same palette,
and get something comparable to the original and more
aesthetically pleasing.</p>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/dithering-errordiffusion.png" class="gallery" title="That looks a lot better!" ><img src="https://images.cyotek.com/image/thumbnail/devblog/dithering-errordiffusion.png" alt="That looks a lot better!" decoding="async" loading="lazy" /></a><figcaption>That looks a lot better!</figcaption></figure><h2 id="types-of-dithering">Types of dithering</h2>
<p>There are several different types of dithering, mostly falling
into <strong>Ordered</strong> or <strong>Unordered</strong> categories.</p>
<p>Ordered dithering uses a patterned matrix in order to dither the
image. An example of this is the very distinctive (and
nostalgic!) <em>Bayer</em> algorithm.</p>
<p>Unordered, or error diffusion, dithering calculates an error
value for each pixel and then propagates this to the
neighbouring pixels, often with very good results. The most well
known of these is <em>Floyd–Steinberg</em>, although there are several
more such as <em>Burkes</em>, and <em>Sierra</em>.</p>
<blockquote>
<p>You could potentially use dithering for applications other
than images. An image is simply a block of pixel data, i.e.
colours. Colours are just numbers, and so is a great deal of
other data. So in theory you can dither a lot more than &quot;just&quot;
images.</p>
</blockquote>
<h2 id="dithering-via-error-diffusion">Dithering via Error Diffusion</h2>
<p>For at least the first part of this series, I will be
concentrating on error diffusion. For this algorithm, you scan
the image from left to right, top to bottom and visit each
pixel. Then, for each pixel, you calculate a value known as the
&quot;error&quot;.</p>
<p>After calculating the error it is then applied to one or more
neighbouring values that haven't yet been processed. Generally,
this would mean adjusting at least 3 neighbouring cells, but
depending on the algorithm this could be quite a few more. I'll
go into this in more detail when I describe individual dithering
algorithms in subsequent posts.</p>
<blockquote>
<p>So how do you determine the error? Well, hopefully is clear
that you don't dither an image as a single process. There has
to be another piece in the puzzle, a process to transform a
value. The <em>error</em> therefore is the difference between the
original and new values. When it comes to images, typically
this is going to a form of colour reduction, for example 32bit
(16 million colours) to 8bit (256 colours).</p>
</blockquote>
<p>The diagram below tries to show what I mean - the grey boxes are
pixels that have been processed. The blue box is the pixel that
is currently being transformed, with the green therefore being
unprocessed pixels and candidates for the error diffusion. The
arrows simply highlight that the candidates are always forward
of the current pixel, and not behind it.</p>
<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/dithering-diagram.png" class="gallery" title="A small illustration to try and demonstrate how the error diffusion works" ><img src="https://images.cyotek.com/image/devblog/dithering-diagram.png" alt="A small illustration to try and demonstrate how the error diffusion works" decoding="async" loading="lazy" /></a><figcaption>A small illustration to try and demonstrate how the error diffusion works</figcaption></figure>
<blockquote>
<p>It's worth repeating that the error is <strong>not</strong> applied to any
previously transformed value. If you do modify an already
processed value, then you would need to have some way of
reprocessing it (as the combined value+error may not be valid
for your reduction method), which could get messy fast.</p>
</blockquote>
<h2 id="next-steps">Next Steps</h2>
<p>Hopefully this article serves as at least a basic and high level
overview of dithering - additional posts will deal with the
actual implementation of dithering.</p>
<h2 id="update-history">Update History</h2>
<ul>
<li>2015-06-06 - 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/an-introduction-to-dithering-images .
</small></p>Richard Mosshttps://www.cyotek.com/richard.moss@cyotek.comEssential Algorithms - A Book Reviewurn:uuid:11832fc1-553b-4bfb-9bb7-7092ade6f9d32015-03-07T09:03:37Z2015-03-07T09:03:37Z<figure class="screenshot" ><a href="https://images.cyotek.com/image/devblog/lrg.jpg" class="gallery" title="Essential Algorithms Book Cover" ><img src="https://images.cyotek.com/image/thumbnail/devblog/lrg.jpg" alt="Essential Algorithms Book Cover" decoding="async" loading="lazy" /></a><figcaption>Essential Algorithms Book Cover</figcaption></figure>
<p>This post is a review (or possibly some long winded rambling) of
the book <a href="http://www.wiley.com/go/essentialalgorithms" rel="external nofollow noopener">Essential Algorithms: A Practical Approach to Computer
Algorithms</a> by Rod Stephens and published by Wiley.</p>
<blockquote>
<p>Disclaimer: I received a copy of this book (with a personal
signed inscription too :)) directly from Rod with the
condition that I review the book. This has not influenced my
review except that I have tried to do a decent job rather than
just picking a star and saying I liked it.</p>
</blockquote>
<h2 id="quick-overview">Quick overview</h2>
<p>The book has quite a few chapters covering a pretty good
selection of algorithms, including</p>
<ul>
<li>Numerical Algorithms</li>
<li>Linked Lists, Arrays, Stacks and Queues, Hash Tables</li>
<li>Sorting</li>
<li>Searching</li>
<li>Recursion</li>
<li>Trees, Balanced Trees, Decision Trees</li>
<li>Basic Network Algorithms, More Network Algorithms</li>
<li>String Algorithms</li>
<li>Cryptography</li>
<li>Complexity Theory</li>
<li>Distributed Algorithms</li>
</ul>
<p>There's also a glossary as you would expect with this sort of
reference, and an appendix with the answers to all the practice
questions - you will need this!</p>
<p>Each chapter is divided into sections, and ends with a summary
and a set of practice questions, some of which are marked with
one or more <code>*</code> to indicate a tougher problems. Standard stuff!</p>
<p>There's also sample code available from the book's website.</p>
<h2 id="tell-me-about-the-book-already">Tell me about the book already</h2>
<p>I don't have a very strong maths background, and there is a
distinct lack of material on either mathematics or algorithms to
be found in my selection of programming books. I do have one
other book on the subject of algorithms/data structures - it is
so dry and filled with source code in an unfamiliar language I
haven't even attempted to read it yet.</p>
<p>Essential Algorithms on the other hand, is a book I found to be
very approachable, bar a hiccup or two.</p>
<p>When I buy computer books, they are pretty much always for a
specific language or technology, but I think Essential
Algorithms is actually language agnostic. While the accompanying
downloadable source is in C#, the code in the book is pseudo
code written as plain English (or perhaps Rod's version of
Beginners All-purpose Symbo... <em>cough</em>). I actually found this
refreshing as when trying to grasp a tenet of the algorithms Rod
was describing I didn't have to &quot;think code&quot; which is more
helpful than it sounds.</p>
<h2 id="my-head">My head!</h2>
<p>So I mentioned hiccups. What were these? Well, my initial foray
into the book was slightly bewildering. The very first chapter,
describes various performance characteristics (Big O notation)
and chapter two dives right into numerical algorithms. This
second chapter actually covers quite a lot, but I did find it
difficult to grasp. I don't find fault with Rod's writing for
this, but my lack of knowledge on mathematics. With that said,
it looked interesting enough that I am determined to get enough
knowledge to be able to read this chapter and understand it!</p>
<h2 id="when-is-an-algorithm-not-an-algorithm">When is an algorithm not an algorithm</h2>
<p>Chapters three through five cover linked lists, arrays, stacks
and queues, something I suspect any C# developer would
recognise. Even though I'm intimately familiar with these data
structures, and, (with the exception of linked lists) use them
regularly, I still discovered quite a few new things I hadn't
considered regarding implementation and advanced usage of such
structures, which never occurred to me when using black box
implementations.</p>
<p>An example of this is sentinel values to avoid having to write
code to handle special cases (such as the start or end of a
linked list). Seems obvious but I hadn't thought of it -
assuming I was aware of the special case I'd write extra code to
handle it.</p>
<h2 id="sorting-and-searching">Sorting and Searching</h2>
<p>I suppose every programmer can write a bubble sort without even
thinking about it, but Essential Algorithms covers no less than
8 different ways of performing a sort.</p>
<p>Closely tied to sorting is searching, as it is more efficient to
search sorted data. Oddly however, this is an incredibly short
chapter - barely 6 pages. However, it does include binary and
interpolation search algorithms which are much better than the
usual linear search that I would normally do.</p>
<p>With that said, the book then follows on with a detailed chapter
on hash tables which can also help you find data extremely fast.</p>
<h2 id="seeing-the-forest-for-the-trees">Seeing the forest for the trees</h2>
<p>Many people are familiar with a tree as a means of presenting
hierarchical data, but that's not what the chapters on trees
cover. Essential Algorithms describes binary trees, complete
trees, sorted trees, how to traverse trees, how to search trees,
expression evaluation, the list goes on.</p>
<p>I found this chapter engrossing as I could dimly see the light
bulb flickering of how I could make use of these techniques.</p>
<p>This is then followed by a chapter on balanced trees (AVL trees
and b-trees). I started getting a bit lost here, although it
didn't help due to my schedule I was reading through the last
chapters very piecemeal, a few pages here, a page or two there.
While I started to get glimmers of ideas from the first two
chapters on trees, the 3rd tree chapter - Decision Trees - was
another head scratcher.</p>
<h2 id="networks-are-trees-with-added-epic">Networks are trees with added epic</h2>
<p>There are two chapters which deal with networks. As with the
first couple of tree chapters, I also found these chapters quite
interesting, with the caveat I couldn't immediately see how I
can use this knowledge in my code. They cover network traversal,
short path detection, map colouring (whoever would believe that
automatically colouring a map in as few shades as possible would
be so hard!) and a bit more besides.</p>
<h2 id="my-head-is-hurting-again">My head is hurting again</h2>
<p>The last few chapters deal with cryptology (interesting, but
there's no way I'm going to try reinventing that wheel, I'll use
managed black box classes!), complexity theory (I gave up trying
to understand it) and distributed algorithms, which falls neatly
under the parallel processing banner and so again should be
somewhat familiar to C# developers. I wondered why this was the
last chapter as the complexity theory was so complicated it
should have been at the end.</p>
<h2 id="sample-code">Sample Code</h2>
<p>I haven't tried most of the exercises offered at the end of each
chapter, so I can't comment on the accuracy of these. And, as I
haven't finished them I've avoided looking in detail at the
source code examples. The ones I have browsed don't seem to be
bad, and while are not extensively commented (so you'll probably
have to have the book to hand for reference), they do include
enough comments for you to know what's going on, and the code
itself is not written in an obtuse fashion. Even from the brief
look I'd taken I could see things I could learn from so yet more
bonus.</p>
<h2 id="in-conclusion">In conclusion</h2>
<p>Unlike many programming books I have bought in the past,
Essential Algorithms is actually a book that I want to read
again, both to pick up what eluded me the first time around, and
to help me visualize ways of using what I have learned in the
code I write.</p>
<p>However, if like me you don't have a strong head for maths, you
might struggle with some of the chapters.</p>
<h2 id="update-history">Update History</h2>
<ul>
<li>2015-03-07 - 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/essential-algorithms-a-book-review .
</small></p>Richard Mosshttps://www.cyotek.com/richard.moss@cyotek.com