内容简介:大家好,我系苍王。以下是我这个系列的相关文章,有兴趣可以参考一下,可以给个喜欢或者关注我的文章。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毫秒以上,那么首次显示到屏幕上会黑屏一下。
如果大家有优化的方法可以告诉我这边,我也继续试验完善。
新建一个专栏群,希望有兴趣的同学多多讨论。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Usability for the Web
Tom Brinck、Darren Gergle、Scott D. Wood / Morgan Kaufmann / 2001-10-15 / USD 65.95
Every stage in the design of a new web site is an opportunity to meet or miss deadlines and budgetary goals. Every stage is an opportunity to boost or undercut the site's usability. Thi......一起来看看 《Usability for the Web》 这本书的介绍吧!