Android opengl ES实现后台绘图并保存为bitmap

最近在android 上有个构思,就是如何使用opengl ES在后台绘制个3D图片,然后把这个绘制好的图片保存成bitmap格式。。。想了好几天,也尝试了多种方法,但是都不行,一开始尝试用GLSurfaceView的方式,但是这样会导致我的Activity和渲染的东东发生联系,我想要要的结果是无论如何我的主Acivity都不能和我渲染的图片发生任何关系(也就是说主Acitivity不能显示任何我渲染的东西出来)。

首先来说的话,opengl es是来自于Opengl(精简版),ES针对嵌入式灵巧的设备(embided device),而opengl是针对PC这样的超级怪物
,这也就不难理解它为什么要被"瘦身"了,在opengl中有个双缓冲的概念,也就是说前面显示,后面画图,这样可以达到无闪烁的境界。所以理论上来说我们应该也要效仿这种方式,将图片绘制到后台缓冲中,达到目的。这里先贴个opengl的方式:

[cpp]  view plain copy
  1. glutInitDisplayMode(GLUT_RGB|GLUT_DOUBLE);  
  2. // draw   
  3. ...  
  4. // draw end  
  5. pixeldata = (GlutByte)malloc(width*height*bytes);  
  6. glReadPixels(x, y, width, height, GL_BGR_EXT, GL_UNSIGNED_BYTE, pixeldata);  
这个用到glut包,上面几个是关键函数,如果大家想知道如何去画bitmap的话,下面我也贴下画bitmap的方式,无非就是把读到的像素值pixeldata最后写道bitmap文件中,不过这里要注意两点,1个是bitmap的像素排列格式是BGR,所以当你试图去
[cpp]  view plain copy
  1. glReadPixels  
获取原始像素的时候请使用GL_BGR_EXT这个参数,其次bitmap是个结构体,在C,C++代码处写起来还是需要一定的格式的,不然生成的bitmap文件有问题,具体的格式可以去查,我贴下我从网上找来的一段实现bitmap的代码(经过验证这个是可用的,写这个的人还是比较靠谱的,赞一个),如下:

[cpp]  view plain copy
  1. typedef long LONG;  
  2. typedef unsigned char BYTE;  
  3. typedef unsigned int DWORD;  
  4. typedef unsigned short WORD;  
  5.   
  6.   
  7. typedef struct {  
  8.         WORD    bfType;  
  9.         DWORD   bfSize;  
  10.         WORD    bfReserved1;  
  11.         WORD    bfReserved2;  
  12.         DWORD   bfOffBits;  
  13. } BMPFILEHEADER_T;  
  14.   
  15. typedef struct{  
  16.         DWORD      biSize;  
  17.         DWORD       biWidth;  
  18.         DWORD       biHeight;  
  19.         WORD       biPlanes;  
  20.         WORD       biBitCount;  
  21.         DWORD      biCompression;  
  22.         DWORD      biSizeImage;  
  23.         DWORD       biXPelsPerMeter;  
  24.         DWORD       biYPelsPerMeter;  
  25.         DWORD      biClrUsed;  
  26.         DWORD      biClrImportant;  
  27. } BMPINFOHEADER_T;  
  28.   
  29. void init();  
  30. void display();  
  31.   
  32. static GLubyte *PixelData;  
  33.   
  34. void Snapshot( BYTE * pData, int width, int height,  char * filename ,DWORD size)  
  35. {  
  36.   
  37.       // 位图第一部分,文件信息  
  38.        BMPFILEHEADER_T bfh={0};  
  39.        bfh.bfType = (WORD)0x4d42;  //bm  
  40.        bfh.bfSize = (DWORD)(size+54);  
  41.        bfh.bfReserved1 = 0; // reserved  
  42.        bfh.bfReserved2 = 0; // reserved  
  43.        bfh.bfOffBits = 54;  
  44.        // 位图第二部分,数据信息  
  45.        BMPINFOHEADER_T bih={0};  
  46.        bih.biSize = 40;  
  47.        bih.biWidth = width;  
  48.        bih.biHeight = height;  
  49.        bih.biPlanes = 1;  
  50.        bih.biBitCount = 24; //24真彩色位图  
  51.        bih.biCompression = 0;  
  52.        bih.biSizeImage = 0;  
  53.        bih.biXPelsPerMeter = 0;  
  54.        bih.biYPelsPerMeter = 0;  
  55.        bih.biClrUsed = 0;  
  56.        bih.biClrImportant = 0;  
  57.   
  58.        FILE * fp = fopen(filename,"wb");  
  59.        if( !fp ) return;  
  60.        fwrite( &bfh.bfType,1,2,fp );  
  61.        fwrite( &bfh.bfSize,1,4,fp );  
  62.        fwrite( &bfh.bfReserved1,1,2,fp );  
  63.        fwrite( &bfh.bfReserved2,1,2,fp );  
  64.        fwrite( &bfh.bfOffBits,1,4,fp );  
  65.        fwrite( &bih,1,sizeof(BMPINFOHEADER_T),fp );  
  66.        fwrite(pData,1,size,fp);  
  67.        fclose( fp );  
  68.   
  69. }  

废话不多说开始入正题:

先构思下,我们需要要建个很一般的Acitivity,然后在上面加个按钮,当点击按钮的时候,开始在后台绘制图片,然后将图片的pixel读出来,转化成bitmap 保存。

Idea有了,那么开始干活。

1 建立个Acivity,按照Android的工程步骤提示在eclipse里面建立,这个不多说,我是App文盲,我都知道怎么做。

2 在本地Avivity的onCreate里面添加button和button监听事件,并且在里面处理初始化后台画图的一些操作。

[java]  view plain copy
  1. btn.setOnClickListener(new OnClickListener() {  
  2.     @Override   
  3.      public void onClick(View v)    
  4.      {    
  5.          // TODO Auto-generated method stub    
  6.         //prepare init EGL environment   
  7.          BackDraw = new BackDraw(); //init backdraw  
  8.          Log.d("GlActivity:""render in background");  
  9.   
  10.      }    
  11.  });         
3  下面就是BackDraw的类的具体搭建了,我在这个类的构造函数中去初始化EGL环境,为后面的画图渲染创造条件。这个里面需要说明的是,一般我们想要用opengl渲染图片或者绘制图片都是通过GLSurfaceView.render做的,目的是通过在onDrawFrame里面调用gl函数在后台framebuffer中画图片,然后把图片显示到前台,这个一般是自动的。如果你不改任何东西,那么只要你一画好,你就会在前台看到你画的东东。那么如何将图片画在后台,而不自动显示到前台呢,我仔细看了下GLSurfaceView的实现,这个是继承于SurfaceView类,这个类在surfacecreate里面有我们想要的参考代码,这里不作具体说明,我只想说灵魂就是1个函数

[java]  view plain copy
  1. eglCreatePbufferSurface  
这个函数是在内存中创建1个off-screen的framebuffer,我们绘制图片可以在这个上面绘制,具体每个函数干么用的可以参考EGL官方网站的函数说明 EGL HOME , 这里不多描述了,总之在我们要画图之前,我们先要解决如何构建画图的环境,画在哪的问题,在我们开始搭建环境之间,EGL需要有些属性建立,如长宽,像素的byte大小,Surface类型等等,如下面
[cpp]  view plain copy
  1.        private int[] version = new int[2];   
  2. EGLConfig[] configs = new EGLConfig[1];  
  3. int[] num_config = new int[1];  
  4.        //EglchooseConfig used this config  
  5.        int[] configSpec ={  
  6.     EGL10.EGL_SURFACE_TYPE, EGL10.EGL_PBUFFER_BIT,   
  7.     EGL10.EGL_RED_SIZE, 8,  
  8.     EGL10.EGL_GREEN_SIZE, 8,  
  9.     EGL10.EGL_BLUE_SIZE, 8,  
  10.     EGL10.EGL_ALPHA_SIZE, 8,  
  11.     EGL10.EGL_NONE   
  12.  };  
  13.        //eglCreatePbufferSurface used this config   
  14.         int attribListPbuffer[] = {  
  15.         EGL10.EGL_WIDTH, 480,  
  16.         EGL10.EGL_HEIGHT, 800,  
  17.         EGL10.EGL_NONE  
  18.  };  
这里面要说明的是
[cpp]  view plain copy
  1. EGL10.EGL_SURFACE_TYPE, EGL10.EGL_PBUFFER_BIT,  
如果你要创建的是PbufferSurface(后台显示)类型,就要说明这个,如果要创建WindowSurface(前台显示),需要EGL10.EGL_WINDOW_BIT类型,还有的是属性数组最好不要乱加值,有些函数只能接受特定的值,如果你乱加,函数在执行的时候会失败,比如
[cpp]  view plain copy
  1. attribListPbuffer  
它是eglCreatePbufferSurface函数在创建PbufferSuface时传入的属性,它只接受三个属性,我一开始加了个其他的属性,结果导致创建失败。

还有个要提到的,就是attribListPbuffer[]数组,一定要把长宽的配置设置了

[cpp]  view plain copy
  1. EGL10.EGL_WIDTH, 480,  
  2. EGL10.EGL_HEIGHT, 800,  
因为如果不设置,默认是0,如果你等会在这个surface上画图你会悲催的哭,因为你无论怎么画,画到地球毁灭,最后在surface上的只有空气。。。如果你要画480*800的图,那么你就把surface的长宽也相应的设下。


好了属性配置好了,下面就是构造EGL环境,做的事情可以概括为三件事

1 弄个PbufferSurface出来

2 弄个context出来,并把这个context绑定到surface中

3 通过context弄个GL对象,用这个GL对象绘制渲染图片。

Here we go...

[java]  view plain copy
  1.       private void initEGL()  
  2.   
  3.   
  4. mEgl = (EGL10)EGLContext.getEGL();  
  5.   
  6. EGLDisplay mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);  
  7.   
  8. mEgl.eglInitialize(mEglDisplay, version);  
  9.   
  10. mEgl.eglChooseConfig(mEglDisplay, configSpec, configs, 1, num_config);  
  11.   
  12. EGLConfig mEglConfig = configs[0];  
  13.   
  14. EGLContext mEglContext = mEgl.eglCreateContext(mEglDisplay, mEglConfig,  
  15.         EGL10.EGL_NO_CONTEXT,  
  16.         null);  
  17. if (mEglContext == EGL10.EGL_NO_CONTEXT)  
  18. {  
  19.     //mEgl.eglDestroySurface(mEglDisplay, mEglSurface);  
  20.     Log.d("ERROR:""no CONTEXT");  
  21. }  
  22. //注意这个attribListPbuffer,属性表  
  23. EGLSurface mEglPBSurface = mEgl.eglCreatePbufferSurface(mEglDisplay, mEglConfig, attribListPbuffer);  
  24. if (mEglPBSurface == EGL10.EGL_NO_SURFACE)  
  25. {  
  26.     //mEgl.eglDestroySurface(mEglDisplay, mEglPBSurface);  
  27.     int ec = mEgl.eglGetError();  
  28.     if (ec == EGL10.EGL_BAD_DISPLAY)  
  29.     {  
  30.         Log.d("ERROR:""EGL_BAD_DISPLAY");  
  31.     }  
  32.     if (ec == EGL10.EGL_BAD_DISPLAY)  
  33.     {  
  34.         Log.d("ERROR:""EGL_BAD_DISPLAY");  
  35.     }  
  36.     if (ec == EGL10.EGL_NOT_INITIALIZED)  
  37.     {  
  38.         Log.d("ERROR:""EGL_NOT_INITIALIZED");  
  39.     }  
  40.     if (ec == EGL10.EGL_BAD_CONFIG)  
  41.     {  
  42.         Log.d("ERROR:""EGL_BAD_CONFIG");  
  43.     }  
  44.     if (ec == EGL10.EGL_BAD_ATTRIBUTE)  
  45.     {  
  46.         Log.d("ERROR:""EGL_BAD_ATTRIBUTE");  
  47.     }  
  48.     if (ec == EGL10.EGL_BAD_ALLOC)  
  49.     {  
  50.         Log.d("ERROR:""EGL_BAD_ALLOC");  
  51.     }  
  52.     if (ec == EGL10.EGL_BAD_MATCH)  
  53.     {  
  54.         Log.d("ERROR:""EGL_BAD_MATCH");  
  55.     }                     
  56. }  
  57.   
  58.   
  59. if (!mEgl.eglMakeCurrent(mEglDisplay, mEglPBSurface, mEglPBSurface,  
  60.         mEglContext))//这里mEglPBSurface,意思是画图和读图都是从mEglPbSurface开始  
  61.               {  
  62.       
  63.     Log.d("ERROR:""bind failed ECODE:"+mEgl.eglGetError());  
  64.               }  
  65.   
  66.               GL10 gl = (GL10) mEglContext.getGL();  
  67.          }  

