Color gradient algorithm

后端 未结 4 1310
别那么骄傲
别那么骄傲 2020-12-23 15:17

Given two rgb colors and a rectangle, I\'m able to create a basic linear gradient. This blog post gives very good explanation on how to create it. But I want to add one more

4条回答
  •  生来不讨喜
    2020-12-23 15:59

    I wanted to point out the common mistake that happens in color mixing when people try average the r, g, and b components:

    R = (R1 + R2) / 2;
    G = (G1 + G2) / 2;
    B = (B1 + B2) / 2;
    

    You can watch the excellent 4 Minute Physics video on the subject:

    Computer Color is Broken

    The short version is that trying to niavely mixing two colors by averaging the components is wrong:

    R = R1*(1-mix) + R2*mix;
    G = G1*(1-mix) + G2*mix;
    B = B1*(1-mix) + B2*mix;
    

    The problem is that RGB colors on computers are in the sRGB color space. And those numerical values have a gamma of approx 2.4 applied. In order to mix the colors correctly you must first undo this gamma adjustment:

    • undo the gamma adjustment
    • apply your r,g,b mixing algorithm above
    • reapply the gamma

    Without applying the inverse gamma, the mixed colors are darker than they're supposed to be. This can be seen in a side-by-side color gradient experiment.

    • Top (wrong): without accounting for sRGB gamma
    • Bottom (right): with accounting for sRGB gamma

    The algorithm

    Rather than the naive:

    //This is the wrong algorithm. Don't do this
    Color ColorMixWrong(Color c1, Color c2, Single mix)
    {
       //Mix [0..1]
       //  0   --> all c1
       //  0.5 --> equal mix of c1 and c2
       //  1   --> all c2
       Color result;
    
       result.r = c1.r*(1-mix) + c2.r*(mix);
       result.g = c1.g*(1-mix) + c2.g*(mix);
       result.b = c1.b*(1-mix) + c2.b*(mix);
    
       return result;
    }
    

    The correct form is:

    //This is the wrong algorithm. Don't do this
    Color ColorMix(Color c1, Color c2, Single mix)
    {
       //Mix [0..1]
       //  0   --> all c1
       //  0.5 --> equal mix of c1 and c2
       //  1   --> all c2
    
       //Invert sRGB gamma compression
       c1 = InverseSrgbCompanding(c1);
       c2 = InverseSrgbCompanding(c2);
    
       result.r = c1.r*(1-mix) + c2.r*(mix);
       result.g = c1.g*(1-mix) + c2.g*(mix);
       result.b = c1.b*(1-mix) + c2.b*(mix);
    
       //Reapply sRGB gamma compression
       result = SrgbCompanding(result);
    
       return result;
    }
    

    The gamma adjustment of sRGB isn't quite just 2.4. They actually have a linear section near black - so it's a piecewise function.

    Color InverseSrgbCompanding(Color c)
    {
        //Convert color from 0..255 to 0..1
        Single r = c.r / 255;
        Single g = c.g / 255;
        Single b = c.b / 255;
    
        //Inverse Red, Green, and Blue
        if (r > 0.04045) r = Power((r+0.055)/1.055, 2.4) else r = r / 12.92;
        if (g > 0.04045) g = Power((g+0.055)/1.055, 2.4) else g = g / 12.92;
        if (b > 0.04045) b = Power((b+0.055)/1.055, 2.4) else b = b / 12.92;
    
        //return new color. Convert 0..1 back into 0..255
        Color result;
        result.r = r*255;
        result.g = g*255;
        result.b = b*255;
    
        return result;
    }
    

    And you re-apply the companding as:

    Color SrgbCompanding(Color c)
    {
        //Convert color from 0..255 to 0..1
        Single r = c.r / 255;
        Single g = c.g / 255;
        Single b = c.b / 255;
    
        //Apply companding to Red, Green, and Blue
        if (r > 0.0031308) r = 1.055*Power(r, 1/2.4)-0.055 else r = r * 12.92;
        if (g > 0.0031308) g = 1.055*Power(g, 1/2.4)-0.055 else g = g * 12.92;
        if (b > 0.0031308) b = 1.055*Power(b, 1/2.4)-0.055 else b = b * 12.92;
    
        //return new color. Convert 0..1 back into 0..255
        Color result;
        result.r = r*255;
        result.g = g*255;
        result.b = b*255;
    
        return result;
    }
    

    Update: Mark's right

    I tested @MarkRansom comment that the color blending in linear RGB space is good when colors are equal RGB total value; but the linear blending scale does not seem linear - especially for the black-white case.

    So i tried mixing in Lab color space, as my intuition suggested (as well as this photography stackexchange answer):






提交回复
热议问题