最近在android 上有个构思,就是如何使用opengl ES在后台绘制个3D图片,然后把这个绘制好的图片保存成bitmap格式。。。想了好几天,也尝试了多种方法,但是都不行,一开始尝试用GLSurfaceView的方式,但是这样会导致我的Activity和渲染的东东发生联系,我想要要的结果是无论如何我的主Acivity都不能和我渲染的图片发生任何关系(也就是说主Acitivity不能显示任何我渲染的东西出来)。
首先来说的话,opengl es是来自于Opengl(精简版),ES针对嵌入式灵巧的设备(embided device),而opengl是针对PC这样的超级怪物
,这也就不难理解它为什么要被"瘦身"了,在opengl中有个双缓冲的概念,也就是说前面显示,后面画图,这样可以达到无闪烁的境界。所以理论上来说我们应该也要效仿这种方式,将图片绘制到后台缓冲中,达到目的。这里先贴个opengl的方式:
- glutInitDisplayMode(GLUT_RGB|GLUT_DOUBLE);
- // draw
- ...
- // draw end
- pixeldata = (GlutByte)malloc(width*height*bytes);
- glReadPixels(x, y, width, height, GL_BGR_EXT, GL_UNSIGNED_BYTE, pixeldata);
- glReadPixels
- typedef long LONG;
- typedef unsigned char BYTE;
- typedef unsigned int DWORD;
- typedef unsigned short WORD;
- typedef struct {
- WORD bfType;
- DWORD bfSize;
- WORD bfReserved1;
- WORD bfReserved2;
- DWORD bfOffBits;
- } BMPFILEHEADER_T;
- typedef struct{
- DWORD biSize;
- DWORD biWidth;
- DWORD biHeight;
- WORD biPlanes;
- WORD biBitCount;
- DWORD biCompression;
- DWORD biSizeImage;
- DWORD biXPelsPerMeter;
- DWORD biYPelsPerMeter;
- DWORD biClrUsed;
- DWORD biClrImportant;
- } BMPINFOHEADER_T;
- void init();
- void display();
- static GLubyte *PixelData;
- void Snapshot( BYTE * pData, int width, int height, char * filename ,DWORD size)
- {
- // 位图第一部分,文件信息
- BMPFILEHEADER_T bfh={0};
- bfh.bfType = (WORD)0x4d42; //bm
- bfh.bfSize = (DWORD)(size+54);
- bfh.bfReserved1 = 0; // reserved
- bfh.bfReserved2 = 0; // reserved
- bfh.bfOffBits = 54;
- // 位图第二部分,数据信息
- BMPINFOHEADER_T bih={0};
- bih.biSize = 40;
- bih.biWidth = width;
- bih.biHeight = height;
- bih.biPlanes = 1;
- bih.biBitCount = 24; //24真彩色位图
- bih.biCompression = 0;
- bih.biSizeImage = 0;
- bih.biXPelsPerMeter = 0;
- bih.biYPelsPerMeter = 0;
- bih.biClrUsed = 0;
- bih.biClrImportant = 0;
- FILE * fp = fopen(filename,"wb");
- if( !fp ) return;
- fwrite( &bfh.bfType,1,2,fp );
- fwrite( &bfh.bfSize,1,4,fp );
- fwrite( &bfh.bfReserved1,1,2,fp );
- fwrite( &bfh.bfReserved2,1,2,fp );
- fwrite( &bfh.bfOffBits,1,4,fp );
- fwrite( &bih,1,sizeof(BMPINFOHEADER_T),fp );
- fwrite(pData,1,size,fp);
- fclose( fp );
- }
废话不多说开始入正题:
先构思下,我们需要要建个很一般的Acitivity,然后在上面加个按钮,当点击按钮的时候,开始在后台绘制图片,然后将图片的pixel读出来,转化成bitmap 保存。
Idea有了,那么开始干活。
1 建立个Acivity,按照Android的工程步骤提示在eclipse里面建立,这个不多说,我是App文盲,我都知道怎么做。
2 在本地Avivity的onCreate里面添加button和button监听事件,并且在里面处理初始化后台画图的一些操作。
- btn.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v)
- {
- // TODO Auto-generated method stub
- //prepare init EGL environment
- BackDraw = new BackDraw(); //init backdraw
- Log.d("GlActivity:", "render in background");
- }
- });
- eglCreatePbufferSurface
- private int[] version = new int[2];
- EGLConfig[] configs = new EGLConfig[1];
- int[] num_config = new int[1];
- //EglchooseConfig used this config
- int[] configSpec ={
- EGL10.EGL_SURFACE_TYPE, EGL10.EGL_PBUFFER_BIT,
- EGL10.EGL_RED_SIZE, 8,
- EGL10.EGL_GREEN_SIZE, 8,
- EGL10.EGL_BLUE_SIZE, 8,
- EGL10.EGL_ALPHA_SIZE, 8,
- EGL10.EGL_NONE
- };
- //eglCreatePbufferSurface used this config
- int attribListPbuffer[] = {
- EGL10.EGL_WIDTH, 480,
- EGL10.EGL_HEIGHT, 800,
- EGL10.EGL_NONE
- };
- EGL10.EGL_SURFACE_TYPE, EGL10.EGL_PBUFFER_BIT,
- attribListPbuffer
还有个要提到的,就是attribListPbuffer[]数组,一定要把长宽的配置设置了
- EGL10.EGL_WIDTH, 480,
- EGL10.EGL_HEIGHT, 800,
好了属性配置好了,下面就是构造EGL环境,做的事情可以概括为三件事
1 弄个PbufferSurface出来
2 弄个context出来,并把这个context绑定到surface中
3 通过context弄个GL对象,用这个GL对象绘制渲染图片。
Here we go...
- private void initEGL()
- mEgl = (EGL10)EGLContext.getEGL();
- EGLDisplay mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
- mEgl.eglInitialize(mEglDisplay, version);
- mEgl.eglChooseConfig(mEglDisplay, configSpec, configs, 1, num_config);
- EGLConfig mEglConfig = configs[0];
- EGLContext mEglContext = mEgl.eglCreateContext(mEglDisplay, mEglConfig,
- EGL10.EGL_NO_CONTEXT,
- null);
- if (mEglContext == EGL10.EGL_NO_CONTEXT)
- {
- //mEgl.eglDestroySurface(mEglDisplay, mEglSurface);
- Log.d("ERROR:", "no CONTEXT");
- }
- //注意这个attribListPbuffer,属性表
- EGLSurface mEglPBSurface = mEgl.eglCreatePbufferSurface(mEglDisplay, mEglConfig, attribListPbuffer);
- if (mEglPBSurface == EGL10.EGL_NO_SURFACE)
- {
- //mEgl.eglDestroySurface(mEglDisplay, mEglPBSurface);
- int ec = mEgl.eglGetError();
- if (ec == EGL10.EGL_BAD_DISPLAY)
- {
- Log.d("ERROR:", "EGL_BAD_DISPLAY");
- }
- if (ec == EGL10.EGL_BAD_DISPLAY)
- {
- Log.d("ERROR:", "EGL_BAD_DISPLAY");
- }
- if (ec == EGL10.EGL_NOT_INITIALIZED)
- {
- Log.d("ERROR:", "EGL_NOT_INITIALIZED");
- }
- if (ec == EGL10.EGL_BAD_CONFIG)
- {
- Log.d("ERROR:", "EGL_BAD_CONFIG");
- }
- if (ec == EGL10.EGL_BAD_ATTRIBUTE)
- {
- Log.d("ERROR:", "EGL_BAD_ATTRIBUTE");
- }
- if (ec == EGL10.EGL_BAD_ALLOC)
- {
- Log.d("ERROR:", "EGL_BAD_ALLOC");
- }
- if (ec == EGL10.EGL_BAD_MATCH)
- {
- Log.d("ERROR:", "EGL_BAD_MATCH");
- }
- }
- if (!mEgl.eglMakeCurrent(mEglDisplay, mEglPBSurface, mEglPBSurface,
- mEglContext))//这里mEglPBSurface,意思是画图和读图都是从mEglPbSurface开始
- {
- Log.d("ERROR:", "bind failed ECODE:"+mEgl.eglGetError());
- }
- GL10 gl = (GL10) mEglContext.getGL();
- }
以上具体的初始化流程我是参考 http://blog.sina.com.cn/s/blog_413978670100bxsl.html . 小提示: 有的时候在这些创建过程中,会有失败,我们可以通过调用
- mEgl.eglGetError();
- eglCreatePbufferSurface
好了现在该有的都有了,下面就是开始在你"纸"上画图了,具体怎么画我就不多说了吧,无非是什么gl.clear()啊。。。。
Ok现在图画完了,那么该保存图片了,这些图片的像素值都被保存在刚才我们建立的PB Framebuffer中,也就是那个存在于内存中的off-screen surface.下面就是读图了,和Opengl一样,读取当前framebuffer中的像素的方式都是gl.glReadPixels这个函数,实现如下,注意它的pixel参数是IntBuffer类型
- IntBuffer PixelBuffer = IntBuffer.allocate(width*height);
- PixelBuffer.position(0);
- gl.glReadPixels(0, 0, width, height, GL10.GL_RGBA, GL10.GL_UNSIGNED_BYTE, PixelBuffer);
好了现在framebuffer中的像素已经被读到pixelBuffer中了,这里面还要说明的是因为我的是RGBA格式所以1个像素是4个字节,如果是RGB那么就是3字节,分配内存的时候要注意。
原始像素有了,最后就是画bitmap了,Android有个创建bitmap的方法,如下:
- PixelBuffer.position(0);//这里要把读写位置重置下
- int pix[] = new int[width*height];
- PixelBuffer.get(pix);//这是将intbuffer中的数据赋值到pix数组中
- Bitmap bmp = Bitmap.createBitmap(pix, width, height,Bitmap.Config.ARGB_8888);//pix是上面读到的像素
- FileOutputStream fos = null;
- try {
- fos = new FileOutputStream("/sdcard/screen.png");//注意app的sdcard读写权限问题
- } catch (FileNotFoundException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- bmp.compress(CompressFormat.PNG, 100, fos);//压缩成png,100%显示效果
- try {
- fos.flush();
- } catch (IOException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
PS:我也是刚刚研究这些东西,可能还有不全面的,只供参考,有什么问题,希望大家热心指认,谢谢。
转自:http://blog.csdn.net/helldevil/article/details/7513946