移動(dòng)端濾鏡開發(fā)(五)普通濾鏡開發(fā)

寫在前面的話

<p>
上一篇文章對(duì)簡單濾鏡實(shí)現(xiàn)有一定的講解,那么這一篇?jiǎng)t是對(duì)圖像處理更加深層次的說明,對(duì)于一張圖片怎么處理起來效果會(huì)看起來更好呢?我想大部分人首先就會(huì)想到PS軟件,確實(shí)對(duì)于圖像的處理PS有很多的功能,一般處理圖片呢會(huì)用到下面這些工具

圖1 圖片處理

通過這些工具可以對(duì)圖片各種修改,當(dāng)然除了這種處理之外難免還會(huì)出現(xiàn)加上水印或者邊框這種類型的需求,那么這種修改一般用PS中的圖層混合,ps中的圖層混合如下

圖2 圖層混合模式

接下來就來分別說明下關(guān)于圖層混合與圖片處理

一.圖層混合

所謂圖層混合模式就是指一個(gè)層與其下圖層的色彩疊加方式,在這之前我們所使用的是正常模式,除了正常以外,還有很多種混合模式,它們都可以產(chǎn)生迥異的合成效果。

首先看正常模式

圖2 正常模式

接下來試下不同的效果,如柔光模式

圖3 柔光模式

大家也可以對(duì)其他的效果也可以進(jìn)行更多的嘗試與了解

1.圖片混合模式介紹

上面的Ps圖層混合的圖我們看到很多混合模式,那么這里就一一介紹下這些混合模式
<p>
1.溶解模式

溶解模式下混合色的不透明度及填充都是100%的話,我們就看不到基色圖層。降低混合色圖層的不透明度后,我們就會(huì)發(fā)現(xiàn)結(jié)果色中出現(xiàn)了很多細(xì)小的顆粒。這些顆粒會(huì)隨著混合色的不透明度變化。不透明度越低混合色圖層就被溶解的越多。剩下的部分就越少。不透明度越高混合色圖層被溶解的部分就越少,剩下的部分就越多,結(jié)果色就越接近混合色。

2.變暗模式

變暗混合模式下,它會(huì)把混合色與基色進(jìn)行對(duì)比,分別選擇R,G,B三組數(shù)值中最小的數(shù)值,也就是最暗的顏色作為結(jié)果色的數(shù)值。

B<=A: C=B
B>=A: C=A

3.正片疊底

正片疊底混合原理:它是按照混合色與基色中的各R,G,B值計(jì)算,計(jì)算公式:結(jié)果色R=混合色R * 基色R / 255,G值與B值同樣的方法計(jì)算。最后得到的R,G,B值就是結(jié)果色的顏色。

4.顏色加深

顏色加深可以快速增加圖片的暗部。它的計(jì)算公式:結(jié)果色 = (基色 + 混合色 - 255)* 255 / 混合色。其中(基色 + 混合色 - 255)如果出現(xiàn)負(fù)數(shù)就直接歸0。因此在基色與混合色都較暗的時(shí)候都是直接變成黑色的。這樣結(jié)果色的暗部就會(huì)增加。整體效果看上去對(duì)比較為強(qiáng)烈。

5.線性加深

線性加深的計(jì)算公式是:結(jié)果色 = 基色 + 混合色 - 255,如果基色 + 混合色的數(shù)值小于255,結(jié)果色就為0。由這個(gè)公式可以看出,畫面暗部會(huì)直接變成黑色。因此畫面整體會(huì)更暗。白色與基色混合得到基色,黑色與基色混合得到黑色。

6.深色模式

深色模式是通過計(jì)算混合色與基色的所有通道的數(shù)值,然后選擇數(shù)值較小的作為結(jié)果色。因此結(jié)果色只跟混合色或基色相同,不會(huì)產(chǎn)生出另外的顏色。白色與基色混合色得到基色,黑色與基色混合得到黑色。深色模式中,混合色與基色的數(shù)值是固定的,我們顛倒位置后,混合色出來的結(jié)果色是沒有變化的。

7.變亮模式

變亮模式跟變暗模式是相對(duì)的,它是通過混合色與基色的相關(guān)數(shù)值進(jìn)行比較,選擇較大的數(shù)值作為結(jié)果色。因此結(jié)果色會(huì)更亮,同時(shí)顏色也會(huì)變化。

8.濾色模式

濾色模式與正片疊底模式相對(duì)。它的計(jì)算公式是:255 - 混合色的補(bǔ)色 * 基色補(bǔ)色 / 255。得到的數(shù)據(jù)會(huì)比混合及基色更大,因此結(jié)果色會(huì)更亮。從計(jì)算公式也可以看出基色或混合色任何一項(xiàng)為255也就是白色,結(jié)果色數(shù)值就是255為白色。任何一項(xiàng)數(shù)值為0,也就是為黑色的話,結(jié)果色就跟數(shù)值不為0的一致。

9.顏色減淡

