OpenGL实现饱和度调整

2,598 阅读4分钟

RGB和HSL

RGB是我们熟悉的颜色表示方法,或许是最常见的表示方式。它由Red,Green,Blue三个颜色的按比例混合来表示我们能够感知的颜色。实际上这也是人眼感知颜色的模式,人眼有三种感光元素,分别能够感知三种不同的颜色(Red,Green,Blue),然后在视网膜叠加形成最终的颜色。

例如RGB 0x000000, 0xFFFFFF分别表示黑色和白色,分别代表的是0份的RGB混合和1份的RGB混合得到的颜色,同等份的RGB混合得到的是不同等级的灰色,从全黑到全白。而0xFFFF00则表示黄色,它是1份的红色和1份的绿色混合的结果。

RGB的表示方式虽然模拟了人对色彩的感受形式,但是这种加性的颜色模型却在做图像处理的时候不是特别方便。举例来说我们有个桔色,它的值分别为217,118,33,当我们想得到这个颜色一半的鲜艳度时,我们需要RGB的值分别为186,132,92。这从它原本颜色并不能通过简单的加减来实现,非常的不直观,如下图所示。

所以发明了HSL这种颜色表示方式,这是颜色的另外一种表示方式,在这种表示下,图像的操作变得非常的直观。HSL是Hue,Saturation和Lightness的缩写,分别表示了颜色的三个性质,色度,饱和度和亮度。

HUE

Hue通常就是我们所说的颜色,区分某个颜色和另外一个颜色不同,取值是0到360。可以想象我们把所有的颜色围城一圈,首先是红色,然后依次是黄色,橙色,绿色,蓝色,最后又回到红色,所以称之为色度。

Saturation

Saturation是饱和度的意思,它代表的是这个颜色的纯度是多少,按照百分比取值从0%到100%。其中0%的饱和度定义为黑色。

Lightness

Lightness表示的是亮度,它表示这个颜色的亮度值是多少,取值从0~100%。

RGB和HSL互相转换

HSL是RGB的另一种颜色表示形式,而且他们可以进行互相转换,也就是可以从一个RGB得到它对于的HSL,反之亦然。

RGB转换为HSL

首先对于一个取值为0~255的RGB的值,我们归一化到0~1,并且计算它最大值和最小值的差:

R' = R/255  
G' = G/255  
B' = B/255  
Cmax = max(R', G', B')  
Cmin = min(R', G', B')  
Δ = Cmax - Cmin  

然后可以得到,Hue的计算方式为

Saturation的计算方式为
Lightne的计算方式为

L = (Cmax + Cmin) / 2

HSL转换为RGB

对于HSL的取值H为0~360,S和L取值为0~1.

C = (1 - |2L - 1|) × S  
X = C × (1 - |(H / 60°) mod 2 - 1|)  
m = L - C/2

(R,G,B) = ((R'+m)×255, (G'+m)×255,(B'+m)×255)

饱和度调整OpenGL实现

根据上面想要单独调整饱和度的算法,比如把整副图片的饱和度减半,实际上的思路就是先根据RGB计算HSL的值,然后调整HSL值中的Saturation,在把HSL还原为RGB。整个代码如下。

rgb2hsl

vec3 rgb2hsl(vec3 color) {
    float h, s, l;
    float r = color.r;
    float g = color.g;
    float b = color.b;
    float Cmax = max(max(r, g), b);
    float Cmin = min(min(r, g), b);

    l = (Cmax + Cmin) / 2.0;
    if (Cmax == Cmin) {
        h = 0.0;
        s = 0.0;
    } else {
        float delta = Cmax - Cmin;
        s = (l > 0.5) ? delta / (2.0 - l * 2.0) : delta / (l * 2.0);

        if (r > g && r > b) {
            h = (g - b) / delta + ((g < b) ? 6.0 : 0.0);
        } else if (g > b) {
            h = (b - r) / delta + 2.0;
        } else {
            h = (r - g) / delta + 4.0;
        }
        h = h / 6.0;
    }
    return vec3(h, s, l);
}

hsl2rgb

float hue2rgb(float M1, float M2, float hue) {
    float c;
    if (hue < 0.0) {
        hue += 1.0;
    } else if (hue > 1.0) {
        hue -= 1.0;
    }
    
    if ((6.0 * hue) < 1.0) {
        c = (M1 + (M2 - M1) * hue * 6.0);
    } else if ((2.0 * hue) < 1.0) {
        c = M2;
    } else if ((3.0 * hue) < 2.0) {
        c = (M1 + (M2 - M1) * ((2.0/3.0) - hue) * 6.0);
    } else {
        c = M1;
    }
    return c;
}

vec3 hsl2rgb(vec3 hsl) {
    float M1, M2;
    float hue = hsl.x;
    float saturation = hsl.y;
    float lightness = hsl.z;
    vec3 color;
    if (saturation == 0.0) {
        color.r = lightness;
        color.g = lightness;
        color.b = lightness;
    } else {
        if (lightness < 0.5) {
            M2 = lightness * (1.0 + saturation);
        } else {
            M2 = lightness + saturation - lightness * saturation;
        }
        M1 = (2.0 * lightness - M2);
        color.r = hue2rgb(M1, M2, hue + (1.0/3.0));
        color.g = hue2rgb(M1, M2, hue);
        color.b = hue2rgb(M1, M2, hue - (1.0/3.0));
    }
    return color;
}

单独调整Saturation

void main() {
    vec4 textureColor = texture(u_texture, TexCoord);
    vec3 hsl = rgb2hsl(textureColor.rgb);
    hsl.y *= 0.5;
    vec3 color = hsl2rgb(hsl);
    FragColor = vec4(color, 1.0);
}

另外一种饱和度调整思路

实际上根据饱和度的定义,饱和度就算描述的颜色的纯度值,所以这里还有另外一种非常简单的调整图像饱和度的方式。首先根据每个像素的RGB值计算出它对于的灰度值,也就是Y的值,可以得到这个像素对应的一个灰色vec3(y)。然后饱和度增加和减少就是相应的减去或者增加对应比例的灰色值,实现算法如下。

const mediump vec3 L = vec3(0.2125, 0.7154, 0.0721);
void main() {
    vec4 textureColor = texture(u_texture, TexCoord);
    float luminance = dot(textureColor.rgb, L);
    FragColor = vec4(mix(vec3(luminance), textureColor.rgb, saturation), textureColor.w);
}

下面分别是原图和上述两种方式饱和度降低一半的效果图。

参考

en.wikipedia.org/wiki/RGB_co… en.wikipedia.org/wiki/HSL_an… purple11.com/basics/hue-… www.rapidtables.com/convert/col… www.rapidtables.com/convert/col… github.com/jseidelin/w…