基于GLUT与GLEW的OpenGL屏幕捕获:如何获取位图头?
解决GLUT+GLEW环境下填充BITMAPINFOHEADER实现OpenGL视频捕获的问题
不用先把帧存成BMP再提取头信息!你可以直接从当前OpenGL窗口的参数和帧缓冲特性来填充BITMAPINFOHEADER结构体,全程不需要中间文件。下面一步步给你讲怎么搞:
逐个字段填充说明
BITMAPINFOHEADER的每个字段都可以通过OpenGL/GLUT的现有接口直接获取或计算,不需要依赖外部文件:
- biSize:固定为
sizeof(BITMAPINFOHEADER),这是结构体自身的大小,Windows需要这个来识别结构体类型。 - biWidth:就是你的GLUT窗口宽度,用
glutGet(GLUT_WINDOW_WIDTH)直接获取。 - biHeight:这里要注意OpenGL帧缓冲的坐标是从左下角开始的,而Windows BMP默认是从左上角开始的。所以把这个值设为窗口高度的负数(比如
-glutGet(GLUT_WINDOW_HEIGHT)),这样BITMAP会自动按从上到下的顺序存储像素,和OpenGL帧缓冲的读取结果匹配,避免后续还要翻转图像。 - biPlanes:固定为
1,Windows的BITMAP规范里这个字段始终是1,不用改。 - biBitCount:对应你
glReadPixels使用的像素格式:- 如果用
GL_RGBA读取32位像素,设为32; - 如果用
GL_RGB读取24位像素,设为24;
这个必须和你读取像素的格式严格对应,否则颜色会错乱。
- 如果用
- biCompression:设为
BI_RGB,表示不压缩,因为我们从OpenGL读的是原始像素数据,不需要压缩处理。 - biSizeImage:计算整个帧的字节数,公式是
每行字节数 × 帧高度的绝对值。其中每行字节数需要对齐到4字节(Windows BITMAP的要求),计算方式是:((biWidth × biBitCount + 31) / 32) × 4。 - biXPelsPerMeter / biYPelsPerMeter:视频生成不需要这个参数,直接设为
0就行。 - biClrUsed / biClrImportant:如果是24/32位真彩色,这两个都设为
0,因为不需要调色板。
完整代码示例
下面是填充BITMAPINFOHEADER和读取OpenGL帧的代码片段:
#include <GL/glew.h> #include <GL/glut.h> #include <cstring> #include <algorithm> // 用于std::swap // 填充BITMAPINFOHEADER BITMAPINFOHEADER initBitmapHeader() { BITMAPINFOHEADER header{}; int winWidth = glutGet(GLUT_WINDOW_WIDTH); int winHeight = glutGet(GLUT_WINDOW_HEIGHT); header.biSize = sizeof(BITMAPINFOHEADER); header.biWidth = winWidth; header.biHeight = -winHeight; // 负数值匹配OpenGL帧缓冲方向 header.biPlanes = 1; header.biBitCount = 32; // 我们用32位RGBA格式 header.biCompression = BI_RGB; // 计算每行对齐后的字节数 int bytesPerRow = ((winWidth * header.biBitCount + 31) / 32) * 4; header.biSizeImage = bytesPerRow * winHeight; // 剩余字段设为0 header.biXPelsPerMeter = 0; header.biYPelsPerMeter = 0; header.biClrUsed = 0; header.biClrImportant = 0; return header; } // 捕获单帧并转换为BITMAP格式 unsigned char* captureOpenGLFrame(const BITMAPINFOHEADER& header) { int winWidth = glutGet(GLUT_WINDOW_WIDTH); int winHeight = glutGet(GLUT_WINDOW_HEIGHT); unsigned char* pixelData = new unsigned char[header.biSizeImage]; // 从OpenGL读取像素(RGBA格式) glReadPixels(0, 0, winWidth, winHeight, GL_RGBA, GL_UNSIGNED_BYTE, pixelData); // 转换RGBA为BGRA(Windows BITMAP的32位格式是BGRA) for (int i = 0; i < header.biSizeImage; i += 4) { std::swap(pixelData[i], pixelData[i + 2]); // 交换R和B通道 } return pixelData; }
和AVI类结合的注意事项
- 初始化时调用一次SetBitmapHeader:在你创建AVI写入器实例后,只需要把上面初始化好的
BITMAPINFOHEADER传给SetBitmapHeader一次,不需要每一帧都调用。 - 帧率设置:记得在AVI初始化时设置好正确的帧率(比如30fps),这样生成的视频播放速度才正常。
- 内存管理:每次捕获帧后,把像素数据传给AVI类的写入函数后,要记得释放
pixelData的内存,避免内存泄漏。 - 关于GetActiveView():那个函数是MFC框架里的,而你用的是GLUT,完全不需要它——我们自己填充的
BITMAPINFOHEADER已经包含了所有需要的信息。
这样你就可以直接把OpenGL的帧数据传给AVI类,不需要中间存BMP文件啦!
内容的提问来源于stack exchange,提问作者anako
相关产品推荐
相关产品推荐