顏色減淡是通過混合色及基色的各通道顏色值進(jìn)行對(duì)比,減少二者的對(duì)比度使基色的變亮來反映混合色。它的計(jì)算公式:結(jié)果色 = 基色 + (混合色 * 基色) / (255 - 混合色)。混合色為黑色,結(jié)果色就等于基色,混合色為白色結(jié)果色就為白色。基色為黑色結(jié)果色就為黑色。

10.線性減淡

線性減淡是通過查看每個(gè)通道的顏色信息,并通過增加亮度使基色變亮以反映混合色。它的計(jì)算公式:結(jié)果色 = 基色 +混合色,其中基色與混合色的數(shù)值大于255,系統(tǒng)就默認(rèn)為最大值也就是255。
由公式可以分析出混合色為黑色結(jié)果色就等于基色,混合色為白色結(jié)果色就為白色?;惨粯印N覀冾嵉够旌仙盎奈恢?,結(jié)果色也不會(huì)變化。

11.淺色模式

淺色模式是通過計(jì)算混合色與基色所有通道的數(shù)值總和,哪個(gè)數(shù)值大就選為結(jié)果色。因此結(jié)果色只能在混合色與基色中選擇,不會(huì)產(chǎn)生第三種顏色。與深色模式剛好相反。

12.疊加模式

疊加模式比較特別,它是通過分析基色個(gè)通道的數(shù)值,對(duì)顏色進(jìn)行正片疊加或?yàn)V色混合,結(jié)果色保留基色的明暗對(duì)比,因此結(jié)果色以基色為主導(dǎo)。
計(jì)算公式:
基色 < = 128:結(jié)果色 = 混合色 * 基色 / 128;基色 > 128:結(jié)果色 = 255 - (255 - 混合色)* (255 - 基色) / 128。從公式可以看出,結(jié)果色會(huì)根據(jù)基色的顏色數(shù)值選擇不同的計(jì)算公式。

13.柔光模式

柔光模式是根據(jù)混合色的通道數(shù)值選擇不同的公式計(jì)算混合色。數(shù)值大于128的時(shí)候,結(jié)果色就比基色稍亮;數(shù)值小于或等于128,結(jié)果色就比基色稍暗。柔光模式是以基色為主導(dǎo),混合色只相應(yīng)改變局部明暗。其中混合色為黑色,結(jié)果色不會(huì)為黑色,只比結(jié)果色稍暗,混合色為中性色,結(jié)果色跟基色一樣。
計(jì)算公式:
混合色 <=128:結(jié)果色 = 基色 + (2 * 混合色 - 255) * (基色 - 基色 * 基色 / 255) / 255;
混合色 >128: 結(jié)果色 = 基色 + (2 * 混合色 - 255) * (Sqrt(基色/255)*255 - 基色)/255。

14.強(qiáng)光模式

強(qiáng)光模式跟疊加模式十分類似,只是在計(jì)算的時(shí)候需要通過混合色來控制,混合色的數(shù)值小于或等于128的時(shí)候,顏色會(huì)變暗;混合色的數(shù)值大于128的時(shí)候,顏色會(huì)變亮。混合色為白色,結(jié)果色就為白色;混合色為黑色,結(jié)果為黑色?;旌仙鸬街鲗?dǎo)作用。
計(jì)算公式:
混合色 <= 128:結(jié)果色 = 混合色 * 基色 / 128;
混合色 > 128 :結(jié)果色 = 255 - (255 - 混合色) * (255 - 基色) / 128.

15.亮光模式

亮光模式是通過增加或減少對(duì)比度是顏色變暗或變亮,具體取決于混合色的數(shù)值?;旌仙戎行曰疑?,結(jié)果色就相應(yīng)的變暗,混合色比中性灰色亮,結(jié)果色就相應(yīng)的變亮。有點(diǎn)類似顏色加深或顏色減淡。
計(jì)算公式:
A---基色;B—混合色
C=A-(255-A)(255-2B)/2B 當(dāng)混合色>128時(shí)
C=A+[A
(2B-255)]/[255-(2B-255)

16.線性光模式

線性光模式通過減少或增加亮度,來使顏色加深或減淡。具體取決于混合色的數(shù)值?;旌仙珨?shù)值比中性灰色暗的時(shí)候進(jìn)行相應(yīng)的加深混合;混合色的數(shù)值比中性灰色亮的時(shí)候進(jìn)行減淡混合。這里的加深及減淡時(shí)線性加深或線性減淡。
計(jì)算公式:結(jié)果色 = 2 * 混合色 + 基色 -255。數(shù)值大于255取255。

17.點(diǎn)光模式

點(diǎn)光模式會(huì)根據(jù)混合色的顏色數(shù)值替換相應(yīng)的顏色。如果混合色數(shù)值小于中性灰色,那么就替換比混合色亮的像素;相反混合色的數(shù)值大于中性灰色,則替換比混合色暗的像素,因此混合出來的顏色對(duì)比較大。
計(jì)算公式:
基色 < 2 * 混合色 - 255:結(jié)果色 = 2 * 混合色 - 255;
2 * 混合色 - 255 < 基色 < 2 * 混合色 :結(jié)果色 = 基色;
基色 > 2 * 混合色:結(jié)果色 = 2 * 混合色。

