安卓截屏和图片画角线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类中的代码

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容

  • 视频格式封装——H264 转载自 http://blog.csdn.net/yangzhongxuan/artic...
    microchip阅读 2,362评论 0 1
  • 前言 为什么需要视频编码呢?比如当前屏幕是1280*720一秒30张图片.那么我们一秒的视频数据是1280 * 7...
    Leon_520阅读 1,975评论 0 6
  • 在目前,无论在各个行只要和视频相关的,我们都可以看见H264相关的身影,H264作为目前使用最广泛的视频压缩标准,...
    DramaScript阅读 21,546评论 7 56
  • 一、H264的NAL单元详解1、VCL只关心编码部分,重点在于编码算法以及在特定硬件平台的实现 (1)SODB 是...
    Magic11阅读 1,787评论 0 2
  • 1 “最近钱还够吗?你一定要按时吃饭,要多吃一点。” “不要一天都到外面乱跑,好好看书,不要耽误了学习。找工作的事...
    林花逝阅读 1,936评论 0 1