4、glDrawPixels的用法和举例
glDrawPixels函数与glReadPixels函数相比,参数内容大致相同。它的第一、二、三、四个参数分别对应于glReadPixels函数的第三、四、五、六个参数,依次表示图象宽度、图象高度、像素数据内容、像素数据在内存中的格式。两个函数的最后一个参数也是对应的,glReadPixels中表示像素读取后存放在内存中的位置,glDrawPixels则表示用于绘制的像素数据在内存中的位置。
注意到glDrawPixels函数比glReadPixels函数少了两个参数,这两个参数在glReadPixels中分别是表示图象的起始位置。在glDrawPixels中,不必显式的指定绘制的位置,这是因为绘制的位置是由另一个函数glRasterPos*来指定的。glRasterPos*函数的参数与glVertex*类似,通过指定一个二维/三维/四维坐标,OpenGL将自动计算出该坐标对应的屏幕位置,并把该位置作为绘制像素的起始位置。
很自然的,我们可以从BMP文件中读取像素数据,并使用glDrawPixels绘制到屏幕上。我们选择Windows XP默认的桌面背景Bliss.bmp作为绘制的内容(如果你使用的是Windows XP系统,很可能可以在硬盘中搜索到这个文件。当然你也可以使用其它BMP文件来代替,只要它是24位的BMP文件。注意需要修改代码开始部分的FileName的定义),先把该文件复制一份放到正确的位置,我们在程序开始时,就读取该文件,从而获得图象的大小后,根据该大小来创建合适的OpenGL窗口,并绘制像素。
绘制像素本来是很简单的过程,但是这个程序在骨架上与前面的各种示例程序稍有不同,所以我还是打算给出一份完整的代码。
- #include <gl/glut.h>
- #define FileName "Bliss.bmp"
-
- static GLint ImageWidth;
- static GLint ImageHeight;
- static GLint PixelLength;
- static GLubyte* PixelData;
-
- #include <stdio.h>
- #include <stdlib.h>
-
- void display(void)
- {
- // 清除屏幕并不必要
- // 每次绘制时,画面都覆盖整个屏幕
- // 因此无论是否清除屏幕,结果都一样
- // glClear(GL_COLOR_BUFFER_BIT);
-
- // 绘制像素
- glDrawPixels(ImageWidth, ImageHeight,
- GL_BGR_EXT, GL_UNSIGNED_BYTE, PixelData);
-
- // 完成绘制
- glutSwapBuffers();
- }
-
- int main(int argc, char* argv[])
- {
- // 打开文件
- FILE* pFile = fopen("Bliss.bmp", "rb");
- if( pFile == 0 )
- exit(0);
-
- // 读取图象的大小信息
- fseek(pFile, 0x0012, SEEK_SET);
- fread(&ImageWidth, sizeof(ImageWidth), 1, pFile);
- fread(&ImageHeight, sizeof(ImageHeight), 1, pFile);
-
- // 计算像素数据长度
- PixelLength = ImageWidth * 3;
- while( PixelLength % 4 != 0 )
- ++PixelLength;
- PixelLength *= ImageHeight;
-
- // 读取像素数据
- PixelData = (GLubyte*)malloc(PixelLength);
- if( PixelData == 0 )
- exit(0);
-
- fseek(pFile, 54, SEEK_SET);
- fread(PixelData, PixelLength, 1, pFile);
-
- // 关闭文件
- fclose(pFile);
-
- // 初始化GLUT并运行
- glutInit(&argc, argv);
- glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);
- glutInitWindowPosition(100, 100);
- glutInitWindowSize(ImageWidth, ImageHeight);
- glutCreateWindow(FileName);
- glutDisplayFunc(&display);
- glutMainLoop();
-
- // 释放内存
- // 实际上,glutMainLoop函数永远不会返回,这里也永远不会到达
- // 这里写释放内存只是出于一种个人习惯
- // 不用担心内存无法释放。在程序结束时操作系统会自动回收所有内存
- free(PixelData);
-
- return 0;
- }
这里仅仅是一个简单的显示24位BMP图象的程序,如果读者对BMP文件格式比较熟悉,也可以写出适用于各种BMP图象的显示程序,在像素处理时,它们所使用的方法是类似的。
OpenGL在绘制像素之前,可以对像素进行若干处理。最常用的可能就是对整个像素图象进行放大/缩小。使用glPixelZoom来设置放大/缩小的系数,该函数有两个参数,分别是水平方向系数和垂直方向系数。例如设置glPixelZoom(0.5f, 0.8f);则表示水平方向变为原来的50%大小,而垂直方向变为原来的80%大小。我们甚至可以使用负的系数,使得整个图象进行水平方向或垂直方向的翻转(默认像素从左绘制到右,但翻转后将从右绘制到左。默认像素从下绘制到上,但翻转后将从上绘制到下。因此,glRasterPos*函数设置的“开始位置”不一定就是矩形的左下角)。
5、glCopyPixels的用法和举例
从效果上看,glCopyPixels进行像素复制的操作,等价于把像素读取到内存,再从内存绘制到另一个区域,因此可以通过glReadPixels和glDrawPixels组合来实现复制像素的功能。然而我们知道,像素数据通常数据量很大,例如一幅1024*768的图象,如果使用24位BGR方式表示,则需要至少1024*768*3字节,即2.25兆字节。这么多的数据要进行一次读操作和一次写操作,并且因为在glReadPixels和glDrawPixels中设置的数据格式不同,很可能涉及到数据格式的转换。这对CPU无疑是一个不小的负担。使用glCopyPixels直接从像素数据复制出新的像素数据,避免了多余的数据的格式转换,并且也可能减少一些数据复制操作(因为数据可能直接由显卡负责复制,不需要经过主内存),因此效率比较高。
glCopyPixels函数也通过glRasterPos*系列函数来设置绘制的位置,因为不需要涉及到主内存,所以不需要指定数据在内存中的格式,也不需要使用任何指针。
glCopyPixels函数有五个参数,第一、二个参数表示复制像素来源的矩形的左下角坐标,第三、四个参数表示复制像素来源的举行的宽度和高度,第五个参数通常使用GL_COLOR,表示复制像素的颜色,但也可以是GL_DEPTH或GL_STENCIL,分别表示复制深度缓冲数据或模板缓冲数据。
值得一提的是,glDrawPixels和glReadPixels中设置的各种操作,例如glPixelZoom等,在glCopyPixels函数中同样有效。
下面看一个简单的例子,绘制一个三角形后,复制像素,并同时进行水平和垂直方向的翻转,然后缩小为原来的一半,并绘制。绘制完毕后,调用前面的grab函数,将屏幕中所有内容保存为grab.bmp。其中WindowWidth和WindowHeight是表示窗口宽度和高度的常量。
- void display(void)
- {
-
- glClear(GL_COLOR_BUFFER_BIT);
-
-
- glBegin(GL_TRIANGLES);
- glColor3f(1.0f, 0.0f, 0.0f); glVertex2f(0.0f, 0.0f);
- glColor3f(0.0f, 1.0f, 0.0f); glVertex2f(1.0f, 0.0f);
- glColor3f(0.0f, 0.0f, 1.0f); glVertex2f(0.5f, 1.0f);
- glEnd();
- glPixelZoom(-0.5f, -0.5f);
- glRasterPos2i(1, 1);
- glCopyPixels(WindowWidth/2, WindowHeight/2,
- WindowWidth/2, WindowHeight/2, GL_COLOR);
-
-
- glutSwapBuffers();
- grab();
- }
(zhengym02) |