18.實(shí)色混合

實(shí)色混合是把混合色顏色中的紅、綠、藍(lán)通道數(shù)值,添加到基色的RGB值中。結(jié)果色的R、G、B通道的數(shù)值只能是255或0。因此結(jié)構(gòu)色只有一下八種可能:紅、綠、藍(lán)、黃、青、洋紅、白、黑。由此看以看出結(jié)果色是非常純的顏色。
計(jì)算公式:
混合色 + 基色 < 255:結(jié)果色 = 0 ;混合色 + 基色 >= 255:結(jié)果色 = 255。

19.差值模式

差值模式通過查看每個(gè)通道的數(shù)值,用基色減去混合色或用混合色減去基色。具體取決于混合色與基色那個(gè)通道的數(shù)值更大。白色與任何顏色混合得到反相色,黑色與任何顏色混合顏色不變。
計(jì)算公式:
結(jié)果色 = 絕對(duì)值(混合色 - 基色)

20.排除模式

排除模式是跟差值模式非常類似的混合模式,只是排除模式的結(jié)果色對(duì)比度沒有差值模式強(qiáng)。白色與基色混合得到基色補(bǔ)色,黑色與基色混合得到基色。
計(jì)算公式:
結(jié)果色 = (混合色 + 基色) - 混合色 * 基色 / 128。

21.減去模式

減去模式查看各通道的顏色信息,并從基色中減去混合色。如果出現(xiàn)負(fù)數(shù)就剪切為零;與基色相同的顏色混合得到黑色;白色與基色混合得到黑色;黑色與基色混合得到基色。
計(jì)算公式:
結(jié)果色 = 基色 - 混合色。

22.劃分模式

劃分模式查看每個(gè)通道的顏色信息,并用基色分割混合色?;珨?shù)值大于或等于混合色數(shù)值,混合出的顏色為白色?;珨?shù)值小于混合色,結(jié)果色比基色更暗。因此結(jié)果色對(duì)比非常強(qiáng)。白色與基色混合得到基色,黑色與基色混合得到白色。
計(jì)算公式:
結(jié)果色 = (基色 / 混合色) * 255。

23.色相模式

輸出圖像的色調(diào)為上層,飽和度和亮度保持為下層。對(duì)于灰色上層,結(jié)果為去色的下層。

24.飽和度模式

輸出圖像的飽和度為上層,色調(diào)和亮度保持為下層。

25.顏色模式

輸出圖像的亮度為下層,色調(diào)和飽和度保持為上層。

26.明度模式

輸出圖像的亮度為上層,色調(diào)和飽和度保持為下層。

2.OpenGl實(shí)現(xiàn)混合模式

<p>
這里還是以之前的OpenGl顯示圖片為例子,但是由于圖層混合模式需要兩張圖片,所以對(duì)之前的代碼進(jìn)行小的修改

首先增加新的圖片紋理

int[] mTexNames2 = new int[2];
GLES20.glGenTextures(1, mTexNames2, 1);

bitmap = BitmapFactory.decodeResource(mResources, R.drawable.p_300px2);
GLES20.glActiveTexture(GLES20.GL_TEXTURE1);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTexNames2[0]);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER,
        GLES20.GL_LINEAR);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER,
        GLES20.GL_LINEAR);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S,
        GLES20.GL_REPEAT);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T,
        GLES20.GL_REPEAT);
GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);
bitmap.recycle();

注意我這里glActiveTexture與之前不同,為一個(gè)新的紋理,后面繪制新的圖片也需要用這個(gè)紋理

接下來獲取 shader 代碼中的新的變量索引

private int mTexSamplerHandle2;

@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
    ...
    mTexCoordHandle = GLES20.glGetAttribLocation(mProgram, "a_texCoord");
    mTexSamplerHandle2 = GLES20.glGetUniformLocation(mProgram, "s_texture2");
    ...
}

最后繪制

@Override
public void onDrawFrame(GL10 gl) {
    ...
    GLES20.glUniform1i(mTexSamplerHandle2, 1);
    ...
}

接下來就是需要修改片段著色器程序了根據(jù)不同的模式來創(chuàng)建不同的程序

這里我們選取幾個(gè)效果實(shí)現(xiàn)

(1).正常模式

<p>

正常模式下,則為第二張疊加在第一張圖片上面,對(duì)于我們輸出的顏色需要從圖片一和圖片二共同獲取,所以這時(shí)候第二張圖片透明部分則顯示為第一張圖片,不透明部分顯示第二張圖片

接下來上片段著色器程序代碼

varying highp vec2 v_texCoord;

uniform sampler2D s_texture;
uniform sampler2D s_texture2;

void main() {

    mediump vec4 textureColor = texture2D(s_texture, v_texCoord);
    mediump vec4 textureColor2 = texture2D(s_texture2, v_texCoord);

    vec4 outputColor;
    outputColor.r = textureColor2.r + textureColor.r * textureColor.a * (1.0 - textureColor2.a);
    outputColor.g = textureColor2.g + textureColor.g * textureColor.a * (1.0 - textureColor2.a);
    outputColor.b = textureColor2.b + textureColor.b * textureColor.a * (1.0 - textureColor2.a);

    outputColor.a = textureColor2.a + textureColor.a * (1.0 - textureColor2.a);

    gl_FragColor = outputColor;

}

