3

I am trying to convert a double value (which is between 0 and 1) to RGB Color. In the code below you can see what Im trying to do but I feel that there is something wrong with this algorithm. Im not getting all the colors. Maybe there is a loose of information when I convert from double to int or Im not sure...but please have a look at it and if you have any suggestion or any other method(validated one) please let me know:

    private Color generateRGB(double X)
    {
        Color color;
        if (X >= 0.5) //red and half of green colors
        {
            int Red = (int)((2 * X - 1) * 255);
            int Green = (int)((2 - 2 * X) * 255);
            int Blue = 0;
            color = Color.FromArgb(Red, Green, Blue);
        }
        else  // blue and half of green colors
        {
            int Red = 0;
            int Green = (int)((2 * X) * 255);
            int Blue = (int)((1 - 2 * X) * 255);
            color = Color.FromArgb(Red, Green, Blue);
        }
        return color;
    }

Here is the image that expresses the best what im trying to do.

https://www.dropbox.com/s/bvs3a9m9nc0rk5e/20131121_143044%20%281%29.jpg

[Updated]

That`s how I did and it seems a good solution. Please have a look at it and tell me weather this is better representation or not (maybe those who have a better knowledge for Color Spaces could give a feedback about it)

I have used an HSVtoRGB conversion algorithm from here: http://www.splinter.com.au/converting-hsv-to-rgb-colour-using-c/. Knowing that my values are at [0,1] interval, Im extending this interval to [0, 360] in order to use the algorithm for converting HSV to RGB. Im using s and v equal to 1 in my case. Here is the code for better explanation.

        private Color generateRGB(double X)
        {
            Color color;

            int red;
            int green;
            int blue;
            HsvToRgb(X*360,1,1,out red,out green,out blue);

            color = Color.FromArgb(red, green, blue);

            return color;
        }
Drill
  • 169
  • 2
  • 3
  • 12
  • 2
    So RGB colors have 3 independent axes. A double has only 1. Which path through the color cube are you trying to get? – AShelly Nov 21 '13 at 12:19
  • Knowing that if the value X is between 0.5 to 1 it can be any color with blue equal to 0. Otherwise it will be any color with red equal to 0. – Drill Nov 21 '13 at 12:27
  • `Knowing that if the value X is between 0.5 to 1 it can be any color with blue equal to 0. Otherwise it will be any color with red equal to 0`. It is not easy to understand this. Try to formulate this better, maybe then you can write correct code. – Alex F Nov 21 '13 at 12:30
  • Possible duplicate of http://stackoverflow.com/questions/2353211/hsl-to-rgb-color-conversion assuming the double is the 'hue' value. – Slate Nov 21 '13 at 12:34
  • Please refer to the image I have just attached to understand what Im doing – Drill Nov 21 '13 at 12:35
  • 3
    You'll e.g. never get white as a color with your code, because either blue is 0 or red is 0. Like AShelly say, we'd need to know at least some boundaries of the expected behavior. – Thomas Weller Nov 21 '13 at 12:36
  • 1
    Also - the formulas you've provided on your drawings have a tendency to favor green tints - is that your intention? – decPL Nov 21 '13 at 12:37
  • 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. – Drill Nov 21 '13 at 12:39
  • shouldn't you have two doubles according to your sketch? you've got a x-axis and a y-axis, so you should use two parameters – Herm Nov 21 '13 at 12:50
  • But I have just one double value(derived from some calculations) and now I want to display it to the user as RGB color. Actually Im trying to display the map of nodes (Self Organizing Maps algorithm) where each node has a double value (weight)...and Im going to represent this node as a rectangle with the color generated from that double value – Drill Nov 21 '13 at 12:54
  • You say you want all colors, but that requires something like a space-filling curve. These inevitably have the property that two values far apart get mapped close together. This makes them a poor fit for representing an intensity, since the inverse takes two visually indistinguishable colors to distant values. Are you sure you want to visit 256*256*256 colors? Your diagram is essentially two line segments through the cube, it visits like 256 colors. Humbly, I think you need to rethink your requirements and make sure you want what you are asking for. – bmm6o Nov 23 '13 at 00:29
  • Maybe I couldnt express what I want. I dont want the whole color space but let`s say that I want the best possible representation od double values between 0 and 1. – Drill Nov 23 '13 at 10:10

