您的位置:首页 > 编程语言 > Qt开发

【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文件,将类声明修改如下:

#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);  //告诉系统对透视进行修正
}
函数只是做了小的改动,我们自己调用自己写的函数为图像分配内存,读入数据,完成拼接,转换为纹理后,我们释放了之前分配的内存。并不难理解,大家自己看吧。

现在就可以运行程序查看效果了!

全部教程中需要的资源文件点此下载
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: