音視頻開發(fā)之旅(13) OpenGL ES 濾鏡 (篇一)

目錄

  1. 顏色和濾鏡的基本知識
  2. 實踐:通過ColorFilter實現(xiàn)顏色顏色調(diào)節(jié)
  3. 實踐:圖片濾鏡(黑白霸琴、冷暖色)
  4. 遇到的問題
  5. 資料
  6. 收獲

一慧起、顏色和濾鏡的基本知識

我們是如何看到圖不同顏色的?


圖片來源:[播放器色覺輔助功能開發(fā)楼肪,助力提升色覺障礙用戶的視頻觀看體驗]

不同波長的光具有不同的顏色始鱼,在我們可見光范圍內(nèi)藍(lán)色光波長是短波千埃,長波長的光呈現(xiàn)紅色。
我們?nèi)祟愑腥N不同的視錐細(xì)胞赠堵,它們對不同的光有不同的敏感度小渊,由于不同人的視錐細(xì)胞,也造成了世界上有4%-6%的色弱或者色盲者茫叭。


圖片來源:[播放器色覺輔助功能開發(fā)酬屉,助力提升色覺障礙用戶的視頻觀看體驗]

我們這篇學(xué)習(xí)的濾鏡,對于視覺有障礙的人群可以起到視覺校正的作用。同時對于視覺正常的人群呐萨,也可以通過濾鏡使色彩發(fā)生變化杀饵,適合不同的場景,比如垛吗,哀悼日模式凹髓,在或者應(yīng)用更廣的短視頻或者直播中的美顏、濾鏡等功能怯屉,增加一些趣味性蔚舀。

了解背景后,下面我們從顏色的三個要素來一起學(xué)習(xí)下顏色锨络。
顏色三要素:色調(diào)(色相)赌躺、飽和度、亮度

色調(diào)是區(qū)別各種不同色彩的最準(zhǔn)確的標(biāo)準(zhǔn)羡儿,任何黑白灰以外的顏色都有色相的屬性礼患,而色相也就是由原色、間色和復(fù)色來構(gòu)成的掠归。色相缅叠,色彩可呈現(xiàn)出來的質(zhì)的面貌。
根據(jù)色環(huán)的色彩排列虏冻,相鄰色相混合肤粱,飽和度基本不變(如紅黃相混合所得的橙色)。對比色相混合厨相,最易降低飽和度领曼,以至成為灰暗色彩
亮度不僅決定物體照明程度,而且決定物體表面的反射系數(shù)蛮穿。如果我們看到的光線來源于光源庶骄,那么亮度決定于光源的強(qiáng)度。如果我們看到的是來源于物體表面反射的光線践磅,那么亮度決定于照明的光源的強(qiáng)度和物體表面的反射系數(shù)单刁。
來自 顏色三要素百科

圖片來自:[色環(huán)百科]

在android中有ColorMatrix顏色矩陣工具類,幫我們封裝實現(xiàn)了顏色的三要素的調(diào)節(jié)以及不同矩陣相乘的實現(xiàn)府适。

色調(diào)

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();
        }
    }

我們可以看到色調(diào)的調(diào)節(jié)有兩個參數(shù)羔飞,參數(shù)axis代表 圍繞哪種顏色進(jìn)行旋轉(zhuǎn),degress是指旋轉(zhuǎn)的角度细溅。范圍是【-180度,180度】
比如下面這個矩陣就是axis為紅色時的結(jié)果儡嘶。


飽和度
飽和度(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;
    }

參數(shù)sat的代表飽和度的強(qiáng)弱喇聊。范圍是【0,1】
例如蹦狂,當(dāng)sat為0時誓篱,RGB對應(yīng)的值為0.213f朋贬,0.715f,0.072f窜骄,這是就可以實現(xiàn)黑白模式

亮度

    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;
    }

四個參數(shù)分別代表rgba四個通道的的范圍锦募,范圍區(qū)間在【0,1】邻遏。

