BMP文件格式


BMP图像又称为Bitmap(位图),是Windows系统中广泛采用的图像格式。BMP文件的数据按照从文件头开始的先后顺序分为四个部分:

我们一般见到的图像以24位图像为主,即R、G、B三种颜色各用8个bit来表示,这样的图像称为真彩色,这种格式不需要调色表,所以位图信息头后紧跟的、从文件头开始偏移54个字节就是位图数据了。

BMP图像文件四字节对齐规则

为了提高处理效率,BMP文件在每一行采用一种4对齐的机制,就是如果一行的数据长度(以位为单位)不是4的整数倍的话,则填充一些空白数据使它补齐为4的整数倍。例如对于一个23*24的24位BMP文件来说,其每一行的字节数=23*3=69,应该补齐为72,所以23*24跟24824的BMP所占用的空间是一样的,都是24*24*3位。BMP文件占用内存是大于等于图像宽*高*每一像素的字节数的,为BMP文件分配内存空间时一定要考虑到这一点。


读取图像数据

glReadPixels用于从帧缓存里读取一个像素块,该函数总共有7个参数,函数原型:

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)(除此之外,还可以读取其它内容,例如深度缓冲区的深度数据等)。
  • 第六个参数表示读取的内容保存到内存时所使用的格式,例如:GL_UNSIGNED_BYTE会把各种数据保存为GLubyte,GL_FLOAT会把各种数据保存为GLfloat等。
  • 第七个参数表示一个指针,像素数据被读取后,将被保存到这个指针所表示的地址。注意,需要保证该地址有足够的可以使用的空间,每一行都要补齐4Byte整数倍,以容纳读取的像素数据。

在使用glReadPixels把像素内容读入内存之前,需要设置一下内存中这些像素的保存方式,保证每一行数据的字节数是4的整数倍。通过调用glPixelStore(GL_UNPACK_ALIGNMENT,4),保证OpenGL会按照4字节对齐的方式保存BMP文件。

生成文件头和信息头

我们不必深入探究BMP文件头和信息头内部数据的组织形式,这部分内容可以通过读取并复制另一幅BMP文件的对应信息实现,只需要修改一下图像的宽高信息就可以了。

对于24位不压缩的BMP文件,图像的宽高数据都是一个4字节的32位整数,在BMP文件中的地址分别是)0X0012和0X0016,用Windows的画图板新建一个图像并保存为24位位图文件,通过fread读取该图像整个的文件头和信息头数据,通过fwrtie修改宽高数据。

以下是glReadPixels的应用实例,在窗口上显示画面之后调用glReadPixels读取当前窗口中的图像数据,并保存为本地BMP文件(主函数借用之前太阳地球模型)。

#include "glut.h"
#include <stdio.h>
#include <stdlib.h> #define WindowWidth 400
#define WindowHeight 400 #define BMP_Header_Length 54
void grab(void)
{
FILE* pDummyFile; //指向另一bmp文件,用于复制它的文件头和信息头数据
FILE* pWritingFile; //指向要保存截图的bmp文件
GLubyte* pPixelData; //指向新的空的内存,用于保存截图bmp文件数据
GLubyte BMP_Header[BMP_Header_Length];
GLint i, j;
GLint PixelDataLength; //BMP文件数据总长度 // 计算像素数据的实际长度
i = WindowWidth * 3; // 得到每一行的像素数据长度
while( i%4 != 0 ) // 补充数据,直到i是的倍数
++i;
PixelDataLength = i * WindowHeight; //补齐后的总位数 // 分配内存和打开文件
pPixelData = (GLubyte*)malloc(PixelDataLength);
if( pPixelData == 0 )
exit(0); pDummyFile = fopen("bitmap1.bmp", "rb");//只读形式打开
if( pDummyFile == 0 )
exit(0); pWritingFile = fopen("grab.bmp", "wb"); //只写形式打开
if( pWritingFile == 0 )
exit(0); //把读入的bmp文件的文件头和信息头数据复制,并修改宽高数据
fread(BMP_Header, sizeof(BMP_Header), 1, pDummyFile); //读取文件头和信息头,占据54字节
fwrite(BMP_Header, sizeof(BMP_Header), 1, pWritingFile);
fseek(pWritingFile, 0x0012, SEEK_SET); //移动到0X0012处,指向图像宽度所在内存
i = WindowWidth;
j = WindowHeight;
fwrite(&i, sizeof(i), 1, pWritingFile);
fwrite(&j, sizeof(j), 1, pWritingFile); // 读取当前画板上图像的像素数据
glPixelStorei(GL_UNPACK_ALIGNMENT, 4); //设置4位对齐方式
glReadPixels(0, 0, WindowWidth, WindowHeight,
GL_BGR_EXT, GL_UNSIGNED_BYTE, pPixelData); // 写入像素数据
fseek(pWritingFile, 0, SEEK_END);
//把完整的BMP文件数据写入pWritingFile
fwrite(pPixelData, PixelDataLength, 1, pWritingFile); // 释放内存和关闭文件
fclose(pDummyFile);
fclose(pWritingFile);
free(pPixelData);
} static GLfloat angle = 0.0f;
void myDisplay(void)
{
glClearColor(0.3,0.7,0.5,0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //清理颜色和深度缓存 // 创建透视效果视图
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(80.0f, 1.0f, 1.0f, 20.0f); glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt(0.0, 12.0, 10.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0); // 定义太阳光源,它是一种白色的光源
{
GLfloat sun_light_position[] = {0.0f, 0.0f, 0.0f, 1.0f}; //光源的位置在世界坐标系圆心,齐次坐标形式
GLfloat sun_light_ambient[] = {0.0f, 0.0f, 0.0f, 1.0f}; //RGBA模式的环境光,为0
GLfloat sun_light_diffuse[] = {1.0f, 1.0f, 1.0f, 1.0f}; //RGBA模式的漫反射光,全白光
GLfloat sun_light_specular[] = {1.0f, 1.0f, 1.0f, 1.0f}; //RGBA模式下的镜面光 ,全白光
glLightfv(GL_LIGHT0, GL_POSITION, sun_light_position);
glLightfv(GL_LIGHT0, GL_AMBIENT, sun_light_ambient);
glLightfv(GL_LIGHT0, GL_DIFFUSE, sun_light_diffuse);
glLightfv(GL_LIGHT0, GL_SPECULAR, sun_light_specular); //开启灯光
glEnable(GL_LIGHT0);
glEnable(GL_LIGHTING);
glEnable(GL_DEPTH_TEST);
} // 定义太阳的材质并绘制太阳
{
GLfloat sun_mat_ambient[] = {0.0f, 0.0f, 0.0f, 1.0f}; //定义材质的环境光颜色,为0
GLfloat sun_mat_diffuse[] = {0.0f, 0.0f, 0.0f, 1.0f}; //定义材质的漫反射光颜色,为0
GLfloat sun_mat_specular[] = {0.0f, 0.0f, 0.0f, 1.0f}; //定义材质的镜面反射光颜色,为0
GLfloat sun_mat_emission[] = {0.8f, 0.0f, 0.0f, 1.0f}; //定义材质的辐射广颜色,为偏红色
GLfloat sun_mat_shininess = 0.0f;
glMaterialfv(GL_FRONT, GL_AMBIENT, sun_mat_ambient);
glMaterialfv(GL_FRONT, GL_DIFFUSE, sun_mat_diffuse);
glMaterialfv(GL_FRONT, GL_SPECULAR, sun_mat_specular);
glMaterialfv(GL_FRONT, GL_EMISSION, sun_mat_emission);
glMaterialf (GL_FRONT, GL_SHININESS, sun_mat_shininess);
glutSolidSphere(3.0, 40, 32);
} // 定义地球的材质并绘制地球
{
GLfloat earth_mat_ambient[] = {0.0f, 0.0f, 1.0f, 1.0f}; //定义材质的环境光颜色,骗蓝色
GLfloat earth_mat_diffuse[] = {0.0f, 0.0f, 0.5f, 1.0f}; //定义材质的漫反射光颜色,偏蓝色
GLfloat earth_mat_specular[] = {1.0f, 0.0f, 0.0f, 1.0f}; //定义材质的镜面反射光颜色,红色
GLfloat earth_mat_emission[] = {0.0f, 0.0f, 0.0f, 1.0f}; //定义材质的辐射光颜色,为0
GLfloat earth_mat_shininess = 30.0f;
glMaterialfv(GL_FRONT, GL_AMBIENT, earth_mat_ambient);
glMaterialfv(GL_FRONT, GL_DIFFUSE, earth_mat_diffuse);
glMaterialfv(GL_FRONT, GL_SPECULAR, earth_mat_specular);
glMaterialfv(GL_FRONT, GL_EMISSION, earth_mat_emission);
glMaterialf (GL_FRONT, GL_SHININESS, earth_mat_shininess);
glRotatef(angle, 0.0f, -1.0f, 0.0f);
glTranslatef(7.0f, 0.0f, 0.0f);
glutSolidSphere(3.0, 40, 32);
}
glutSwapBuffers();
grab();
} void myIdle(void)
{
angle += 1.0f;
if( angle >= 360.0f )
angle = 0.0f;
myDisplay();
} int main(int argc, char* argv[])
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE);
glutInitWindowPosition(200, 200);
glutInitWindowSize(400, 400);
glutCreateWindow("OpenGL光照演示");
glutDisplayFunc(&myDisplay);
glutIdleFunc(&myIdle);
glutMainLoop();
return 0;
}