代碼就不做過多解釋了,接下來運(yùn)行如下

圖5 正常模式

(2).柔光模式

<p>

柔光模式是根據(jù)混合色的通道數(shù)值選擇不同的公式計(jì)算混合色。數(shù)值大于128的時(shí)候,結(jié)果色就比基色稍亮;數(shù)值小于或等于128,結(jié)果色就比基色稍暗。柔光模式是以基色為主導(dǎo),混合色只相應(yīng)改變局部明暗。其中混合色為黑色,結(jié)果色不會(huì)為黑色,只比結(jié)果色稍暗,混合色為中性色,結(jié)果色跟基色一樣。
計(jì)算公式:
混合色 <=128:結(jié)果色 = 基色 + (2 * 混合色 - 255) * (基色 - 基色 * 基色 / 255) / 255;
混合色 >128: 結(jié)果色 = 基色 + (2 * 混合色 - 255) * (Sqrt(基色/255)*255 - 基色)/255。

接下來上片段著色器程序代碼

varying highp vec2 v_texCoord;

uniform sampler2D s_texture;
uniform sampler2D s_texture2;

void main() {

    mediump vec4 textureColor = texture2D(s_texture, v_texCoord);
    mediump vec4 textureColor2 = texture2D(s_texture2, v_texCoord);

    float ra;
    if(textureColor2.r <= 0.5){
        ra = textureColor.r + (2.0 * textureColor2.r - 1.0) * (textureColor.r - textureColor.r * textureColor.r);
    }else{
        ra = textureColor.r + (2.0 * textureColor2.r - 1.0) * (sqrt(textureColor.r) - textureColor.r);
    }

    float ga;
    if(textureColor2.g <= 0.5){
        ga = textureColor.g + (2.0 * textureColor2.g - 1.0) * (textureColor.g - textureColor.g * textureColor.g);
    }else{
        ga = textureColor.g + (2.0 * textureColor2.g - 1.0) * (sqrt(textureColor.g) - textureColor.g);
    }

    float ba;
    if(textureColor2.b <= 0.5){
        ba = textureColor.b + (2.0 * textureColor2.b - 1.0) * (textureColor.b - textureColor.b * textureColor.b);
    }else{
        ba = textureColor.b + (2.0 * textureColor2.b - 1.0) * (sqrt(textureColor.b) - textureColor.b);
    }

    gl_FragColor = vec4(ra, ga, ba, 1.0);
}

運(yùn)行如下

圖6 柔光模式

(3).線性加深模式

<p>

線性加深的計(jì)算公式是:結(jié)果色 = 基色 + 混合色 - 255,如果基色 + 混合色的數(shù)值小于255,結(jié)果色就為0。由這個(gè)公式可以看出,畫面暗部會(huì)直接變成黑色。因此畫面整體會(huì)更暗。白色與基色混合得到基色,黑色與基色混合得到黑色。

接下來上片段著色器程序代碼


varying highp vec2 v_texCoord;

uniform sampler2D s_texture;
uniform sampler2D s_texture2;

void main() {

    mediump vec4 textureColor = texture2D(s_texture, v_texCoord);
    mediump vec4 textureColor2 = texture2D(s_texture2, v_texCoord);

    gl_FragColor = textureColor + textureColor2 - vec4(1.0);

}

運(yùn)行如下:

Paste_Image.png

更多的效果大家可以去自己嘗試

二.圖片處理

關(guān)于圖層混合我們已經(jīng)用GLSL語言去實(shí)現(xiàn)并運(yùn)行實(shí)現(xiàn)了需要的效果了,接下來就是關(guān)于圖片處理了,上面的圖我們看到圖片處理的方法太多了,我們這里首先先挑兩個(gè)處理方法來說明

(1).曲線調(diào)整

曲線調(diào)整是Photoshop的最常用的重要功能之一,所以了解他的實(shí)現(xiàn)與原理是很有必要的。

首先看下PhotoShop中的曲線調(diào)整

圖1 曲線調(diào)整框

當(dāng)我們改變這條曲線的時(shí)候就會(huì)對(duì)圖像產(chǎn)生處理

如下

圖2 曲線調(diào)整
圖3 曲線調(diào)整對(duì)應(yīng)圖片的變化圖片
1.曲線調(diào)整原理

<p>
對(duì)于一個(gè)RGB圖像, 可以對(duì)R, G, B 通道進(jìn)行獨(dú)立的曲線調(diào)整,即,對(duì)三個(gè)通道分別使用三條曲線(Curve)。還可以再增加一條曲線對(duì) 三個(gè)通道進(jìn)行整體調(diào)整。 因此,對(duì)一個(gè)圖像,可以用四條曲線調(diào)整。最終的結(jié)果,是四條曲線調(diào)整后合并產(chǎn)生的結(jié)果。

我們首先對(duì)單一的通道調(diào)整,如圖

圖4 單通道曲線調(diào)整

