OpenGL中位图的操作
mhr18 2025-01-03 17:28 54 浏览 0 评论
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), 1, pFile); // 读取宽度
fseek(pFile, 0x0016, SEEK_SET); // 移动到0x0016位置
// 由于上一句执行后本就应该在0x0016位置
// 所以这一句可省略
fread(&height, sizeof(height), 1, 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 % 4 != 0 ) // 修正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)
该函数总共有七个参数。
前四个参数可以得到一个矩形,该矩形所包括的像素都会被读取出来。(第一、二个参数表示了矩形的左下角横、纵坐标,坐标以窗口最左下角为零,最右上角为最大值;第三、四个参数表示了矩形的宽度和高度)
【粉丝福利】Qt开发学习资料包、大厂面试题、技术视频和学习路线图,包括(Qt C++基础,数据库编程,Qt项目实战、Qt框架、QML、Opencv、qt线程等等)有需要的可以进企鹅裙937552610领取哦~
第五个参数表示读取的内容,例如: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 = 4;
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[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 <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 * 3; // 得到每一行的像素数据长度
while( i%4 != 0 ) // 补充数据,直到i是的倍数
++i; // 本来还有更快的算法,
// 但这里仅追求直观,对速度没有太高要求
PixelDataLength = i * WindowHeight;
// 分配内存和打开文件
pPixelData = (GLubyte*)malloc(PixelDataLength);
if( pPixelData == 0 )
exit(0);
pDummyFile = fopen("dummy.bmp", "rb");
if( pDummyFile == 0 )
exit(0);
pWritingFile = fopen("grab.bmp", "wb");
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文件了。
5. glreadpixels读取的是哪里的内存??
当有前景和背景两个缓存区时,如果先调用
glReadBuffer(GL_FRONT);
然后glreadpixels读取的就是前景(GL_FRONT)缓存区。
SwapBuffers 可让 背景前景交换,如果在调用 SwapBuffers 后,则读取的 背景前景就换个,每SwapBuffers 一次,就换个一次。
相关推荐
- Redis合集-使用benchmark性能测试
-
采用开源Redis的redis-benchmark工具进行压测,它是Redis官方的性能测试工具,可以有效地测试Redis服务的性能。本次测试使用Redis官方最新的代码进行编译,详情请参见Redis...
- Java简历总被已读不回?面试挂到怀疑人生?这几点你可能真没做好
-
最近看了几十份简历,发现大部分人不是技术差,而是不会“卖自己”——一、简历死穴:你写的不是经验,是岗位说明书!反面教材:ד使用SpringBoot开发项目”ד负责用户模块功能实现”救命写法:...
- redission YYDS(redission官网)
-
每天分享一个架构知识Redission是一个基于Redis的分布式Java锁框架,它提供了各种锁实现,包括可重入锁、公平锁、读写锁等。使用Redission可以方便地实现分布式锁。red...
- 从数据库行锁到分布式事务:电商库存防超卖的九重劫难与破局之道
-
2023年6月18日我们维护的电商平台在零点刚过3秒就遭遇了严重事故。监控大屏显示某爆款手机SKU_IPHONE13_PRO_MAX在库存仅剩500台时,订单系统却产生了1200笔有效订单。事故复盘发...
- SpringBoot系列——实战11:接口幂等性的形而上思...
-
欢迎关注、点赞、收藏。幂等性不仅是一种技术需求,更是数字文明对确定性追求的体现。在充满不确定性的网络世界中,它为我们建立起可依赖的存在秩序,这或许正是技术哲学最深刻的价值所在。幂等性的本质困境在支付系...
- 如何优化系统架构设计缓解流量压力提升并发性能?Java实战分享
-
如何优化系统架构设计缓解流量压力提升并发性能?Java实战分享在高流量场景下。首先,我需要回忆一下常见的优化策略,比如负载均衡、缓存、数据库优化、微服务拆分这些。不过,可能还需要考虑用户的具体情况,比...
- Java面试题: 项目开发中的有哪些成长?该如何回答
-
在Java面试中,当被问到“项目中的成长点”时,面试官不仅想了解你的技术能力,更希望看到你的问题解决能力、学习迭代意识以及对项目的深度思考。以下是回答的策略和示例,帮助你清晰、有说服力地展示成长点:一...
- 互联网大厂后端必看!Spring Boot 如何实现高并发抢券逻辑?
-
你有没有遇到过这样的情况?在电商大促时,系统上线了抢券活动,结果活动刚一开始,服务器就不堪重负,出现超卖、系统崩溃等问题。又或者用户疯狂点击抢券按钮,最后却被告知无券可抢,体验极差。作为互联网大厂的后...
- 每日一题 |10W QPS高并发限流方案设计(含真实代码)
-
面试场景还原面试官:“如果系统要承载10WQPS的高并发流量,你会如何设计限流方案?”你:“(稳住,我要从限流算法到分布式架构全盘分析)…”一、为什么需要限流?核心矛盾:系统资源(CPU/内存/数据...
- Java面试题:服务雪崩如何解决?90%人栽了
-
服务雪崩是指微服务架构中,由于某个服务出现故障,导致故障在服务之间不断传递和扩散,最终造成整个系统崩溃的现象。以下是一些解决服务雪崩问题的常见方法:限流限制请求速率:通过限流算法(如令牌桶算法、漏桶算...
- 面试题官:高并发经验有吗,并发量多少,如何回复?
-
一、有实际高并发经验(建议结构)直接量化"在XX项目中,系统日活用户约XX万,核心接口峰值QPS达到XX,TPS处理能力为XX/秒。通过压力测试验证过XX并发线程下的稳定性。"技术方案...
- 瞬时流量高并发“保命指南”:这样做系统稳如泰山,老板跪求加薪
-
“系统崩了,用户骂了,年终奖飞了!”——这是多少程序员在瞬时大流量下的真实噩梦?双11秒杀、春运抢票、直播带货……每秒百万请求的冲击,你的代码扛得住吗?2025年了,为什么你的系统一遇高并发就“躺平”...
- 其实很多Java工程师不是能力不够,是没找到展示自己的正确姿势。
-
其实很多Java工程师不是能力不够,是没找到展示自己的正确姿势。比如上周有个小伙伴找我,五年经验但简历全是'参与系统设计''优化接口性能'这种空话。我就问他:你做的秒杀...
- PHP技能评测(php等级考试)
-
公司出了一些自我评测的PHP题目,现将题目和答案记录于此,以方便记忆。1.魔术函数有哪些,分别在什么时候调用?__construct(),类的构造函数__destruct(),类的析构函数__cal...
- 你的简历在HR眼里是青铜还是王者?
-
你的简历在HR眼里是青铜还是王者?兄弟,简历投了100份没反应?面试总在第三轮被刷?别急着怀疑人生,你可能只是踩了这些"隐形求职雷"。帮3630+程序员改简历+面试指导和处理空窗期时间...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- oracle位图索引 (63)
- oracle批量插入数据 (62)
- oracle事务隔离级别 (53)
- oracle 空为0 (50)
- oracle主从同步 (55)
- oracle 乐观锁 (51)
- redis 命令 (78)
- php redis (88)
- redis 存储 (66)
- redis 锁 (69)
- 启动 redis (66)
- redis 时间 (56)
- redis 删除 (67)
- redis内存 (57)
- redis并发 (52)
- redis 主从 (69)
- redis 订阅 (51)
- redis 登录 (54)
- redis 面试 (58)
- 阿里 redis (59)
- redis 搭建 (53)
- redis的缓存 (55)
- lua redis (58)
- redis 连接池 (61)
- redis 限流 (51)