【Qt OpenGL教程】29:Blitter函数
2015-08-16 19:19
495 查看
第29课:Blitter函数 (参照NeHe)
这次教程中,我们将介绍类似于DirectDraw的blit(其实blit函数在许多绘图库都有),我们将用代码自己来实现它。它的作用非常简单,就是把一块纹理的贴到另一块纹理上。想想,有了这个函数,我们就可以自由拼接纹理了,是不是很棒?
这一课中,我们不但会实现blit函数,我们还将讲解如何来加载特定的*.raw图片。raw格式的图片,可以理解为CMOS或者CCD图像感应器将捕捉到的光源信号转化为数字信号的原始数据。可悲的是,Qt并没有提供加载raw图片的方法,所以我们只能自己写了(这是处理图像的一课,与OpenGL关系不大)!
程序运行时效果如下:
下面进入教程:
我们这次将在第06课的基础上修改代码,我们只讲解新的内容,旧的内容到这里大家应该掌握得很好了才对。首先打开myglwidget.h文件,将类声明修改如下:
接下来,我们需要打开myglwidget.cpp,加上声明#include <QMessageBox>,对构造函数进行修改,很简单不多解释,具体代码如下:
下面,我们来看allocateTextureBuffer()、deallocateTexture()函数的定义,具体代码如下:
首先是allocateTextureBuffer()函数,这个函数用来为图像结构体分配内存。函数中,我们为ti和c分别分配内存,其中为c分配时,其大小是w*h*f;然后如果分配不成功,则利用QMessageBox来报告错误,并退出程序。
然后是deallocateTexture()函数,这个函数用来释放我们分配的内存。函数中,我们把t和data指向的内存都释放掉。
继续,我们来看loadRAWData()和buildTexture()函数的定义,具体代码如下:
首先是loadRAWData()函数,这个函数用于加载raw图像的数据。我在网上查到raw图像的格式似乎根据硬件的不同,是由差别的,所以NeHe给的这个函数应该是只针对他给的图片是有效的(虽然有点可惜,但是我们还是有收获的)。函数中,我们先定义了变量stride来保存每一行数据的字节数,接着打开文件,如果文件不存在则报错退出程序;如果文件存在,我们通过一个循环来读取我们的数据,我们从图像的最下面一行开始,一向上行一行地读取每一行的数据。然后我们再利用一个循环来读取每一行的数据,并在内层再加一个循环来读取每一个像素的数据,并把alpha设置为255。
然后是buildTexture()函数,这个函数利用我们定义的图像结构体里的数据来创建纹理。一开始调用glGenTextures为纹理分配空间,然后绑定纹理,设置过滤器,然后利用结构体里的数据创建一个纹理,最后返回纹理的地址。
还有,我们来定义blit函数,这才是我们这节课的重点,前面4个函数都是直接或间接为了这个函数作铺垫,具体代码如下:
最后,对于initializeGL()、resizeGL()、paintGL()函数,后两个函数可以继续用第06课的,我们只需要修改initializeGL()函数,具体代码如下:
现在就可以运行程序查看效果了!
全部教程中需要的资源文件点此下载
这次教程中,我们将介绍类似于DirectDraw的blit(其实blit函数在许多绘图库都有),我们将用代码自己来实现它。它的作用非常简单,就是把一块纹理的贴到另一块纹理上。想想,有了这个函数,我们就可以自由拼接纹理了,是不是很棒?
这一课中,我们不但会实现blit函数,我们还将讲解如何来加载特定的*.raw图片。raw格式的图片,可以理解为CMOS或者CCD图像感应器将捕捉到的光源信号转化为数字信号的原始数据。可悲的是,Qt并没有提供加载raw图片的方法,所以我们只能自己写了(这是处理图像的一课,与OpenGL关系不大)!
程序运行时效果如下:
下面进入教程:
我们这次将在第06课的基础上修改代码,我们只讲解新的内容,旧的内容到这里大家应该掌握得很好了才对。首先打开myglwidget.h文件,将类声明修改如下:
#ifndef MYGLWIDGET_H #define MYGLWIDGET_H #include <QWidget> #include <QGLWidget> typedef struct Texture_Image //图像结构体 { int width; int height; int format; //格式(图像每一像素内存) unsigned char *data; //储存图像数据 }* P_TEXTURE_IMAGE; class MyGLWidget : public QGLWidget { Q_OBJECT public: explicit MyGLWidget(QWidget *parent = 0); ~MyGLWidget(); protected: //对3个纯虚函数的重定义 void initializeGL(); void resizeGL(int w, int h); void paintGL(); void keyPressEvent(QKeyEvent *event); //处理键盘按下事件 private: //为图像结构体分配内存 P_TEXTURE_IMAGE allocateTextureBuffer(GLuint w, GLuint h, GLuint f); void deallocateTexture(P_TEXTURE_IMAGE t); //释放图像结构体内存 //读取图像结构体数据 void loadRAWData(const char *filename, P_TEXTURE_IMAGE buffer); GLuint buildTexture(P_TEXTURE_IMAGE tex); //建立纹理 //将一个纹理贴到另一个纹理上 void blit(P_TEXTURE_IMAGE src, P_TEXTURE_IMAGE dst, int src_xstart, int src_ystart, int src_width, int src_height, int dst_xstart, int dst_ystart, bool blend, int alpha); private: bool fullscreen; //是否全屏显示 GLfloat m_xRot; //绕x轴旋转的角度 GLfloat m_yRot; //绕y轴旋转的角度 GLfloat m_zRot; //绕z轴旋转的角度 P_TEXTURE_IMAGE t1, t2; //图像结构体指针 GLuint m_Texture; //储存一个纹理 }; #endif // MYGLWIDGET_H首先我们新定义了一个结构体Texture_Image,把其指针重命名为P_TEXTURE_IMAGE。结构体中width、height指图像像素的宽和高;format指每一个像素的位深,也就是每一个像素所占的内存大小;data指向用于储存图像数据的内存。接着我们定义了该结构体的两个指针t1、t2。最后,我们声明了5个新的函数,函数的作用大家先看注释,后面定义的时候再一起解释。
接下来,我们需要打开myglwidget.cpp,加上声明#include <QMessageBox>,对构造函数进行修改,很简单不多解释,具体代码如下:
MyGLWidget::MyGLWidget(QWidget *parent) : QGLWidget(parent) { fullscreen = false; m_xRot = 0.0f; m_yRot = 0.0f; m_zRot = 0.0f; QTimer *timer = new QTimer(this); //创建一个定时器 //将定时器的计时信号与updateGL()绑定 connect(timer, SIGNAL(timeout()), this, SLOT(updateGL())); timer->start(10); //以10ms为一个计时周期 }
下面,我们来看allocateTextureBuffer()、deallocateTexture()函数的定义,具体代码如下:
P_TEXTURE_IMAGE MyGLWidget::allocateTextureBuffer(GLuint w, GLuint h, GLuint f) { P_TEXTURE_IMAGE ti = NULL; unsigned char *c = NULL; ti = (P_TEXTURE_IMAGE)malloc(sizeof(Texture_Image));//分配图像结构体内存 if (ti != NULL) { ti->width = w; //设置宽度 ti->height = h; //设置高度 ti->format = f; //设置格式(位深/8) c = (unsigned char *)malloc(w * h *f); //分配w*h*f字节来存放图像数据 if (c != NULL ) { ti->data = c; } else { QMessageBox::warning(this, "内存不足", "分配图像内存错误", QMessageBox::Ok); exit(1); } } else { QMessageBox::warning(this, "内存不足", "分配图像结构体内存错误", QMessageBox::Ok); exit(1); } return ti; //返回图像结构体指针 }
void MyGLWidget::deallocateTexture(P_TEXTURE_IMAGE t) { if (t != NULL) { if (t->data != NULL) { free(t->data); //释放存放图像数据的内存 } free(t); //释放图像结构体的内存 } }
首先是allocateTextureBuffer()函数,这个函数用来为图像结构体分配内存。函数中,我们为ti和c分别分配内存,其中为c分配时,其大小是w*h*f;然后如果分配不成功,则利用QMessageBox来报告错误,并退出程序。
然后是deallocateTexture()函数,这个函数用来释放我们分配的内存。函数中,我们把t和data指向的内存都释放掉。
继续,我们来看loadRAWData()和buildTexture()函数的定义,具体代码如下:
void MyGLWidget::loadRAWData(const char *filename, P_TEXTURE_IMAGE buffer) { int stride = buffer->width * buffer->format; //记录每一行的字节数 FILE *f = fopen(filename, "rb"); //打开文件 if (f != NULL) //如果文件存在 { for (int i=buffer->height-1; i>=0; i--) //循环所有的行,从最下面的行开始读入 { unsigned char *p = buffer->data + (i*stride); for (int j=0; j<buffer->width; j++) //读取每一行的数据 { for (int k=0; k<buffer->format-1; k++, p++) { *p = fgetc(f); //读取一个字节 } *p = 255; //把255储存在alpha通道中 p++; } } fclose(f); //关闭文件 } else { QMessageBox::warning(this, "不能打开文件", "图像错误", QMessageBox::Ok); exit(1); } }
GLuint MyGLWidget::buildTexture(P_TEXTURE_IMAGE tex) { GLuint texture; glGenTextures(1, &texture); //创建纹理空间,并记录其地址 glBindTexture(GL_TEXTURE_2D, texture); //绑定纹理 //设置过滤器为线性过滤 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); //在内存中创建一个纹理 gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGB, tex->width, tex->height, GL_RGBA, GL_UNSIGNED_BYTE, tex->data); return texture; //返回纹理地址 }
首先是loadRAWData()函数,这个函数用于加载raw图像的数据。我在网上查到raw图像的格式似乎根据硬件的不同,是由差别的,所以NeHe给的这个函数应该是只针对他给的图片是有效的(虽然有点可惜,但是我们还是有收获的)。函数中,我们先定义了变量stride来保存每一行数据的字节数,接着打开文件,如果文件不存在则报错退出程序;如果文件存在,我们通过一个循环来读取我们的数据,我们从图像的最下面一行开始,一向上行一行地读取每一行的数据。然后我们再利用一个循环来读取每一行的数据,并在内层再加一个循环来读取每一个像素的数据,并把alpha设置为255。
然后是buildTexture()函数,这个函数利用我们定义的图像结构体里的数据来创建纹理。一开始调用glGenTextures为纹理分配空间,然后绑定纹理,设置过滤器,然后利用结构体里的数据创建一个纹理,最后返回纹理的地址。
还有,我们来定义blit函数,这才是我们这节课的重点,前面4个函数都是直接或间接为了这个函数作铺垫,具体代码如下:
/*blit函数将一个纹理贴到另一个纹理上 * src为源图像,dst为目标图像 * src_xstart,src_ystart为要复制的部分在源图像中的位置 * src_width,src_height为复制的部分的宽度和高度 * dst_xstart,dst_ystart为复制到目标图像时的起始位置 * blend设置是否启用混合,true为启用,false为不启用 * alpha设置源图像中在混合时所占的百分比 */ void MyGLWidget::blit(P_TEXTURE_IMAGE src, P_TEXTURE_IMAGE dst, int src_xstart, int src_ystart, int src_width, int src_height, int dst_xstart, int dst_ystart, bool blend, int alpha) { if (alpha > 255) //保证alpha的值有效 { alpha = 255; } if (alpha < 0) { alpha = 0; } //计算要复制的像素在源图像数据中的开始行 unsigned char *s = src->data + (src_ystart*src->width*src->format); //计算要复制的像素在目标图像数据中的开始行 unsigned char *d = dst->data + (dst_ystart*dst->width*dst->format); for (int i=0; i<src_height; i++) //循环每一行 { s = s + (src_xstart*src->format); //移动到这一行要复制像素的开始位置 d = d + (dst_xstart*dst->format); for (int j=0; j<src_width; j++) //循环复制一行 { for (int k=0; k<src->format; k++, d++, s++) //复制每一个字节 { if (blend) //如果启用了混合 { //根据alpha值计算颜色值 *d = ((*s * alpha) + (*d * (255-alpha))) >> 8; } else { *d = *s; //否则直接复制 } } } //移动到下一行 d = d + (dst->width - (src_width+dst_xstart))*dst->format; s = s + (src->width - (src_width+src_xstart))*src->format; } }函数的参数有点多,在函数开头有各个参数的注释,大家自己看。一开始,我们先保证传进来的alpha值为0~255,接着我们让指针s和d分别指向源图像和目标图像要复制的起始行。进入循环,我们让s和d指向当前行要复制的像素的开始位置,然后循环复制每一行。复制过程,我们检查blend是否为true,如果为true,则根据alpha的值计算颜色值,否则直接复制颜色值,一次循环的结束,把指针s和d移动到需要要复制的下一行。
最后,对于initializeGL()、resizeGL()、paintGL()函数,后两个函数可以继续用第06课的,我们只需要修改initializeGL()函数,具体代码如下:
void MyGLWidget::initializeGL() //此处开始对OpenGL进行所以设置 { t1 = allocateTextureBuffer(256, 256, 4); //为t1分配内存 loadRAWData("D:/QtOpenGL/QtImage/Monitor.raw", t1); //读入t1的数据 t2 = allocateTextureBuffer(256, 256, 4); //为t2分配内存 loadRAWData("D:/QtOpenGL/QtImage/GL.raw", t2); //读入t2的数据 blit(t2, t1, 127, 127, 128, 128, 64, 64, true, 127);//调用blit函数实现图像的拼接 m_Texture = buildTexture(t1); //创建纹理 deallocateTexture(t1); //释放t1的内存 deallocateTexture(t2); //释放t2的内存 glEnable(GL_TEXTURE_2D); //启用纹理映射 glClearColor(0.0f, 0.0f, 0.0f, 0.0f); //黑色背景 glShadeModel(GL_SMOOTH); //启用阴影平滑 glClearDepth(1.0); //设置深度缓存 glEnable(GL_DEPTH_TEST); //启用深度测试 glDepthFunc(GL_LEQUAL); //所作深度测试的类型 glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); //告诉系统对透视进行修正 }函数只是做了小的改动,我们自己调用自己写的函数为图像分配内存,读入数据,完成拼接,转换为纹理后,我们释放了之前分配的内存。并不难理解,大家自己看吧。
现在就可以运行程序查看效果了!
全部教程中需要的资源文件点此下载
相关文章推荐
- MQTT初步使用
- Qt-Style-Sheets-语法
- qt Qstring 转 char*
- Qt Style Sheets--简介
- Qt中如何用指针返回参数
- QT中模拟鼠标点击事件
- JMS,MOM,MQTT概念与联系
- QT_QAction
- QT_QMainWindow
- Qt5该插件机制(2)--QxxxFactory类和QFactoryLoader类别
- Qt 中update()和repaint()的区别
- SPOJ QTREE 树链剖分
- Qt5.x windows中文乱码问题的解决方法(两种乱码问题)
- QTP/UFT 11.5 特性和安装的配置要求
- 【Qt OpenGL教程】28:贝塞尔曲面
- QT简单绘图
- Ubuntu安装Qt及QT Creator
- 【Qt OpenGL教程】27:阴影
- Ubuntu Qt5.5 Mysql driver
- 【Qt】]两种解决Qt5显示中文乱码的方法