Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    text
    copied!<p>In one of your comments you said: "no My intention is to include all the colors and I dont want to favor any of them. Simply I would like the best way to convert a double value to an RGB color"</p> <p>So you don't care about what the actual relationship is between the <code>double</code> and the <code>Color</code> and you don't want to operate on the <code>double</code> values in a way which is somehow consistent with their <code>Color</code> counterparts. In that case, things are easier than you expected.</p> <p>Might I remind you that an RGB colour is composed of 3 bytes, although, for combinatorial reasons, the .NET BCL class <code>Color</code> offers the 3 components as <code>int</code> values.</p> <p>So you have 3 bytes ! A <code>double</code> occupies 8 bytes. If my assumption is correct, at the end of this answer you might be considering <code>float</code> as a better candidate (if a smaller footprint is important for you, of course).</p> <p>Enough chit chat, on to the actual problem. The approach I'm about to lay out is not so much linked with mathematics as it is with memory management and encoding.</p> <p>Have you heard about the <code>StructLayoutAttribute</code> attribute and it's entourage, the <code>FieldOffsetAttribute</code> attribute ? In case you haven't you will probably be awed by them.</p> <p>Say you have a struct, let's call it <code>CommonDenominatorBetweenColoursAndDoubles</code>. Let's say it contains 4 public fields, like so:</p> <pre><code>public struct CommonDenominatorBetweenColoursAndDoubles { public byte R; public byte G; public byte B; public double AsDouble; } </code></pre> <p>Now, say you want to orchestrate the compiler and imminent runtime in such a way so the <code>R</code>, the <code>G</code> and the <code>B</code> fields (each of which take up 1 byte) are laid out contiguously and that the <code>AsDouble</code> field overlaps them in it's first 3 bytes and continues with it's own, exclusively remaining 5 bytes. How do you do that ?</p> <p>You use the aforementioned attributes to specify:</p> <ol> <li>The fact that you're taking control of the <code>struct</code>'s layout (be careful, with great power comes great responsibility)</li> <li>The facts that <code>R</code>, <code>G</code> and <code>B</code> start at the 0th, 1st and 2nd bytes within the <code>struct</code> (since we know that <code>byte</code> occupies 1 byte) and that <code>AsDouble</code> also starts at the 0th byte, within the <code>struct</code>.</li> </ol> <p>The attributes are found in <code>mscorlib.dll</code> under the <code>System.Runtime.InteropServices</code> namespace and you can read about them here <a href="http://msdn.microsoft.com/en-us/library/system.runtime.interopservices.structlayoutattribute%28v=vs.110%29.aspx" rel="nofollow noreferrer">StructLayout</a> and here <a href="http://msdn.microsoft.com/en-us/library/system.runtime.interopservices.fieldoffsetattribute%28v=vs.110%29.aspx" rel="nofollow noreferrer">FieldOffset</a>.</p> <p>So you can achieve all of that like so:</p> <pre><code>[StructLayout(LayoutKind.Explicit)] public struct CommonDenominatorBetweenColoursAndDoubles { [FieldOffset(0)] public byte R; [FieldOffset(1)] public byte G; [FieldOffset(2)] public byte B; [FieldOffset(0)] public double AsDouble; } </code></pre> <p>Here's what the memory within an instance of the <code>struct</code> (kinda) looks like:</p> <p><img src="https://i.stack.imgur.com/AfziY.png" alt="Diagram showing memory details of converter struct"></p> <p>And what better way to wrap it all up than a couple of extension methods:</p> <pre><code>public static double ToDouble(this Color @this) { CommonDenominatorBetweenColoursAndDoubles denom = new CommonDenominatorBetweenColoursAndDoubles (); denom.R = (byte)@this.R; denom.G = (byte)@this.G; denom.B = (byte)@this.B; double result = denom.AsDouble; return result; } public static Color ToColor(this double @this) { CommonDenominatorBetweenColoursAndDoubles denom = new CommonDenominatorBetweenColoursAndDoubles (); denom.AsDouble = @this; Color color = Color.FromArgb ( red: denom.R, green: denom.G, blue: denom.B ); return color; } </code></pre> <p>I also tested this to make sure it's bullet-proof and by what I can tell, you won't have to worry about a thing:</p> <pre><code>for (int x = 0; x &lt; 255; x++) { for (int y = 0; y &lt; 255; y++) { for (int z = 0; z &lt; 255; z++) { var c1 = Color.FromArgb (x, y, z); var d1 = c1.ToDouble (); var c2 = d1.ToColor (); var x2 = c2.R; var y2 = c2.G; var z2 = c2.B; if ((x != x2) || (y != y2) || (z != z2)) Console.Write ("1 error"); } } } </code></pre> <p>This completed without yielding any errors.</p> <p><strong>EDIT</strong></p> <p>Before I begin the edit: If you study the <a href="http://en.wikipedia.org/wiki/Double-precision_floating-point_format#Exponent_encoding" rel="nofollow noreferrer"><code>double</code> encoding standard</a> a bit (which is common between all languages, frameworks and most probably most processors) you will come to the conclusion (which I also tested) that by iterating through all combinations of the 3 least significant bytes (the 24 least significant bits) of an 8 byte double, which is what we're doing right here, you will end up with <code>double</code> values which are mathematically bounded by <code>0</code> at the lower end and <code>double.Epsilon * (256 * 3 - 1)</code> at the other end (inclusively). That is true, of course, if the remaining more significant 5 bytes are filled with <code>0</code>s.</p> <p>In case it's not clear already, <code>double.Epsilon * (256 * 3 - 1)</code> is an incredibly small number which people can't even pronounce. Your best shot at the pronunciation would be: It's the product between <code>2²⁴</code> and the smallest positive <code>double</code> greater than <code>0</code> (which is immensely small) or if it suits you better: <code>8.28904556439245E-317</code>.</p> <p>Within that range you will discover you have precisely <code>256 * 3</code> which is <code>2²⁴</code> "consecutive" <code>double</code> values, which start with <code>0</code> and are separated by the smallest <code>double</code> distance possible.</p> <p>By means of mathematical (logical value) manipulation (not by direct memory addressing) you can easily stretch that range of <code>2²⁴</code> numbers from the original <code>0 .. double.Epsilon * (2²⁴ - 1)</code> to <code>0 .. 1</code>.</p> <p>This is what I'm talking about:</p> <p><img src="https://i.stack.imgur.com/rlS4y.jpg" alt="Linear transformation"></p> <p>Don't mistake <code>double.Epsilon</code> ( or <code>ε</code>) for the exponential letter <code>e</code>. <code>double.Epsilon</code> is somehow a representation of it's calculus counterpart, which could mean the smallest real number which is greater than <code>0</code>.</p> <p>So, just to make sure we're ready for the coding, let's recap what's going on in here:</p> <p>We have <code>N</code> (<code>N</code> being <code>2²⁴</code>) <code>double</code> numbers starting from <code>0</code> and ending in <code>ε * (N-1)</code> (where <code>ε</code>, or <code>double.Epsilon</code> is smallest <code>double</code> greater than <code>0</code>).</p> <p>In a sense, the <code>struct</code> we've created is really just helping us to do this:</p> <pre><code>double[] allDoubles = new double[256 * 256 * 256]; double cursor = 0; int index = 0; for (int r = 0; r &lt; 256; r++) for (int g = 0; g &lt; 256; g++) for (int b = 0; b &lt; 256; b++) { allDoubles[index] = cursor; index++; cursor += double.Epsilon; } </code></pre> <p>So then, why did we go through all that trouble with the <code>struct</code> ? Because it's a lot faster, because it does not involve any mathematical operations, and we're able to access randomly anyone of the <code>N</code> values, based on the <code>R</code>, <code>G</code> and <code>B</code> inputs.</p> <p>Now, on to the linear transformation bit.</p> <p>All we have to do now is a bit of math (which will take a bit longer to compute since it involves floating point operations but will successfully stretch our range of doubles to an equally distributed one between <code>0</code> and <code>1</code>):</p> <p>Within the <code>struct</code> we created earlier, we're going to rename the <code>AsDouble</code> field, make it private and create a new property called <code>AsDouble</code> to handle the transformation (both ways):</p> <pre><code>[StructLayout(LayoutKind.Explicit)] public struct CommonDenominatorBetweenColoursAndDoubles { [FieldOffset(0)] public byte R; [FieldOffset(1)] public byte G; [FieldOffset(2)] public byte B; // we renamed this field in order to avoid simple breaks in the consumer code [FieldOffset(0)] private double _AsDouble; // now, a little helper const private const int N_MINUS_1 = 256 * 256 * 256 - 1; // and maybe a precomputed raw range length private static readonly double RAW_RANGE_LENGTH = double.Epsilon * N_MINUS_1; // and now we're adding a property called AsDouble public double AsDouble { get { return this._AsDouble / RAW_RANGE_LENGTH; } set { this._AsDouble = value * RAW_RANGE_LENGTH; } } } </code></pre> <p>You will be pleasantly surprised to learn that the tests I proposed before this <em>EDIT</em> are still working fine, with this new addition, so you have 0% loss of information and now the range of doubles is equally stretched across <code>0 .. 1</code>.</p>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload