glPixelStorei也可以用于设置其它各种参数。但我们这里并不需要深入讨论了。
现在,我们已经可以把屏幕上的像素读取到内存了,如果需要的话,我们还可以将内存中的数据保存到文件。正确的对照BMP文件格式,我们的程序就可以把屏幕中的图象保存为BMP文件,达到屏幕截图的效果。
我们并没有详细介绍BMP文件开头的54个字节的所有内容,不过这无伤大雅。从一个正确的BMP文件中读取前54个字节,修改其中的宽度和高度信息,就可以得到新的文件头了。假设我们先建立一个1*1大小的24位色BMP,文件名为dummy.bmp,又假设新的BMP文件名称为grab.bmp。则可以编写如下代码:
FILE* pOriginFile = fopen(\FILE* pGrabFile = fopen(\char BMP_Header[54]; GLint width, height;
// 读取dummy.bmp中的头54个字节到数组
fread(BMP_Header, sizeof(BMP_Header), 1, pOriginFile); // 把数组内容写入到新的BMP文件
fwrite(BMP_Header, sizeof(BMP_Header), 1, pGrabFile);
// 修改其中的大小信息
fseek(pGrabFile, 0x0012, SEEK_SET); fwrite(&width, sizeof(width), 1, pGrabFile); fwrite(&height, sizeof(height), 1, pGrabFile);
// 移动到文件末尾,开始写入像素数据 fseek(pGrabFile, 0, SEEK_END);
fclose(pOriginFile); fclose(pGrabFile);
我们给出完整的代码,演示如何把整个窗口的图象抓取出来并保存为BMP文件。
#define WindowWidth 400 #define WindowHeight 400
#include
#define BMP_Header_Length 54 void grab(void) {
FILE* pDummyFile; FILE* pWritingFile; GLubyte* pPixelData;
GLubyte BMP_Header[BMP_Header_Length]; GLint i, j;
GLint PixelDataLength;
// 计算像素数据的实际长度
i = WindowWidth * 3; // 得到每一行的像素数据长度 while( i%4 != 0 ) // 补充数据,直到i是的倍数 ++i; // 本来还有更快的算法,
// 但这里仅追求直观,对速度没有太高要求 PixelDataLength = i * WindowHeight;
// 分配内存和打开文件
pPixelData = (GLubyte*)malloc(PixelDataLength); if( pPixelData == 0 ) exit(0);
pDummyFile = fopen(\ if( pDummyFile == 0 ) exit(0);
pWritingFile = fopen(\ if( pWritingFile == 0 ) exit(0);
// 读取像素
glPixelStorei(GL_UNPACK_ALIGNMENT, 4); glReadPixels(0, 0, WindowWidth, WindowHeight, GL_BGR_EXT, GL_UNSIGNED_BYTE, pPixelData);
// 把dummy.bmp的文件头复制为新文件的文件头 fread(BMP_Header, sizeof(BMP_Header), 1, pDummyFile); fwrite(BMP_Header, sizeof(BMP_Header), 1, pWritingFile); fseek(pWritingFile, 0x0012, SEEK_SET); i = WindowWidth; j = WindowHeight;
fwrite(&i, sizeof(i), 1, pWritingFile); fwrite(&j, sizeof(j), 1, pWritingFile);
// 写入像素数据
fseek(pWritingFile, 0, SEEK_END);
fwrite(pPixelData, PixelDataLength, 1, pWritingFile);
// 释放内存和关闭文件 fclose(pDummyFile); fclose(pWritingFile); free(pPixelData); }
把这段代码复制到以前任何课程的样例程序中,在绘制函数的最后调用grab函数,即可把图象内容保存为BMP文件了。(在我写这个教程的时候,不少地方都用这样的代码进行截图工作,这段代码一旦写好,运行起来是很方便的。)
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
#define FileName \
static GLint ImageWidth; static GLint ImageHeight; static GLint PixelLength; static GLubyte* PixelData;
#include
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(\ 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函数永远不会返回,这里也永远不会到达