Calculation of RGB values given min and max values

Posted on

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][min,max] to [0,2][0,2]
  • realize that the “strength” of a color is 1|valuecolor|1|valuecolor| (where colorcolor is 00 for blue, 11 for green and 22 for red), so compute that
  • clip the computer strength to remove negative numbers
  • finally multiply it by 255255 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

Leave a Reply

Your email address will not be published. Required fields are marked *