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

前言

上一篇文章我们学习了如何在安卓平台搭建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类中的代码

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值