Problem
I made a start at translating the accepted answer’s code here into C#. Any suggestions on the correctness of the translation and the correctness of the original code would be very much appreciated.
public class RgbValues
{
public int Red { get; set; }
public int Green { get; set; }
public int Blue { get; set; }
}
public static RgbValues GetRgbValues(float minimumValue, float maximumValue, float value)
{
var rgbValues = new RgbValues();
var halfmax = (minimumValue + maximumValue) / 2.0;
rgbValues.Blue = (int) Math.Max(0.0, 255.0 * (1.0 - value/halfmax));
rgbValues.Red = (int)Math.Max(0.0, 255.0 * (value / halfmax - 1.0));
rgbValues.Green = 255 - rgbValues.Blue - rgbValues.Red;
return rgbValues;
}
What is a bit worrying is that:
GetRgbValues(10, 10113, 10113)
should really caclulate:
red = 255, green = 0, blue = 0
rather than:
red = 254, green = 1, blue = 0
I guess there is some rounding issue.
Any ideas?
Solution
Calculations like these can be hard to understand, so I would try to strive for something that is clearly correct, not just correct.
What I would do:
- normalize the value from [min,max] to [0,2]
- realize that the “strength” of a color is 1−|value−color| (where color is 0 for blue, 1 for green and 2 for red), so compute that
- clip the computer strength to remove negative numbers
- finally multiply it by 255 and convert to
int
(while also rounding the value, to avoid bias towards smaller numbers)
In code:
public static RgbValues GetRgbValues(float minimum, float maximum, float value)
{
var normalizedValue = Normalize(minimum, maximum, value);
return new RgbValues
{
Blue = Distance(normalizedValue, 0),
Green = Distance(normalizedValue, 1),
Red = Distance(normalizedValue, 2)
};
}
private static float Normalize(float minimum, float maximum, float value)
{
return (value - minimum) / (maximum - minimum) * 2;
}
private static int Distance(float value, float color)
{
var distance = Math.Abs(value - color);
var colorStrength = 1 - distance;
if (colorStrength < 0)
colorStrength = 0;
return (int)Math.Round(colorStrength * 255);
}
This code is longer than the original, but I think it’s also clearer about what it does. I didn’t figure out what your code did until I read the SO question, I think it’s much more likely I would have succeeded with this code.
That’s because the formula is not correct, but with the specific values of minimumValue
and maximumValue
in the question, the result happens to be the same.
The value of halfmax
should be half the difference between the min and max:
double halfmax = (maximumValue - minimumValue) / 2.0;
The minimumvalue
should be subtracted from value
:
rgbValues.Blue = (int)Math.Max(0.0, 255.0 * (1 - (value - minimiumValue) / halfmax));
rgbValues.Red = (int)Math.Max(0.0, 255.0 * ((value - minimiumValue) / halfmax - 1));
The basis for the formula is this way of interpolating any color component between a start value and end value at the min and max points, respectively:
color = start + (end - start) * (value - min) / (max - min)
With the blue starting at 1 and ending at -1, and red starting at -1 and ending at 1, you get:
blue = 1 - 2 * (value - min) / (max - min)
red = -1 + 2 * (value - min) / (max - min)
The inverse of 2 / (max - min)
is what was calculated as halfmax
, giving:
blue = 1 - (value - min) / halfmax
red = -1 + (value - min) / halfmax