Android OpenGLES2.0(十五)——利用EGL后台处理图像

在Android OpenGLES2.0(十二)——FBO离屏渲染中,记录了采用FBO进行离屏渲染的方式来进行后台处理图像,但是在使用时,示例中还是用到了GLSurfaceView控件来提供GL环境。怎样完全抛开GLSurfaceView来进行图像处理呢?

OpenGLES和EGL

OpenGL(全写Open Graphics Library)是指定义了一个跨编程语言、跨平台的编程接口规格的专业的图形程序接口。它用于三维图像(二维的亦可),是一个功能强大,调用方便的底层图形库。
EGL是介于诸如OpenGL 或OpenVG的Khronos渲染API与底层本地平台窗口系统的接口。它被用于处理图形管理、表面/缓冲捆绑、渲染同步及支援使用其他Khronos API进行的高效、加速、混合模式2D和3D渲染。

由上可以理解为,OpenGLES的使用,离不开EGL。而我们在使用GLSurfaceView时并不需要和EGL有所交集,是因为GLSurfaceView在内部做好了相关的工作,为我们使用OpenGLES创造好了环境。所以GLSurfaceView可以当做一个EGL使用的示例来看。

EGL使用步骤

GLSurfaceView就不去去分析了,直接给出结论。Android中EGL的使用步骤为:

  1. 取得EGL实例
  2. 选择Display
  3. 选择Config
  4. 创建Surface
  5. 创建Context
  6. 指定当前的环境为绘制环境

代码实现

根据EGL的使用步骤,先来创建一个EGL环境创建的帮助类,代码相关说明在类中给出了注释:

public class EGLHelper {

    public EGL10 mEgl;
    public EGLDisplay mEglDisplay;
    public EGLConfig mEglConfig;
    public EGLSurface mEglSurface;
    public EGLContext mEglContext;
    public GL10 mGL;

    private static final int EGL_CONTEXT_CLIENT_VERSION=0x3098;

    public static final int SURFACE_PBUFFER=1;
    public static final int SURFACE_PIM=2;
    public static final int SURFACE_WINDOW=3;

    private int surfaceType=SURFACE_PBUFFER;
    private Object surface_native_obj;

    private int red=8;
    private int green=8;
    private int blue=8;
    private int alpha=8;
    private int depth=16;
    private int renderType=4;
    private int bufferType=EGL10.EGL_SINGLE_BUFFER;
    private EGLContext shareContext=EGL10.EGL_NO_CONTEXT;


    public void config(int red,int green,int blue,int alpha,int depth,int renderType){
        this.red=red;
        this.green=green;
        this.blue=blue;
        this.alpha=alpha;
        this.depth=depth;
        this.renderType=renderType;
    }

    public void setSurfaceType(int type,Object ... obj){
        this.surfaceType=type;
        if(obj!=null){
            this.surface_native_obj=obj[0];
        }
    }

    public GlError eglInit(int width,int height){
        int[] attributes = new int[] {
                EGL10.EGL_RED_SIZE, red,  //指定RGB中的R大小(bits)
                EGL10.EGL_GREEN_SIZE, green, //指定G大小
                EGL10.EGL_BLUE_SIZE, blue,  //指定B大小
                EGL10.EGL_ALPHA_SIZE, alpha, //指定Alpha大小,以上四项实际上指定了像素格式
                EGL10.EGL_DEPTH_SIZE, depth, //指定深度缓存(Z Buffer)大小
                EGL10.EGL_RENDERABLE_TYPE, renderType, //指定渲染api版本, EGL14.EGL_OPENGL_ES2_BIT
                EGL10.EGL_NONE };  //总是以EGL10.EGL_NONE结尾

        //获取Display
        mEgl= (EGL10)EGLContext.getEGL();
        mEglDisplay=mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);

        int[] version=new int[2];    //主版本号和副版本号
        mEgl.eglInitialize(mEglDisplay,version);
        //选择Config
        int[] configNum=new int[1];
        mEgl.eglChooseConfig(mEglDisplay,attributes,null,0,configNum);
        if(configNum[0]==0){
            return GlError.ConfigErr;
        }
        EGLConfig[] c=new EGLConfig[configNum[0]];
        mEgl.eglChooseConfig(mEglDisplay,attributes,c,configNum[0],configNum);
        mEglConfig=c[0];
        //创建Surface
        int[] surAttr=new int[]{
            EGL10.EGL_WIDTH,width,
            EGL10.EGL_HEIGHT,height,
            EGL10.EGL_NONE
        };
        mEglSurface=createSurface(surAttr);
        //创建Context
        int[] contextAttr=new int[]{
            EGL_CONTEXT_CLIENT_VERSION,2,
            EGL10.EGL_NONE
        };
        mEglContext=mEgl.eglCreateContext(mEglDisplay,mEglConfig,shareContext,contextAttr);
        makeCurrent();
        return GlError.OK;
    }

    public void makeCurrent(){
        mEgl.eglMakeCurrent(mEglDisplay,mEglSurface,mEglSurface,mEglContext);
        mGL= (GL10)mEglContext.getGL();
    }

    public void destroy(){
        mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE,
            EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT);
        mEgl.eglDestroySurface(mEglDisplay, mEglSurface);
        mEgl.eglDestroyContext(mEglDisplay, mEglContext);
        mEgl.eglTerminate(mEglDisplay);
    }

    private EGLSurface createSurface(int[] attr){
        switch (surfaceType){
            case SURFACE_WINDOW:
                return mEgl.eglCreateWindowSurface(mEglDisplay,mEglConfig,surface_native_obj,attr);
            case SURFACE_PIM:
                return mEgl.eglCreatePixmapSurface(mEglDisplay,mEglConfig,surface_native_obj,attr);
            default:
                return mEgl.eglCreatePbufferSurface(mEglDisplay,mEglConfig,attr);
        }
    }

}

