天天看點

濾鏡-Color Lookup Table(ColorLUT)技術移動端濾鏡開發(五)普通濾鏡開發寫在前面的話

移動端濾鏡開發(五)普通濾鏡開發

寫在前面的話

上一篇文章對簡單濾鏡實作有一定的講解,那麼這一篇則是對圖像處理更加深層次的說明,對于一張圖檔怎麼處理起來效果會看起來更好呢?我想大部分人首先就會想到PS軟體,确實對于圖像的處理PS有很多的功能,一般處理圖檔呢會用到下面這些工具
濾鏡-Color Lookup Table(ColorLUT)技術移動端濾鏡開發(五)普通濾鏡開發寫在前面的話
圖1 圖檔處理 通過這些工具可以對圖檔各種修改,當然除了這種處理之外難免還會出現加上水印或者邊框這種類型的需求,那麼這種修改一般用PS中的圖層混合,ps中的圖層混合如下
濾鏡-Color Lookup Table(ColorLUT)技術移動端濾鏡開發(五)普通濾鏡開發寫在前面的話
圖2 圖層混合模式 接下來就來分别說明下關于圖層混合與圖檔處理

一.圖層混合

所謂圖層混合模式就是指一個層與其下圖層的色彩疊加方式,在這之前我們所使用的是正常模式,除了正常以外,還有很多種混合模式,它們都可以産生迥異的合成效果。 首先看正常模式
濾鏡-Color Lookup Table(ColorLUT)技術移動端濾鏡開發(五)普通濾鏡開發寫在前面的話
圖2 正常模式 接下來試下不同的效果,如柔光模式
濾鏡-Color Lookup Table(ColorLUT)技術移動端濾鏡開發(五)普通濾鏡開發寫在前面的話
圖3 柔光模式 大家也可以對其他的效果也可以進行更多的嘗試與了解

1.圖檔混合模式介紹

