android使用opengles渲染一张图片
一、概述
案例:使用opengles+egl渲染一张图片
关键类介绍:
1.新建一个DrawPictureActivity.java用于充当显示容器:初始化SurfaceView并设置SurfaceView的callback回调函数。并在其onSurfaceCreated函数中对DrawPicture对象进行初始化。
2.新建DrawPicture.java用于java层和native层进行通讯
3.新建picture_controller.cpp用于和DrawPicture.java沟通java传递给jni层的数据。并在其init方法中创建ANativeWindow、提取Bitmap中的像素数组,初始化C++类Picture类
4.新建picture.cpp类,此类为整个渲染过程提供:OpenGL ES上下文环境、显示设备EGLSurface、GPU程序(shader)、OpenGL ES程序
渲染步骤:
1.DrawPictureActivity.java中在其SurfaceHolder.addCallback的回调函数中初始化DrawPicture.java类,并调用DrawPicture对象的initBitmap方法将客户端的bitmap传递给jni层
2.在jni层的(picture_controller.cpp)initBitmap方法中,根据传递过来的Surface对ANativeWindow进行创建,根据传递过来的Bitmap对其中的像素进行提取。
3.实例化C++的Picture并调用其init方法,在init方法中会初始化OpenGL ES上下文环境以及显示设备EGLSurface,并调用eglMakeCurrent来为当前线程绑定上下文及显示设备。
3.调用C++的Picture实例中的renderPicture方法,通过此方法,a.创建一个纹理id、绑定纹理id、设置纹理的过滤方式及平铺方式、并将从第二步中提取出来的像素数组上传到这个纹理上。b.创建shader 。c.创建opengles程序 。d.给opengles着色器传递坐标数据(顶点坐标、纹理坐标)。e.调用glDrawArray方法开始绘制纹理 f.调用eglSwapBuffers方法将back frame buffer 和front frame buffer交互,将图像显示到用户屏幕上。
二、关键代码
1.DrawPictureActivity.java:初始化SurfaceView并得到SurfaceHolder,然后给SurfaceHolder设置回调函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | surfaceView = findViewById(R.id.pictureSurfaceView); holder = surfaceView.getHolder(); holder.addCallback( new SurfaceHolder.Callback() { @Override public void surfaceCreated(@NonNull SurfaceHolder holder) { drawPicture = new DrawPicture(); parseBitmap(); Log.e( "surfaceCreated" , "" + pixels.length); // drawPicture.initByte(bitmapWidth, bitmapHeight, bitmap2RGB(bitmap), holder.getSurface()); drawPicture.initBitmap(bitmap,holder.getSurface()); } @Override public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) { drawPicture.onSurfaceChanged(width,height); } @Override public void surfaceDestroyed(@NonNull SurfaceHolder holder) { } }); |
2.DrawPicture.java:和jni沟通的类
public native void initBitmap(Bitmap bitmap,Surface surface); /** * 当surface发生改变的时候更新width、height * * @param mWidth 宽度 * @param mHeight 高度 */ public native void onSurfaceChanged(int mWidth, int mHeight);
3.picture_controller.cpp :提取bitmap像素数组、初始化ANativeWindow、初始化Picture C++类
extern "C" JNIEXPORT void JNICALL Java_com_yw_ywmediaplayer_activity_nativeinterface_DrawPicture_initBitmap(JNIEnv *env, jobject thiz, jobject bitmap, jobject surface) { void* pixels =NULL; AndroidBitmapInfo bitmapInfo; int ret = AndroidBitmap_getInfo(env,bitmap,&bitmapInfo); if(ret<0){ LOGE("get bitmap info error"); return; } LOGE("bitmapInfo:width=[%d],height=[%d]",bitmapInfo.width,bitmapInfo.height); if(bitmapInfo.format!=ANDROID_BITMAP_FORMAT_RGBA_8888){ LOGE("not support RGBA8888"); return; } ret = AndroidBitmap_lockPixels(env,bitmap,&pixels); if(ret<0){ LOGE("get bitmap pixels fail"); return; } _window = ANativeWindow_fromSurface(env, surface); LOGE("实例化Picture"); picture = new Picture(bitmapInfo.width, bitmapInfo.height, pixels, _window); LOGE("picture->init()"); picture->init(); AndroidBitmap_unlockPixels(env,bitmap); }
3.Picture C++类
a.初始化OpenGL ES上下文环境及显示设备
/** * 初始化egl并创建eglsurface */ void Picture::init() { eglCore = new EGLCore(); eglCore->init(); eglSurface = eglCore->createWindowSurface(_window); //给当前线程绑定上下文环境及显示设备 eglCore->makeCurrent(eglSurface); }
b.设置窗口大小及清除各种缓冲区
void Picture::initViewPort() { //设置窗口大小 glViewport(0, 0, screenWidth, screenHeight); LOGE("screenWidth=[%d],screenHeight=[%d]",screenWidth,screenHeight); glClearColor(0.0f, 0.0f, 1.0f, 0.0f); glClear(GL_COLOR_BUFFER_BIT); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); //reset xoffset yoffset resizeTexture(); }
c.创建纹理id、绑定纹理id、设置纹理过滤方式及纹理的重复映射和简约映射方式、将像素数组上传到纹理上
/**创建纹理*/ //创建并绑定纹理 LOGE("glGenTextures"); GLuint textureId = 0; glGenTextures(1, &textureId); LOGE("glBindTexture"); //此处创建纹理以后需要绑定纹理id,并给纹理设置过滤方式及映射方式,不然会黑屏 glBindTexture(GL_TEXTURE_2D, textureId);//在opengles操作过程中必须告诉opengles操作的是哪个纹理,所以要调用操作opengles提供的绑定纹理的方法绑定纹理 /**设置纹理参数*/ //设置纹理的过滤方式(双线性过滤):当纹理对象(可以理解为一张图片)被渲染到物体表面上的时候(实际上是OpenGL绘制管线将纹理的元素映射到OpenGL生成的片段上的时候),有可能要被放大或者缩小,而当其放大或者缩小的时候,具体应该如何确定每个像素是如何被填充的,就由开发者配置的纹理对象的纹理过滤器来指明 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); //设置纹理映射过程中用到的重复映射和简约映射规则:将该纹理的s轴和t轴的坐标设置为GL_CLAMP_TO_EDGE类型,因为纹理坐标可以超出(0,1)的范围,而按照上述设置规则,所有大于1的纹理值都要设置为1,所有小于0的值都要置为0。 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); /**将RGBA表示的橡树数组上传到纹理上*/ //将RGBA数组表示的像素内容上传到纹理上 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bitmapWidth, bitmapHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, (uint8_t *) pixels);
d.创建opengles可执行程序及着色器
/**创建opengles显卡可执行程序*/ //创建opengles程序 LOGE("glCreateProgram"); GLuint programId = glCreateProgram(); GLuint vertexShader = createShader(GL_VERTEX_SHADER, VERTEX_SHADER_SOURCE); GLuint framgentShader = createShader(GL_FRAGMENT_SHADER, FRAGMENT_SHADER_SOURCE); LOGE("glAttachShader"); glAttachShader(programId, vertexShader); glAttachShader(programId, framgentShader); LOGE("glLinkProgram"); glLinkProgram(programId); GLint status; glGetProgramiv(programId, GL_LINK_STATUS, &status); if (status == GL_FALSE) { GLint bufLength = 0; glGetProgramiv(programId, GL_INFO_LOG_LENGTH, &bufLength); if (bufLength) { char *buf = (char *) malloc(bufLength); if (buf) { glGetProgramInfoLog(programId, bufLength, NULL, buf); LOGI("Could not link program:\n%s\n", buf); free(buf); } } glDeleteProgram(programId); programId = 0; return -1; } GLint mPositionHandle = glGetAttribLocation(programId, "position"); GLint mTextureHandle = glGetAttribLocation(programId, "texcoord"); GLint mUniformSampler = glGetUniformLocation(programId, "yuvTexSampler"); LOGE("glUseProgram"); glUseProgram(programId);
/** * 创建shader * @param shaderType shader类型 * @param source shader程序字符串 * @return shader */ GLuint Picture::createShader(GLenum shaderType, const char *source) { GLint status; GLuint shader = glCreateShader(shaderType); glShaderSource(shader, 1, &source, NULL); glCompileShader(shader); glGetShaderiv(shader, GL_COMPILE_STATUS, &status); if (!status) { GLint infoLen = 0; glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen); if (infoLen) { char *buf = (char *) malloc(infoLen); if (buf) { glGetShaderInfoLog(shader, infoLen, NULL, buf); LOGI("Could not compile shader %d:\n%s\n", shaderType, buf); free(buf); } } else { LOGI("Guessing at GL_INFO_LOG_LENGTH size\n"); char *buf = (char *) malloc(0x1000); if (buf) { glGetShaderInfoLog(shader, 0x1000, NULL, buf); LOGI("Could not compile shader %d:\n%s\n", shaderType, buf); free(buf); } } glDeleteShader(shader); shader = 0; } return shader; }
e.设置顶点坐标、纹理坐标、激活纹理并使用纹理
LOGE("glGetAttribLocation"); //设置顶点坐标 glVertexAttribPointer(mPositionHandle, 2, GL_FLOAT, 0, 0, VERTEXS_POINTS); glEnableVertexAttribArray(mPositionHandle); //设置纹理坐标 LOGE("glGetAttribLocation"); GLfloat TEXTURE_POINTS[] = { xOffset, yOffset, xOffset, 1.0f - yOffset, 1.0f - xOffset, yOffset, 1.0f - xOffset, 1.0f - yOffset }; glVertexAttribPointer(mTextureHandle, 2, GL_FLOAT, 0, 0, TEXTURE_POINTS); glEnableVertexAttribArray(mTextureHandle); glActiveTexture(GL_TEXTURE0); // glBindTexture(GL_TEXTURE_2D, textureId); glUniform1i(mUniformSampler, 0); checkGlError("glUniform1i");
f.开始绘制
//开始绘制 glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
g.将back frame buffer和front frame buffer进行交换,并将图像显示到屏幕上
LOGE("将front frame buffer 与back frame buffer交换并显示到屏幕上"); eglCore->swapBuffers(eglSurface);
h.到此结束,运行后就能显示出图片了。
三、示例图片
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
2014-11-19 this computer meets the requirements for HAXM,but intel Virtualization Technology (VT-x) is not turned on
2013-11-19 移动产品设计书籍推荐