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

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

接下來就來分別說明下關(guān)于圖層混合與圖片處理
一.圖層混合
所謂圖層混合模式就是指一個(gè)層與其下圖層的色彩疊加方式,在這之前我們所使用的是正常模式,除了正常以外,還有很多種混合模式,它們都可以產(chǎn)生迥異的合成效果。
首先看正常模式

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

大家也可以對(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)行如下

(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)行如下

(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)行如下:

更多的效果大家可以去自己嘗試
二.圖片處理
關(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)整

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


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)整,如圖

圖中,橫軸是輸入,比左到右分別表示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),模擬后,我們看生成的圖片是什么效果

所以經(jīng)過我們模擬后的效果與Photoshop中的效果一致
(2).色階調(diào)整
上面說了曲線調(diào)整,接下來看看PhotoShop的色階調(diào)整

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

可以看到圖片的變化如下

所以我們來了解下色階調(diào)整帶來的變化
1.色階調(diào)整的原理
<p>
色階是什么:色階就是用直方圖描述出的整張圖片的明暗信息
我們繼續(xù)看色階調(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é)果。
我們以紅色通道為例,如下

這里:輸入色階值為:黑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)行的效果圖

通過與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è)位置上的像素則為處理后的顏色。
如圖所示

這個(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的正方形,配上顏色如下圖所示:

以右上角正方形為例,這代表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é)果如下:

這張圖內(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);
}
下面這一張則是初始的顏色二維圖

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

用上面的圖來進(jìn)行PS的調(diào)整
調(diào)整如下:
曲線調(diào)整: 紅色通道調(diào)整 --> 曝光度調(diào)整
最后顯示效果如下

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

接下來用上面的GLSL語言來驗(yàn)證,運(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~~~