上面的Ps圖層混合的圖我們看到很多混合模式,那麼這裡就一一介紹下這些混合模式 1.溶解模式 溶解模式下混合色的不透明度及填充都是100%的話,我們就看不到基色圖層。降低混合色圖層的不透明度後,我們就會發現結果色中出現了很多細小的顆粒。這些顆粒會随着混合色的不透明度變化。不透明度越低混合色圖層就被溶解的越多。剩下的部分就越少。不透明度越高混合色圖層被溶解的部分就越少,剩下的部分就越多,結果色就越接近混合色。 2.變暗模式 變暗混合模式下,它會把混合色與基色進行對比,分别選擇R,G,B三組數值中最小的數值,也就是最暗的顔色作為結果色的數值。 B<=A: C=B B>=A: C=A 3.正片疊底 正片疊底混合原理:它是按照混合色與基色中的各R,G,B值計算,計算公式:結果色R=混合色R * 基色R / 255,G值與B值同樣的方法計算。最後得到的R,G,B值就是結果色的顔色。 4.顔色加深 顔色加深可以快速增加圖檔的暗部。它的計算公式:結果色 = (基色 + 混合色 - 255)* 255 / 混合色。其中(基色 + 混合色 - 255)如果出現負數就直接歸0。是以在基色與混合色都較暗的時候都是直接變成黑色的。這樣結果色的暗部就會增加。整體效果看上去對比較為強烈。 5.線性加深 線性加深的計算公式是:結果色 = 基色 + 混合色 - 255,如果基色 + 混合色的數值小于255,結果色就為0。由這個公式可以看出,畫面暗部會直接變成黑色。是以畫面整體會更暗。白色與基色混合得到基色,黑色與基色混合得到黑色。 6.深色模式 深色模式是通過計算混合色與基色的所有通道的數值,然後選擇數值較小的作為結果色。是以結果色隻跟混合色或基色相同,不會産生出另外的顔色。白色與基色混合色得到基色,黑色與基色混合得到黑色。深色模式中,混合色與基色的數值是固定的,我們颠倒位置後,混合色出來的結果色是沒有變化的。 7.變亮模式 變亮模式跟變暗模式是相對的,它是通過混合色與基色的相關數值進行比較,選擇較大的數值作為結果色。是以結果色會更亮,同時顔色也會變化。 8.濾色模式 濾色模式與正片疊底模式相對。它的計算公式是:255 - 混合色的補色 * 基色補色 / 255。得到的資料會比混合及基色更大,是以結果色會更亮。從計算公式也可以看出基色或混合色任何一項為255也就是白色,結果色數值就是255為白色。任何一項數值為0,也就是為黑色的話,結果色就跟數值不為0的一緻。 9.顔色減淡 顔色減淡是通過混合色及基色的各通道顔色值進行對比,減少二者的對比度使基色的變亮來反映混合色。它的計算公式:結果色 = 基色 + (混合色 * 基色) / (255 - 混合色)。混合色為黑色,結果色就等于基色,混合色為白色結果色就為白色。基色為黑色結果色就為黑色。 10.線性減淡 線性減淡是通過檢視每個通道的顔色資訊,并通過增加亮度使基色變亮以反映混合色。它的計算公式:結果色 = 基色 +混合色,其中基色與混合色的數值大于255,系統就預設為最大值也就是255。 由公式可以分析出混合色為黑色結果色就等于基色,混合色為白色結果色就為白色。基色也一樣。我們颠倒混合色及基色的位置,結果色也不會變化。 11.淺色模式 淺色模式是通過計算混合色與基色所有通道的數值總和,哪個數值大就選為結果色。是以結果色隻能在混合色與基色中選擇,不會産生第三種顔色。與深色模式剛好相反。 12.疊加模式 疊加模式比較特别,它是通過分析基色個通道的數值,對顔色進行正片疊加或濾色混合,結果色保留基色的明暗對比,是以結果色以基色為主導。 計算公式: 基色 < = 128:結果色 = 混合色 基色 / 128;基色 > 128:結果色 = 255 - (255 - 混合色) (255 - 基色) / 128。從公式可以看出,結果色會根據基色的顔色數值選擇不同的計算公式。 13.柔光模式 柔光模式是根據混合色的通道數值選擇不同的公式計算混合色。數值大于128的時候,結果色就比基色稍亮;數值小于或等于128,結果色就比基色稍暗。柔光模式是以基色為主導,混合色隻相應改變局部明暗。其中混合色為黑色,結果色不會為黑色,隻比結果色稍暗,混合色為中性色,結果色跟基色一樣。 計算公式: 混合色 <=128:結果色 = 基色 + (2 混合色 - 255) (基色 - 基色 基色 / 255) / 255; 混合色 >128: 結果色 = 基色 + (2 混合色 - 255) (Sqrt(基色/255)255 - 基色)/255。 14.強光模式 強光模式跟疊加模式十分類似,隻是在計算的時候需要通過混合色來控制,混合色的數值小于或等于128的時候,顔色會變暗;混合色的數值大于128的時候,顔色會變亮。混合色為白色,結果色就為白色;混合色為黑色,結果為黑色。混合色起到主導作用。 計算公式: 混合色 <= 128:結果色 = 混合色 基色 / 128; 混合色 > 128 :結果色 = 255 - (255 - 混合色) (255 - 基色) / 128. 15.亮光模式 亮光模式是通過增加或減少對比度是顔色變暗或變亮,具體取決于混合色的數值。混合色比中性灰色暗,結果色就相應的變暗,混合色比中性灰色亮,結果色就相應的變亮。有點類似顔色加深或顔色減淡。 計算公式: A---基色;B—混合色 C=A-(255-A)(255-2B)/2B 當混合色>128時 C=A+[A(2B-255)]/[255-(2B-255) 16.線性光模式 線性光模式通過減少或增加亮度,來使顔色加深或減淡。具體取決于混合色的數值。混合色數值比中性灰色暗的時候進行相應的加深混合;混合色的數值比中性灰色亮的時候進行減淡混合。這裡的加深及減淡時線性加深或線性減淡。 計算公式:結果色 = 2 * 混合色 + 基色 -255。數值大于255取255。 17.點光模式 點光模式會根據混合色的顔色數值替換相應的顔色。如果混合色數值小于中性灰色,那麼就替換比混合色亮的像素;相反混合色的數值大于中性灰色,則替換比混合色暗的像素,是以混合出來的顔色對比較大。 計算公式: 基色 < 2 混合色 - 255:結果色 = 2 混合色 - 255; 2 混合色 - 255 < 基色 < 2 混合色 :結果色 = 基色; 基色 > 2 混合色:結果色 = 2 混合色。 18.實色混合 實色混合是把混合色顔色中的紅、綠、藍通道數值,添加到基色的RGB值中。結果色的R、G、B通道的數值隻能是255或0。是以結構色隻有一下八種可能:紅、綠、藍、黃、青、洋紅、白、黑。由此看以看出結果色是非常純的顔色。 計算公式: 混合色 + 基色 < 255:結果色 = 0 ;混合色 + 基色 >= 255:結果色 = 255。 19.內插補點模式 內插補點模式通過檢視每個通道的數值,用基色減去混合色或用混合色減去基色。具體取決于混合色與基色那個通道的數值更大。白色與任何顔色混合得到反相色,黑色與任何顔色混合顔色不變。 計算公式: 結果色 = 絕對值(混合色 - 基色) 20.排除模式 排除模式是跟內插補點模式非常類似的混合模式,隻是排除模式的結果色對比度沒有內插補點模式強。白色與基色混合得到基色補色,黑色與基色混合得到基色。 計算公式: 結果色 = (混合色 + 基色) - 混合色 * 基色 / 128。 21.減去模式 減去模式檢視各通道的顔色資訊,并從基色中減去混合色。如果出現負數就剪切為零;與基色相同的顔色混合得到黑色;白色與基色混合得到黑色;黑色與基色混合得到基色。 計算公式: 結果色 = 基色 - 混合色。 22.劃分模式 劃分模式檢視每個通道的顔色資訊,并用基色分割混合色。基色數值大于或等于混合色數值,混合出的顔色為白色。基色數值小于混合色,結果色比基色更暗。是以結果色對比非常強。白色與基色混合得到基色,黑色與基色混合得到白色。 計算公式: 結果色 = (基色 / 混合色) * 255。 23.色相模式 輸出圖像的色調為上層,飽和度和亮度保持為下層。對于灰色上層,結果為去色的下層。 24.飽和度模式 輸出圖像的飽和度為上層,色調和亮度保持為下層。 25.顔色模式 輸出圖像的亮度為下層,色調和飽和度保持為上層。 26.明度模式 輸出圖像的亮度為上層,色調和飽和度保持為下層。

2.OpenGl實作混合模式

這裡還是以之前的OpenGl顯示圖檔為例子,但是由于圖層混合模式需要兩張圖檔,是以對之前的代碼進行小的修改 首先增加新的圖檔紋理
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與之前不同,為一個新的紋理,後面繪制新的圖檔也需要用這個紋理 接下來擷取 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);
    ...
}
           
