• OpenGL(十) 截屏并保存BMP文件



    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;  
    }  

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

             


  • 相关阅读:
    【iOS】修改App名字无效的原因排查方式
    取消延迟执行函数cancelPreviousPerformRequestsWithTarget
    xcode11后增加SceneDelegate、创建新项目删除SceneDelegate
    区块链技术
    如何控制css动画的开始和停止
    addEventListener中的回调函数的this指向是哪里
    网络空间安全基础(待续)
    程序设计基础(C语言)(已完结)
    数据结构基础(待续)
    3'h1+case(1)
  • 原文地址:https://www.cnblogs.com/mtcnn/p/9411917.html
Copyright © 2020-2023  润新知