《OpenGL從入門到放棄09》濾鏡拍照,了解一下钻哩?

最近比較忙屹堰,還花時間寫了幾篇Xposed的文章,所以O(shè)penGL 更新有點慢憋槐,不過還是要堅持呀~

上一篇文章《OpenGL從入門到放棄08》相機預(yù)覽双藕,這樣講就好理解了詳細介紹了相機預(yù)覽,并且封裝了一個 Camera2BaseFilter 類阳仔,
主要是為了方便實現(xiàn)不同濾鏡效果忧陪。

跟濾鏡有關(guān)的內(nèi)容還有濾鏡預(yù)覽、濾鏡拍照近范、開濾鏡錄視頻嘶摊,這些都會講,這篇文章主要學(xué)習(xí)一下濾鏡預(yù)覽评矩、濾鏡拍照叶堆,而濾鏡錄視頻比較麻煩一些,所以后面單獨寫一篇吧~

如果各位有讀完上一篇文章斥杜,這篇文章理解起來就很簡單了虱颗。

一沥匈、濾鏡預(yù)覽

先增加各種不同濾鏡對應(yīng)的片元著色器,然后在 ShaderManager中預(yù)加載著色器代碼忘渔, ShaderManager《OpenGL從入門到放棄07》濾鏡高帖,一定要看,不虧 這一篇文章里有說明了畦粮,本次就新增著色器散址,照貓畫虎

先看下最終實現(xiàn)的濾鏡預(yù)覽效果,gif壓縮了不清晰宣赔,效果還是可以看出來的预麸,大家可以自己去實踐一下~


濾鏡-預(yù)覽.gif

1、黑白濾鏡著色器

頂點著色器
camera2_vertex_shader_base.glsl

attribute vec4 aPosition;
uniform mat4 uMVPMatrix;
attribute vec4 aTexCoord;
varying vec2 vTextureCoord; //傳給片元著色器
varying vec4 vPosition; //傳給片元著色器
void main()
{
    vTextureCoord = (uMVPMatrix * aTexCoord).xy;
    gl_Position = aPosition;
    vPosition = aPosition;
}

片元著色器
camera2_fragment_shader_gray.glsl

#extension GL_OES_EGL_image_external : require
precision mediump float;
uniform samplerExternalOES uTextureSampler;
varying vec2 vTextureCoord;
void main()
{
  vec4 vCameraColor = texture2D(uTextureSampler, vTextureCoord);
  float fGrayColor = (0.3*vCameraColor.r + 0.59*vCameraColor.g + 0.11*vCameraColor.b);
  gl_FragColor = vec4(fGrayColor, fGrayColor, fGrayColor, 1.0);
}

看到這里儒将,是不是很熟悉
《OpenGL從入門到放棄07》濾鏡吏祸,一定要看,不虧
這一篇文章介紹了好幾種濾鏡實現(xiàn)椅棺,都是關(guān)于圖片的犁罩,這里相機預(yù)覽同樣適用,只不過紋理不一樣了两疚,相機預(yù)覽床估,用到的紋理是擴展紋理samplerExternalOES,替換一下诱渤,然后其它基本一樣丐巫。

2、黑白濾鏡實現(xiàn)類

public class Camera2FilterGray extends Camera2BaseFilter {

    public Camera2FilterGray(Context context, int textureId) {
        super(context, textureId);
    }

    @Override
    protected ShaderManager.Param getProgram() {
        return ShaderManager.getParam(ShaderManager.CAMERA_GRAY_SHADER);
    }
}

黑白著色器的加載在ShaderManager

        //黑白
        insertParam(CAMERA_GRAY_SHADER, GLUtil.loadFromAssetsFile(context, "shader/camera2/camera2_vertex_shader_base.glsl")
                , GLUtil.loadFromAssetsFile(context, "shader/camera2/camera2_fragment_shader_gray.glsl"));

添加一個濾鏡就只需要以上步驟勺美,然后在切換濾鏡的點擊事件中替換濾鏡即可递胧。
其它濾鏡就不再講了,如果前面文章都看懂赡茸,那么這個對你來說應(yīng)該是so easy了缎脾。