接下來就是需要修改片段着色器程式了根據不同的模式來建立不同的程式 這裡我們選取幾個效果實作

(1).正常模式

正常模式下,則為第二張疊加在第一張圖檔上面,對于我們輸出的顔色需要從圖檔一和圖檔二共同擷取,是以這時候第二張圖檔透明部分則顯示為第一張圖檔,不透明部分顯示第二張圖檔 接下來上片段着色器程式代碼
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;

}
           
代碼就不做過多解釋了,接下來運作如下
濾鏡-Color Lookup Table(ColorLUT)技術移動端濾鏡開發(五)普通濾鏡開發寫在前面的話
圖5 正常模式

(2).柔光模式

柔光模式是根據混合色的通道數值選擇不同的公式計算混合色。數值大于128的時候,結果色就比基色稍亮;數值小于或等于128,結果色就比基色稍暗。柔光模式是以基色為主導,混合色隻相應改變局部明暗。其中混合色為黑色,結果色不會為黑色,隻比結果色稍暗,混合色為中性色,結果色跟基色一樣。 計算公式: 混合色 <=128:結果色 = 基色 + (2 混合色 - 255) (基色 - 基色 基色 / 255) / 255; 混合色 >128: 結果色 = 基色 + (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);
}
           
運作如下
濾鏡-Color Lookup Table(ColorLUT)技術移動端濾鏡開發(五)普通濾鏡開發寫在前面的話
圖6 柔光模式

(3).線性加深模式

線性加深的計算公式是:結果色 = 基色 + 混合色 - 255,如果基色 + 混合色的數值小于255,結果色就為0。由這個公式可以看出,畫面暗部會直接變成黑色。是以畫面整體會更暗。白色與基色混合得到基色,黑色與基色混合得到黑色。 接下來上片段着色器程式代碼
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);

}
           
運作如下:
濾鏡-Color Lookup Table(ColorLUT)技術移動端濾鏡開發(五)普通濾鏡開發寫在前面的話
Paste_Image.png 更多的效果大家可以去自己嘗試

二.圖檔處理

關于圖層混合我們已經用GLSL語言去實作并運作實作了需要的效果了,接下來就是關于圖檔處理了,上面的圖我們看到圖檔處理的方法太多了,我們這裡首先先挑兩個處理方法來說明

(1).曲線調整