圖中,橫軸是輸入,比左到右分別表示0到255. 縱軸是輸出,從下到上分別表示0到255.

該曲線由三個(gè)點(diǎn)定義,座標(biāo)分別為: 點(diǎn)1(0,0), 點(diǎn)2(85,143),點(diǎn)3(255,255)
點(diǎn)1和點(diǎn)3是默認(rèn)產(chǎn)生的,點(diǎn)2是我們新增加的。這樣這三個(gè)點(diǎn)就生成了一條曲線

所以圖片中紅色通道的值都要按這條曲線來生成新的值,藍(lán)綠通道的值不發(fā)生改變

比如之前一個(gè)點(diǎn)的RGB值為(85,125,125),那么經(jīng)過紅色通道的調(diào)整后,這個(gè)點(diǎn)的RGB值為(143,125,125)

同理于藍(lán)色通道,綠色通道以及整個(gè)RGB通道的曲線調(diào)整

到這里大家基本了解了曲線調(diào)整的流程了,那么接下來就需要知道這條曲線是如何生成的

2.曲線生產(chǎn)

<p>

結(jié)合上面的圖,我們可以發(fā)現(xiàn)這條曲線的特點(diǎn),僅需要定義幾個(gè)控制點(diǎn),就可以定義一條平滑的曲線,且曲線同時(shí)通過所有控制點(diǎn)。根據(jù)這個(gè)特性我們可以判斷這里的曲線是三次樣條插值Cubic Spline Interpolation(簡稱Spline插值),生成曲線時(shí),只需要給出幾個(gè)控制點(diǎn),調(diào)用曲線生成函數(shù)即可,然后可以根據(jù)這個(gè)函數(shù)來生成新的RGB值。

三次樣條函數(shù):

定義:函數(shù)S(x)∈C2[a,b] ,且在每個(gè)小區(qū)間[ xj,xj+1 ]上是三次多項(xiàng)式,其中a =x0 <x1<...< xn= b 是給定節(jié)點(diǎn),則稱S(x)是節(jié)點(diǎn)x0,x1,...xn上的三次樣條函數(shù)。若在節(jié)點(diǎn)x j 上給定函數(shù)值Yj= f (Xj).( j =0, 1, , n) ,并成立S(xj ) =yj .( j= 0, 1, , n) ,則稱S(x)為三次樣條插值函數(shù)。
實(shí)際計(jì)算時(shí)還需要引入邊界條件才能完成計(jì)算。邊界通常有自然邊界(邊界點(diǎn)的二階導(dǎo)為0),夾持邊界(邊界點(diǎn)導(dǎo)數(shù)給定),非扭結(jié)邊界(使兩端點(diǎn)的三階導(dǎo)與這兩端點(diǎn)的鄰近點(diǎn)的三階導(dǎo)相等)。

3.曲線調(diào)整模擬

<p>

要模擬出曲線調(diào)整的效果,首先要實(shí)現(xiàn)三次樣條函數(shù)

這個(gè)我就不做過多的介紹了,網(wǎng)上也有不少例子

上代碼

public class Chazhi{
    
    int[][] range = new int[50][2];
    int number = 0;
    int choice = 0;
    
    public double Thrice(int inputx)//三次自然樣條插值算法
    {
        double a[] = new double[50];
        double b[] = new double[50];
        double c[] = new double[50];
        double d[] = new double[50];
        double h[] = new double[50];
        double s = 0;
        double x = 0;
        int temp = 0;
        double s1[] = new double[50];
        double s2[] = new double[50];

        for(int i = 0; i < number - 1; i ++)
        {
            for(int j = number - 2; j >= i; j --)
            {
                if(range[j+1][0]<range[j][0])
                {
                    temp = range[j + 1][0];
                    range[j + 1][0] = range[j][0];
                    range[j][0] = temp;
                    temp = range[j + 1][1];
                    range[j + 1][1] = range[j][1];
                    range[j][1] = temp;                 
                }
            }
        }

        for(int k = 0; k <= number - 2; k ++)
        {
            h[k] = range[k+1][0] - range[k][0];
        }
        a[1] = 2 * (h[0] + h[1]);
        for(int k = 2; k <= number - 2; k ++)
        {
            a[k] = 2 * (h[k-1] + h[k]) - h[k-1] * h[k-1] / a[k-1];
        }
        for(int k = 1; k <= number - 1; k ++)
        {
            c[k] = (range[k][1] - range[k - 1][1])/h[k-1];
        }
        for(int k = 1; k <= number - 2; k ++)
        {
            d[k] = 6*(c[k+1]-c[k]);
        }
        b[1] = d[1];
        for(int k = 2; k <= number - 2; k ++)
        {
            b[k] = d[k]-b[k-1]*h[k-1]/a[k];
        }
        s2[number-2] = b[number - 2]/a[number - 2];
        for(int k = number - 3; k >=1; k --)
        {
            s2[k] = (b[k]-h[k]*s2[k+1])/a[k];
        }
        s2[0]=0;
        s2[number - 1]=0;
        
        if(inputx == 255 || inputx == 0){
            return inputx;
        }
        
        for(int k = 0; k <= number - 2; k ++)
        {
            if(inputx < range[k + 1][0]){
                
                s1[k] = c[k + 1] - s2[k + 1] * h[k] / 6 - s2[k] * h[k] / 3;
                s = range[k][1] + s1[k] * (inputx - range[k][0]) + s2[k] * (inputx - range[k][0]) * (inputx - range[k][0]) / 2 +
                    (s2[k + 1]-s2[k]) * (inputx - range[k][0]) * (inputx - range[k][0]) * (inputx - range[k][0]) / 6 / h[k];    

                break;
            }   
        }
        return s;
    }
    
}

