OpenGL中位图的操作

  OpenGL中位图的操作(glReadPixels,glDrawPixels和glCopyPixels应用举例)。

1. BMP文件格式简单介绍

  BMP文件是一种像素文件,它保存了一幅图象中所有的像素。这种文件格式可以保存单色位图、16色或256色索引模式像素图、24位真彩色图象,每种模式 种单一像素的大小分别为1/8字节,1/2字节,1字节和3字节。目前最常见的是256色BMP和24位色BMP。这种文件格式还定义了像素保存的几种方 法,包括不压缩、RLE压缩等。常见的BMP文件大多是不压缩的。

  这里为了简单起见,我们仅讨论24位色不使用压缩的BMP。(如果你使用Windows自带的画图程序,很容易绘制出一个符合以上要求的BMP)Windows所使用的BMP文件,在开始处有一个文件头,大小为54字节。保存了包括文件格式标识、颜色数、图象大小、压缩方式等信息,因为我们仅讨论
24位色不压缩的BMP,所以文件头中的信息基本不需要注意,只有“大小”这一项对我们比较有用。图象的宽度和高度都是一个32位整数,在文件中的地址分
别为0x0012和0x0016,于是我们可以使用以下代码来读取图象的大小信息:

GLint width, height; // 使用OpenGL的GLint类型,它是32位的。
// 而C语言本身的int则不一定是32位的。
FILE* pFile;
// 在这里进行“打开文件”的操作
fseek(pFile, 0x0012, SEEK_SET); // 移动到0x0012位置
fread(&width, sizeof(width), , pFile); // 读取宽度
fseek(pFile, 0x0016, SEEK_SET); // 移动到0x0016位置
// 由于上一句执行后本就应该在0x0016位置
// 所以这一句可省略
fread(&height, sizeof(height), , pFile); // 读取高度

  54个字节以后,如果是16色或256色BMP,则还有一个颜色表,但24位色BMP没有这个,我们这里不考虑。

  接下来就是实际的像素数据了。24位色的BMP文件中,每三个字节表示一个像素的颜色

  注意:

  【1】OpenGL通常使用RGB来表示颜色,但BMP文件则采用BGR,就是说,顺序被反过来了。
  【2】另外需要注意的地方是:像素的数据量并不一定完全等于图象的高度乘以宽度乘以每一像素的字节数,而是可能略大于这个值。原因是BMP文件采用了一种“对
齐”的机制,每一行像素数据的长度若不是4的倍数,则填充一些数据使它是4的倍数
。这样一来,一个17*15的24位BMP大小就应该是834字节(每行
17个像素,有51字节,补充为52字节,乘以15得到像素数据总长度780,再加上文件开始的54字节,得到834字节)。分配内存时,一定要小心,不
能直接使用“图象的高度乘以宽度乘以每一像素的字节数”来计算分配空间的长度,否则有可能导致分配的内存空间长度不足,造成越界访问,带来各种严重后果。

  一个很简单的计算数据长度的方法如下:

int LineLength, TotalLength;
LineLength = ImageWidth * BytesPerPixel; // 每行数据长度大致为图象宽度乘以
// 每像素的字节数
while( LineLength % != ) // 修正LineLength使其为4的倍数
++LineLenth;
TotalLength = LineLength * ImageHeight; // 数据总长 = 每行长度 * 图象高度

  这并不是效率最高的方法,但由于这个修正本身运算量并不大,使用频率也不高,我们就不需要再考虑更快的方法了。

2. 简单的OpenGL像素操作

   OpenGL提供了简洁的函数来操作像素:
  【1】  glReadPixels:读取一些像素。当前可以简单理解为“把已经绘制好的像素(它可能已经被保存到显卡的显存中)读取到内存”。

  【2】  glDrawPixels:绘制一些像素。当前可以简单理解为“把内存中一些数据作为像素数据,进行绘制”。
  【3】  glCopyPixels:复制一些像素。当前可以简单理解为“把已经绘制好的像素从一个位置复制到另一个位置”。虽然从功能上看,好象等价于先读取像素
再绘制像素,但实际上它不需要把已经绘制的像素(它可能已经被保存到显卡的显存中)转换为内存数据,然后再由内存数据进行重新的绘制,所以要比先读取后绘
制快很多。
  

这三个函数可以完成简单的像素读取、绘制和复制任务,但实际上也可以完成更复杂的任务。当前,我们仅讨论一些简单的应用。由于这几个函数的参数数目比较多,下面我们分别介绍。

