Question

I am fond of random generation - and random colors - so I decided to combine them both and made a simple 2d landscape generator. What my idea was is to, depending on how high a block is, (yes, the terrain is made of blocks) make it lighter or darker, where things nearest the top are lighter, and towards the bottom are darker. I got it working in grayscale, but as I figured out, you cannot really use a base RGB color and make it lighter, given that the ratio between RGB values, or anything of the sort, seem to be unusable. Solution? HSL. Or perhaps HSV, to be honest I still don't know the difference. I am referring to H 0-360, S & V/L = 0-100. Although... well, 360 = 0, so that is 360 values, but if you actually have 0-100, that is 101. Is it really 0-359 and 1-100 (or 0-99?), but color selection editors (currently referring to GIMP... MS paint had over 100 saturation) allow you to input such values?

Anyhow, I found a formula for HSL->RGB conversion (here & here. As far as I know, the final formulas are the same, but nonetheless I will provide the code (note that this is from the latter easyrgb.com link):

Hue_2_RGB

float Hue_2_RGB(float v1, float v2, float vH)             //Function Hue_2_RGB
{
if ( vH < 0 )
    vH += 1;

if ( vH > 1 )
    vH -= 1;

if ( ( 6 * vH ) < 1 )
    return ( v1 + ( v2 - v1 ) * 6 * vH );

if ( ( 2 * vH ) < 1 )
    return ( v2 );

if ( ( 3 * vH ) < 2 )
    return ( v1 + ( v2 - v1 ) * ( ( 2 / 3 ) - vH ) * 6 );

return ( v1 );
}

and the other piece of code:

float var_1 = 0, var_2 = 0;

        if (saturation == 0)                       //HSL from 0 to 1
        {
           red = luminosity * 255;                      //RGB results from 0 to 255
           green = luminosity * 255;
           blue = luminosity * 255;
        }
        else
        {
            if ( luminosity < 0.5 )
                var_2 = luminosity * (1 + saturation);
            else
                var_2 = (luminosity + saturation) - (saturation * luminosity);

            var_1 = 2 * luminosity - var_2;

            red = 255 * Hue_2_RGB(var_1, var_2, hue + ( 1 / 3 ) );
            green = 255 * Hue_2_RGB( var_1, var_2, hue );
            blue = 255 * Hue_2_RGB( var_1, var_2, hue - ( 1 / 3 ) );
        }

Sorry, not sure of a good way to fix the whitespace on those.

I replaced H, S, L values with my own names, hue, saturation, and luminosity. I looked it back over, but unless I am missing something I replaced it correctly. The hue_2_RGB function, though, is completely unedited, besides the parts needed for C++. (e.g. variable type). I also used to have ints for everything - R, G, B, H, S, L - then it occured to me... HSL was a floating point for the formula - or at least, it would seem it should be. So I made variable used (var_1, var_2, all the v's, R, G, B, hue, saturation, luminosity) to floats. So I don't beleive it is some sort of data loss error here. Additionally, before entering the formula, I have hue /= 360, saturation /= 100, and luminosity /= 100. Note that before that point, I have hue = 59, saturation = 100, and luminosity = 70. I believe I got the hue right as 360 to ensure 0-1, but trying /= 100 didn't fix it either.

and so, my question is, why is the formula not working? Thanks if you can help.

EDIT: if the question is not clear, please comment on it.

Was it helpful?

Solution

Your premise is wrong. You can just scale the RGB color. The Color class in Java for example includes commands called .darker() and .brighter(), these use a factor of .7 but you can use anything you want.

public Color darker() {
    return new Color(Math.max((int)(getRed()  *FACTOR), 0),
                     Math.max((int)(getGreen()*FACTOR), 0),
                     Math.max((int)(getBlue() *FACTOR), 0),
                     getAlpha());
}

public Color brighter() {
    int r = getRed();
    int g = getGreen();
    int b = getBlue();
    int alpha = getAlpha();

    /* From 2D group:
     * 1. black.brighter() should return grey
     * 2. applying brighter to blue will always return blue, brighter
     * 3. non pure color (non zero rgb) will eventually return white
     */
    int i = (int)(1.0/(1.0-FACTOR));
    if ( r == 0 && g == 0 && b == 0) {
        return new Color(i, i, i, alpha);
    }
    if ( r > 0 && r < i ) r = i;
    if ( g > 0 && g < i ) g = i;
    if ( b > 0 && b < i ) b = i;

    return new Color(Math.min((int)(r/FACTOR), 255),
                     Math.min((int)(g/FACTOR), 255),
                     Math.min((int)(b/FACTOR), 255),
                     alpha);
}

In short, multiply all three colors by the same static factor and you will have the same ratio of colors. It's a lossy operation and you need to be sure to crimp the colors to stay in range (which is more lossy than the rounding error).

Frankly any conversion to RGB to HSV is just math, and changing the HSV V factor is just math and changing it back is more math. You don't need any of that. You can just do the math. Which is going to be make the max component color greater without messing up the ratio between the colors.

--

If the question is more specific and you simply want better results. There are better ways to calculate this. You rather than static scaling the lightness (L does not refer to luminosity) you can convert to a luma component. Which is basically weighted in a specific way. Color science and computing is dealing with human observers and they are more important than the actual math. To account for some of these human quirks there's a need to "fix things" to be more similar to what the average human perceives. Luma scales as follows:

Y = 0.2126 R + 0.7152 G + 0.0722 B

This similarly is reflected in the weights 30,59,11 which are wrongly thought to be good color distance weights. These weighs are the color's contribution to the human perception of brightness. For example the brightest blue is seen by humans to be pretty dark. Whereas yellow (exactly opposed to blue) is seen to be so damned bright that you can't even make it out against a white background. A number of colorspaces Y'CbCr included account for these differences in perception of lightness by scaling. Then you can change that value and it will be scaled again when you scale it back.

Resulting in a different color, which should be more akin to what humans would say is a "lighter" version of the same color. There are better and better approximations of this human system and so using better and fancier math to account for it will typically give you better and better results.

For a good overview that touches on these issues. http://www.compuphase.com/cmetric.htm

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top