Android opengl ES 實現後台繪圖並儲存成bitmap

來源:互聯網
上載者:User

最近在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 endpixeldata = (GlutByte)malloc(width*height*bytes);glReadPixels(x, y, width, height, GL_BGR_EXT, GL_UNSIGNED_BYTE, pixeldata);

這個用到glut包,上面幾個是關鍵函數,如果大家想知道如何去畫bitmap的話,下面我也貼下畫bitmap的方式,無非就是把讀到的像素值pixeldata最後寫道bitmap檔案中,不過這裡要注意兩點,1個是bitmap的像素排列格式是BGR,所以當你試圖去

glReadPixels

擷取原始像素的時候請使用GL_BGR_EXT這個參數,其次bitmap是個結構體,在C,C++代碼處寫起來還是需要一定的格式的,不然產生的bitmap檔案有問題,具體的格式可以去查,我貼下我從網上找來的一段實現bitmap的代碼(經過驗證這個是可用的,寫這個的人還是比較靠譜的,贊一個),如下:

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");            }          });       

3  下面就是BackDraw的類的具體搭建了,我在這個類的建構函式中去初始化EGL環境,為後面的畫圖渲染創造條件。這個裡面需要說明的是,一般我們想要用opengl渲染圖片或者繪製圖片都是通過GLSurfaceView.render做的,目的是通過在onDrawFrame裡面調用gl函數在後台framebuffer中畫圖片,然後把圖片顯示到前台,這個一般是自動的。如果你不改任何東西,那麼只要你一畫好,你就會在前台看到你畫的東東。那麼如何將圖片畫在後台,而不自動顯示到前台呢,我仔細看了下GLSurfaceView的實現,這個是繼承於SurfaceView類,這個類在surfacecreate裡面有我們想要的參考代碼,這裡不作具體說明,我只想說靈魂就是1個函數

eglCreatePbufferSurface

這個函數是在記憶體中建立1個off-screen的framebuffer,我們繪製圖片可以在這個上面繪製,具體每個函數幹麼用的可以參考EGL官方網站的函數說明EGL HOME, 這裡不多描述了,總之在我們要畫圖之前,我們先要解決如何構建畫圖的環境,畫在哪的問題,在我們開始搭建環境之間,EGL需要有些屬性建立,如長寬,像素的byte大小,Surface類型等等,如下面

        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,

如果你要建立的是PbufferSurface(後台顯示)類型,就要說明這個,如果要建立WindowSurface(前台顯示),需要EGL10.EGL_WINDOW_BIT類型,還有的是屬性數組最好不要亂加值,有些函數只能接受特定的值,如果你亂加,函數在執行的時候會失敗,比如

attribListPbuffer

它是eglCreatePbufferSurface函數在建立PbufferSuface時傳入的屬性,它只接受三個屬性,我一開始加了個其他的屬性,結果導致建立失敗。

還有個要提到的,就是attribListPbuffer[]數組,一定要把長寬的配置設定了

EGL10.EGL_WIDTH, 480,EGL10.EGL_HEIGHT, 800,

因為如果不設定,預設是0,如果你等會在這個surface上畫圖你會悲催的哭,因為你無論怎麼畫,畫到地球毀滅,最後在surface上的只有空氣。。。如果你要畫480*800的圖,那麼你就把surface的長寬也相應的設下。

好了屬性配置好了,下面就是構造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();

擷取上次egl執行函數的錯誤碼,通過比較錯誤碼,可以找到錯誤問題點,我的

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();   }}

好了,這下完是Ok了,打完收工。

PS:我也是剛剛研究這些東西,可能還有不全面的,只供參考,有什麼問題,希望大家熱心指認,謝謝。


相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.