3. glReadPixels的用法和举例

 void glReadPixels( GLint x, GLint y, GLsizei width,GLsizei height, GLenum format, GLenum type, GLvoid *pixels)

  该函数总共有七个参数。

  前四个参数可以得到一个矩形,该矩形所包括的像素都会被读取出来。(第一、二个参数表示了矩形的左下角横、纵坐标,坐标以窗口最左下角为零,最右上角为最大值;第三、四个参数表示了矩形的宽度和高度)

  第五个参数表示读取的内容,例如:GL_RGB就会依次读取像素的红、绿、蓝三种数据,GL_RGBA则会依次读取像素的红、绿、蓝、alpha四种数
据,GL_RED则只读取像素的红色数据(类似的还有GL_GREEN,GL_BLUE,以及GL_ALPHA)。如果采用的不是RGBA颜色模式,而是
采用颜色索引模式,则也可以使用GL_COLOR_INDEX来读取像素的颜色索引。目前仅需要知道这些,但实际上还可以读取其它内容,例如深度缓冲区的
深度数据等。

  第六个参数表示读取的内容保存到内存时所使用的格式,例如:GL_UNSIGNED_BYTE会把各种数据保存为GLubyte,GL_FLOAT会把各种数据保存为GLfloat等。

  第七个参数表示一个指针,像素数据被读取后,将被保存到这个指针所表示的地址。注意,需要保证该地址有足够的可以使用的空间,以容纳读取的像素数据。例如
一幅大小为256*256的图象,如果读取其RGB数据,且每一数据被保存为GLubyte,总大小就是:256*256*3
= 196608字节,即192千字节。如果是读取RGBA数据,则总大小就是256*256*4 =
262144字节,即256千字节。

注意:glReadPixels实际上是从缓冲区中读取数据,如果使用了双缓冲区,则默认是从正在显示的缓冲(即前缓冲)中读取,而绘制工作是默认绘制到后缓冲区的。因此,如果需要读取已经绘制好的像素,往往需要先交换前后缓冲。

3. 解决OpenGL常用的RGB像素数据与BMP文件的BGR像素数据顺序不一致问题

  可以使用一些代码交换每个像素的第一字节和第三字节,使得RGB的数据变成BGR的数据。

  当然也可以使用另外的方式解决问题:新版本的OpenGL除了可 以使用GL_RGB读取像素的红、绿、蓝数据外,也可以使用GL_BGR按照相反的顺序依次读取像素的蓝、绿、红数据,这样就与BMP文件格式相吻合了。 即使你的gl/gl.h头文件中没有定义这个GL_BGR,也没有关系,可以尝试使用GL_BGR_EXT。虽然有的OpenGL实现(尤其是旧版本的实 现)并不能使用GL_BGR_EXT,但我所知道的Windows环境下各种OpenGL实现都对GL_BGR提供了支持,毕竟Windows中各种表示 颜色的数据几乎都是使用BGR的顺序,而非RGB的顺序。这可能与IBM-PC的硬件设计有关。

4. 消除BMP文件中“对齐”带来的影响

  实际上OpenGL也支持使用了这种“对齐”方式的像素数据。只要通过glPixelStore修改“像素保存时对齐的方式”就可以了。像这样:

int alignment = ;
glPixelStorei(GL_UNPACK_ALIGNMENT, alignment);

  第一个参数表示“设置像素的对齐值”,第二个参数表示实际设置为多少。这里像素可以单字节对齐(实际上就是不使用对齐)、双字节对齐(如果长度为奇数,则 再补一个字节)、四字节对齐(如果长度不是四的倍数,则补为四的倍数)、八字节对齐。分别对应alignment的值为1, 2, 4, 8

  实际上,默认的值是4,正好与BMP文件的对齐方式相吻合。

  glPixelStorei也可以用于设置其它各种参数。

4. 应用截屏程序

  现在,我们已经可以把屏幕上的像素读取到内存了,如果需要的话,我们还可以将内存中的数据保存到文件。正确的对照BMP文件格式,我们的程序就可以把屏幕中的图象保存为BMP文件,达到屏幕截图的效果。

  我们并没有详细介绍BMP文件开头的54个字节的所有内容,不过这无伤大雅。从一个正确的BMP文件中读取前54个字节,修改其中的宽度和高度信息,就可 以得到新的文件头了。假设我们先建立一个1*1大小的24位色BMP,文件名为dummy.bmp,又假设新的BMP文件名称为grab.bmp。

