一、文件格式

Bmp文件是非常常用的位图文件,无论是游戏还是其他都被广泛使用。针对bmp文件的处理也有一堆现成的api进行调用,然而文件内部究竟怎样,如何自己来解析这样的文件呢?为了消除无聊,我用了几天时间来研究了一下,同时作为学习笔记,进行记录。

首先,整个bmp文件的内容可以分为3到4块。之所以分为3到4块而不是固定的值,是因为,对于bmp来说可能存在调色板或者一些掩码。具体稍候讨论。

第一块是bmp的文件头用于描述整个bmp文件的情况。结构如下:

typedef struct tagBITMAPFILEHEADER {   WORD    bfType;      DWORD   bfSize;   WORD    bfReserved1;   WORD    bfReserved2;   DWORD   bfOffBits; } BITMAPFILEHEADER, *PBITMAPFILEHEADER;

这些信息相当有用,如果你想直接来解析bmp文件。第一个bfType用于表示文件类型,如果它是bmp文件,那么它这个位置的值一定是”BM” 也就是0x4D42。第二个bfSize表示整个文件的字节数。第三第四个 则保留,目前无意义,最后一个相当重要,表示,位图的数据信息离文件头的偏移量,以字节为单位。

第二块是位图信息头,即BITMAPINFOHEADER,用于描述整个位图文件的情况。以下挑重要的数据进行解释

typedef struct tagBITMAPINFOHEADER{

DWORD  biSize; //表示本结构的大小

LONG   biWidth; //位图的宽度

LONG   biHeight; //位图的高度

WORD   biPlanes; //永远为1 ,由于没有用过所以 没做研究 附msdn解释

//Specifies the number of planes for the target device. This value must be set to 1.

WORD   biBitCount;//位图的位数  分为1 4 8 16 24 32 本文没对1 4 进行研究

DWORD  biCompression; //本以为压缩类型,但是却另外有作用,稍候解释

DWORD  biSizeImage; //表示位图数据区域的大小以字节为单位

LONG   biXPelsPerMeter;

LONG   biYPelsPerMeter;

DWORD  biClrUsed;

DWORD  biClrImportant;

} BITMAPINFOHEADER, *PBITMAPINFOHEADER;

第三块就是调色板信息或者掩码部分,如果是8位位图则存放调色板 ;16 与32位 位图则存放RGB颜色的掩码,这些掩码以DWORD大小来存放。

最后一块就是位图的数据实体。

以上文件信息可以在任意一篇bmp文件结构的文章中找到描述,所以本文只是稍微带过。

二、4字节对其问题

关于数据读取。Bmp文件有个重要特性,那就是对于数据区域而言,每行的数据它必须凑满4字节,如果没有满,则用冗余的数据来补齐。这个特性直接影响到我们读取位图数据的方法,因为在我们看来(x,y)的数据应该在 y*width+x这样的位置上 但是因为会有冗余信息 那么必须将width用width+该行的冗余量来处理,而由于位图文件有不同的位数,所以这样的计算也不尽相同。

下面列出计算偏移量的一般公式。

首先将位图信息读入一个UCHAR 的buffer中 :

8位:

int pitch;

if(width%4==0){

pitch=width;

}else{

pitch=width+4-width%4;

}

index=buffer[y*pitch+x]; 因为8位位图的数据区域存放的是调色板索引值,所以只需读取这个index

16位

int pitch=width+width%2;

buffer[(y*pitch+x)*2]

buffer[(i*pitch+j)*2+1]

两个UCHAR内,存放的是(x,y)处的颜色信息

24位

int pitch=width%4;

buffer[(y*width+x)*3+y*pitch];

buffer[(y*width+x)*3+y*pitch+1];

buffer[(y*width+x)*3+y*pitch+2];

32位

由于一个象素就是4字节 所以无需补齐

虽然计算比较繁琐,但是这些计算是必须的,否则当你的位图每行的象素数不是4的倍数,那么y*width+x带给你的是一个扭曲的图片,当然如果你想做这样的旋转,也不错啊,至少我因为一开始没有考虑(不知道这个特性)让一个每行象素少1字节的16位图片变成了扭曲的菱形。

三、有了数据分离RGB分量。

由于我的测试代码用了GDI,所以我必须讲得到的某一个点的值分离成 24位模式下的RGB分离,这不是一件容易的工作。位图麻烦的地方之一就是他的格式太多,所以我们还是要分格式再讨论。

8位

通过第二部分提到的操作我们得到了一个index,这个值的范围是0~255 一共256个 正好是调色板的颜色数量。

在8位bmp图片中 数据信息前256个RGBQUAD的大小开始就是调色板的信息。不过如果要组织成调色板还要一定的转换因为里面是RGBQUAD信息 r b 两个与调色板中的顺序是颠倒的。因为我不需要调色板设置所以我字节读取到RGBQUAD数组中,并且通过下面的表达式获取RGB值:

UCHAR r=quad[index].rgbRed;

UCHAR g=quad[index].rgbGreen;

UCHAR b=quad[index].rgbBlue;

16位

这是最麻烦的一个。因为在处理时有555 565 两种格式的区别,而且还有所谓压缩类型的区别。

之前的bitmapinfoheader里面提到一个biCompression

现在我们分两种情况讨论:BI_RGB和BI_BITFIELDS

当他等于BI_RGB时 只有555 这种格式,所以可以放心大胆的进行如下的数据分离:

UCHAR b=buffer[(i*pitch+j)*2]&0x1F;

UCHAR g=(((buffer[(i*pitch+j)*2+1]<<6)&0xFF)>>3)+(buffer[(i*pitch+j)*2]>>5);

UCHAR r=(buffer[(i*pitch+j)*2+1]<<1)>>3;

希望不要被这个表达式折磨的眼花缭乱,我想既然你在看这篇文章,你就有能力阅读这样的代码,否则只能说你还没有到阅读这方面的地步,需要去学习基础的语法了。

有一点值得提醒的是由于有较多的位操作 ,所以在处理的时候在前一次操作的上面加上一对括号,我就曾经因为没有加而导致出现误差,另外虽然buffer中一个元素代表的是一个UCHAR 但是右移操作会自动增长为两字节 所以需要在进行一次与操作截取低位的1字节数据。

现在讨论BI_BITFIELDS。

这个模式下 既可以有555 也可以有565 。

555 格式 xrrrrrgggggbbbbb

565 格式 rrrrrggggggbbbbb

显然不同的格式处理不同,所以我们要首先判断处到底属于那种格式。

Bitmapinfoheader的biCompression为BI_BITFIELDS时,在位图数据区域前存在一个RGB掩码的描述是3个DWORD值,我们只需要读取其中的R或者G的掩码,来判断是那种格式。

以红色掩码为例 0111110000000000的时候就是555格式 1111100000000000就是565格式。

以下是565格式时的数据分离:

UCHAR b=buffer[(i*pitch+j)*2]&0x1F;

UCHAR g=(((buffer[(i*pitch+j)*2+1]<<5)&0xFF)>>2)+(buffer[(i*pitch+j)*2]>>5);

UCHAR r=buffer[(i*pitch+j)*2+1]>>3;

现在我们得到了RGB各自的分量,但是还有一个新的问题,那就是由于两字节表示了3个颜色  555下每个颜色最多到0x1F 565格式下最大的绿色分量也就0x3F。所以我们需要一个转换 color=color*255/最大颜色数 即可

如565下RGB(r*0xFF/0x1F,g*0xFF/0x3F,b*0xFF/0x1F)

24位

UCHAR b=buffer[(i*width+j)*3+realPitch];

UCHAR g=buffer[(i*width+j)*3+1+realPitch];

UCHAR r=buffer[(i*width+j)*3+2+realPitch];

32位

UCHAR b=buffer[(i*width+j)*4];

UCHAR g=buffer[(i*width+j)*4+1];

UCHAR r=buffer[(i*width+j)*4+2];

四、剩余的问题

当数据取到了,颜色也分离出来了 ,但是可能你绘出的位图是倒转的,这是因为有些位图的确是翻转的。通过bitmapinfoheader的biHeight可以判断是正常还是翻转,当biHeight>0的时候颠倒,它小于0的时候正常,不过测试写到现在看到的文件都是颠倒过来的。

五、相关测试代码:

采用MFC 目的只是实现自行解析位图文件

void CBmpTestView::OnDraw(CDC* pDC)

{

CBmpTestDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

// TODO: 在此处为本机数据添加绘制代码

if(filename==""){

return;

}

FILE *fp=fopen(filename,"r");

if(fp==NULL){

pDC->TextOut(100,200,"no file found");

return;

}

BITMAPFILEHEADER fileheader;

BITMAPINFO info;

fread(&fileheader,sizeof(fileheader),1,fp);

if(fileheader.bfType!=0x4D42){

pDC->TextOut(100,200,"无位图文件请选择位图文件");

fclose(fp);

return ;

}

fread(&info.bmiHeader,sizeof(BITMAPINFOHEADER),1,fp);

long width=info.bmiHeader.biWidth;

long height=info.bmiHeader.biHeight;

UCHAR *buffer=new UCHAR[info.bmiHeader.biSizeImage];

fseek(fp,fileheader.bfOffBits,0);

fread(buffer,info.bmiHeader.biSizeImage,1,fp);

if(info.bmiHeader.biBitCount==8){

int pitch;

if(width%4==0){

pitch=width;

}else{

pitch=width+4-width%4;

}

RGBQUAD quad[256];

fseek(fp,fileheader.bfOffBits-sizeof(RGBQUAD)*256,0);

fread(quad,sizeof(RGBQUAD)*256,1,fp);

if(height>0){

//height>0 表示图片颠倒

for(int i=0;i<height;i++){

for(int j=0;j<width;j++){

int index=buffer[i*pitch+j];

UCHAR r=quad[index].rgbRed;

UCHAR g=quad[index].rgbGreen;

UCHAR b=quad[index].rgbBlue;

pDC->SetPixel(j,height-i,RGB(r,g,b));

}

}

}else{

for(int i=0;i<0-height;i++){

for(int j=0;j<width;j++){

int index=buffer[i*pitch+j];

UCHAR r=quad[index].rgbRed;

UCHAR g=quad[index].rgbGreen;

UCHAR b=quad[index].rgbBlue;

pDC->SetPixel(j,i,RGB(r,g,b));

}

}

}

}else if(info.bmiHeader.biBitCount==16){

int pitch=width+width%2;

if(height>0){

//height>0 表示图片颠倒

if(info.bmiHeader.biCompression==BI_RGB){

//该模式只有555

for(int i=0;i<height;i++){

for(int j=0;j<width;j++){

//5 5 5 格式

UCHAR b=buffer[(i*pitch+j)*2]&0x1F;

UCHAR g=(((buffer[(i*pitch+j)*2+1]<<6)&0xFF)>>3)+(buffer[(i*pitch+j)*2]>>5);

UCHAR r=(buffer[(i*pitch+j)*2+1]<<1)>>3;

pDC->SetPixel(j,height-i,RGB((r*0xFF)/0x1F,(g*0xFF)/0x1F,(b*0xFF)/0x1F));

}

}

}else if(info.bmiHeader.biCompression==BI_BITFIELDS){

//该模式在bitmapinfoheader之后存在RGB掩码 每个掩码1 DWORD

fseek(fp,fileheader.bfOffBits-sizeof(DWORD )*3,0);

DWORD  rMask;

fread(&rMask,sizeof(DWORD ),1,fp);

if(rMask==0x7C00){

// 5 5 5 格式

MessageBeep(0);

for(int i=0;i<height;i++){

for(int j=0;j<width;j++){

UCHAR b=buffer[(i*pitch+j)*2]&0x1F;

UCHAR g=(((buffer[(i*pitch+j)*2+1]<<6)&0xFF)>>3)+(buffer[(i*pitch+j)*2]>>5);

UCHAR r=(buffer[(i*pitch+j)*2+1]<<1)>>3;

pDC->SetPixel(j,height-i,RGB((r*0xFF)/0x1F,(g*0xFF)/0x1F,(b*0xFF)/0x1F));

}

}

}else if(rMask==0xF800){

//5 6 5 格式

for(int i=0;i<height;i++){

for(int j=0;j<width;j++){

UCHAR b=buffer[(i*pitch+j)*2]&0x1F;

UCHAR g=(((buffer[(i*pitch+j)*2+1]<<5)&0xFF)>>2)+(buffer[(i*pitch+j)*2]>>5);

UCHAR r=buffer[(i*pitch+j)*2+1]>>3;

pDC->SetPixel(j,height-i,RGB(r*0xFF/0x1F,g*0xFF/0x3F,b*0xFF/0x1F));

}

}

}

}

}else{

if(info.bmiHeader.biCompression==BI_RGB){

//该模式只有555

for(int i=0;i<0-height;i++){

for(int j=0;j<width;j++){

//5 5 5 格式

UCHAR b=buffer[(i*pitch+j)*2]&0x1F;

UCHAR g=(((buffer[(i*pitch+j)*2+1]<<6)&0xFF)>>3)+(buffer[(i*pitch+j)*2]>>5);

UCHAR r=(buffer[(i*pitch+j)*2+1]<<1)>>3;

pDC->SetPixel(j,i,RGB((r*0xFF)/0x1F,(g*0xFF)/0x1F,(b*0xFF)/0x1F));

}

}

}else if(info.bmiHeader.biCompression==BI_BITFIELDS){

//该模式在bitmapinfoheader之后存在RGB掩码 每个掩码1 DWORD

fseek(fp,fileheader.bfOffBits-sizeof(DWORD )*3,0);

DWORD  rMask;

fread(&rMask,sizeof(DWORD ),1,fp);

if(rMask==0x7C00){

// 5 5 5 格式

MessageBeep(0);

for(int i=0;i<0-height;i++){

for(int j=0;j<width;j++){

UCHAR b=buffer[(i*pitch+j)*2]&0x1F;

UCHAR g=(((buffer[(i*pitch+j)*2+1]<<6)&0xFF)>>3)+(buffer[(i*pitch+j)*2]>>5);

UCHAR r=(buffer[(i*pitch+j)*2+1]<<1)>>3;

pDC->SetPixel(j,i,RGB((r*0xFF)/0x1F,(g*0xFF)/0x1F,(b*0xFF)/0x1F));

}

}

}else if(rMask==0xF800){

//5 6 5 格式

for(int i=0;i<0-height;i++){

for(int j=0;j<width;j++){

UCHAR b=buffer[(i*pitch+j)*2]&0x1F;

UCHAR g=(((buffer[(i*pitch+j)*2+1]<<5)&0xFF)>>2)+(buffer[(i*pitch+j)*2]>>5);

UCHAR r=buffer[(i*pitch+j)*2+1]>>3;

pDC->SetPixel(j,i,RGB(r*0xFF/0x1F,g*0xFF/0x3F,b*0xFF/0x1F));

}

}

}

}

}

//pDC->TextOut(100,200,"16位图");

}else if(info.bmiHeader.biBitCount==24){

int pitch=width%4;

//b g r

if(height>0){

//height>0 表示图片颠倒

for(int i=0;i<height;i++){

int realPitch=i*pitch;

for(int j=0;j<width;j++){

UCHAR b=buffer[(i*width+j)*3+realPitch];

UCHAR g=buffer[(i*width+j)*3+1+realPitch];

UCHAR r=buffer[(i*width+j)*3+2+realPitch];

pDC->SetPixel(j,height-i,RGB(r,g,b));

}

}

}else{

for(int i=0;i<0-height;i++){

int realPitch=i*pitch;

for(int j=0;j<width;j++){

UCHAR b=buffer[(i*width+j)*3+realPitch];

UCHAR g=buffer[(i*width+j)*3+1+realPitch];

UCHAR r=buffer[(i*width+j)*3+2+realPitch];

pDC->SetPixel(j,i,RGB(r,g,b));

}

}

}

//pDC->TextOut(100,200,"24位图");

}else if(info.bmiHeader.biBitCount==32){

// b g r a

if(height>0){

//height>0 表示图片颠倒

for(int i=0;i<0-height;i++){

for(int j=0;j<width;j++){

UCHAR b=buffer[(i*width+j)*4];

UCHAR g=buffer[(i*width+j)*4+1];

UCHAR r=buffer[(i*width+j)*4+2];

pDC->SetPixel(j,height-i,RGB(r,g,b));

}

}

}else{

for(int i=0;i<height;i++){

for(int j=0;j<width;j++){

UCHAR b=buffer[(i*width+j)*4];

UCHAR g=buffer[(i*width+j)*4+1];

UCHAR r=buffer[(i*width+j)*4+2];

pDC->SetPixel(j,i,RGB(r,g,b));

}

}

}

//pDC->TextOut(100,200,"32位图");

}

delete buffer;

fclose(fp);

}

BMP文件结构的探索 【转】相关推荐

  1. BMP文件结构及图像每行字节计算方法

    BMP文件结构 1. BMP文件组成 BMP文件由文件头.位图信息头.颜色信息和图形数据四部分组成.文件头主要包含文件的大小.文件类型.图像数据偏离文件头的长度等信息:位图信息头包含图象的尺寸信息.图 ...

  2. 浅析BMP位图文件结构(含Demo)[转]

    <!--done-->   关于BMP位图格式在网上可以找到比较详细的相关文档,有兴趣的可以搜索标题为"BMP文件结构的探索"的文章,可以在搜索结果中找到一个WORD文 ...

  3. bmp文件格式详细解析

    先区分几个概念:16色和16位色一样吗? 不一样! 颜色位数,即是用多少位字节表示的值,每一位可以表示0和1两值.通常图片的颜色深度,简称色深,就是用位数来表示的,所以,我通常会看到8位色,16位色, ...

  4. 【转】BMP图像文件格式

    5.1  BMP图像文件格式 BMP图像文件格式是游戏中常用的图像资源文件格式,BMP图像文件起源早,程序员对BMP都比较熟悉,再加上BMP格式简单,读取和写入非常容易实现,所以无论Windows的还 ...

  5. java dib文件 加位图文件头_位图(bmp)文件格式分析

    位图(bmp)文件格式分析 作者:深蓝(由博主分享) 一.什么是位图 计算机能以位图和矢量图格式显示图像. 1.位图(Bitmap): 图像又称点阵图或光栅图,它使用我们称为像素(象素,Pixel)的 ...

  6. C语言将raw转为bmp,RAW格式数据转BMP格式数据

    1.BMP文件结构,注意字节对齐 __packed struct tagBITMAPFILEHEADER { T_U16 bfType; T_U32 bfSize; T_U16 bfReserved1 ...

  7. 【正点原子I.MX6U-MINI应用篇】5、嵌入式Linux在LCD上显示BMP、JPG、PNG图片

    一.BMP图像介绍与显示 我们常用的图片格式有很多,一般最常用的有三种:JPEG(或 JPG).PNG.BMP和GIF.其中 JPEG(或JPG).PNG以及 BMP 都是静态图片,而 GIF 则可以 ...

  8. 【笔记】位图(.bmp)和矢量图(Vector):位图是点阵图或光栅图,使用像素的一格一格来描述图像,放大以后每一个像素看就像是一个个的马赛克;矢量图是使用直线和曲线来描述图形,可以无限方法,不会失真

    一.什么是位图 计算机能以位图和矢量图格式显示图像. 1.位图(Bitmap): 图像又称点阵图或光栅图,它使用我们称为像素(象素,Pixel)的一格一格的小点来描述图像.计算机屏幕其实就是一张包含大 ...

  9. 位图文件(BMP)格式分析

    一.文件格式  Bmp文件是非常常用的位图文件,无论是游戏还是其他都被广泛使用.针对bmp文件的处理也有一堆现成的api进行调用,然而文件内部究竟怎样,如何自己来解析这样的文件呢?为了消除无聊,我用了 ...

最新文章

  1. php 5.4.36 sqlserver,PHP:PHP5.4连接SQLSERVER
  2. android 电视安装apk文件损坏,android - 无法在Android电视盒上安装APK - 堆栈内存溢出...
  3. boost::gil::view_is_basic用法的测试程序
  4. python饼形图_Python | 饼形图
  5. 使用Material Design的Flutter应用
  6. linux开发之uboot移植 -- uboot简介
  7. 服务器空文件夹无法删除怎么办,为什么文件夹删不掉
  8. Python中随机漫步的实现
  9. 【读点论文】A ConvNet for the 2020s,结合swin transformer的结构设计和训练技巧调整resnet网络,在类似的FLOPs和参数量取得更好一点的效果
  10. 支付宝当面付php接入源码下载,支付宝当面付(扫码支付)实例源码下载(官方)
  11. 美国计算机专业nlp大学排名,美国人工智能专业排名前十的学校
  12. 域服务器 文件服务器,域服务器文件服务器
  13. ArcBlock ⑨ 月报 | 稳步向前 创建未来
  14. thinkpad E570重装系统后声音不能外放
  15. STM32学习100步之第七十二-七十六步——U盘、TF卡与单片机的通信(利用SPI总线通信)
  16. 20165212 学习基础和C语言基础调查
  17. ubuntu18.04 ros-melodic 在安装ros依赖包时总是出现E软件包无法定位
  18. 遗传算法求解一元函数最大值||python
  19. 【OWA】03安装部署:OWA(Office Web Apps)安装和部署
  20. 十二 iOS之 图片擦除

热门文章

  1. 信息学奥赛一本通 2061:【例1.2】梯形面积
  2. 理论基础 —— 树 —— 树的存储结构
  3. 图论 —— 差分约束系统
  4. 求10000以内n的阶乘(信息学奥赛一本通-T1172)
  5. 统计单词数(信息学奥赛一本通-T1400)
  6. 理论基础 —— 排序 —— 鸡尾酒排序
  7. 哪些集合不能使用迭代器_Rust能力养成之(6):集合体与迭代器
  8. python导入包相当于什么_Python中使用语句导入模块或包的机制研究
  9. c语言开发破解pdf软件,ARM处理器与C语言开发应用(第2版) PDF
  10. 编译原理什么是移进规约冲突_我这个人不懂什么CPU,于是我用代码模拟出了一个...