2 Answers2

12

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"

So you don't care about what the actual relationship is between the double and the Color and you don't want to operate on the double values in a way which is somehow consistent with their Color counterparts. In that case, things are easier than you expected.

Might I remind you that an RGB colour is composed of 3 bytes, although, for combinatorial reasons, the .NET BCL class Color offers the 3 components as int values.

So you have 3 bytes ! A double occupies 8 bytes. If my assumption is correct, at the end of this answer you might be considering float as a better candidate (if a smaller footprint is important for you, of course).

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.

Have you heard about the StructLayoutAttribute attribute and it's entourage, the FieldOffsetAttribute attribute ? In case you haven't you will probably be awed by them.

Say you have a struct, let's call it CommonDenominatorBetweenColoursAndDoubles. Let's say it contains 4 public fields, like so:

public struct CommonDenominatorBetweenColoursAndDoubles {
    public byte R;
    public byte G;
    public byte B;

    public double AsDouble;
}

Now, say you want to orchestrate the compiler and imminent runtime in such a way so the R, the G and the B fields (each of which take up 1 byte) are laid out contiguously and that the AsDouble field overlaps them in it's first 3 bytes and continues with it's own, exclusively remaining 5 bytes. How do you do that ?

You use the aforementioned attributes to specify:

  1. The fact that you're taking control of the struct's layout (be careful, with great power comes great responsibility)
  2. The facts that R, G and B start at the 0th, 1st and 2nd bytes within the struct (since we know that byte occupies 1 byte) and that AsDouble also starts at the 0th byte, within the struct.

The attributes are found in mscorlib.dll under the System.Runtime.InteropServices namespace and you can read about them here StructLayout and here FieldOffset.

So you can achieve all of that like so:

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

}

Here's what the memory within an instance of the struct (kinda) looks like:

Diagram showing memory details of converter struct

And what better way to wrap it all up than a couple of extension methods:

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;

}

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:

for (int x = 0; x < 255; x++) {
for (int y = 0; y < 255; y++) {
for (int z = 0; z < 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");

}
}
}

This completed without yielding any errors.

EDIT

Before I begin the edit: If you study the double encoding standard 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 double values which are mathematically bounded by 0 at the lower end and double.Epsilon * (256 * 3 - 1) at the other end (inclusively). That is true, of course, if the remaining more significant 5 bytes are filled with 0s.

In case it's not clear already, double.Epsilon * (256 * 3 - 1) is an incredibly small number which people can't even pronounce. Your best shot at the pronunciation would be: It's the product between 2²⁴ and the smallest positive double greater than 0 (which is immensely small) or if it suits you better: 8.28904556439245E-317.

Within that range you will discover you have precisely 256 * 3 which is 2²⁴ "consecutive" double values, which start with 0 and are separated by the smallest double distance possible.

By means of mathematical (logical value) manipulation (not by direct memory addressing) you can easily stretch that range of 2²⁴ numbers from the original 0 .. double.Epsilon * (2²⁴ - 1) to 0 .. 1.

This is what I'm talking about:

Linear transformation

Don't mistake double.Epsilon ( or ε) for the exponential letter e. double.Epsilon is somehow a representation of it's calculus counterpart, which could mean the smallest real number which is greater than 0.

So, just to make sure we're ready for the coding, let's recap what's going on in here:

We have N (N being 2²⁴) double numbers starting from 0 and ending in ε * (N-1) (where ε, or double.Epsilon is smallest double greater than 0).

In a sense, the struct we've created is really just helping us to do this:

double[] allDoubles = new double[256 * 256 * 256];
double cursor = 0;
int index = 0;

for (int r = 0; r < 256; r++)
for (int g = 0; g < 256; g++)
for (int b = 0; b < 256; b++) {

  allDoubles[index] = cursor;

  index++;
  cursor += double.Epsilon;

}

So then, why did we go through all that trouble with the struct ? Because it's a lot faster, because it does not involve any mathematical operations, and we're able to access randomly anyone of the N values, based on the R, G and B inputs.