使用如下

Chazhi chazhi = new Chazhi();
chazhi.range[chazhi.number][0] = 0;
chazhi.range[chazhi.number][1] = 0;
chazhi.number ++;
chazhi.range[chazhi.number][0] = 114;
chazhi.range[chazhi.number][1] = 176;
chazhi.number ++;
chazhi.range[chazhi.number][0] = 255;
chazhi.range[chazhi.number][1] = 255;
chazhi.number ++;

 r = Math.abs((int) chazhi.Thrice(r));

這里只是對(duì)紅色通道進(jìn)行了曲線模擬并且選取的點(diǎn)為(0,0),(114,176),(255,255),模擬后,我們看生成的圖片是什么效果

圖5 模擬效果

所以經(jīng)過我們模擬后的效果與Photoshop中的效果一致

(2).色階調(diào)整

上面說了曲線調(diào)整,接下來看看PhotoShop的色階調(diào)整

圖1 色階調(diào)整

我們嘗試下去修改輸入色階

如下

圖2 色階調(diào)整輸入色階

可以看到圖片的變化如下

圖3 色階調(diào)整對(duì)應(yīng)圖片的變化

所以我們來了解下色階調(diào)整帶來的變化

1.色階調(diào)整的原理

<p>

色階是什么:色階就是用直方圖描述出的整張圖片的明暗信息

我們繼續(xù)看色階調(diào)整的圖

圖4 色階調(diào)整

從左至右是從暗到亮的像素分布,黑色三角代表最暗地方(純黑),白色三角代表最亮地方(純白)。灰色三角代表中間調(diào)。

每一個(gè)色階定義有兩組值:
一組是輸入色階值,包含黑灰白三個(gè)值, 上圖中:黑點(diǎn)值為0,灰點(diǎn)為1.00,白點(diǎn)為255
另一組是輸入色階值,包含黑白兩個(gè)值,上圖中:輸出色階黑為0,白為255

對(duì)于一個(gè)RGB圖像, 可以對(duì)R, G, B 通道進(jìn)行獨(dú)立的色階調(diào)整,即,對(duì)三個(gè)通道分別使用三個(gè)色階定義值。還可以再對(duì) 三個(gè)通道進(jìn)行整體色階調(diào)整。 因此,對(duì)一個(gè)圖像,可以用四次色階調(diào)整。最終的結(jié)果,是四次調(diào)整后合并產(chǎn)生的結(jié)果。

我們以紅色通道為例,如下

圖5 紅色通道色階調(diào)整

這里:輸入色階值為:黑15,灰1.5,白200   輸出色階值為:黑12,白233

則色階調(diào)整的實(shí)現(xiàn)是:
當(dāng)輸入值<黑點(diǎn)值(15)時(shí),全部變?yōu)檩敵錾A的黑值(12)?!?br> 當(dāng)輸入值>白點(diǎn)(200)時(shí),全部變?yōu)檩敵錾A的白值(233)。
當(dāng)輸入值介于黑值與白值之間(15-200)時(shí),則結(jié)合灰度系數(shù),按比例重新計(jì)算,變?yōu)橐粋€(gè)新的值。

同理于藍(lán)色通道,綠色通道以及整個(gè)RGB通道的曲線調(diào)整

2.色階調(diào)整模擬

<p>

上面已經(jīng)說明了原理,這里就直接把模擬的代碼貼上來了

public class LevlesAdjustment{
    
    Levles[] levles;
    
    class Levles{
        
        int Shadow = 0;  
        double Midtones = 1.00; 
        int Highlight = 255; 

        int OutputShadow = 0;
        int OutputHighlight = 255;
        
        boolean defined = false;
        
        public int doChange(int index){     
            int diff = Highlight - Shadow;  
            int outDiff = (int)(OutputHighlight - OutputShadow);
             double coef = 255.0 / diff;
             double outCoef = outDiff / 255.0;
             double exponent = 1.0 / Midtones;
             int v;
             if ( index <= Shadow ) {
                    v = 0;
               } else {
                    v = (int)((index - Shadow) * coef + 0.5);
                    if (v > 255) v = 255;
               }
             v = (int)( Math.pow(v / 255.0, exponent) * 255.0 + 0.5 );
             return (int) (v * outCoef + OutputShadow + 0.5);
             
        }
    }
    
    public LevlesAdjustment(){
        levles = new Levles[4];
        for(int i = 0; i < levles.length; i ++){
            levles[i] = new Levles();   
        }
        levles[0].Shadow = 10;
        levles[0].Highlight = 24;
        
    }
    