二、拍照

這里思考一個問題:
相機返回的預(yù)覽數(shù)據(jù)流其實我們并沒有更改占卧,只是在渲染到GLSurfaceView的時候我們增加濾鏡顯示遗菠,所以我們拍照保存肯定不能用Camera2的那套,而是要另辟蹊徑华蜒。

onDrawFrame這個方法是會一直調(diào)用的辙纬,每調(diào)用一次,就渲染一幀圖片叭喜,所以我們就可以從onDrawFrame這個方法入手贺拣,當(dāng)點擊拍照的時候,獲取渲染的這一幀數(shù)據(jù),然后保存到本地譬涡。同理錄制視頻就應(yīng)該是不斷的保存每一幀圖片闪幽,這個后面會介紹。

2.1 如何獲取紋理圖片數(shù)據(jù)?

我們現(xiàn)在是要獲取GLSurfaceView中渲染的紋理圖片數(shù)據(jù)涡匀,然后保存到本地沟使,
GLES20.glReadPixels(...) 上場


public static native void glReadPixels(
        int x, //0    左下角的坐標
        int y, //0
        int width, //輸出的寬度
        int height, //輸出的高度
        int format, //GLES20.GL_RGBA
        int type, //GLES20.GL_UNSIGNED_BYTE
        java.nio.Buffer pixels //接收輸出Buffer
    );

這個方法可以獲取渲染在GLSurfaceView中的圖片數(shù)據(jù),
各個參數(shù)的意思:
x渊跋, y

圖像的左下角的坐標,如果想截取整個opengl顯示窗口着倾,取0,0即可

width拾酝,height

截屏范圍

format:指的是opengl中像素存儲的格式,一般有:

GL_ALPHA
GL_RGB
GL_RGBA

type:這個值要和format匹配上卡者,

GL_UNSIGNED_BYTE蒿囤,(0-255)
GL_UNSIGNED_SHORT_5_6_5,
GL_UNSIGNED_SHORT_4_4_4_4,
oGL_UNSIGNED_SHORT_5_5_5_1,
這個每一個通道的范圍在0-2n次方的范圍內(nèi)

pixels:

接收輸出Buffer

2.2 上代碼

2.2.1 onDrawFrame

    boolean mTakePicture = false;

    public void takePicture() {
        mTakePicture = true;
    }
    public void onDrawFrame(GL10 gl) {
        ...
        mCamera2BaseFilter.draw(mTransformMatrix);

        if (mTakePicture) {
            doTakePicture();
        }

    }

onDrawFrame 中,用一個boolean變量標識是否要拍照崇决,在點擊拍照的時候設(shè)置true材诽,然后在doTakePicture 中設(shè)置為false

2.2.3 doTakePicture

    //回調(diào)數(shù)據(jù)的寬,init 賦值
    public int mFrameCallbackWidth;
    public int mFrameCallbackHeight;
    //用于存儲拍照回調(diào)數(shù)據(jù)的buffer恒傻,大小是寬乘以高
    private IntBuffer mOutPutIntBuffer;

    private void doTakePicture() {
        mTakePicture = false;
        if (mOutPutIntBuffer == null) {
            mOutPutIntBuffer = IntBuffer.allocate(mFrameCallbackWidth *
                    mFrameCallbackHeight);
        }

        //todo 這里可以用離屏渲染的方式脸侥,增加水印,后面再加
//        mCamera2BaseFilter.draw(mTransformMatrix);

        //通過這個api獲取畫的這一幀數(shù)據(jù)
        GLES20.glReadPixels(0, 0, mFrameCallbackWidth, mFrameCallbackHeight,
                GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, mOutPutIntBuffer);

        //獲取到圖片數(shù)據(jù)了
        int[] origin = mOutPutIntBuffer.array();
        int[] result = new int[mFrameCallbackWidth * mFrameCallbackHeight];
        //解決方向顛倒問題
        for (int i = 0; i < mFrameCallbackHeight; i++) {
            for (int j = 0; j < mFrameCallbackWidth; j++) {
                int pix = origin[i * mFrameCallbackWidth + j];
                int pb = (pix >> 16) & 0xff;
                int pr = (pix << 16) & 0x00ff0000;
                int pix1 = (pix & 0xff00ff00) | pr | pb;
                result[(mFrameCallbackHeight - i - 1) * mFrameCallbackWidth + j] = pix1;
            }
        }

        //幀數(shù)據(jù)回調(diào), Activity中做保存圖片操作
        if (mCameraCallback != null) {
            mCameraCallback.onFrameCallBack(result);
        }
    }

