tags:

views:

4526

answers:

13

Given a source color of any hue by the system or user, I'd like a simple algorithm I can use to work out a lighter or darker variants of the selected color. Similar to effects used on Windows Live Messenger for styling the user interface.

Language is C# with .net 3.5.

Responding to comment: Color format is (Alpha)RGB. With values as bytes or floats.

Marking answer: For the context of my use (a few simple UI effects), the answer I'm marking as accepted is actually the most simple for this context. However, I've given up votes to the more complex and accurate answers too. Anyone doing more advanced color operations and finding this thread in future should definitely check those out. Thanks SO. :)

+9  A: 

You can convert your color into the HSL color-space, manipulate it there and convert back to your color-space of choice (most likely that's RGB)

Lighter colors have a higher L-value, darker a lower.

Here's the relevant stuff and all the equations:

http://en.wikipedia.org/wiki/HSL_color_space

Another method is to simply interpolate your color with white or black. This will also desaturate the color a bit but it's cheaper to calculate.

Nils Pipenbrinck
A: 

Assuming you get the color as RGB, first convert it to HSV (hue, saturation, value) color space. Then increase/decrease the value to produce lighter/darker shade of the color. Then convert back to RGB.

Dima
+16  A: 

HSV ( Hue / Saturation / Value ) also called HSL ( Hue / Saturation / Lightness ) is just a different color representation.

Using this representation is it easier to adjust the brightness. So convert from RGB to HSV, brighten the 'V', then convert back to RGB.

Below is some C code to convert

void RGBToHSV(unsigned char cr, unsigned char cg, unsigned char cb,double *ph,double *ps,double *pv)
{
double r,g,b;
double max, min, delta;

/* convert RGB to [0,1] */

r = (double)cr/255.0f;
g = (double)cg/255.0f;
b = (double)cb/255.0f;

max = MAXx(r,(MAXx(g,b)));
min = MINx(r,(MINx(g,b)));

pv[0] = max;

/* Calculate saturation */

if (max != 0.0)
 ps[0] = (max-min)/max;
else
 ps[0] = 0.0; 

if (ps[0] == 0.0)
{
 ph[0] = 0.0f; //UNDEFINED;
 return;
}
/* chromatic case: Saturation is not 0, so determine hue */
delta = max-min;

if (r==max)
{
 ph[0] = (g-b)/delta;
}
else if (g==max)
{
 ph[0] = 2.0 + (b-r)/delta;
}
else if (b==max)
{
 ph[0] = 4.0 + (r-g)/delta;
}
ph[0] = ph[0] * 60.0;
if (ph[0] < 0.0)
 ph[0] += 360.0;
}

void HSVToRGB(double h,double s,double v,unsigned char *pr,unsigned char *pg,unsigned char *pb)
{
int i;
double f, p, q, t;
double r,g,b;

if( s == 0 )
{
 // achromatic (grey)
 r = g = b = v;
}
else
{
 h /= 60;   // sector 0 to 5
 i = (int)floor( h );
 f = h - i;   // factorial part of h
 p = v * ( 1 - s );
 q = v * ( 1 - s * f );
 t = v * ( 1 - s * ( 1 - f ) );
 switch( i )
 {
 case 0:
  r = v;
  g = t;
  b = p;
 break;
 case 1:
  r = q;
  g = v;
  b = p;
 break;
 case 2:
  r = p;
  g = v;
  b = t;
 break;
 case 3:
  r = p;
  g = q;
  b = v;
 break;
 case 4:
  r = t;
  g = p;
  b = v;
 break;
 default:  // case 5:
  r = v;
  g = p;
  b = q;
 break;
 }
}
r*=255;
g*=255;
b*=255;

pr[0]=(unsigned char)r;
pg[0]=(unsigned char)g;
pb[0]=(unsigned char)b;
}
KPexEA
A: 

If your colours are in RGB format (or, presumably CMYK), you can use the fairly crude method of increasing the value of each component of the colour. E.g., in HTML colours are represented as three two-digit hex numbers. #ff0000 will give you a bright red, which can then be faded by increasing the values of the G and B componenets by the same amount, such as #ff5555 (gives a lighter red). Presumably for Hue, Saturation and Lightness (HSL) colours, you can just raise the L component, but I can't say for certain; I'm less familiar with this colour space.

As I say, though, this method is quite crude. From my memories of Live Messenger, it sounds like you're trying to do gradients, which can be applied really quite easily in Windows Presentation Foundation (WPF, part of .NET 3.0). WPF supports many different types of gradient brush, including linear and radial gradients.

I can highly recommend Adam Nathan's book Windows Presentation Foundation Unleashed as a good and thorough introduction to WPF.

HTH

alastairs
#ff5555 doesn't give a lighter red, it gives a faded red. Less saturated. This does give off more light, but it's more of a pink than a brighter red.
clahey
Indeed one can use a gradient, but you have to provide colors for each gradient stop.
Nidonocu
+2  A: 

If you are using RGB colors I would transform this color paramaters to HSL (hue, saturation, lightness), modify the lightness parameter and then transform back to RGB. Google around and you'll find a lot of code samples on how to do these color representation transformations (RGB to HSL and viceversa).

This is what I quickly found: http://bytes.com/forum/thread250450.html

Sacha
+12  A: 

Simply multiply the RGB values by the amount you want to modify the level by. If one of the colors is already at the max value, then you can't make it any brighter (using HSV math anyway.)

This gives the exact same result with a lot less math as switching to HSV and then modifying V. This gives the same result as switching to HSL and then modifying L, as long as you don't want to start losing saturation.

clahey
A: 

Any variations in color are better done in HSL/HSV.

A good test is to interpolate between two equivalent values in RGB space and HSL space. The ramp in HSL space looks like a natural progression. In RGB space it typically looks quite unnatural. HSL maps to our visual color space perception much better than RGB.

+1  A: 

I'm guessing you're using RGB with byte values (0 to 255) as that's very common everywhere.

For brighter, average the GB values with the RGB of white. Or, to have some control over how much brighening, mix in them in some proportion. Let fvary from 0.0 to 1.0, then:

Rnew = (1-f)R + f255 Gnew = (1-f)G + f255 Bnew = (1-f)B + f255

For darker, use the RGB of black - which , being all zeros, makes the math easier.

I leave out details such as converting the result back into bytes, which probably you'd want to do.

DarenW
A: 

The idea of converting to HSV or some other color space seems good, and may be necessary for precise color work, but for ordinary purposes the error of working in RGB may not be enough to matter. Also, it can be a pain to deal with boundary cases: RGB is a cube-shaped space, while HSV is not. If working with byte values, you can have many-to-one and one-to-many mappings between the spaces. This may or may not be a problem depending on the application. YMMV

DarenW
+10  A: 

Rich Newman discusses HSL color with respect to .NET System.Drawing.Color on his blog and even provides an HSLColor class that does all the work for you. Convert your System.Drawing.Color to an HSLColor, add/subtract values against the Luminosity, and convert back to System.Drawing.Color for use in your app.

Travis Illig
This was very usefull. Thank you very much for pointing this out.
Tomas Pajonk
+4  A: 

I have used the ControlPaint.Dark() and .Light() in System.Windows.Forms.

GregUzelac
A: 

This website notes that you can use the ControlPaint class within the BCL C# System.Windows.Forms namespace.

TK
+2  A: 

In XNA there is the Color.Lerp static method that does this as the difference between two colours.

Lerp is a mathematical operation between two floats that changes the value of the first by a ratio of the difference between them.

Here's an extension method to do it to a float:

public static float Lerp( this float start, float end, float amount)
{
    float difference = end - start;
    float adjusted = difference * amount;
    return start + adjusted;
}

So then a simple lerp operation between two colours using RGB would be:

public static Color Lerp(this Color colour, Color to, float amount)
{
    // start colours as lerp-able floats
    float sr = colour.R, sg = colour.G, sb = colour.B;

    // end colours as lerp-able floats
    float er = to.R, eg = to.G, eb = to.B;

    // lerp the colours to get the difference
    byte r = (byte) sr.Lerp(er, amount),
         g = (byte) sg.Lerp(eg, amount),
         b = (byte) sb.Lerp(eb, amount);

    // return the new colour
    return Color.FromArgb(r, g, b);
}

An example of applying this would be something like:

// make red 50% lighter:
Color.Red.Lerp( Color.White, 0.5f );

// make red 75% darker:
Color.Red.Lerp( Color.Black, 0.75f );

// make white 10% bluer:
Color.White.Lerp( Color.Blue, 0.1f );
Keith