以上具体的初始化流程我是参考 http://blog.sina.com.cn/s/blog_413978670100bxsl.html . 小提示: 有的时候在这些创建过程中,会有失败,我们可以通过调用
[java]  view plain copy
  1. mEgl.eglGetError();  
获取上次egl执行函数的错误码,通过比较错误码,可以找到错误问题点,我的
[java]  view plain copy
  1. eglCreatePbufferSurface  
就是这么做的,可以参考下。

好了现在该有的都有了,下面就是开始在你"纸"上画图了,具体怎么画我就不多说了吧,无非是什么gl.clear()啊。。。。

Ok现在图画完了,那么该保存图片了,这些图片的像素值都被保存在刚才我们建立的PB Framebuffer中,也就是那个存在于内存中的off-screen surface.下面就是读图了,和Opengl一样,读取当前framebuffer中的像素的方式都是gl.glReadPixels这个函数,实现如下,注意它的pixel参数是IntBuffer类型

[java]  view plain copy
  1. IntBuffer PixelBuffer = IntBuffer.allocate(width*height);  
  2. PixelBuffer.position(0);  
  3. gl.glReadPixels(00, width, height, GL10.GL_RGBA, GL10.GL_UNSIGNED_BYTE, PixelBuffer);   
具体这个函数参数怎么用不解释自己看函数说明。

好了现在framebuffer中的像素已经被读到pixelBuffer中了,这里面还要说明的是因为我的是RGBA格式所以1个像素是4个字节,如果是RGB那么就是3字节,分配内存的时候要注意。

原始像素有了,最后就是画bitmap了,Android有个创建bitmap的方法,如下:

[java]  view plain copy
  1. PixelBuffer.position(0);//这里要把读写位置重置下  
  2. int pix[] = new int[width*height];  
  3. PixelBuffer.get(pix);//这是将intbuffer中的数据赋值到pix数组中  
  4.   
  5. Bitmap bmp = Bitmap.createBitmap(pix, width, height,Bitmap.Config.ARGB_8888);//pix是上面读到的像素  
  6. FileOutputStream fos = null;  
  7. try {  
  8.     fos = new FileOutputStream("/sdcard/screen.png");//注意app的sdcard读写权限问题  
  9. catch (FileNotFoundException e) {  
  10. // TODO Auto-generated catch block  
  11.    e.printStackTrace();  
  12. }   
  13.           
  14.    bmp.compress(CompressFormat.PNG, 100, fos);//压缩成png,100%显示效果  
  15. try {  
  16.     fos.flush();  
  17. catch (IOException e) {  
  18. // TODO Auto-generated catch block  
  19.    e.printStackTrace();  
  20.    }  
  21.                           
  22. }  
好了,这下完是Ok了,打完收工。

PS:我也是刚刚研究这些东西,可能还有不全面的,只供参考,有什么问题,希望大家热心指认,谢谢。


转自:http://blog.csdn.net/helldevil/article/details/7513946


你可能感兴趣的:(android,OpenGL,es)