基本都注釋清楚了盈厘,這里再整理一下這幾個步驟:

  1. 初始化要輸出的 IntBuffer mOutPutIntBuffer睁枕,大小是寬乘以高
  2. 調(diào)用 GLES20.glReadPixels,傳寬高和接收圖片的 mOutPutIntBuffer
  3. 得到mOutPutIntBuffer之后沸手,這時候如果直接保存圖片外遇,會發(fā)現(xiàn)圖片上下顛倒,所以要先解決圖片上下顛倒問題(for循環(huán)自己去理解理解)
  4. 最終將數(shù)據(jù)回調(diào)到Activity中

2.2.4 onFrameCallBack

回調(diào)到Activity中契吉,大概看一下

    public void onFrameCallBack(final int[] bytes) {

        final int width = mCameraV2Renderer.mFrameCallbackWidth;
        final int height = mCameraV2Renderer.mFrameCallbackHeight;
        Log.d(TAG, "onFrameCallBack: bitmap width = "+ width);
        Log.d(TAG, "onFrameCallBack: bitmap height = "+ height);
        new Thread(new Runnable() {
            @Override
            public void run() {
                Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
                IntBuffer byteBuffer = IntBuffer.wrap(bytes);
                bitmap.copyPixelsFromBuffer(byteBuffer);
                saveBitmap(bitmap);
                bitmap.recycle();
            }
        }).start();
    }

int數(shù)組轉(zhuǎn)換成IntBuffer跳仿,然后通過Bitmap的copyPixelsFromBuffer方法,將數(shù)據(jù)拷貝到Bitmap中捐晶,然后就常規(guī)保存Bitmap即可

2.2.5 保存Bitmap

    //圖片保存
    public void saveBitmap(Bitmap bitmap) {
        String path = getSD() + "/OpenGLDemo/photo/";
        File folder = new File(path);
        if (!folder.exists() && !folder.mkdirs()) {
            mHandler.post(new Runnable() {
                @Override
                public void run() {
                    Toast.makeText(Camera2Demo_SurfaceView_Activity.this, "無法保存照片", Toast.LENGTH_SHORT).show();
                }
            });

            return;
        }
        long dataTake = System.currentTimeMillis();
        final String jpegName = path + "_" + dataTake + ".jpg";
        try {
            FileOutputStream fos = new FileOutputStream(jpegName);
            BufferedOutputStream bos = new BufferedOutputStream(fos);
            bitmap.compress(Bitmap.CompressFormat.JPEG, 100, bos);
            bos.flush();
            bos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

        Log.w(TAG, "saveBitmap: 保存成功,耗時:" + (System.currentTimeMillis() - dataTake));
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                Toast.makeText(Camera2Demo_SurfaceView_Activity.this, "保存成功,路徑:" + jpegName, Toast.LENGTH_SHORT).show();
            }
        });

    }

    protected String getSD() {
        return Environment.getExternalStorageDirectory().getAbsolutePath();
    }

6.0 動態(tài)權(quán)限就不說了菲语,我提前申請的



接下來就是見證效果的時候了,點擊拍照租悄,打開圖片


第一次拍照效果

方向?qū)α私骶浚贿^圖片不太對,右邊黑了一塊泣棋?


看下日志打印的大小