在本地程序目录下,保存的截图:

         

OpenGL(十) 截屏并保存BMP文件的更多相关文章

  1. 一个类实现Java截屏并保存到指定文件夹

    不知小伙伴们有没有遇到过使用java来截屏的需求,截屏后保存到指定的目录,在桌面上没有任何体现,完全不知道已经被截屏了.至于截屏后怎么做,可能有的老铁只是好奇想知道某人在干啥?也有的老铁可能想进行文字 ...

  2. Qt实现截屏并保存(转载)

    原博地址:http://blog.csdn.net/qinchunwuhui/article/details/52869451?_t_t_t=0.28889142944202306 目前对应用实现截屏 ...

  3. Mac下使用Automator实现截屏编辑保存

    以前在Windows下使用百度或者搜狗输入法的截图工具很方便.❶快捷键(Alt+X,我设置的是这个),❷选择区域,❸编辑所选区域,包括添加文字,线条框框,调色,❹点击『✔️』选择保存位置,修改文件名保 ...

  4. Swift实现截屏并保存相册

    func saveToLocal() { //截屏 let screenRect = UIScreen.mainScreen().bounds UIGraphicsBeginImageContext( ...

  5. Selenium | 基础入门 | 截屏并保存于本地

    可先参考   Selenium | 基础入门 | 利用Xpath寻找用户框 核心代码: //截屏操作 File srcFile = ((TakesScreenshot)driver).getScree ...

  6. Selenium基础知识(十)截屏

    自动化测试过程中,经常会用截图的方式,更直观的显示展示错误信息:selenium截图的三种方式: driver.get_screenshot_as_file(r'd:\selenium.png') # ...

  7. iOS截屏保存至相册

    #pragma mark 截屏并保存至相册 -(void)screenShotsComplete:(void(^)(UIImage * img)) complete { CGSize imageSiz ...

  8. Unity3d 截屏保存到相册,并且刷新相册

    要做一个截图的功能,并且玩家可以在相册中看到. 做的时候遇到了三个问题: 1.unity自带的截图API,Application.CaptureScreenshot在Android上不生效 2.图片保 ...

  9. app内区域截图利用html2Canvals保存到手机 截屏 (html2Canvas使用版本是:0.5.0-beta3。)

    app内区域截图利用html2Canvals保存到手机 app内有时候需要区域内的截图保存dom为图像,我们可以使用html2Canvas将dom转换成base64图像字符串,然后再利用5+api保存 ...

随机推荐

  1. GitHub的repository的相关操作

    原文地址 https://www.jianshu.com/p/038e8ba10e45 1.准备工作 a.有自己的GitHub账号(https://github.com/)b.在自己本地有安装git软 ...

  2. 【38.24%】【POJ 1201】Intervals

    Time Limit: 2000MS Memory Limit: 65536K Total Submissions: 25902 Accepted: 9905 Description You are ...

  3. php课程 5-18 数组排序和合并拆分函数有哪些

    php课程  5-18   数组排序和合并拆分函数有哪些 一.总结 一句话总结:分类来记.这些函数自己都可以写,费点时间而已. 1.array_combine()和array_merge()的区别是什 ...

  4. Django日志器的使用

    Logging Mudel A quick logging primer Django uses Python’s builtin logging module to perform system l ...

  5. An Overview of Cisco IOS Versions and Naming

    An Overview of Cisco IOS Versions and Naming http://www.ciscopress.com/articles/article.asp?p=210654 ...

  6. 给自己加油,一定要学会MFC!(刚刚发现一篇文章,兼听则明: MFC,一开始就错了)

    我自己对于没有学会MFC始终耿耿于怀,都什么时代了啊,但是我仍然坚持会去学MFC,因为MFC虽然落后与复杂,但是在Windows平台上仍然是无所不能的(其实Windows平台仍然是唯一可以赚钱的平台, ...

  7. HTML5物理游戏开发 - 越野山地自行车(三)粉碎自行车

    自上一章公布到如今已时隔四月,实在对不住大家.让大家久等了~话说不是我不关注我的博客,而是事情一多起来写博客的时间就少了. 待到今日有空了,回头看了看自己曾经写的文章,猛得发现已经四个月不曾写文章了. ...

  8. hdu 4059 The Boss on Mars

    The Boss on Mars Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) ...

  9. [GeekBand] C++ 基础知识之 The Big Three

    本文是GeekBand课程体系中,侯捷老师讲课内容的部分内容总结. 参考书籍如下:Effitive C++ C++ Primer 第五版 http://blog.csdn.net/lwbeyond/a ...

  10. 每天一个JavaScript实例-铺货鼠标点击位置并将元素移动到该位置

    <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content ...