曲線調整是Photoshop的最常用的重要功能之一,是以了解他的實作與原理是很有必要的。 首先看下PhotoShop中的曲線調整
濾鏡-Color Lookup Table(ColorLUT)技術移動端濾鏡開發(五)普通濾鏡開發寫在前面的話
圖1 曲線調整框 當我們改變這條曲線的時候就會對圖像産生處理 如下
濾鏡-Color Lookup Table(ColorLUT)技術移動端濾鏡開發(五)普通濾鏡開發寫在前面的話
圖2 曲線調整
濾鏡-Color Lookup Table(ColorLUT)技術移動端濾鏡開發(五)普通濾鏡開發寫在前面的話
圖3 曲線調整對應圖檔的變化圖檔
1.曲線調整原理
對于一個RGB圖像, 可以對R, G, B 通道進行獨立的曲線調整,即,對三個通道分别使用三條曲線(Curve)。還可以再增加一條曲線對 三個通道進行整體調整。 是以,對一個圖像,可以用四條曲線調整。最終的結果,是四條曲線調整後合并産生的結果。 我們首先對單一的通道調整,如圖
濾鏡-Color Lookup Table(ColorLUT)技術移動端濾鏡開發(五)普通濾鏡開發寫在前面的話
圖4 單通道曲線調整 圖中,橫軸是輸入,比左到右分别表示0到255. 縱軸是輸出,從下到上分别表示0到255. 該曲線由三個點定義,座标分别為: 點1(0,0), 點2(85,143),點3(255,255) 點1和點3是預設産生的,點2是我們新增加的。這樣這三個點就生成了一條曲線 是以圖檔中紅色通道的值都要按這條曲線來生成新的值,藍綠通道的值不發生改變 比如之前一個點的RGB值為(85,125,125),那麼經過紅色通道的調整後,這個點的RGB值為(143,125,125) 同理于藍色通道,綠色通道以及整個RGB通道的曲線調整 到這裡大家基本了解了曲線調整的流程了,那麼接下來就需要知道這條曲線是如何生成的
2.曲線生産
結合上面的圖,我們可以發現這條曲線的特點,僅需要定義幾個控制點,就可以定義一條平滑的曲線,且曲線同時通過所有控制點。根據這個特性我們可以判斷這裡的曲線是三次樣條插值Cubic Spline Interpolation(簡稱Spline插值),生成曲線時,隻需要給出幾個控制點,調用曲線生成函數即可,然後可以根據這個函數來生成新的RGB值。 三次樣條函數: 定義:函數S(x)∈C2[a,b] ,且在每個小區間[ xj,xj+1 ]上是三次多項式,其中a =x0 <x1<...< xn= b 是給定節點,則稱S(x)是節點x0,x1,...xn上的三次樣條函數。若在節點x j 上給定函數值Yj= f (Xj).( j =0, 1, , n) ,并成立S(xj ) =yj .( j= 0, 1, , n) ,則稱S(x)為三次樣條插值函數。 實際計算時還需要引入邊界條件才能完成計算。邊界通常有自然邊界(邊界點的二階導為0),夾持邊界(邊界點導數給定),非扭結邊界(使兩端點的三階導與這兩端點的鄰近點的三階導相等)。
3.曲線調整模拟
要模拟出曲線調整的效果,首先要實作三次樣條函數 這個我就不做過多的介紹了,網上也有不少例子 上代碼
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));
           
這裡隻是對紅色通道進行了曲線模拟并且選取的點為(0,0),(114,176),(255,255),模拟後,我們看生成的圖檔是什麼效果
濾鏡-Color Lookup Table(ColorLUT)技術移動端濾鏡開發(五)普通濾鏡開發寫在前面的話
圖5 模拟效果 是以經過我們模拟後的效果與Photoshop中的效果一緻

(2).色階調整