    public int[] doChange(int[] rgb){
        
        for(int i = 0; i < levles.length; i ++){
            calcDefined(levles[i]);
        }
        
        for (int i = 0; i < levles.length; i++) {
               
            if ( levles[i].defined ) {
                switch (i){
                case 0:
                    rgb[0] = levles[i].doChange(rgb[0]);
                    break;
                case 1:
                    rgb[1] = levles[i].doChange(rgb[1]);
                    break;
                case 2:
                    rgb[2] = levles[i].doChange(rgb[2]);
                    break;
                case 3:
                    rgb[0] = levles[i].doChange(rgb[0]);
                    rgb[1] = levles[i].doChange(rgb[1]);
                    rgb[2] = levles[i].doChange(rgb[2]);
                    break;
                }
            }
        }
        return rgb;     
    }
    
    private void calcDefined(Levles levles){
        if ( levles.Shadow != 0 || levles.Midtones != 1.0 || levles.Highlight != 255 ) {
            levles.defined = true;
            return;
        }
        levles.defined = false;
    }
}   

這里是將紅色通道的Shadow設(shè)置為10,Highlight = 244,接下來看一下運(yùn)行的效果圖

圖6 運(yùn)行效果圖

通過與ps處理的效果圖對(duì)比,顯示效果一致

前面幾個(gè)小點(diǎn)都是對(duì)于PhotoShop中常用的處理的圖片的方法的原理進(jìn)行了分析,那么接下來自然就要考慮一下怎么用OpenGl去實(shí)現(xiàn)這樣的效果呢?

那么第一種方法自然就是模擬出這些處理方法,用glsl語言來實(shí)現(xiàn)這些方法,這么做當(dāng)然是可以的,但是目前這些處理方法大多都是用C來實(shí)現(xiàn)的,glsl語言尚未有可以用的輪子,那么自然我們就要去造這個(gè)輪子,那我們不禁要思考一下這么做的成本和回報(bào)到底是否讓我們滿意,假設(shè)影像的大小為1024x768,那么對(duì)于其中的某一項(xiàng)的處理則總共要786432次運(yùn)算,而且這些運(yùn)動(dòng)大多比較復(fù)雜,所以這么做其實(shí)相對(duì)來說并沒有那么討喜,成本相對(duì)較大,并且結(jié)果并沒有達(dá)到很令人滿意的效果,所以要思考下怎么用別的方式去實(shí)現(xiàn)。

看了這么多的處理原理,我相信大家都應(yīng)該了解每一個(gè)像素的點(diǎn)都將原來的RGB值替換了新的RGB值,所以我們可以建一張表,把所有色彩值經(jīng)過處理之后的結(jié)果記錄起來,然后把每個(gè)像素的色彩值拿去查表,得到處理之后的色彩值,那么我們只要做786432查表動(dòng)作,這樣確實(shí)會(huì)比上面的運(yùn)算快上許多。但是問題還是伴隨會(huì)出現(xiàn),這個(gè)表怎么去獲取又是一個(gè)問題,畢竟美工不可能一點(diǎn)一點(diǎn)給我們計(jì)算這個(gè)表,我們自己去算也是不現(xiàn)實(shí)的,所以有沒有更加高效的方式呢?

答案自然是肯定的,而這一解決方案就是Color Lookup Table(ColorLUT)技術(shù)

(3).Color Lookup Table(ColorLUT)技術(shù)

<p>

上文提到查表的方法時(shí)候提到了怎么去獲取這個(gè)表是一個(gè)問題,那其實(shí)ColorLUT就是為了解決這個(gè)問題的,對(duì)于一個(gè)處理RGB值的表,我們不需要將所有都記錄下來,而是只記下部分的色彩,其他不在表內(nèi)的色彩則用內(nèi)插法取得處理后的結(jié)果。

因?yàn)槊總€(gè)像素的色彩都是由RGB三種顏色組成,因此我們會(huì)以三維陣列的方式來儲(chǔ)存這張表。如果把三維陣列中的每一個(gè)像素想像成三度空間中的一個(gè)點(diǎn),而R、G、B分別代表X、Y、Z的座標(biāo),則陣列中的所有像素可以構(gòu)成一個(gè)正立方體。以RGB 24bits為例,由于R、G、B的值為0~255,因此正方體的長寬高為255x255x255。當(dāng)我們要查某個(gè)像素經(jīng)過處理之后的色彩,只要將該像素處理前的RGB值當(dāng)做X、Y、Z座標(biāo),位于那個(gè)位置上的像素則為處理后的顏色。

如圖所示

圖1 三維空間圖

這個(gè)圖是以4x4x4的正方體X、Y、Z三個(gè)方向都只有在0、85、170、255這些位置有資料,如果座標(biāo)不在這些點(diǎn)上,則必須跟周圍的點(diǎn)做內(nèi)差來得到色彩的近似值。

而這些已有的顏色的值當(dāng)我們通過PhotoShop進(jìn)行各種操作時(shí),則會(huì)將這些值替換成新的值,而這些新的值則就是上面所述的數(shù)組里面的關(guān)鍵值之一了。

