2

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.

  • if it's random, and you use rgb, why you don't use a single channel? you got always random values also in this way... – Jepessen Aug 30 '12 at 20:54
  • Not sure why you think you can't use RGB for this. Simply pick a starting RGB color (you can randomly assign all 3 if you like), then multiply them all by a scalar constant. If the constant is larger than 1, it will become brighter. If it's less than 1 it will become darker. – user1118321 Aug 30 '12 at 21:00
  • Not sure what you mean, Daniele. Can you try to restate that? @user1118321 Does that produce colors that would be identical to changing the luminosity alone? I feel like it would change slightly. It would get lighter, but I have less hue and saturation control, not to mention other light problems, though if nothing else works IO may have to try it. To be honest, though, I think HSL would be easy to use if the problem with the formula can be found. This formula seems to be the same where I have seen it (least four times), and it must have worked for some people. Do you know why it does not work –  Aug 30 '12 at 21:46
  • It won't guarantee that you're getting the same results as only changing the luminosity, but it didn't sound like that was really important for this case. If it is, I recommend using Y'CbCr instead. See my [answer here](http://stackoverflow.com/questions/9234724/how-to-change-hue-of-a-texture-with-glsl/9234854#9234854) for more info. – user1118321 Aug 30 '12 at 23:58
  • Looked at those. Unfortunately, I will readily admit, I am completely lost at how the method you pointed me to worked. (using the chroma). As such, I decided to give your other method a try, and it produced surprisingly satisfactory results, as far as I can tell, so if all else fails, I will probably use that. Although, you seem to know color systems well. Can you, even though you may not recommend it, deduce why it does not work? The one thing neat about it is, you can adjust not only light but hue and saturation. I am not sure when saturation would be extremely useful, but I think hue and- –  Aug 31 '12 at 00:47
  • -and lightness would certainly be useful. For example, I could made landscapes any color by hew. I unsure if the same can be said from RGB base colors (using your first method) to well, but I will nonetheless try it. Similar goes for saturation - I feel like adding the same constant to all would achieve something of the sort, yet to test it. Perhaps it will work after all. Thanks thus far. –  Aug 31 '12 at 00:49

1 Answers1

0

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

Tatarize
  • 10,238
  • 4
  • 58
  • 64
  • Thanks for your answer. My problem is not so much the accuracy of the solution, which you brought up in your point well, so much as how to achieve the effect. The simple static scaling worked, but didn't work well with my light system. For example, in my implementation, we calculate a specific range of light for a a given gradient, you could say, then divide the light to be displayed by the layers it will be displayed on. But, it didn't take into account the base color already had. Still not sure how, unless that is 0 light, but that does not work great. That brings the other problem ... ... –  Sep 06 '12 at 22:21
  • One reason I favored HSL was it starts with a hue and saturation. My fill-in for this is a base color, but it makes it difficult to illustrate a full range of light, and no idea how to do saturation, and I need to figure out how to cap both. This, and picking a hue seems much easier than a base color, provided the lighting works. Hence, why I wanted to try out HSL. I don't want to make a color BRIGHTER, I want to make it with BRIGHTNESS, if you get what I mean. Do you know how this could be achieved with static scaling? –  Sep 06 '12 at 22:24
  • Revisiting old questions. I never got this to work, either, (well, I got the scalar multiplication working decently well). At the time, I did not like the solution because I just wanted to figure out why the formula was not working, because I was just aiming for using that, which worked decently for my purposes, and makes sense from a brighter/darker hue perspective, which to me the channels did not. Still, I think your answer showed effort, and will likely prove a valuable resource when (fairly confident) I revisit this. So, I came back to give you the accept. –  Oct 11 '12 at 03:07
  • Well if you do revisit, take note that linear scaling the color samples is exactly what HSV or RGB needs to do that operation. So the only way to do it better is to do it in such a way that human eyes can see the difference better. This means you want to convert it into YCbCr colorspace or Lab colorspace and then scale the Y or L factor and convert back to RGB. These both have non-linear and eye based corrections for hue and the base colors you start with matter for the operation. – Tatarize Nov 30 '13 at 08:48
  • Though, make sure you are using your time economically. Try getting Color Inspector 3D the addon for ImageJ as it it will let you load an image and view it in a wide array of colorspaces and tweak each component independently. That way you can know you'll be happy with the result before you get to coding. You might end up wanting a more proper lighting algorithm rather than variations of gamma correction based on height. – Tatarize Nov 30 '13 at 08:59