安卓截屏和圖片畫角線OpenGLES(十一)

前言

上一篇文章我們學(xué)習(xí)了如何在安卓平臺(tái)搭建opengl es環(huán)境,如何通過(guò)TextureView加載一張圖片。其實(shí),通過(guò)前面的學(xué)習(xí),那么關(guān)于安卓平臺(tái)如何使用opengl es就掌握了一大部分了,剩下的就是性能等等余下的功能了;本篇文章的目標(biāo)如下:
1、渲染一張圖片之后再在圖片上畫一個(gè)對(duì)角線
2、把步驟一種的內(nèi)容截屏保存到系統(tǒng)相冊(cè)。
對(duì)于第二點(diǎn),可能有人覺(jué)得比較有用,比如視頻直播過(guò)程中,對(duì)某一時(shí)刻畫面進(jìn)行截屏。對(duì),實(shí)現(xiàn)原理和思路其實(shí)是一樣的,接下來(lái)我們逐一講解如何實(shí)現(xiàn)。

opengl es系列文章

opengl es之-基礎(chǔ)概念(一)
opengl es之-GLSL語(yǔ)言(二)
opengl es之-GLSL語(yǔ)言(三)
opengl es之-常用函數(shù)介紹(四)
opengl es之-SurfaceView、GLSurfaceView、TextureView環(huán)境搭建(十)

截屏實(shí)現(xiàn)思路

對(duì)于第一點(diǎn)我這里就不過(guò)多的講解了,其實(shí)比較簡(jiǎn)單,這里主要講一下第二點(diǎn)的實(shí)現(xiàn)思路。通過(guò)上一篇的學(xué)習(xí)我們知道,圖片最終渲染到屏幕上主要經(jīng)過(guò)了(這里以JPG為例)圖片解碼為Bitmap->bitmap上傳到opengl es->調(diào)用glDrawArrays()繪制->調(diào)用EGLSurface的swapBuffers()函數(shù),最終圖片將呈現(xiàn)在屏幕上。截屏,就是將opengl es的渲染結(jié)果讀取出來(lái)(一塊RGBA的像素?cái)?shù)據(jù))生成Bitmap然后在編碼為JPG的過(guò)程,那么這個(gè)動(dòng)作放在哪個(gè)階段呢?顯然要在glDrawArrays()之后,swapBuffers()函數(shù)之前,下面是流程圖

1561274872078.jpg

那截取如何實(shí)現(xiàn)呢?
其實(shí)很簡(jiǎn)單,關(guān)鍵函數(shù)就是glReadPixels(),這個(gè)函數(shù)的具體介紹已經(jīng)在opengl es之-常用函數(shù)介紹(四)詳細(xì)介紹了,這里就不多說(shuō)了。
這個(gè)函數(shù)的功能就是從渲染結(jié)果中讀取像素?cái)?shù)據(jù)到指定的緩沖區(qū),數(shù)據(jù)讀取出來(lái)后就可以轉(zhuǎn)換成Bitmap了,這就是具體思路,好,下面詳細(xì)講一下,關(guān)鍵代碼。

圖片上畫對(duì)角線

講截屏之前,先講一下如何實(shí)現(xiàn)再圖片上畫一條對(duì)角線。我們知道,加載圖片的時(shí)候需要一個(gè)頂點(diǎn)坐標(biāo),紋理坐標(biāo),著色器,最后調(diào)用glDrawArrays()將圖片渲染出來(lái),那畫一條對(duì)角線呢?一樣的思路,需要這些步驟,只不過(guò)畫線不需要紋理坐標(biāo)和在片元著色器中對(duì)紋理進(jìn)行操作了,這里講一下畫線和畫圖片的不一樣地方
1、頂點(diǎn)坐標(biāo)
// 對(duì)角線的頂點(diǎn)坐標(biāo)
private static final float verdata1[] = {
-1.0f,-1.0f,
1.0f,1.0f,
1.0f,-1.0f,
-1.0f,1.0f,
};
2、片元著色器。
頂點(diǎn)著色器和加載圖片是一樣的

// 片元著色器
    private static final String fString = "uniform sampler2D texture;\n" +
            " \n" +
            " varying highp vec2 tex_coord;\n" +
            " \n" +
            " void main(){\n" +
            "     gl_FragColor = texture2D(texture,tex_coord);\n" +
            " }";