利用帮助类,实现一个可以设置Filter的后台图像处理类:

public class GLES20BackEnv {

    private int mWidth;
    private int mHeight;
    private EGLHelper mEGLHelper;

    final static String TAG = "GLES20BackEnv";
    final static boolean LIST_CONFIGS = false;

    private AFilter mFilter;
    Bitmap mBitmap;
    String mThreadOwner;

    public GLES20BackEnv(int width,int height){
        this.mWidth=width;
        this.mHeight=height;
        mEGLHelper=new EGLHelper();
        mEGLHelper.eglInit(width,height);
    }

    public void setThreadOwner(String threadOwner){
        this.mThreadOwner=threadOwner;
    }

    public void setFilter(final AFilter filter) {
        mFilter = filter;

        // Does this thread own the OpenGL context?
        if (!Thread.currentThread().getName().equals(mThreadOwner)) {
            Log.e(TAG, "setRenderer: This thread does not own the OpenGL context.");
            return;
        }
        // Call the renderer initialization routines
        mFilter.create();
        mFilter.size(mWidth, mHeight);
    }

    public Bitmap getBitmap() {
        if (mFilter == null) {
            Log.e(TAG, "getBitmap: Renderer was not set.");
            return null;
        }
        if (!Thread.currentThread().getName().equals(mThreadOwner)) {
            Log.e(TAG, "getBitmap: This thread does not own the OpenGL context.");
            return null;
        }
        mFilter.setTextureId(createTexture(mBitmap));
        mFilter.draw();
        return convertToBitmap();
    }

    public void destroy() {
        mEGLHelper.destroy();
    }


    private Bitmap convertToBitmap() {
        int[] iat = new int[mWidth * mHeight];
        IntBuffer ib = IntBuffer.allocate(mWidth * mHeight);
        mEGLHelper.mGL.glReadPixels(0, 0, mWidth, mHeight, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE,
            ib);
        int[] ia = ib.array();

        // Convert upside down mirror-reversed image to right-side up normal
        // image.
        for (int i = 0; i < mHeight; i++) {
            System.arraycopy(ia, i * mWidth, iat, (mHeight - i - 1) * mWidth, mWidth);
        }
        Bitmap bitmap = Bitmap.createBitmap(mWidth, mHeight, Bitmap.Config.ARGB_8888);
        bitmap.copyPixelsFromBuffer(IntBuffer.wrap(iat));
        return bitmap;
    }

    public void setInput(Bitmap bitmap){
        this.mBitmap=bitmap;
    }

    private int createTexture(Bitmap bmp){
        int[] texture=new int[1];
        if(bmp!=null&&!bmp.isRecycled()){
            //生成纹理
            GLES20.glGenTextures(1,texture,0);
            //生成纹理
            GLES20.glBindTexture(GLES20.GL_TEXTURE_2D,texture[0]);
            //设置缩小过滤为使用纹理中坐标最接近的一个像素的颜色作为需要绘制的像素颜色
            GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER,GLES20.GL_NEAREST);
            //设置放大过滤为使用纹理中坐标最接近的若干个颜色,通过加权平均算法得到需要绘制的像素颜色
            GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,GLES20.GL_TEXTURE_MAG_FILTER,GLES20.GL_LINEAR);
            //设置环绕方向S,截取纹理坐标到[1/2n,1-1/2n]。将导致永远不会与border融合
            GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S,GLES20.GL_CLAMP_TO_EDGE);
            //设置环绕方向T,截取纹理坐标到[1/2n,1-1/2n]。将导致永远不会与border融合
            GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T,GLES20.GL_CLAMP_TO_EDGE);
            //根据以上指定的参数,生成一个2D纹理
            GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bmp, 0);
            return texture[0];
        }
        return 0;
    }

}

然后就可以利用这个后台图像处理的工具类,直接处理图像了:

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