目錄
- 顏色和濾鏡的基本知識
- 實踐:通過ColorFilter實現顏色顏色調節
- 實踐:圖片濾鏡(黑白、冷暖色)
- 遇到的問題
- 資料
- 收穫
一、顏色和濾鏡的基本知識
我們是如何看到圖不同顏色的?
圖片來源:[播放器色覺輔助功能開發,助力提升色覺障礙用戶的視頻觀看體驗]
不同波長的光具有不同的顏色,在我們可見光範圍內藍色光波長是短波,長波長的光呈現紅色。
我們人類有三種不同的視錐細胞,它們對不同的光有不同的敏感度,由於不同人的視錐細胞,也造成了世界上有4%-6%的色弱或者色盲者。
圖片來源:[播放器色覺輔助功能開發,助力提升色覺障礙用戶的視頻觀看體驗]
我們這篇學習的濾鏡,對於視覺有障礙的人羣可以起到視覺校正的作用。同時對於視覺正常的人羣,也可以通過濾鏡使色彩發生變化,適合不同的場景,比如,哀悼日模式,在或者應用更廣的短視頻或者直播中的美顏、濾鏡等功能,增加一些趣味性。
瞭解背景後,下面我們從顏色的三個要素來一起學習下顏色。
顏色三要素:色調(色相)、飽和度、亮度
色調是區別各種不同色彩的最準確的標準,任何黑白灰以外的顏色都有色相的屬性,而色相也就是由原色、間色和複色來構成的。色相,色彩可呈現出來的質的面貌。
根據色環的色彩排列,相鄰色相混合,飽和度基本不變(如紅黃相混合所得的橙色)。對比色相混合,最易降低飽和度,以至成爲灰暗色彩
亮度不僅決定物體照明程度,而且決定物體表面的反射係數。如果我們看到的光線來源於光源,那麼亮度決定於光源的強度。如果我們看到的是來源於物體表面反射的光線,那麼亮度決定於照明的光源的強度和物體表面的反射係數。
來自 顏色三要素百科
圖片來自:[色環百科]
在android中有ColorMatrix顏色矩陣工具類,幫我們封裝實現了顏色的三要素的調節以及不同矩陣相乘的實現。
色調
public void setRotate(int axis, float degrees) {
reset();
double radians = degrees * Math.PI / 180d;
float cosine = (float) Math.cos(radians);
float sine = (float) Math.sin(radians);
switch (axis) {
// Rotation around the red color
case 0:
mArray[6] = mArray[12] = cosine;
mArray[7] = sine;
mArray[11] = -sine;
break;
// Rotation around the green color
case 1:
mArray[0] = mArray[12] = cosine;
mArray[2] = -sine;
mArray[10] = sine;
break;
// Rotation around the blue color
case 2:
mArray[0] = mArray[6] = cosine;
mArray[1] = sine;
mArray[5] = -sine;
break;
default:
throw new RuntimeException();
}
}
我們可以看到色調的調節有兩個參數,參數axis代表 圍繞哪種顏色進行旋轉,degress是指旋轉的角度。範圍是【-180度,180度】
比如下面這個矩陣就是axis爲紅色時的結果。
飽和度
飽和度(saturation)色彩的鮮豔程度
public void setSaturation(float sat) {
reset();
float[] m = mArray;
final float invSat = 1 - sat;
final float R = 0.213f * invSat;
final float G = 0.715f * invSat;
final float B = 0.072f * invSat;
m[0] = R + sat; m[1] = G; m[2] = B;
m[5] = R; m[6] = G + sat; m[7] = B;
m[10] = R; m[11] = G; m[12] = B + sat;
}
參數sat的代表飽和度的強弱。範圍是【0,1】
例如,當sat爲0時,RGB對應的值爲0.213f,0.715f,0.072f,這是就可以實現黑白模式
亮度
public void setScale(float rScale, float gScale, float bScale,
float aScale) {
final float[] a = mArray;
for (int i = 19; i > 0; --i) {
a[i] = 0;
}
a[0] = rScale;
a[6] = gScale;
a[12] = bScale;
a[18] = aScale;
}
四個參數分別代表rgba四個通道的的範圍,範圍區間在【0,1】。
通過上面的介紹,我們瞭解到可以通過修改顏色的三要素實現濾鏡的功能。下面開啓我們的實踐。
二、實踐:ColorFilter對View進行換色
我們先通過顏色矩陣設置固定的值來對普通圖片的顏色修改,實現黑白、暖色、冷色三種變化,然後在通過動態的調整色調、飽和度、以及亮度進行實現圖片的顏色調節。下面開啓我們這一小節的旅程。
首先我們可以直接給View的Bitamp設置ColorFilter,來實現顏色的變化
//黑白模式 Gray=R*0.3+G*0.59+B*0.11
float[] mBWMatrix = {
0.3f,0.59f,0.11f,0,0,
0.3f,0.59f,0.11f,0,0,
0.3f,0.59f,0.11f,0,0,
0,0,0,1,0};
//暖色調的處理可以增加紅綠通道的值
float[] mWarmMatrix = {
2,0,0,0,0,
0,2,0,0,0,
0,0,1,0,0,
0,0,0,1,0};
//冷色調的處理可以通過單一增加藍色通道的值
float[] mCoolMatrix = {
1,0,0,0,0,
0,1,0,0,0,
0,0,2,0,0,
0,0,0,1,0};
public void setImageMatrix(float[] mColorMatrix) {
Bitmap bmp = Bitmap.createBitmap(mBitmap.getWidth(),mBitmap.getHeight(),Bitmap.Config.ARGB_8888);
ColorMatrix colorMatrix = new ColorMatrix();
colorMatrix.set(mColorMatrix);
Canvas canvas = new Canvas(bmp);
Paint paint = new Paint();
paint.setColorFilter(new ColorMatrixColorFilter(colorMatrix));
canvas.drawBitmap(mBitmap,0,0,paint);
ivImage.setImageBitmap(bmp);
}
我們也可以直接通過修改顏色的飽和度來實現黑白色
public static Bitmap handleImageEffect(Bitmap oriBmp, float hue, float saturation, float lum) {
Bitmap bmp = Bitmap.createBitmap(oriBmp.getWidth(), oriBmp.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bmp);
Paint paint = new Paint();
ColorMatrix saturationMatrix = new ColorMatrix();
saturationMatrix.setSaturation(saturation);
paint.setColorFilter(new ColorMatrixColorFilter(saturationMatrix));
canvas.drawBitmap(oriBmp, 0, 0, paint);
return bmp;
}
效果如下:
ColorMatrix也提供了矩陣相乘的功能,這樣就可以同時圖片修改色調、飽和度、亮度。
private float mSaturaion =1;
private float mLum=1;
private float mHue=0;
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
int id = seekBar.getId();
switch (id){
case R.id.sb_hue:
mHue = (progress - midValue) * 1.0F / midValue * 180;
break;
case R.id.sb_saturation:
mSaturaion = progress*1.0f/midValue;
break;
case R.id.sb_lum:
mLum = progress*1.0f/midValue;
break;
}
Log.d(TAG, "onProgressChanged: midValue="+midValue+" mhue="+mHue+" msaturation="+mSaturaion+" mlum="+mLum+" progress="+progress);
ivImage.setImageBitmap(handleImageEffect(mBitmap,mHue,mSaturaion,mLum));
}
/**
* 色調、飽和度、亮度 通過ColorMatrix的PostConcat相乘,對原始圖片進行變換處理
* @param oriBmp
* @param hue 色調調節範圍 -180度至180度
* @param saturation
* @param lum
* @return
*/
public static Bitmap handleImageEffect(Bitmap oriBmp, float hue, float saturation, float lum) {
Bitmap bmp = Bitmap.createBitmap(oriBmp.getWidth(), oriBmp.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bmp);
Paint paint = new Paint();
//調節色調
Log.i(TAG, "handleImageEffect: 色調 rotate="+hue);
ColorMatrix hueMatrix = new ColorMatrix();
hueMatrix.setRotate(0, hue);//圍繞red旋轉 hue角度
hueMatrix.setRotate(1, hue);//圍繞green旋轉 hue角度
hueMatrix.setRotate(2, hue);//圍繞blue旋轉 hue角度
//調節飽和度
Log.i(TAG, "handleImageEffect: 飽和度saturation="+saturation);
ColorMatrix saturationMatrix = new ColorMatrix();
saturationMatrix.setSaturation(saturation);
//調節亮度
Log.i(TAG, "handleImageEffect: 亮度lum="+lum);
ColorMatrix lumMatrix = new ColorMatrix();
lumMatrix.setScale(lum, lum, lum, 1);
ColorMatrix imageMatrix = new ColorMatrix();
imageMatrix.postConcat(hueMatrix);
imageMatrix.postConcat(saturationMatrix);
imageMatrix.postConcat(lumMatrix);
paint.setColorFilter(new ColorMatrixColorFilter(imageMatrix));
canvas.drawBitmap(oriBmp, 0, 0, paint);
return bmp;
}
效果如下:
三、實踐:OpenGL ES實現圖片濾鏡
這一小節,我們通過OpenGL ES來實現顏色的變化,具體流程如下
- 通過GlSurfaceView的Render中進行加載着色器和進行Frame的繪製
- 通過外部設置給glsl傳入不同的濾鏡類型,在glsl中進行根據不同的type進行不同的顏色變化。
頂點着色器和上一篇OpenGL ES添加紋理中基本一致,片元着色器我們要添加兩個uniform類型,分別代表濾鏡類型和濾鏡的顏色向量。
//texture_vertex_shader.glsl
uniform mat4 u_Matrix;
attribute vec4 a_Position;
attribute vec3 a_Color;
attribute vec2 a_TextureCoordinates;
varying vec2 v_TextureCoordinates;
varying vec3 v_Color;
void main()
{
v_TextureCoordinates = a_TextureCoordinates;
v_Color = a_Color;
gl_Position = a_Position;
}
//image_filter_texture_fragment_shader.glsl
precision mediump float;
uniform sampler2D u_TextureUnit;
uniform int u_TypeIndex;
varying vec2 v_TextureCoordinates;
varying vec3 v_Color;
void main()
{
vec4 color = texture2D(u_TextureUnit, v_TextureCoordinates);
if (u_TypeIndex == 0){
gl_FragColor = color;
} else if (u_TypeIndex == 1){
float c = color.r * v_Color.r +
color.g * v_Color.g +
color.b * v_Color.b;
gl_FragColor = vec4(c, c, c, 1.0f);
} else {
vec4 newColor = color + vec4(v_Color, 0.0f);
gl_FragColor = newColor;
}
}
下面看下Render中如何加載着色器和給着色器中屬性設置值
public class ImageFilterRender implements GLSurfaceView.Renderer {
private Context context;
private int textureId;
private TextureShaderProgram textureProgram;
private BgTextureObject textureObject;
public ImageFilterRender(Context context) {
this.context = context;
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
GLES10.glClearColor(0f, 0f, 0f, 0f);
textureObject = new BgTextureObject(ImageBgData.VERTEX_DATA);
String fragmentCode = ShaderHelper.loadAsset(MyApplication.getContext().getResources(), "image_filter_texture_fragment_shader.glsl");
String vertexCode = ShaderHelper.loadAsset(MyApplication.getContext().getResources(), "texture_vertex_shader.glsl");
textureProgram = new TextureShaderProgram(context, vertexCode, fragmentCode);
textureId = TextureHelper.loadTexture(context, R.drawable.bg);
}
private void refreshTexureProgram(String fragmentCode) {
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
GLES20.glViewport(0, 0, width, height);
}
@Override
public void onDrawFrame(GL10 gl) {
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
textureProgram.useProgram();
//
GLES20.glUniform1i(textureProgram.getFilterIndexUniformLocation(), mIndex);
switch (mIndex){
case 0:
//原圖效果,不同處理
break;
case 1:
//黑白濾鏡
GLES20.glVertexAttrib3fv(textureProgram.getProgram(),ImageBgData.GRAY_FILTER_COLOR_DATA,0);
break;
case 2:
//暖色濾鏡
GLES20.glVertexAttrib3fv(textureProgram.getProgram(), ImageBgData.WARM_FILTER_COLOR_DATA, 0);
break;
case 3:
//冷色濾鏡
GLES20.glVertexAttrib3fv(textureProgram.getProgram(), ImageBgData.COOL_FILTER_COLOR_DATA, 0);
break;
}
textureProgram.setUniforms(textureId);
textureObject.bindData(textureProgram);
textureObject.draw();
}
private int mIndex;
public void setFilter(int index) {
mIndex = index;
}
}
效果如下
四、遇到的問題
問題1. 從新加載shader時,報錯:ShaderHelper: loadProgram: glCreateProgram error errorCode=0
目的是想通過點擊按鈕進行 原圖、黑白、暖色、冷色的效果切換。採用了重新加載shadercode和程序的方案,報了上面的錯誤,後來想一想更合理的做法是通過設置不同類型的濾鏡告訴glsl,然後在glsl內部進行處理,這樣在onDrawFrame刷新時即可看到效果,而不用重新創建program。
問題2: ‘u_TypeIndex' : Syntax error: syntax error_
用了vec1或者ivec1, 向量最少是兩個,glsl本身支持int,float類型,我這裏目的只是給片元着色器傳一個tpye類型用於,片元着色器內針對不同濾鏡使用不同算法邏輯。
如何把錄製的mp4轉爲大小合適的gif圖片,用於上傳
ffmpeg -i colorfilter2.mp4 -r 16 -s 320x480 -filter:v "setpts=0.5*PTS" -b 240k colorfilter2.gif
-r 16: 幀率 16fps
-s 320x480: 寬高
-filter:v "setpts=0.5*PTS" : 倍速
五、資料
[Android濾鏡效果實現及原理分析]
[播放器色覺輔助功能開發,助力提升色覺障礙用戶的視頻觀看體驗]
[專欄:Android圖像處理之實時濾鏡]
[專欄:圖像處理]
[Android OpenGL ES(四)-爲平面圖添加濾鏡]
[Android學習筆記22:圖像顏色處理(ColorMatrix)]
六、收穫
- 學習瞭解了顏色和濾鏡的基本知識
- 通過ColorFilter來修改顏色轉換(黑白、暖色、冷色)
- 通過openGl es來修改顏色實現濾鏡效果(黑白、暖色、冷色)
- 加強了對glsl的認知學習
感謝你的閱讀
下一篇我們繼續學習實踐濾鏡,Camera的實時濾鏡。歡迎關注公衆號“音視頻開發之旅”,一起學習成長。
歡迎交流