opengl es的代碼流程是一樣的,這里我貼一下關(guān)鍵代碼,這里就不具體貼出了,具體可以參考我的Demo示例查看。

private void onDraw() {
if (mBitmap == null) {
        MLog.log("mBitmap nulll");
        return;
    }
    int width = mSurface.getWidth();
    int height = mSurface.getHeight();
    MLog.log("width "+width + "height " + height);
    
    GLES20.glViewport(0,0,width,height);
    GLES20.glClearColor(1.0f,0,0,1.0f);
    GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
    // 1、這里是加載圖片紋理的代碼
    .......
    GLUtils.texImage2D(GLES20.GL_TEXTURE_2D,0,GLES20.GL_RGBA,mBitmap,GLES20.GL_UNSIGNED_BYTE,0);
    GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP,0,4);
    
    // 接著畫線
    if (mAddLine) {
        // 2、這里是畫線的代碼
        .........
        GLES20.glLineWidth(5.0f);
        GLES20.glDrawArrays(GLES20.GL_LINES, 0, 4);
    }

    {
      // 3、這里是截屏的代碼
    }
   
    // 必須要有,否則渲染結(jié)果不會(huì)呈現(xiàn)到屏幕上
    mSurface.swapBuffers();   
}

可以看到,畫線只需要調(diào)用glLineWidth()指定線寬然后再調(diào)用glDrawArrays()即可

截屏

上面的代碼段我已經(jīng)標(biāo)出了截屏代碼的位置,沒(méi)錯(cuò),就在哪里,那截屏的代碼如何寫呢?下面貼出具體實(shí)現(xiàn)代碼

// 獲取渲染結(jié)果,以bitmap形式返回
    public Bitmap framebufferToBitmap() throws IOException {

        if (!mEglContext.isCurrent(mEGLSurface)) {
            throw new RuntimeException("Expected EGL context/surface is not current");
        }

        // glReadPixels fills in a "direct" ByteBuffer with what is essentially big-endian RGBA
        // data (i.e. a byte of red, followed by a byte of green...).  While the Bitmap
        // constructor that takes an int[] wants little-endian ARGB (blue/red swapped), the
        // Bitmap "copy pixels" method wants the same format GL provides.
        //
        // Ideally we'd have some way to re-use the ByteBuffer, especially if we're calling
        // here often.
        //
        // Making this even more interesting is the upside-down nature of GL, which means
        // our output will look upside down relative to what appears on screen if the
        // typical GL conventions are used.

        // 讀取設(shè)置字節(jié)對(duì)齊
        GLES20.glPixelStorei(GLES20.GL_PACK_ALIGNMENT,1);
        int width = getWidth();
        int height = getHeight();
        ByteBuffer buf = ByteBuffer.allocateDirect(width * height * 4);
        buf.order(ByteOrder.LITTLE_ENDIAN);
        IntBuffer colobu = IntBuffer.allocate(1);
        GLES20.glGetIntegerv(GLES20.GL_IMPLEMENTATION_COLOR_READ_FORMAT,colobu);
        IntBuffer typebu = IntBuffer.allocate(1);
        GLES20.glGetIntegerv(GLES20.GL_IMPLEMENTATION_COLOR_READ_TYPE,typebu);
        MLog.log("類型 colobu " + colobu.get(0) + "typebu " + typebu.get(0));

        GLES20.glReadPixels(0, 0, width, height, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, buf);
        MLog.log("要讀取的長(zhǎng)和寬 w="+width + " hei " + height);
        /** 遇到問(wèn)題:魅族 pro 7-s一直返回 0x502錯(cuò)誤(GL_INVALID_OPERATION),該錯(cuò)誤根據(jù)官方文檔的解釋是glReadPixels()函數(shù)的format和type和frame buffer
         * 中像素的實(shí)際format、type不匹配造成的,返回錯(cuò)誤之后buf得不到任何數(shù)據(jù)
         * 分析:但實(shí)際上format和type是對(duì)應(yīng)上的,而且buf也讀取到了正確的像素?cái)?shù)據(jù),仍然返回該錯(cuò)誤,不知道為何,有待進(jìn)一步研究。
         * */
        int error = GLES20.glGetError();
        if (error != GLES20.GL_NO_ERROR) {
            String msg = "glReadPixels: glError 0x" + Integer.toHexString(error);
            MLog.log(msg);
//            throw new RuntimeException(msg);
        }
        buf.rewind();

        android.graphics.Matrix matrix = new android.graphics.Matrix();
        matrix.postRotate(180);

        Bitmap bmp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        bmp.copyPixelsFromBuffer(buf);

        // 創(chuàng)建新的圖片
        Bitmap resizedBitmap = Bitmap.createBitmap(bmp, 0, 0,
                bmp.getWidth(), bmp.getHeight(), matrix, true);


        return resizedBitmap;
    }