FILE* pOriginFile = fopen("dummy.bmp", "rb);
FILE* pGrabFile = fopen("grab.bmp", "wb");
char BMP_Header[];
GLint width, height;
// 读取dummy.bmp中的头54个字节到数组
fread(BMP_Header, sizeof(BMP_Header), , pOriginFile);
// 把数组内容写入到新的BMP文件
fwrite(BMP_Header, sizeof(BMP_Header), , pGrabFile); // 修改其中的大小信息
fseek(pGrabFile, 0x0012, SEEK_SET);
fwrite(&width, sizeof(width), , pGrabFile);
fwrite(&height, sizeof(height), , pGrabFile); // 移动到文件末尾,开始写入像素数据
fseek(pGrabFile, , SEEK_END);
fclose(pOriginFile);
fclose(pGrabFile);

  我们给出完整的代码,演示如何把整个窗口的图象抓取出来并保存为BMP文件。

#define WindowWidth  400
#define WindowHeight 400 #include <stdio.h>
#include <stdlib.h> #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 * ; // 得到每一行的像素数据长度
while( i% != ) // 补充数据,直到i是的倍数
++i; // 本来还有更快的算法,
// 但这里仅追求直观,对速度没有太高要求
PixelDataLength = i * WindowHeight; // 分配内存和打开文件
pPixelData = (GLubyte*)malloc(PixelDataLength);
if( pPixelData == )
exit();   pDummyFile = fopen("dummy.bmp", "rb");
if( pDummyFile == )
exit();   pWritingFile = fopen("grab.bmp", "wb");
if( pWritingFile == )
exit(); // 读取像素
glPixelStorei(GL_UNPACK_ALIGNMENT, );
glReadPixels(, , WindowWidth, WindowHeight,
GL_BGR_EXT, GL_UNSIGNED_BYTE, pPixelData); // 把dummy.bmp的文件头复制为新文件的文件头
fread(BMP_Header, sizeof(BMP_Header), , pDummyFile);
fwrite(BMP_Header, sizeof(BMP_Header), , pWritingFile);
fseek(pWritingFile, 0x0012, SEEK_SET);
i = WindowWidth;
j = WindowHeight;
fwrite(&i, sizeof(i), , pWritingFile);
fwrite(&j, sizeof(j), , pWritingFile); // 写入像素数据
fseek(pWritingFile, , SEEK_END);
fwrite(pPixelData, PixelDataLength, , pWritingFile); // 释放内存和关闭文件
fclose(pDummyFile);
fclose(pWritingFile);
free(pPixelData);
}

  把这段代码复制到以前任何课程的样例程序中,在绘制函数的最后调用grab函数,即可把图象内容保存为BMP文件了。

5. glreadpixels读取的是哪里的内存??

  当有前景和背景两个缓存区时,如果先调用
  glReadBuffer(GL_FRONT);
  然后glreadpixels读取的就是前景(GL_FRONT)缓存区。
  SwapBuffers 可让 背景前景交换,如果在调用 SwapBuffers 后,则读取的 背景前景就换个,每SwapBuffers 一次,就换个一次。

  

