美文网首页
安卓截屏和图片画角线OpenGLES(十一)

安卓截屏和图片画角线OpenGLES(十一)

作者: 仙人掌__ | 来源:发表于2019-07-28 19:17 被阅读0次

    前言

    上一篇文章我们学习了如何在安卓平台搭建opengl es环境,如何通过TextureView加载一张图片。其实,通过前面的学习,那么关于安卓平台如何使用opengl es就掌握了一大部分了,剩下的就是性能等等余下的功能了;本篇文章的目标如下:
    1、渲染一张图片之后再在图片上画一个对角线
    2、把步骤一种的内容截屏保存到系统相册。
    对于第二点,可能有人觉得比较有用,比如视频直播过程中,对某一时刻画面进行截屏。对,实现原理和思路其实是一样的,接下来我们逐一讲解如何实现。

    opengl es系列文章

    opengl es之-基础概念(一)
    opengl es之-GLSL语言(二)
    opengl es之-GLSL语言(三)
    opengl es之-常用函数介绍(四)
    opengl es之-SurfaceView、GLSurfaceView、TextureView环境搭建(十)

    截屏实现思路

    对于第一点我这里就不过多的讲解了,其实比较简单,这里主要讲一下第二点的实现思路。通过上一篇的学习我们知道,图片最终渲染到屏幕上主要经过了(这里以JPG为例)图片解码为Bitmap->bitmap上传到opengl es->调用glDrawArrays()绘制->调用EGLSurface的swapBuffers()函数,最终图片将呈现在屏幕上。截屏,就是将opengl es的渲染结果读取出来(一块RGBA的像素数据)生成Bitmap然后在编码为JPG的过程,那么这个动作放在哪个阶段呢?显然要在glDrawArrays()之后,swapBuffers()函数之前,下面是流程图

    1561274872078.jpg
    那截取如何实现呢?
    其实很简单,关键函数就是glReadPixels(),这个函数的具体介绍已经在opengl es之-常用函数介绍(四)详细介绍了,这里就不多说了。
    这个函数的功能就是从渲染结果中读取像素数据到指定的缓冲区,数据读取出来后就可以转换成Bitmap了,这就是具体思路,好,下面详细讲一下,关键代码。

    图片上画对角线

    讲截屏之前,先讲一下如何实现再图片上画一条对角线。我们知道,加载图片的时候需要一个顶点坐标,纹理坐标,着色器,最后调用glDrawArrays()将图片渲染出来,那画一条对角线呢?一样的思路,需要这些步骤,只不过画线不需要纹理坐标和在片元着色器中对纹理进行操作了,这里讲一下画线和画图片的不一样地方
    1、顶点坐标
    // 对角线的顶点坐标
    private static final float verdata1[] = {
    -1.0f,-1.0f,
    1.0f,1.0f,
    1.0f,-1.0f,
    -1.0f,1.0f,
    };
    2、片元着色器。
    顶点着色器和加载图片是一样的

    // 片元着色器
        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的代码流程是一样的,这里我贴一下关键代码,这里就不具体贴出了,具体可以参考我的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、这里是截屏的代码
        }
       
        // 必须要有,否则渲染结果不会呈现到屏幕上
        mSurface.swapBuffers();   
    }
    

    可以看到,画线只需要调用glLineWidth()指定线宽然后再调用glDrawArrays()即可

    截屏

    上面的代码段我已经标出了截屏代码的位置,没错,就在哪里,那截屏的代码如何写呢?下面贴出具体实现代码

    // 获取渲染结果,以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.
    
            // 读取设置字节对齐
            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("要读取的长和宽 w="+width + " hei " + height);
            /** 遇到问题:魅族 pro 7-s一直返回 0x502错误(GL_INVALID_OPERATION),该错误根据官方文档的解释是glReadPixels()函数的format和type和frame buffer
             * 中像素的实际format、type不匹配造成的,返回错误之后buf得不到任何数据
             * 分析:但实际上format和type是对应上的,而且buf也读取到了正确的像素数据,仍然返回该错误,不知道为何,有待进一步研究。
             * */
            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);
    
            // 创建新的图片
            Bitmap resizedBitmap = Bitmap.createBitmap(bmp, 0, 0,
                    bmp.getWidth(), bmp.getHeight(), matrix, true);
    
    
            return resizedBitmap;
        }
    

    1、首先GLES20.glPixelStorei(GLES20.GL_PACK_ALIGNMENT,1);设置读取像素数据时的字节对齐方式,这里表示按照一字节对齐,虽然性能低,但是安全。

    2、接下来ByteBuffer buf = ByteBuffer.allocateDirect(width * height * 4);来分配一块内存(宽4)大小,用来接收像素数据,注意,

    3、接下来很关键了buf.order(ByteOrder.LITTLE_ENDIAN);将这块内存数据的端序转换为小端序,因为java端都是大端序,而opengl es中的数据是小端序,所以这里要进行转换

    4、调用GLES20.glReadPixels(0, 0, width, height, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, buf);从opengl es的渲染缓冲区中读取RGBA数据到这个buf中,

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

    6、前面的文章中我们知道opengl es的中的纹理坐标系和手机系统中的纹理坐标系是刚刚好180度颠倒的,所以这里我们生成Bitmap之后还要进行一个垂直选择180度的翻转,这样截取的图片才是对的,否则是倒立的(有兴趣的可以做个试验)
    android.graphics.Matrix matrix = new android.graphics.Matrix();
    matrix.postRotate(180);
    // 创建新的图片
    Bitmap resizedBitmap = Bitmap.createBitmap(bmp, 0, 0,
    bmp.getWidth(), bmp.getHeight(), matrix, true);
    以上就是截屏代码的详细讲解

    项目地址

    https://github.com/nldzsz/opengles-android
    1、在图片上画对角线参考MySurfaceView类中的代码
    2、截屏参考MySurfaceView类中的代码

    相关文章

      网友评论

          本文标题:安卓截屏和图片画角线OpenGLES(十一)

          本文链接:https://www.haomeiwen.com/subject/oibprctx.html