Now, on to the linear transformation bit.

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 0 and 1):

Within the struct we created earlier, we're going to rename the AsDouble field, make it private and create a new property called AsDouble to handle the transformation (both ways):

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

}

You will be pleasantly surprised to learn that the tests I proposed before this EDIT 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 0 .. 1.

Alexsandr Ter
  • 105
  • 1
  • 4
Eduard Dumitru
  • 3,242
  • 17
  • 31
  • With all these excellent explanations I have to accept this answer even though I also HAVE TO accept that I wanted something different (something that I couldn explain quit well in the explenation). Your answer is right because my question maybe was not enough clear :S. Thank You indeed for this answer. I really learned some great things from this answer and Im going to use these later – Drill Nov 24 '13 at 15:34
  • maybe the best way to explain my needs is using some results taken from matlab...and those results I want to achieve also in C#. In matlab if I have a matrix od double values between 0 and 1 I can easily show it as an HSV image. Example: im = [0 .5 1; .7 .6 .2; .9 .3 .4]; cm = colormap('hsv'); cdata = interp1(linspace(0,1,length(cm)),cm,im); figure;image(cdata). Using a matrix(or array of 256 double values) I am achieving this result: https://www.dropbox.com/s/d6dnbyfzgookjl1/Screenshot%202013-11-24%2004.39.59.png As you can see it is a 16 X 16 map with nodes having values between 0 and 1. – Drill Nov 24 '13 at 15:37
  • ...I achieved this even with c# by using HSVtoRGB algorithm explained on my question. I want exactly the same thing but not with HSV. I want it with RGB color space. because HSV doesnt include some of the colors. I hope im clear...and if im not then please just skip evertyhing I asked here and sorry for inconveniences. – Drill Nov 24 '13 at 15:44
  • With SOM there has to be a relation between double values that are close to each other (EX 0.001 and 0.002) and this relation between two close values should also be mapped when representing nodes(each node has its own double value) using colors...but with your code I couldnt achieve it. I know there is a way to achieve it because people did it before me and I am researching on how to do it but still the best result is usgin HSVtoRGB algorithm with H = double value, S = 1 and V = 1; – Drill Nov 24 '13 at 15:49
  • Using double values from 0...256 (0, 1 ,2 ,3 ,4 ,5...256) I got this result from matlab:https://www.dropbox.com/s/d6dnbyfzgookjl1/Screenshot%202013-11-24%2004.39.59.png ...and using your code I got this result...https://www.dropbox.com/s/nf684xeialxn52g/Screenshot%202013-11-24%2004.54.57.png – Drill Nov 24 '13 at 15:57
  • It's evident that my code and the Matlab code don't do the same thing. I'm in my car, driving :), wait up, I'll try and brush up on my Matlab and try to make another *edit* sometime tonight (GMT +2). Btw: the first dropbox link is broken, but I guess the idea is it's not a grayscale gradient like the second one... – Eduard Dumitru Nov 24 '13 at 16:01
  • bthw the link is fixed. Thnx a lot for reading. Have a nice drive :) – Drill Nov 24 '13 at 16:11
  • ...and using double values (1, 2, 3, ... 3600) I got these result using matlab: dropbox.com/s/ro1fkm2x8cy8cye/… ... and this result using your code: dropbox.com/s/z3kizpxi6q8lvbd/… . As you can see im simply creating a map of 60 X 60 nodes (3600 nodes at all) and each node has its own double value. – Drill Nov 24 '13 at 16:15
  • ...and finally in matlab using double values (1, 2, 3, ... 3600) and forming a matrix of doubles with the dimenson of 60 x 60 nodes I have this result: https://www.dropbox.com/s/o5ghfr58wu4t342/untitled.png. It is RGB in Matlab. I want the same result in c#. I dont want to use matlab functions as dlls in c# as it is a slow process. Finally I hope you understood what I want to achieve – Drill Nov 24 '13 at 17:13
  • Hello again. I've looked and looked again :) and from what I can see, you've already got a pretty good solution. If you're not delighted by your personal .NET mimic or the MatLab interpolation thingie, then I must point out the following facts: colormap('hsv') sounds familiar and universally understood but you might want to make sure what it's all about (I don't have Matlab installed). Try to print out the exact R,G,B components of the entire Mx3 array and see if they match the results of iterating from 0 to 360 on the Hsv2Rgb method you're using – Eduard Dumitru Nov 24 '13 at 18:23
  • The second fact which I wanted to talk about is the last image you've uploaded (the one with 60x60 nodes). I've analyzed it a bit and found that there are exactly 64 distinct colors in that image. But this is not the issue. The problem is that, if you've generated it, somehow like you showed us a MatLab snippet earlier (using interpolation and the hsv colormap), it's quite strange. Why is it so ? Because, besides the 64 distinct colors, there are also 18 distinct brightness values. I was expecting exactly one. That's what 'hsv' seems to be doing (but is probably not) in MatLab. – Eduard Dumitru Nov 24 '13 at 18:30
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/41812/discussion-between-eduard-dumitru-and-xxx) – Eduard Dumitru Nov 24 '13 at 18:30
  • @Alexsandr Ter, thank you for spotting that missing `double` – Eduard Dumitru May 31 '17 at 12:28
