Algorithm to modify brightness for RGB image?

Adding to Mark Ransom's Answer: It would be better to use the said factor with a 255 constant and add it to the current color-value:

float brightnessFac = //between -1.0 and 1.0    
byte brightnessRed = red + (255f * brightnessFac);

If you just do with a factor between 0.0 and 1.0

byte brightnessRed = red * brightnessFac;

A value of 0 stays zero.


My recommendation would be the same as ChrisA.'s answer, with one difference:

Use the HSP color space instead, as it is an approximation of Photoshop's algorithm and has better results.


For the sake of not just linking to HSP's site (which frankly should be more than enough; it's just that I don't like to answer without examples), here is my C# implementation, which follows the site's:

#region Definitions
//Perceived brightness to Red ratio.
private const double Pr = .299;
//Perceived brightness to Green ratio.
private const double Pg = .587;
//Perceived brightness to Blue ratio.
private const double Pb = .114;
#endregion

//Expected ranges: Hue = 0-359... Other values = 0-1
public static ColorRGB ToRGB(double hue, double saturation, double perceivedBrightness, double alpha) {
    //Check values within expected range
    hue = hue < 0 ? 0 : hue > 359 ? 359 : hue;
    saturation = saturation < 0 ? 0 : saturation > 1 ? 1 : saturation;
    perceivedBrightness = perceivedBrightness < 0 ? 0 : perceivedBrightness > 1 ? 1 : perceivedBrightness;
    alpha = alpha < 0 ? 0 : alpha > 1 ? 1 : alpha;
    //Conversion
    var minOverMax = 1 - saturation;
    double r, g, b;
    if (minOverMax > 0) {
        double part;
        if (hue < 0.166666666666667D) { //R>G>B
            hue = 6 * (hue - 0); part = 1 + hue * (1 / minOverMax - 1);
            b = perceivedBrightness / Math.Sqrt(Pr / minOverMax / minOverMax + Pg * part * part + Pb);
            r = b / minOverMax; g = b + hue * (r - b);
        }
        else if (hue < 0.333333333333333D) { //G>R>B
            hue = 6 * (-hue + 0.333333333333333D); part = 1 + hue * (1 / minOverMax - 1);
            b = perceivedBrightness / Math.Sqrt(Pg / minOverMax / minOverMax + Pr * part * part + Pb);
            g = b / minOverMax; r = b + hue * (g - b);
        }
        else if (hue < 0.5D) {   //  G>B>R
            hue = 6 * (hue - 0.333333333333333D); part = 1 + hue * (1 / minOverMax - 1);
            r = perceivedBrightness / Math.Sqrt(Pg / minOverMax / minOverMax + Pb * part * part + Pr);
            g = r / minOverMax; b = r + hue * (g - r);
        }
        else if (hue < 0.666666666666667D) { //B>G>R
            hue = 6 * (-hue + 0.666666666666667D); part = 1 + hue * (1 / minOverMax - 1);
            r = perceivedBrightness / Math.Sqrt(Pb / minOverMax / minOverMax + Pg * part * part + Pr);
            b = r / minOverMax; g = r + hue * (b - r);
        }
        else if (hue < 0.833333333333333D) { //B>R>G
            hue = 6 * (hue - 0.666666666666667D); part = 1 + hue * (1 / minOverMax - 1);
            g = perceivedBrightness / Math.Sqrt(Pb / minOverMax / minOverMax + Pr * part * part + Pg);
            b = g / minOverMax; r = g + hue * (b - g);
        }
        else { //R>B>G
            hue = 6 * (-hue + 1D); part = 1 + hue * (1 / minOverMax - 1);
            g = perceivedBrightness / Math.Sqrt(Pr / minOverMax / minOverMax + Pb * part * part + Pg);
            r = g / minOverMax; b = g + hue * (r - g);
        }
    }
    else {
        if (hue < 0.166666666666667D) { //R>G>B
            hue = 6 * (hue - 0D); r = Math.Sqrt(perceivedBrightness * perceivedBrightness / (Pr + Pg * hue * hue)); g = r * hue; b = 0;
        }
        else if (hue < 0.333333333333333D) { //G>R>B
            hue = 6 * (-hue + 0.333333333333333D); g = Math.Sqrt(perceivedBrightness * perceivedBrightness / (Pg + Pr * hue * hue)); r = g * hue; b = 0;
        }
        else if (hue < 0.5D) { //G>B>R
            hue = 6 * (hue - 0.333333333333333D); g = Math.Sqrt(perceivedBrightness * perceivedBrightness / (Pg + Pb * hue * hue)); b = g * hue; r = 0;
        }
        else if (hue < 0.666666666666667D) { //B>G>R
            hue = 6 * (-hue + 0.666666666666667D); b = Math.Sqrt(perceivedBrightness * perceivedBrightness / (Pb + Pg * hue * hue)); g = b * hue; r = 0;
        }
        else if (hue < 0.833333333333333D) { //B>R>G
            hue = 6 * (hue - 0.666666666666667D); b = Math.Sqrt(perceivedBrightness * perceivedBrightness / (Pb + Pr * hue * hue)); r = b * hue; g = 0;
        }
        else { //R>B>G
            hue = 6 * (-hue + 1D); r = Math.Sqrt(perceivedBrightness * perceivedBrightness / (Pr + Pb * hue * hue)); b = r * hue; g = 0;
        }
    }
    return new ColorRGB(r, g, b, alpha);
}

//Expected ranges: 0-1 on all components
public static ColorHSP FromRGB(double red, double green, double blue, double alpha) {
    //Guarantee RGB values are in the correct ranges
    red = red < 0 ? 0 : red > 1 ? 1 : red;
    green = green < 0 ? 0 : green > 1 ? 1 : green;
    blue = blue < 0 ? 0 : blue > 1 ? 1 : blue;
    alpha = alpha < 0 ? 0 : alpha > 1 ? 1 : alpha;
    //Prepare & cache values for conversion
    var max = MathExtensions.Max(red, green, blue);
    var min = MathExtensions.Min(red, green, blue);
    var delta = max - min;
    double h, s, p = Math.Sqrt(0.299 * red + 0.587 * green + 0.114 * blue);
    //Conversion
    if (delta.Equals(0)) h = 0;
    else if (max.Equals(red)) {
        h = (green - blue) / delta % 6;
    }
    else if (max.Equals(green)) h = (blue - red) / delta + 2;
    else h = (red - green) / delta + 4;
    h *= 60;
    if (h < 0) h += 360;
    if (p.Equals(0))
        s = 0;
    else
        s = delta / p;
    //Result
    return new ColorHSP(h, s, p, alpha);
}

Map from RGB to HSL (Hue/saturation/luminance), keep the hue and saturation the same, and just modify the luminance and then do the backward mapping from HSL to RGB.

You can read more about the RGB to HSL and HSL to RGB transformations here.


The easiest way is to multiply each of the R,G,B values by some constant - if the constant is >1 it will make it brighter, and if <1 it will be darker. If you're making it brighter then you must test each value to make sure it doesn't go over the maximum (usually 255).

Not only is this simpler than the translation from RGB to HSL and back again, but it more closely approximates what happens when you shine a different amount of light at a physical object.