通過上面的介紹糠亩,我們了解到可以通過修改顏色的三要素實現(xiàn)濾鏡的功能。下面開啟我們的實踐准验。

二赎线、實踐:ColorFilter對View進(jìn)行換色

我們先通過顏色矩陣設(shè)置固定的值來對普通圖片的顏色修改,實現(xiàn)黑白糊饱、暖色垂寥、冷色三種變化,然后在通過動態(tài)的調(diào)整色調(diào)另锋、飽和度滞项、以及亮度進(jìn)行實現(xiàn)圖片的顏色調(diào)節(jié)。下面開啟我們這一小節(jié)的旅程夭坪。

首先我們可以直接給View的Bitamp設(shè)置ColorFilter文判,來實現(xiàn)顏色的變化

   //黑白模式 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};

    //暖色調(diào)的處理可以增加紅綠通道的值
    float[] mWarmMatrix = {
            2,0,0,0,0,
            0,2,0,0,0,
            0,0,1,0,0,
            0,0,0,1,0};

    //冷色調(diào)的處理可以通過單一增加藍(lán)色通道的值
    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);
    }

我們也可以直接通過修改顏色的飽和度來實現(xiàn)黑白色

    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也提供了矩陣相乘的功能,這樣就可以同時圖片修改色調(diào)台舱、飽和度律杠、亮度。

   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));

    }

  /**
     * 色調(diào)竞惋、飽和度柜去、亮度 通過ColorMatrix的PostConcat相乘,對原始圖片進(jìn)行變換處理
     * @param oriBmp
     * @param hue 色調(diào)調(diào)節(jié)范圍 -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();

        //調(diào)節(jié)色調(diào)
        Log.i(TAG, "handleImageEffect: 色調(diào) rotate="+hue);
        ColorMatrix hueMatrix = new ColorMatrix();
        hueMatrix.setRotate(0, hue);//圍繞red旋轉(zhuǎn) hue角度
        hueMatrix.setRotate(1, hue);//圍繞green旋轉(zhuǎn) hue角度
        hueMatrix.setRotate(2, hue);//圍繞blue旋轉(zhuǎn) hue角度

        //調(diào)節(jié)飽和度
        Log.i(TAG, "handleImageEffect: 飽和度saturation="+saturation);
        ColorMatrix saturationMatrix = new ColorMatrix();
        saturationMatrix.setSaturation(saturation);

        //調(diào)節(jié)亮度
        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實現(xiàn)圖片濾鏡

這一小節(jié)嗓奢,我們通過OpenGL ES來實現(xiàn)顏色的變化,具體流程如下

  1. 通過GlSurfaceView的Render中進(jìn)行加載著色器和進(jìn)行Frame的繪制
  2. 通過外部設(shè)置給glsl傳入不同的濾鏡類型浑厚,在glsl中進(jìn)行根據(jù)不同的type進(jìn)行不同的顏色變化股耽。

頂點著色器和上一篇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中如何加載著色器和給著色器中屬性設(shè)置值

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;

    }
}

效果如下


源碼已上傳到github

四敢艰、遇到的問題

問題1. 從新加載shader時诬乞,報錯:ShaderHelper: loadProgram: glCreateProgram error errorCode=0

目的是想通過點擊按鈕進(jìn)行 原圖、黑白、暖色震嫉、冷色的效果切換森瘪。采用了重新加載shadercode和程序的方案,報了上面的錯誤票堵,后來想一想更合理的做法是通過設(shè)置不同類型的濾鏡告訴glsl扼睬,然后在glsl內(nèi)部進(jìn)行處理,這樣在onDrawFrame刷新時即可看到效果悴势,而不用重新創(chuàng)建program窗宇。

問題2: ‘u_TypeIndex' : Syntax error: syntax error_

用了vec1或者ivec1, 向量最少是兩個瞳浦,glsl本身支持int担映,float類型,我這里目的只是給片元著色器傳一個tpye類型用于叫潦,片元著色器內(nèi)針對不同濾鏡使用不同算法邏輯蝇完。

如何把錄制的mp4轉(zhuǎn)為大小合適的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濾鏡效果實現(xiàn)及原理分析]
[播放器色覺輔助功能開發(fā)短蜕,助力提升色覺障礙用戶的視頻觀看體驗]
[專欄:Android圖像處理之實時濾鏡]
[專欄:圖像處理]
[Android OpenGL ES(四)-為平面圖添加濾鏡]
[Android學(xué)習(xí)筆記22:圖像顏色處理(ColorMatrix)]

六、收獲

  1. 學(xué)習(xí)了解了顏色和濾鏡的基本知識
  2. 通過ColorFilter來修改顏色轉(zhuǎn)換(黑白傻咖、暖色朋魔、冷色)
  3. 通過openGl es來修改顏色實現(xiàn)濾鏡效果(黑白、暖色卿操、冷色)
  4. 加強(qiáng)了對glsl的認(rèn)知學(xué)習(xí)

感謝你的閱讀

下一篇我們繼續(xù)學(xué)習(xí)實踐濾鏡警检,Camera的實時濾鏡。歡迎關(guān)注公眾號“音視頻開發(fā)之旅”害淤,一起學(xué)習(xí)成長扇雕。

歡迎交流

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市窥摄,隨后出現(xiàn)的幾起案子镶奉,更是在濱河造成了極大的恐慌,老刑警劉巖崭放,帶你破解...
    沈念sama閱讀 206,723評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件哨苛,死亡現(xiàn)場離奇詭異,居然都是意外死亡币砂,警方通過查閱死者的電腦和手機(jī)建峭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來决摧,“玉大人亿蒸,你說我怎么就攤上這事使碾。” “怎么了祝懂?”我有些...
    開封第一講書人閱讀 152,998評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長拘鞋。 經(jīng)常有香客問我砚蓬,道長,這世上最難降的妖魔是什么盆色? 我笑而不...
    開封第一講書人閱讀 55,323評論 1 279
  • 正文 為了忘掉前任灰蛙,我火速辦了婚禮,結(jié)果婚禮上隔躲,老公的妹妹穿的比我還像新娘摩梧。我一直安慰自己,他們只是感情好宣旱,可當(dāng)我...
    茶點故事閱讀 64,355評論 5 374
  • 文/花漫 我一把揭開白布仅父。 她就那樣靜靜地躺著,像睡著了一般浑吟。 火紅的嫁衣襯著肌膚如雪笙纤。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,079評論 1 285
  • 那天组力,我揣著相機(jī)與錄音省容,去河邊找鬼。 笑死燎字,一個胖子當(dāng)著我的面吹牛腥椒,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播候衍,決...
    沈念sama閱讀 38,389評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼笼蛛,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了脱柱?” 一聲冷哼從身側(cè)響起伐弹,我...
    開封第一講書人閱讀 37,019評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎榨为,沒想到半個月后惨好,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,519評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡随闺,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,971評論 2 325
  • 正文 我和宋清朗相戀三年日川,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片矩乐。...
    茶點故事閱讀 38,100評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡龄句,死狀恐怖回论,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情分歇,我是刑警寧澤傀蓉,帶...
    沈念sama閱讀 33,738評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站职抡,受9級特大地震影響葬燎,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜缚甩,卻給世界環(huán)境...
    茶點故事閱讀 39,293評論 3 307
  • 文/蒙蒙 一谱净、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧擅威,春花似錦壕探、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,289評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至厉熟,卻和暖如春捻艳,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背庆猫。 一陣腳步聲響...
    開封第一講書人閱讀 31,517評論 1 262
  • 我被黑心中介騙來泰國打工认轨, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人月培。 一個月前我還...
    沈念sama閱讀 45,547評論 2 354
  • 正文 我出身青樓嘁字,卻偏偏與公主長得像,于是被迫代替她去往敵國和親杉畜。 傳聞我的和親對象是個殘疾皇子纪蜒,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,834評論 2 345

推薦閱讀更多精彩內(nèi)容