1

As mentioned above in the comments the formulas you've drawn do not satisfy your condition of uniformly spanning the whole color range. I believe this should work (not the only possible solution by far):

*Edit: fixed the formula, the previous did not generate all possible colors

int red = Math.Min((int)(X * 256), 255);
int green = Math.Min((int)((X * 256 - red) * 256), 255);
int blue = Math.Min((int)(((X * 256 - red) * 256 - green) * 256), 255);

Math.Min is employed to fix the border scenario as X -> 1D

decPL
  • 5,384
  • 1
  • 26
  • 36
  • Im going to try it. Thank You – Drill Nov 21 '13 at 12:52
  • Unofrtunately I have tried this but still not all the colors are shown in the map of nodes (I know from double values that there should be some other colors apart from the ones that are shown using this formula :S). In a similar implementation in matlab I see there more colors than in my application but I dont know how they did it... – Drill Nov 21 '13 at 15:02
  • Please disregard the previous version, not enough sleep. This should work correctly now. – decPL Nov 21 '13 at 16:11
  • Well there is something wrong definitely because still it is not working. Im sure that should be a way but still I couldn find one. I was hoping that here someone can know such a way... – Drill Nov 21 '13 at 18:02
  • How exactly is this 'not working'? You claim there are colors that are not represented by this? Could you determine their RGB value? Also - are you certain the numbers do not have some specific distribution that might be important here? – decPL Nov 22 '13 at 08:21
  • OK I have a list of double values starting from 1 - 256. I normalize it between 0 and 1 and then I use your method to represent these values as colors and this is the result: https://www.dropbox.com/s/51ue1khgusrndqj/Untitled.png It only represents white to black values. And if I have doubles from 0-3600 this is the result: https://www.dropbox.com/s/s6ks3j6d3113ka0/Untitled1.png. In the first case there are 256 nodes each representing its own double value as color...in the second case there are 3600 nodes on grind each representing its own double value as well. – Drill Nov 22 '13 at 14:46
  • ...P.S. As soon as I get sure that your method is really showing colors I will accept your answer. – Drill Nov 22 '13 at 15:01
  • How can you expect to display all possible color values if you have 256 numbers only? And then for 3600? There's a total of 16.7 million colors. The code I've provided will work correctly for any choice of double between 0 and 1; if you limit yourself to N/256 or N/3600 it will display only some of the colors. Once again - do you have more details about what your number represents and what possible values you could expect? – decPL Nov 25 '13 at 08:54
  • ...and finally in matlab using double values (1, 2, 3, ... 3600) and forming a matrix of doubles with the dimenson of 60 x 60 nodes I have this result: dropbox.com/s/o5ghfr58wu4t342/untitled.png. It is in RGB Color Space and I got this in Matlab just by using imagesc(M) where M is the matrix of double values. I want the same result in c#. You know I achieved a similar result in .net by using HSV but I need RGB. I dont want to use matlab functions as dlls in c# as it is a slow process. – Drill Nov 25 '13 at 11:06