1、首先GLES20.glPixelStorei(GLES20.GL_PACK_ALIGNMENT,1);設(shè)置讀取像素?cái)?shù)據(jù)時(shí)的字節(jié)對(duì)齊方式,這里表示按照一字節(jié)對(duì)齊,雖然性能低,但是安全。

2、接下來(lái)ByteBuffer buf = ByteBuffer.allocateDirect(width * height * 4);來(lái)分配一塊內(nèi)存(寬4)大小,用來(lái)接收像素?cái)?shù)據(jù),注意,

3、接下來(lái)很關(guān)鍵了buf.order(ByteOrder.LITTLE_ENDIAN);將這塊內(nèi)存數(shù)據(jù)的端序轉(zhuǎn)換為小端序,因?yàn)閖ava端都是大端序,而opengl es中的數(shù)據(jù)是小端序,所以這里要進(jìn)行轉(zhuǎn)換

4、調(diào)用GLES20.glReadPixels(0, 0, width, height, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, buf);從opengl es的渲染緩沖區(qū)中讀取RGBA數(shù)據(jù)到這個(gè)buf中,

5、生成Bitmap
Bitmap bmp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
bmp.copyPixelsFromBuffer(buf);

6、前面的文章中我們知道opengl es的中的紋理坐標(biāo)系和手機(jī)系統(tǒng)中的紋理坐標(biāo)系是剛剛好180度顛倒的,所以這里我們生成Bitmap之后還要進(jìn)行一個(gè)垂直選擇180度的翻轉(zhuǎn),這樣截取的圖片才是對(duì)的,否則是倒立的(有興趣的可以做個(gè)試驗(yàn))
android.graphics.Matrix matrix = new android.graphics.Matrix();
matrix.postRotate(180);
// 創(chuàng)建新的圖片
Bitmap resizedBitmap = Bitmap.createBitmap(bmp, 0, 0,
bmp.getWidth(), bmp.getHeight(), matrix, true);
以上就是截屏代碼的詳細(xì)講解

項(xiàng)目地址

https://github.com/nldzsz/opengles-android
1、在圖片上畫對(duì)角線參考MySurfaceView類中的代碼
2、截屏參考MySurfaceView類中的代碼

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 視頻格式封裝——H264 轉(zhuǎn)載自 http://blog.csdn.net/yangzhongxuan/artic...
    microchip閱讀 2,446評(píng)論 0 1
  • 前言 為什么需要視頻編碼呢?比如當(dāng)前屏幕是1280*720一秒30張圖片.那么我們一秒的視頻數(shù)據(jù)是1280 * 7...
    Leon_520閱讀 2,148評(píng)論 0 6
  • 在目前,無(wú)論在各個(gè)行只要和視頻相關(guān)的,我們都可以看見(jiàn)H264相關(guān)的身影,H264作為目前使用最廣泛的視頻壓縮標(biāo)準(zhǔn),...
    DramaScript閱讀 22,147評(píng)論 7 56
  • 一、H264的NAL單元詳解1、VCL只關(guān)心編碼部分,重點(diǎn)在于編碼算法以及在特定硬件平臺(tái)的實(shí)現(xiàn) (1)SODB 是...
    Magic11閱讀 1,880評(píng)論 0 2
  • 1 “最近錢還夠嗎?你一定要按時(shí)吃飯,要多吃一點(diǎn)。” “不要一天都到外面亂跑,好好看書,不要耽誤了學(xué)習(xí)。找工作的事...
    林花逝閱讀 2,191評(píng)論 0 1

友情鏈接更多精彩內(nèi)容