前言
上一篇文章我們學(xué)習(xí)了如何在安卓平臺(tái)搭建opengl es環(huán)境,如何通過(guò)TextureView加載一張圖片。其實(shí),通過(guò)前面的學(xué)習(xí),那么關(guān)于安卓平臺(tái)如何使用opengl es就掌握了一大部分了,剩下的就是性能等等余下的功能了;本篇文章的目標(biāo)如下:
1、渲染一張圖片之后再在圖片上畫一個(gè)對(duì)角線
2、把步驟一種的內(nèi)容截屏保存到系統(tǒng)相冊(cè)。
對(duì)于第二點(diǎn),可能有人覺(jué)得比較有用,比如視頻直播過(guò)程中,對(duì)某一時(shí)刻畫面進(jìn)行截屏。對(duì),實(shí)現(xiàn)原理和思路其實(shí)是一樣的,接下來(lái)我們逐一講解如何實(shí)現(xiàn)。
opengl es系列文章
opengl es之-基礎(chǔ)概念(一)
opengl es之-GLSL語(yǔ)言(二)
opengl es之-GLSL語(yǔ)言(三)
opengl es之-常用函數(shù)介紹(四)
opengl es之-SurfaceView、GLSurfaceView、TextureView環(huán)境搭建(十)
截屏實(shí)現(xiàn)思路
對(duì)于第一點(diǎn)我這里就不過(guò)多的講解了,其實(shí)比較簡(jiǎn)單,這里主要講一下第二點(diǎn)的實(shí)現(xiàn)思路。通過(guò)上一篇的學(xué)習(xí)我們知道,圖片最終渲染到屏幕上主要經(jīng)過(guò)了(這里以JPG為例)圖片解碼為Bitmap->bitmap上傳到opengl es->調(diào)用glDrawArrays()繪制->調(diào)用EGLSurface的swapBuffers()函數(shù),最終圖片將呈現(xiàn)在屏幕上。截屏,就是將opengl es的渲染結(jié)果讀取出來(lái)(一塊RGBA的像素?cái)?shù)據(jù))生成Bitmap然后在編碼為JPG的過(guò)程,那么這個(gè)動(dòng)作放在哪個(gè)階段呢?顯然要在glDrawArrays()之后,swapBuffers()函數(shù)之前,下面是流程圖

