内容简介:大家好,我系苍王。以下是我这个系列的相关文章,有兴趣可以参考一下,可以给个喜欢或者关注我的文章。OpenGL和音视频相关的文章,将会在
大家好,我系苍王。
以下是我这个系列的相关文章,有兴趣可以参考一下,可以给个喜欢或者关注我的文章。
OpenGL和音视频相关的文章,将会在 [OpenGL]未来视觉-MagicCamera3实用开源库 当中给大家呈现 里面会记录我编写这个库的一些经历和经验。
这一章是写图片加载。
你用Android上开发,如果想直接加载一个图片会怎么做?
1.直接使用一个ImageView加载图片?
2.使用一个view的onDraw来绘制一个Bitmap图片数据?
这里ImageView其实在源码里都是通过onDraw方法使用canvas画图片(Drawable对象,并不是bitmap) 而Canvas对象实现在底层中使用了skcanvas库,用于将图片转换到底层硬件可以显示的数据。
下面一个简单的ImageView源码分析 ImageView核心源码分析
你需要知道几点
1.ImageView的canvas是通过绘制Drawable对象绘制,并不是bitmap
2.scaleType等转换,是通过 java 内置Matrix矩阵函数做转换的,最终通过canvas设置matrix矩阵
为什么你的canvas那么慢?浅析Android的canvas性能
这是canvas绘制的原理的一些分析
1.canvas是实用skia库来绘制的,实用cpu计算绘制
2.Android普通的view都是继承于GLES20RecordingCanvas,这个类绘制图都带有硬件加速
3.SurfaceView TextureView里面的Canvas并不是 GLES20RecordingCanvas,所以要特别注意,但是其实用纹理渲染是实用GPU的。
4.从Android绘制效率上说,硬件加速绘制>opengl绘制>canvas绘制
自定义view笔记-之关于硬件加速这篇是关于硬件加速的一些浅析
1.view无法强制开启硬件加速,只能强制关闭。
2.并不是view的绘制操作都支持硬件加速
那还有其他方式绘制图片吗?如果我要在图片中加入一些滤镜效果应该怎么做? 我们可以使用SurfaceView、TextureView或者GLSurfaceView来绘制图片,他们都会持有canvas对象,而且这些对象都是非硬件加速的。同时他们都可以自定义使用opengles来绘制。
我们上面说过普通的view的canvas都是使用GLES20RecordingCanvas来绘制,从名字上来看就知道是用opengles2.0版本来做硬件加速的转换编写了。
那么我们也是可以通过这些view来自定义加载Opengles的。之前已经介绍过MagicCamera3中相机是使用SurfaceView+Opengles的纹理加载方式来编写的。
GLSurfaceView其本身就自带了GLThread并初始化了EGL环境,系统默认mode==RENDERMODE_CONTINUOUSLY,这样系统会自动重绘;mode==RENDERMODE_WHEN_DIRTY时,只有surfaceCreate的时候会绘制一次,然后就需要通过requestRender()方法主动请求重绘。同时也提到,如果你的界面不需要频繁的刷新最好是设置成RENDERMODE_WHEN_DIRTY,这样可以降低CPU和GPU的活动,可以省电。
而SurfaceView你只会在触发的时候绘制一次,没有模式可以切换。GLSurfaceView是继承于SurfaceView。
下面就说一下怎么使用SurfaceView来绘制一个纹理图片
首先是要初始化Opengles,和之前介绍的摄像头的opengl的初始化类似,但是要传入surface对象,assets对象,图片的地址,以及图片的角度。
private fun initOpenGL(surface: Surface){ mExecutor.execute { //传入surface对象,assets对象,图片地址和图片角度 val textureId = OpenGLJniLib.magicImageFilterCreate(surface,BaseApplication.context.assets,imagePath,ExifUtil.getExifOrientation(imagePath)) if (textureId < 0){ Log.e(TAG, "surfaceCreated init OpenGL ES failed!") return@execute } mSurfaceTexture = SurfaceTexture(textureId) //如果帧图有改变就画图 mSurfaceTexture?.setOnFrameAvailableListener { //画图 drawOpenGL() } } } 复制代码
这里如果你有办法使用C++读取到图片数据头Exif数据,最好还是使用C++来做,这边因为网上找了很久都没有能简单使用C++读取exif数据的方法,故在讨巧的使用java层解析读取好,然后再传入native,至于角度有什么作用,就看后面的解析吧。
//图片滤镜surfaceView初始化的时候创建 JNIEXPORT jint JNICALL Java_com_cangwang_magic_util_OpenGLJniLib_magicImageFilterCreate(JNIEnv *env, jobject obj, jobject surface,jobject assetManager,jstring imgPath,jint degree) { std::unique_lock<std::mutex> lock(gMutex); if(glImageFilter){ //停止摄像头采集并销毁 glImageFilter->stop(); delete glImageFilter; glImageFilter = nullptr; } //初始化native window ANativeWindow *window = ANativeWindow_fromSurface(env,surface); //初始化app内获取数据管理 aAssetManager= AAssetManager_fromJava(env,assetManager); //初始化图片 jstring转为std::string const char* addressStr = env->GetStringUTFChars(imgPath,0); std::string nativeAddress = addressStr; glImageFilter = new ImageFilter(window,aAssetManager,nativeAddress,degree); env->ReleaseStringUTFChars(imgPath, addressStr); //创建 return glImageFilter->create(); } 复制代码
初始化时还需要设置图片角度
void ImageFilter::setFilter(AAssetManager* assetManager) { if(filter != nullptr){ filter->destroy(); } filter = new MagicNoneFilter(assetManager); filter->setPool(pool); //调整滤镜中的图片的方向问题 filter->setOrientation(degree); ALOGD("set filter success"); } void GPUImageFilter::setOrientation(int degree) { this->degree = degree; //获取绘制时需要的角度变换,这里只是兼容图片0,90,180,270度 mGLTextureBuffer = getRotation(degree, false, false); } 复制代码
兼容角度计算,这个是在shader加载的时候需要调整角度的
//获取角度 float* getRotation(int degree, const bool flipHorizontal, const bool flipVertical){ const float* rotateTex; //调整角度 switch (degree){ case 90: rotateTex = TEXTURE_ROTATED_90; break; case 180: rotateTex = TEXTURE_ROTATED_180; break; case 270: rotateTex = TEXTURE_ROTATED_270; break; case 0: default: rotateTex = TEXTURE_NO_ROTATION; break; } //垂直翻转 if (flipHorizontal){ const static float flipTran[]={ flip(rotateTex[0]),rotateTex[1], flip(rotateTex[2]),rotateTex[3], flip(rotateTex[4]),rotateTex[5], flip(rotateTex[6]),rotateTex[7] }; return const_cast<float *>(flipTran); } //水平翻转 if (flipVertical){ const static float flipTran[]={ rotateTex[0],flip(rotateTex[1]), rotateTex[2],flip(rotateTex[3]), rotateTex[4],flip(rotateTex[5]), rotateTex[6],flip(rotateTex[7]) }; return const_cast<float *>(flipTran); } return const_cast<float *>(rotateTex); } 复制代码
创建纹理
int ImageFilter::create() { //初始化,清空视口颜色 glDisable(GL_DITHER); glClearColor(0,0,0,0); glEnable(GL_CULL_FACE); glEnable(GL_DEPTH_TEST); //创建EGL环境 if (!mEGLCore->buildContext(mWindow,EGL_NO_CONTEXT)){ return -1; } //图片初始化 if (imageInput!= nullptr){ imageInput->init(); } //滤镜初始化 if (filter!= nullptr) filter->init(); //获取纹理id mTextureId = get2DTextureID(); ALOGD("get textureId success"); return mTextureId; } 复制代码
输入视口大小,这里需要设置显示图片显示尺寸,以及屏幕尺寸
void ImageFilter::change(int width, int height) { //设置视口 glViewport(0,0,width,height); this->mScreenWidth = width; this->mScreenHeight = height; if (imageInput!= nullptr){ //触发输入大小更新 imageInput->onInputSizeChanged(width, height); //初始化图片帧缓冲 imageInput->initFrameBuffer(imageInput->mImageWidth,imageInput->mImageHeight); if (filter != nullptr){ //设置滤镜宽高 filter->onInputSizeChanged(width,height); //设置图片的宽高 filter->onInputDisplaySizeChanged(imageInput->mImageWidth,imageInput->mImageHeight); //设置矩阵 setMatrix(width,height); //初始化滤镜帧缓冲 filter->initFrameBuffer(imageInput->mImageWidth,imageInput->mImageHeight); } else{ //销毁图片帧缓冲 imageInput->destroyFrameBuffers(); } } } 复制代码
这个网上找的时候,网上图片是以0度为标准,可以用以下代码来显示。通过正交投影很简单就能完成。
public void onSurfaceChanged(GL10 glUnused, int width, int height) { // Set the OpenGL viewport to fill the entire surface. glViewport(0, 0, width, height); final float aspectRatio = width > height ? (float) width / (float) height : (float) height / (float) width; if (width > height) { // Landscape orthoM(projectionMatrix, 0, -aspectRatio, aspectRatio, -1f, 1f, -1f, 1f); } else { // Portrait or square orthoM(projectionMatrix, 0, -1f, 1f, -aspectRatio, aspectRatio, -1f, 1f); } } 复制代码
但是图片的角度会对图片大小显示比例会有影响,如果调整不正确,显示会问题非常凸显,这里就只区分90和270,还得通过屏幕尺寸、图片角度、图片尺寸来计算出正交矩阵,有些相机拍照后保存的图片是偏移这两种角度的。这里屏幕一直是竖屏方向,还没测试过横屏。
void ImageFilter::setMatrix(int width,int height){ memcpy(mvpMatrix,NONE_MATRIX,16); if (degree == 90 || degree == 270){ //先判断角度 float x; if(imageInput->mImageHeight>imageInput->mImageWidth){ //图片宽比高要大 ,屏幕宽/屏幕高 * 屏幕高/屏幕宽 x = width / (float) height * (float) imageInput->mImageHeight / imageInput->mImageWidth; } else{ //图片宽比高要大 ,屏幕高/屏幕宽 * 屏幕高/屏幕宽 x = height / (float) width * (float) imageInput->mImageHeight / imageInput->mImageWidth; } ALOGD("x=%f",x); orthoM(mvpMatrix, 0, -1, 1, -x, x, -1, 1); } else{ //图片高比宽要大 ,屏幕宽/屏幕高 * 屏幕高/屏幕宽 float y; if(imageInput->mImageHeight>imageInput->mImageWidth){ y = width / (float) height * (float) imageInput->mImageHeight / imageInput->mImageWidth; } else{ //图片高比宽要大 ,屏幕高/屏幕宽 * 屏幕宽/屏幕高 y = height / (float) width * ((float) imageInput->mImageWidth / imageInput->mImageHeight); } ALOGD("y=%f",y); orthoM(mvpMatrix, 0, -1, 1, -y, y, -1, 1); } filter->setMvpMatrix(mvpMatrix); } 复制代码
这里计算后显示到屏幕的尺寸是正常的。通过正交矩阵来做缩放比例,视口还是屏幕尺寸。
这种加载比屏幕大很多的图片的时候,会需要一定的延迟,因为解析成纹理也是需要时间的。
经过计算使用stb_image来加载3840*2160的图片,小米6上耗时700毫秒以上,那么首次显示到屏幕上会黑屏一下。
如果大家有优化的方法可以告诉我这边,我也继续试验完善。
新建一个专栏群,希望有兴趣的同学多多讨论。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- OpenGL ES 入门之旅 -- GLSL纹理单元和纹理翻转解决策略
- OpenGL ES入门: 渲染金字塔 - 颜色、纹理、纹理与颜色混合填充以及GLKit实现
- WebGL 纹理详解
- WebGL 纹理颜色原理
- Unity中纹理格式探究
- 游戏制作之路(48)地形纹理工具
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
C程序设计题解与上机指导
谭浩强 / 清华大学 / 2000-9-1 / 19.50元
《C程序设计题解与上机指导(第2版)》是和谭浩强编著的《C程序设计》(第二版)(清华大学出版社1999年出版)配合使用的参考书,内容包括:(1)《程序设计》(第二版)一书的习题和参考解答,包括了该书各章的全部习题,共约150题,对全部编程题都给出了参考解答;(2)上机指南,详细介绍了在当前广泛使用的Turbo C集成环境下编辑、编译、调试和运行程序的方法,并简要介绍了Borland C++的使用方......一起来看看 《C程序设计题解与上机指导》 这本书的介绍吧!