我們繼續(xù)看上面的圖畢竟是三維圖,我們無法進(jìn)行處理,所以把z軸的面去出來拼接在一起則會(huì)成為一個(gè)8x8的圖片,這個(gè)圖片是包含4個(gè)4x4的正方形,配上顏色如下圖所示:

圖2 顏色二維圖

以右上角正方形為例,這代表Z=0的平面,而X軸由左至右,Y軸為由上至下,左上角第一個(gè)像素代表位于(0,0,0)的點(diǎn),第二個(gè)像素代表位于(85,0,0)的點(diǎn),以此類推,由于這些像素代表的是未處理前的顏色,因此第一個(gè)像素的RGB值為0,0,0,第二的像素的RGB值為85,0,0

接著我們將這張圖經(jīng)過Photoshop降低色彩飽和度,結(jié)果如下:

圖3 顏色二維圖

這張圖內(nèi)的每個(gè)像素,代表每個(gè)座標(biāo)點(diǎn)處理之后的色彩。也就是說,如果我們想知道一個(gè)RGB值為85,0,0的像素降低飽和度之后的顏色,可從正立方體中位于(85,0,0)的點(diǎn)得知,也就是這張圖中左上角第二個(gè)像素的顏色,RGB值為68,16,16。

所以我們可以將這一技術(shù)運(yùn)用到我們的濾鏡開發(fā)中,從而避免上面的尷尬問題,接下來就是實(shí)現(xiàn)上面的ColorLUT技術(shù)

ColorLUT實(shí)現(xiàn)

ColorLUT的實(shí)現(xiàn)無非就是用GLSL的語言來實(shí)現(xiàn)識(shí)別上面的顏色二維圖方法,這個(gè)我就不做過多的介紹了,這個(gè)在GPUImage里面已經(jīng)有實(shí)現(xiàn),我就不重復(fù)造輪子了,這里直接拿過來使用,代碼如下,自己理解

varying highp vec2 textureCoordinate;
uniform sampler2D inputImageTexture;
uniform sampler2D inputImageTexture2;

uniform lowp float intensity;

void main() {

    highp vec4 textureColor = texture2D(inputImageTexture, textureCoordinate);
    highp float blueColor = textureColor.b * 63.0;

    highp vec2 quad1;
    quad1.y = floor(floor(blueColor) / 8.0);
    quad1.x = floor(blueColor) - (quad1.y * 8.0);

    highp vec2 quad2;
    quad2.y = floor(ceil(blueColor) / 8.0);
    quad2.x = ceil(blueColor) - (quad2.y * 8.0);

    highp vec2 texPos1;
    texPos1.x = (quad1.x * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.r);
    texPos1.y = (quad1.y * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.g);

    highp vec2 texPos2;
    texPos2.x = (quad2.x * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.r);
    texPos2.y = (quad2.y * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.g);

    lowp vec4 newColor1 = texture2D(inputImageTexture2, texPos1);
    lowp vec4 newColor2 = texture2D(inputImageTexture2, texPos2);

    lowp vec4 newColor = mix(newColor1, newColor2, fract(blueColor));
    gl_FragColor = vec4(newColor.rgb, textureColor.w);

}

下面這一張則是初始的顏色二維圖

圖4 顏色初始二維圖

接下來就是來驗(yàn)證下ColorLUT的實(shí)際情況了

圖5 驗(yàn)證圖

用上面的圖來進(jìn)行PS的調(diào)整

調(diào)整如下:

曲線調(diào)整: 紅色通道調(diào)整 --> 曝光度調(diào)整

最后顯示效果如下

圖6 PS處理圖

接下來對(duì)上面的初始顏色二維圖進(jìn)行同樣的調(diào)整,如下

圖7 顏色二維圖處理

接下來用上面的GLSL語言來驗(yàn)證,運(yùn)行效果如下

圖8 ColorLUT驗(yàn)證

通過運(yùn)行圖與前面的PS效果圖對(duì)比,我們發(fā)現(xiàn)基本一致,所以通過ColorLUT技術(shù)解決了濾鏡中關(guān)于濾鏡生產(chǎn)這一大問題,所以只需要視覺同學(xué)對(duì)圖片進(jìn)行美化后用同樣的流程處理一遍顏色二維圖,就可以快速的生產(chǎn)大量優(yōu)質(zhì)的濾鏡了

寫在后面的話

<p>

這一大幅篇章主要是介紹了如何使用圖層混合技術(shù)與ColorLUT技術(shù)解決了生產(chǎn)力的大問題,這樣確實(shí)普通的濾鏡開發(fā)都可以通過這種方式來實(shí)現(xiàn),但是一些特殊的濾鏡,比如馬賽克濾鏡,油畫濾鏡,粉筆畫濾鏡,這些效果的實(shí)現(xiàn)用ColorLUT技術(shù)不能實(shí)現(xiàn),所以我們需要使用其他的方法,那么對(duì)于這些特殊濾鏡的實(shí)現(xiàn),我們在后面再進(jìn)行說明,peace~~~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容