那截取如何實(shí)現(xiàn)呢?
其實(shí)很簡(jiǎn)單,關(guān)鍵函數(shù)就是glReadPixels(),這個(gè)函數(shù)的具體介紹已經(jīng)在opengl es之-常用函數(shù)介紹(四)詳細(xì)介紹了,這里就不多說(shuō)了。
這個(gè)函數(shù)的功能就是從渲染結(jié)果中讀取像素?cái)?shù)據(jù)到指定的緩沖區(qū),數(shù)據(jù)讀取出來(lái)后就可以轉(zhuǎn)換成Bitmap了,這就是具體思路,好,下面詳細(xì)講一下,關(guān)鍵代碼。
圖片上畫對(duì)角線
講截屏之前,先講一下如何實(shí)現(xiàn)再圖片上畫一條對(duì)角線。我們知道,加載圖片的時(shí)候需要一個(gè)頂點(diǎn)坐標(biāo),紋理坐標(biāo),著色器,最后調(diào)用glDrawArrays()將圖片渲染出來(lái),那畫一條對(duì)角線呢?一樣的思路,需要這些步驟,只不過(guò)畫線不需要紋理坐標(biāo)和在片元著色器中對(duì)紋理進(jìn)行操作了,這里講一下畫線和畫圖片的不一樣地方
1、頂點(diǎn)坐標(biāo)
// 對(duì)角線的頂點(diǎn)坐標(biāo)
private static final float verdata1[] = {
-1.0f,-1.0f,
1.0f,1.0f,
1.0f,-1.0f,
-1.0f,1.0f,
};
2、片元著色器。
頂點(diǎn)著色器和加載圖片是一樣的
// 片元著色器
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的代碼流程是一樣的,這里我貼一下關(guān)鍵代碼,這里就不具體貼出了,具體可以參考我的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、這里是截屏的代碼
}
// 必須要有,否則渲染結(jié)果不會(huì)呈現(xiàn)到屏幕上
mSurface.swapBuffers();
}
可以看到,畫線只需要調(diào)用glLineWidth()指定線寬然后再調(diào)用glDrawArrays()即可
截屏
上面的代碼段我已經(jīng)標(biāo)出了截屏代碼的位置,沒(méi)錯(cuò),就在哪里,那截屏的代碼如何寫呢?下面貼出具體實(shí)現(xiàn)代碼
// 獲取渲染結(jié)果,以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.
// 讀取設(shè)置字節(jié)對(duì)齊
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("要讀取的長(zhǎng)和寬 w="+width + " hei " + height);
/** 遇到問(wèn)題:魅族 pro 7-s一直返回 0x502錯(cuò)誤(GL_INVALID_OPERATION),該錯(cuò)誤根據(jù)官方文檔的解釋是glReadPixels()函數(shù)的format和type和frame buffer
* 中像素的實(shí)際format、type不匹配造成的,返回錯(cuò)誤之后buf得不到任何數(shù)據(jù)
* 分析:但實(shí)際上format和type是對(duì)應(yīng)上的,而且buf也讀取到了正確的像素?cái)?shù)據(jù),仍然返回該錯(cuò)誤,不知道為何,有待進(jìn)一步研究。
* */
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);
// 創(chuàng)建新的圖片
Bitmap resizedBitmap = Bitmap.createBitmap(bmp, 0, 0,
bmp.getWidth(), bmp.getHeight(), matrix, true);
return resizedBitmap;
}
1、首先GLES20.glPixelStorei(GLES20.GL_PACK_ALIGNMENT,1);設(shè)置讀取像素?cái)?shù)據(jù)時(shí)的字節(jié)對(duì)齊方式,這里表示按照一字節(jié)對(duì)齊,雖然性能低,但是安全。
2、接下來(lái)ByteBuffer buf = ByteBuffer.allocateDirect(width * height * 4);來(lái)分配一塊內(nèi)存(寬高4)大小,用來(lái)接收像素?cái)?shù)據(jù),注意,
3、接下來(lái)很關(guān)鍵了buf.order(ByteOrder.LITTLE_ENDIAN);將這塊內(nèi)存數(shù)據(jù)的端序轉(zhuǎn)換為小端序,因?yàn)閖ava端都是大端序,而opengl es中的數(shù)據(jù)是小端序,所以這里要進(jìn)行轉(zhuǎn)換
4、調(diào)用GLES20.glReadPixels(0, 0, width, height, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, buf);從opengl es的渲染緩沖區(qū)中讀取RGBA數(shù)據(jù)到這個(gè)buf中,
5、生成Bitmap
Bitmap bmp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
bmp.copyPixelsFromBuffer(buf);
6、前面的文章中我們知道opengl es的中的紋理坐標(biāo)系和手機(jī)系統(tǒng)中的紋理坐標(biāo)系是剛剛好180度顛倒的,所以這里我們生成Bitmap之后還要進(jìn)行一個(gè)垂直選擇180度的翻轉(zhuǎn),這樣截取的圖片才是對(duì)的,否則是倒立的(有興趣的可以做個(gè)試驗(yàn))
android.graphics.Matrix matrix = new android.graphics.Matrix();
matrix.postRotate(180);
// 創(chuàng)建新的圖片
Bitmap resizedBitmap = Bitmap.createBitmap(bmp, 0, 0,
bmp.getWidth(), bmp.getHeight(), matrix, true);
以上就是截屏代碼的詳細(xì)講解
項(xiàng)目地址
https://github.com/nldzsz/opengles-android
1、在圖片上畫對(duì)角線參考MySurfaceView類中的代碼
2、截屏參考MySurfaceView類中的代碼