OpenGL中位图的操作(glReadPixels,glDrawPixels等)的更多相关文章

  1. (转)OpenGL中位图的操作(glReadPixels,glDrawPixels和glCopyPixels应用举例)

    (一)BMP文件格式简单介绍 BMP文件是一种像素文件,它保存了一幅图象中所有的像素.这种文件格式可以保存单色位图.16色或256色索引模式像素图.24位真彩色图象,每种模式种单一像素的大小分别为1/ ...

  2. Eclipse中通过Android模拟器调用OpenGL ES2.0函数操作步骤

    原文地址: Eclipse中通过Android模拟器调用OpenGL ES2.0函数操作步骤 - 网络资源是无限的 - 博客频道 - CSDN.NET http://blog.csdn.net/fen ...

  3. OpenGL 中的三维纹理操作

    #define _CRT_SECURE_NO_WARNINGS #include <gl/glut.h> #include <stdio.h> #include <std ...

  4. OpenGL中平移、旋转、缩放矩阵堆栈操作

    在OpenGL中,图元的几何变换均为线性变换,通过矩阵变换实现.OpenGL中的坐标用齐次坐标表示,即(x,y,z)表示成(x',y',z',h),其中x=x'/h; y=y'/h; z=z'/h. ...

  5. openGL中的gl,glu,glut

    OpenGL函数库相关的API有核心库(gl).实用库(glu).辅助库(aux).实用工具库(glut).窗口库(glx.agl.wgl)和扩展函数库等.gl是核心,glu是对gl的部分封装.glx ...

  6. OpenGL中的帧缓存

    OpenGL中的帧缓存 在OpenGL窗口中, 左下角的像素为(0, 0). 一般而言, 像素(x, y)占据的矩形区域左下角为(x, y), 右上角为(x+1, y+1). 1. 缓存及其用途 [1 ...

  7. CSharpGL(26)在opengl中实现控件布局/渲染文字

    CSharpGL(26)在opengl中实现控件布局/渲染文字 效果图 如图所示,可以将文字.坐标轴固定在窗口的一角. 下载 CSharpGL已在GitHub开源,欢迎对OpenGL有兴趣的同学加入( ...

  8. OpenGL中实现双缓冲技术

    在OpenGL中实现双缓冲技术的一种简单方法: 1.在调用glutInitDisplayMode函数时, 开启GLUT_DOUBLE,即glutInitDisplayMode(GLUT_RGB | G ...

  9. OpenGL中各种坐标系的理解[转]

    OPENGL坐标系可分为:世界坐标系和当前绘图坐标系. 世界坐标系:在OpenGL中,世界坐标系是以屏幕中心为原点(0, 0, 0),且是始终不变的.你面对 屏幕,你的右边是x正轴,上面是y正轴,屏幕 ...

随机推荐

  1. 写一个singleton

    第一种:饱汉模式 public class SingleTon { private SingleTon(){ } //实例化放在静态代码块里可提高程序的执行效率,但也可能更占用空间 private f ...

  2. Python模拟登录的几种方法

    目录 方法一:直接使用已知的cookie访问 方法二:模拟登录后再携带得到的cookie访问 方法三:模拟登录后用session保持登录状态 方法四:使用无头浏览器访问 正文 方法一:直接使用已知的c ...

  3. windows服务启动的进程无窗口

    勾选允许服务与桌面交互 指服务是否在桌面上提供用户界面,当服务启动后不论是谁登录都能使用.只有作为 LocalSystem 帐户(由“此帐户”指定)运行时,该选项才能使用. 如果一个服务需要界面(比如 ...

  4. 'wmic' 不是内部或外部命令,也不是可运行的程序 解决方法

    在环境变量中查看变量名Path下的变量值是否有以下讯息? 如没有复制(Ctrl+C).黏贴(Ctrl+V)→确定即可. %SystemRoot%;%SystemRoot%\system32;%Syst ...

  5. 用SQL2000还原bak文件

    1.右击SQL Server 2000实例下的“数据库”文件夹.就是master等数据库上一级的那个图标.选择“所有任务”,“还原数据库” 2.在“还原为数据库”中填上你希望恢 复的数据库名字.这个名 ...

  6. 通过html5 的EventSource来进行数据推送

    以往我们要获取服务器的数据更新,一般通过ajax的定时请求,不过这样效率就低了.我们通过html5的EventSource可以很方便的获取服务器的数据更新,不过IE好像不支持.   例1如下: ind ...

  7. 给dede添加栏目图片和栏目描述

    有的时候我们希望调用栏目时把栏目的图片和描述调出来,但dede好像没有提供栏目图片这个功能,而栏目的描述也是给meta:Description使用的,不是很方便.   所以我们需要自已给dede添加图 ...

  8. Spring的3.0提供了一种:SpEL注入方式(了解)

    1. SpEL:Spring Expression Language是Spring的表达式语言,有一些自己的语法 2. 语法 * #{SpEL} 3. 例如如下的代码 <!-- SpEL的方式 ...

  9. 开发中经典sql总结

    1.说明:显示文章.提交人和最后回复时间 select a.title,a.username,b.adddate ,(select max(adddate) from table where tabl ...

  10. 讲真的,千万别得罪会PS的人

    传说中有一种软件炒鸡可怕 那就是PS,专业术语是photoshop! 它能让你貌美如花 也能让你瞬间丑得掉渣 更可怕的是网络上大神的出现 简直让我们难以想象的厉害! 下面大家一起来欣赏一下 那些大神帮 ...