D/Camera2Demo_SurfaceView: onFrameCallBack: bitmap width = 1670
D/Camera2Demo_SurfaceView: onFrameCallBack: bitmap height = 1252

圖片的大薪赫堋:


image.png

這個是個bug,這個大小是Camer2獲取到的大小潭辈,而實際上拍照應(yīng)該是取GLSurfaceView的大小

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        glViewport(0, 0, width, height);
        Log.i(TAG, "onSurfaceChanged: " + width + ", " + height);
        mFrameCallbackWidth = width;
        mFrameCallbackHeight = height;
    }

onSurfaceChanged 中獲取這個大小鸯屿,然后重新拍照看下

拍照

嗯澈吨,這就對了,

最終幾個濾鏡拍照效果:


濾鏡拍照.gif
拍照結(jié)果.gif

gif有點模糊寄摆,原圖是很清晰的~

拍照到這里就結(jié)束了谅辣,后面還有拍照添加貼紙、拍視頻等功能要講婶恼,敬請期待吧~

源碼學(xué)習(xí)
之前的文章
《OpenGL從入門到放棄01 》一些基本概念
《OpenGL從入門到放棄02 》GLSurfaceView和Renderer
《OpenGL從入門到放棄03 》相機和視圖
《OpenGL從入門到放棄04 》畫一個長方形
《OpenGL從入門到放棄05 》著色器語言
《OpenGL從入門到放棄06 》紋理圖片顯示
《OpenGL從入門到放棄07》濾鏡桑阶,一定要看,不虧
《OpenGL從入門到放棄08》相機預(yù)覽勾邦,這樣講就好理解了

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蚣录,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子眷篇,更是在濱河造成了極大的恐慌萎河,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,820評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蕉饼,死亡現(xiàn)場離奇詭異虐杯,居然都是意外死亡,警方通過查閱死者的電腦和手機昧港,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評論 3 399
  • 文/潘曉璐 我一進店門擎椰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人慨飘,你說我怎么就攤上這事确憨。” “怎么了瓤的?”我有些...
    開封第一講書人閱讀 168,324評論 0 360
  • 文/不壞的土叔 我叫張陵休弃,是天一觀的道長。 經(jīng)常有香客問我圈膏,道長塔猾,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,714評論 1 297
  • 正文 為了忘掉前任稽坤,我火速辦了婚禮丈甸,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘尿褪。我一直安慰自己蒿褂,他們只是感情好洼冻,可當(dāng)我...
    茶點故事閱讀 68,724評論 6 397
  • 文/花漫 我一把揭開白布畜份。 她就那樣靜靜地躺著玄叠,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上臼闻,一...
    開封第一講書人閱讀 52,328評論 1 310
  • 那天鸿吆,我揣著相機與錄音,去河邊找鬼述呐。 笑死惩淳,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的乓搬。 我是一名探鬼主播思犁,決...
    沈念sama閱讀 40,897評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼进肯!你這毒婦竟也來了抒倚?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,804評論 0 276
  • 序言:老撾萬榮一對情侶失蹤坷澡,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后含蓉,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體频敛,經(jīng)...
    沈念sama閱讀 46,345評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,431評論 3 340
  • 正文 我和宋清朗相戀三年馅扣,在試婚紗的時候發(fā)現(xiàn)自己被綠了斟赚。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,561評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡差油,死狀恐怖拗军,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情蓄喇,我是刑警寧澤发侵,帶...
    沈念sama閱讀 36,238評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站妆偏,受9級特大地震影響刃鳄,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜钱骂,卻給世界環(huán)境...
    茶點故事閱讀 41,928評論 3 334
  • 文/蒙蒙 一叔锐、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧见秽,春花似錦愉烙、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,417評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春勺择,著一層夾襖步出監(jiān)牢的瞬間创南,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,528評論 1 272
  • 我被黑心中介騙來泰國打工省核, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留稿辙,地道東北人。 一個月前我還...
    沈念sama閱讀 48,983評論 3 376
  • 正文 我出身青樓气忠,卻偏偏與公主長得像邻储,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子旧噪,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,573評論 2 359

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