上面說了曲線調整,接下來看看PhotoShop的色階調整
濾鏡-Color Lookup Table(ColorLUT)技術移動端濾鏡開發(五)普通濾鏡開發寫在前面的話
圖1 色階調整 我們嘗試下去修改輸入色階 如下
濾鏡-Color Lookup Table(ColorLUT)技術移動端濾鏡開發(五)普通濾鏡開發寫在前面的話
圖2 色階調整輸入色階 可以看到圖檔的變化如下
濾鏡-Color Lookup Table(ColorLUT)技術移動端濾鏡開發(五)普通濾鏡開發寫在前面的話
圖3 色階調整對應圖檔的變化 是以我們來了解下色階調整帶來的變化
1.色階調整的原理
色階是什麼:色階就是用直方圖描述出的整張圖檔的明暗資訊 我們繼續看色階調整的圖
濾鏡-Color Lookup Table(ColorLUT)技術移動端濾鏡開發(五)普通濾鏡開發寫在前面的話
圖4 色階調整 從左至右是從暗到亮的像素分布,黑色三角代表最暗地方(純黑),白色三角代表最亮地方(純白)。灰色三角代表中間調。 每一個色階定義有兩組值: 一組是輸入色階值,包含黑灰白三個值, 上圖中:黑點值為0,灰點為1.00,白點為255 另一組是輸入色階值,包含黑白兩個值,上圖中:輸出色階黑為0,白為255 對于一個RGB圖像, 可以對R, G, B 通道進行獨立的色階調整,即,對三個通道分别使用三個色階定義值。還可以再對 三個通道進行整體色階調整。 是以,對一個圖像,可以用四次色階調整。最終的結果,是四次調整後合并産生的結果。 我們以紅色通道為例,如下
濾鏡-Color Lookup Table(ColorLUT)技術移動端濾鏡開發(五)普通濾鏡開發寫在前面的話
圖5 紅色通道色階調整 這裡:輸入色階值為:黑15,灰1.5,白200   輸出色階值為:黑12,白233 則色階調整的實作是: 當輸入值<黑點值(15)時,全部變為輸出色階的黑值(12)。  當輸入值>白點(200)時,全部變為輸出色階的白值(233)。 當輸入值介于黑值與白值之間(15-200)時,則結合灰階系數,按比例重新計算,變為一個新的值。 同理于藍色通道,綠色通道以及整個RGB通道的曲線調整
2.色階調整模拟
上面已經說明了原理,這裡就直接把模拟的代碼貼上來了
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設定為10,Highlight = 244,接下來看一下運作的效果圖
濾鏡-Color Lookup Table(ColorLUT)技術移動端濾鏡開發(五)普通濾鏡開發寫在前面的話
圖6 運作效果圖 通過與ps處理的效果圖對比,顯示效果一緻 前面幾個小點都是對于PhotoShop中常用的處理的圖檔的方法的原理進行了分析,那麼接下來自然就要考慮一下怎麼用OpenGl去實作這樣的效果呢? 那麼第一種方法自然就是模拟出這些處理方法,用glsl語言來實作這些方法,這麼做當然是可以的,但是目前這些處理方法大多都是用C來實作的,glsl語言尚未有可以用的輪子,那麼自然我們就要去造這個輪子,那我們不禁要思考一下這麼做的成本和回報到底是否讓我們滿意,假設影像的大小為1024x768,那麼對于其中的某一項的處理則總共要786432次運算,而且這些運動大多比較複雜,是以這麼做其實相對來說并沒有那麼讨喜,成本相對較大,并且結果并沒有達到很令人滿意的效果,是以要思考下怎麼用别的方式去實作。 看了這麼多的處理原理,我相信大家都應該了解每一個像素的點都将原來的RGB值替換了新的RGB值,是以我們可以建一張表,把所有色彩值經過處理之後的結果記錄起來,然後把每個像素的色彩值拿去查表,得到處理之後的色彩值,那麼我們隻要做786432查表動作,這樣确實會比上面的運算快上許多。但是問題還是伴随會出現,這個表怎麼去擷取又是一個問題,畢竟美工不可能一點一點給我們計算這個表,我們自己去算也是不現實的,是以有沒有更加高效的方式呢? 答案自然是肯定的,而這一解決方案就是Color Lookup Table(ColorLUT)技術

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

上文提到查表的方法時候提到了怎麼去擷取這個表是一個問題,那其實ColorLUT就是為了解決這個問題的,對于一個處理RGB值的表,我們不需要将所有都記錄下來,而是隻記下部分的色彩,其他不在表内的色彩則用内插法取得處理後的結果。 因為每個像素的色彩都是由RGB三種顔色組成,是以我們會以三維陣列的方式來儲存這張表。如果把三維陣列中的每一個像素想像成三度空間中的一個點,而R、G、B分别代表X、Y、Z的座标,則陣列中的所有像素可以構成一個正立方體。以RGB 24bits為例,由于R、G、B的值為0~255,是以正方體的長寬高為255x255x255。當我們要查某個像素經過處理之後的色彩,隻要将該像素處理前的RGB值當做X、Y、Z座标,位于那個位置上的像素則為處理後的顔色。 如圖所示
濾鏡-Color Lookup Table(ColorLUT)技術移動端濾鏡開發(五)普通濾鏡開發寫在前面的話
圖1 三維空間圖 這個圖是以4x4x4的正方體X、Y、Z三個方向都隻有在0、85、170、255這些位置有資料,如果座标不在這些點上,則必須跟周圍的點做内差來得到色彩的近似值。 而這些已有的顔色的值當我們通過PhotoShop進行各種操作時,則會将這些值替換成新的值,而這些新的值則就是上面所述的數組裡面的關鍵值之一了。 我們繼續看上面的圖畢竟是三維圖,我們無法進行處理,是以把z軸的面去出來拼接在一起則會成為一個8x8的圖檔,這個圖檔是包含4個4x4的正方形,配上顔色如下圖所示:
濾鏡-Color Lookup Table(ColorLUT)技術移動端濾鏡開發(五)普通濾鏡開發寫在前面的話
圖2 顔色二維圖 以右上角正方形為例,這代表Z=0的平面,而X軸由左至右,Y軸為由上至下,左上角第一個像素代表位于(0,0,0)的點,第二個像素代表位于(85,0,0)的點,以此類推,由于這些像素代表的是未處理前的顔色,是以第一個像素的RGB值為0,0,0,第二的像素的RGB值為85,0,0 接着我們将這張圖經過Photoshop降低色彩飽和度,結果如下:
濾鏡-Color Lookup Table(ColorLUT)技術移動端濾鏡開發(五)普通濾鏡開發寫在前面的話
圖3 顔色二維圖 這張圖内的每個像素,代表每個座标點處理之後的色彩。也就是說,如果我們想知道一個RGB值為85,0,0的像素降低飽和度之後的顔色,可從正立方體中位于(85,0,0)的點得知,也就是這張圖中左上角第二個像素的顔色,RGB值為68,16,16。 是以我們可以将這一技術運用到我們的濾鏡開發中,進而避免上面的尴尬問題,接下來就是實作上面的ColorLUT技術
ColorLUT實作
ColorLUT的實作無非就是用GLSL的語言來實作識别上面的顔色二維圖方法,這個我就不做過多的介紹了,這個在GPUImage裡面已經有實作,我就不重複造輪子了,這裡直接拿過來使用,代碼如下,自己了解
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);

}
           
下面這一張則是初始的顔色二維圖
濾鏡-Color Lookup Table(ColorLUT)技術移動端濾鏡開發(五)普通濾鏡開發寫在前面的話
圖4 顔色初始二維圖 接下來就是來驗證下ColorLUT的實際情況了
濾鏡-Color Lookup Table(ColorLUT)技術移動端濾鏡開發(五)普通濾鏡開發寫在前面的話
圖5 驗證圖 用上面的圖來進行PS的調整 調整如下: 曲線調整: 紅色通道調整 --> 曝光度調整 最後顯示效果如下
濾鏡-Color Lookup Table(ColorLUT)技術移動端濾鏡開發(五)普通濾鏡開發寫在前面的話
圖6 PS處理圖 接下來對上面的初始顔色二維圖進行同樣的調整,如下
濾鏡-Color Lookup Table(ColorLUT)技術移動端濾鏡開發(五)普通濾鏡開發寫在前面的話
圖7 顔色二維圖處理 接下來用上面的GLSL語言來驗證,運作效果如下
濾鏡-Color Lookup Table(ColorLUT)技術移動端濾鏡開發(五)普通濾鏡開發寫在前面的話
圖8 ColorLUT驗證 通過運作圖與前面的PS效果圖對比,我們發現基本一緻,是以通過ColorLUT技術解決了濾鏡中關于濾鏡生産這一大問題,是以隻需要視覺同學對圖檔進行美化後用同樣的流程處理一遍顔色二維圖,就可以快速的生産大量優質的濾鏡了

寫在後面的話

這一大幅篇章主要是介紹了如何使用圖層混合技術與ColorLUT技術解決了生産力的大問題,這樣确實普通的濾鏡開發都可以通過這種方式來實作,但是一些特殊的濾鏡,比如馬賽克濾鏡,油畫濾鏡,粉筆畫濾鏡,這些效果的實作用ColorLUT技術不能實作,是以我們需要使用其他的方法,那麼對于這些特殊濾鏡的實作,我們在後面再進行說明,peace~~~ 作者:前世小書童連結:http://www.jianshu.com/p/2f188d2b79f1來源:簡書