文本内容转载自《数字图像处理编程入门》,代码为自己实现

1.1图和调色板的概念

如今Windows(3.x以及95,98,NT)系列已经成为绝大多数用户使用的操作系统,它比DOS成功的一个重要因素是它可视化的漂亮界面。那么Windows是如何显示图象的呢?这就要谈到位图(bitmap)。

我们知道,普通的显示器屏幕是由许许多多点构成的,我们称之为象素。显示时采用扫描的方法:电子枪每次从左到右扫描一行,为每个象素着色,然后从上到下这样扫描若干行,就扫过了一屏。为了防止闪烁,每秒要重复上述过程几十次。例如我们常说的屏幕分辨率为640×480,刷新频率为70Hz,意思是说每行要扫描640个象素,一共有480行,每秒重复扫描屏幕70次。

我们称这种显示器为位映象设备。所谓位映象,就是指一个二维的象素矩阵,而位图就是采用位映象方法显示和存储的图象。举个例子,图1.1是一幅普通的黑白位图,图1.2是被放大后的图,图中每个方格代表了一个象素。我们可以看到:整个骷髅就是由这样一些黑点和白点组成的。

                                                                     

1.1    骷髅                                                                            图1.2     放大后的骷髅位图

那么,彩色图是怎么回事呢?

我们先来说说三元色RGB概念。

我们知道,自然界中的所有颜色都可以由红、绿、蓝(R,G,B)组合而成。有的颜色含有红色成分多一些,如深红;有的含有红色成分少一些,如浅红。针对含有红色成分的多少,可以分成0到255共256个等级,0级表示不含红色成分;255级表示含有100%的红色成分。同样,绿色和蓝色也被分成256级。这种分级概念称为量化。

这样,根据红、绿、蓝各种不同的组合我们就能表示出256×256×256,约1600万种颜色。这么多颜色对于我们人眼来说已经足够丰富了。

表1.1     常见颜色的RGB组合值

颜色

R

G

B

255

0

0

0

255

0

绿

0

0

255

255

255

0

255

0

255

0

255

255

255

255

255

0

0

0

128

128

128

你大概已经明白了,当一幅图中每个象素赋予不同的RGB值时,能呈现出五彩缤纷的颜色了,这样就形成了彩色图。的确是这样的,但实际上的做法还有些差别。

让我们来看看下面的例子。

有一个长宽各为200个象素,颜色数为16色的彩色图,每一个象素都用R、G、B三个分量表示。因为每个分量有256个级别,要用8位(bit),即一个字节(byte)来表示,所以每个象素需要用3个字节。整个图象要用200×200×3,约120k字节,可不是一个小数目呀!如果我们用下面的方法,就能省的多。

因为是一个16色图,也就是说这幅图中最多只有16种颜色,我们可以用一个表:表中的每一行记录一种颜色的R、G、B值。这样当我们表示一个象素的颜色时,只需要指出该颜色是在第几行,即该颜色在表中的索引值。举个例子,如果表的第0行为255,0,0(红色),那么当某个象素为红色时,只需要标明0即可。

让我们再来计算一下:16种状态可以用4位(bit)表示,所以一个象素要用半个字节。整个图象要用200×200×0.5,约20k字节,再加上表占用的字节为3×16=48字节.整个占用的字节数约为前面的1/6,省很多吧?

这张R、G、B的表,就是我们常说的调色板(Palette),另一种叫法是颜色查找表LUT(Look UpTable),似乎更确切一些。Windows位图中便用到了调色板技术。其实不光是Windows位图,许多图象文件格式如pcx、tif、gif等都用到了。所以很好地掌握调色板的概念是十分有用的。

有一种图,它的颜色数高达256×256×256种,也就是说包含我们上述提到的R、G、B颜色表示方法中所有的颜色,这种图叫做真彩色图(true color)。真彩色图并不是说一幅图包含了所有的颜色,而是说它具有显示所有颜色的能力,即最多可以包含所有的颜色。表示真彩色图时,每个象素直接用R、G、B三个分量字节表示,而不采用调色板技术。原因很明显:如果用调色板,表示一个象素也要用24位,这是因为每种颜色的索引要用24位(因为总共有224种颜色,即调色板有224行),和直接用R,G,B三个分量表示用的字节数一样,不但没有任何便宜,还要加上一个256×256×256×3个字节的大调色板。所以真彩色图直接用R、G、B三个分量表示,它又叫做24位色图。

1.2 bmp文件格式

介绍完位图和调色板的概念,下面就让我们来看一看Windows的位图文件(.bmp文件)的格式是什么样子的。

bmp文件大体上分成四个部分,如图1.3所示。

位图文件头BITMAPFILEHEADER

位图信息头BITMAPINFOHEADER

调色板Palette

实际的位图数据ImageDate

图1.3     Windows位图文件结构示意图

第一部分为位图文件头BITMAPFILEHEADER,是一个结构,其定义如下:

typedefstruct tagBITMAPFILEHEADER {

WORD          bfType;

DWORD bfSize;

WORD          bfReserved1;

WORD          bfReserved2;

DWORDbfOffBits;

}BITMAPFILEHEADER;

这个结构的长度是固定的,为14个字节(WORD为无符号16位整数,DWORD为无符号32位整数),各个域的说明如下:

bfType

指定文件类型,必须是0x424D,即字符串“BM”,也就是说所有.bmp文件的头两个字节都是“BM”。

bfSize

指定文件大小,包括这14个字节。

bfReserved1,bfReserved2     

为保留字,不用考虑

bfOffBits

为从文件头到实际的位图数据的偏移字节数,即图1.3中前三个部分的长度之和。

第二部分为位图信息头BITMAPINFOHEADER,也是一个结构,其定义如下:

typedefstruct tagBITMAPINFOHEADER{

DWORD biSize;

LONG           biWidth;

LONG           biHeight;

WORD          biPlanes;

WORD          biBitCount

DWORD biCompression;

DWORD biSizeImage;

LONG           biXPelsPerMeter;

LONG           biYPelsPerMeter;

DWORD biClrUsed;

DWORD biClrImportant;

}BITMAPINFOHEADER;

这个结构的长度是固定的,为40个字节(LONG为32位整数),各个域的说明如下:

biSize

指定这个结构的长度,为40。

biWidth

指定图象的宽度,单位是象素。

biHeight

指定图象的高度,单位是象素。

biPlanes

必须是1,不用考虑。

biBitCount

指定表示颜色时要用到的位数,常用的值为1(黑白二色图), 4(16色图), 8(256色), 24(真彩色图)(新的.bmp格式支持32位色,这里就不做讨论了)。

biCompression

指定位图是否压缩,有效的值为BI_RGB,BI_RLE8,BI_RLE4,BI_BITFIELDS(都是一些Windows定义好的常量)。要说明的是,Windows位图可以采用RLE4,和RLE8的压缩格式,但用的不多。我们今后所讨论的只有第一种不压缩的情况,即biCompression为BI_RGB的情况。

biSizeImage

指定实际的位图数据占用的字节数,其实也可以从以下的公式中计算出来:

biSizeImage=biWidth’ × biHeight

要注意的是:上述公式中的biWidth’必须是4的整倍数(所以不是biWidth,而是biWidth’,表示大于或等于biWidth的,最接近4的整倍数。举个例子,如果biWidth=240,则biWidth’=240;如果biWidth=241,biWidth’=244)。

如果biCompression为BI_RGB,则该项可能为零

biXPelsPerMeter

指定目标设备的水平分辨率,单位是每米的象素个数,关于分辨率的概念,我们将在第4章详细介绍。

biYPelsPerMeter

指定目标设备的垂直分辨率,单位同上。

biClrUsed

指定本图象实际用到的颜色数,如果该值为零,则用到的颜色数为2biBitCount

biClrImportant

指定本图象中重要的颜色数,如果该值为零,则认为所有的颜色都是重要的。

第三部分为调色板Palette,当然,这里是对那些需要调色板的位图文件而言的。有些位图,如真彩色图,前面已经讲过,是不需要调色板的,BITMAPINFOHEADER后直接是位图数据。

调色板实际上是一个数组,共有biClrUsed个元素(如果该值为零,则有2biBitCount个元素)。数组中每个元素的类型是一个RGBQUAD结构,占4个字节,其定义如下:

typedefstruct tagRGBQUAD {

BYTE   rgbBlue; //该颜色的蓝色分量

BYTE   rgbGreen; //该颜色的绿色分量

BYTE   rgbRed; //该颜色的红色分量

BYTE   rgbReserved; //保留值

} RGBQUAD;

第四部分就是实际的图象数据了。对于用到调色板的位图,图象数据就是该象素颜在调色板中的索引值。对于真彩色图,图象数据就是实际的R、G、B值。下面针对2色、16色、256色位图和真彩色位图分别介绍。

对于2色位图,用1位就可以表示该象素的颜色(一般0表示黑,1表示白),所以一个字节可以表示8个象素。

对于16色位图,用4位可以表示一个象素的颜色,所以一个字节可以表示2个象素。

对于256色位图,一个字节刚好可以表示1个象素。

对于真彩色图,三个字节才能表示1个象素,哇,好费空间呀!没办法,谁叫你想让图的颜色显得更亮丽呢,有得必有失嘛。

要注意两点:

(1)    每一行的字节数必须是4的整倍数,如果不是,则需要补齐。这在前面介绍biSizeImage时已经提到了。

(2)    一般来说,.bMP文件的数据从下到上,从左到右的。也就是说,从文件中最先读到的是图象最下面一行的左边第一个象素,然后是左边第二个象素……接下来是倒数第二行左边第一个象素,左边第二个象素……依次类推,最后得到的是最上面一行的最右一个象素。

开发工具:vc++6.0,Win32 控制台程序

    1. /**
    2. * 程序名: WorkBmp.cpp
    3. * 功  能: 读取和显示24位BMP图像,并把图像数据输入到ImageData.txt中
    4. * 24位bmp可以通过画图程序中的另存为的文件类型中可以选择
    5. * bmp文件放到工程目录下
    6. */
    7. #include <iostream.h>
    8. #include <stdio.h>
    9. #include <windows.h>
    10. #include <fstream.h>
    11. int biWidth;  //图像宽
    12. int biHeight;  //图像高
    13. int biBitCount; //图像类型,每像素位数
    14. //RGBQUAD *pColorTable;  //颜色表指针
    15. unsigned char *pBmpBuf;  //存储图像数据
    16. int lineByte;         //图像数据每行字节数
    17. /**
    18. * 函数名: readBmp
    19. * 参  数: bmpName -- bmp文件名
    20. * 功  能: 读入bmp文件,并获取相应的信息
    21. */
    22. bool readBmp(char *bmpName)
    23. {
    24. FILE *fp;
    25. if( (fp = fopen(bmpName,"rb")) == NULL)  //以二进制的方式打开文件
    26. {
    27. cout<<"The file "<<bmpName<<"was not opened"<<endl;
    28. return FALSE;
    29. }
    30. if(fseek(fp,sizeof(BITMAPFILEHEADER),SEEK_CUR))  //跳过BITMAPFILEHEADE
    31. {
    32. cout<<"跳转失败"<<endl;
    33. return FALSE;
    34. }
    35. BITMAPINFOHEADER infoHead;
    36. fread(&infoHead,sizeof(BITMAPINFOHEADER),1,fp);   //从fp中读取BITMAPINFOHEADER信息到infoHead中,同时fp的指针移动
    37. biWidth = infoHead.biWidth;
    38. biHeight = infoHead.biHeight;
    39. biBitCount = infoHead.biBitCount;
    40. lineByte = (biWidth*biBitCount/8+3)/4*4;   //lineByte必须为4的倍数
    41. //24位bmp没有颜色表,所以就直接到了实际的位图数据的起始位置
    42. pBmpBuf = new unsigned char[lineByte * biHeight];
    43. fread(pBmpBuf,sizeof(char),lineByte * biHeight,fp);
    44. fclose(fp);   //关闭文件
    45. return TRUE;
    46. }
    47. /**
    48. * 函数名: saveBmp
    49. * 参  数: bmpName -- bmp文件名
    50. * 功  能: 将bmp位图文件的相关信息,写入新创建的文件中
    51. */
    52. bool saveBmp(char *bmpName)
    53. {
    54. FILE *fp;
    55. if( (fp = fopen(bmpName,"wb") )== NULL)   //以二进制写入方式打开
    56. {
    57. cout<<"打开失败!"<<endl;
    58. return FALSE;
    59. }
    60. //设置BITMAPFILEHEADER参数
    61. BITMAPFILEHEADER fileHead;
    62. fileHead.bfType = 0x4D42;
    63. fileHead.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + lineByte * biHeight;
    64. fileHead.bfReserved1 = 0;
    65. fileHead.bfReserved2 = 0;
    66. fileHead.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
    67. fwrite(&fileHead,sizeof(BITMAPFILEHEADER),1,fp);
    68. //设置BITMAPINFOHEADER参数
    69. BITMAPINFOHEADER infoHead;
    70. infoHead.biSize = 40;
    71. infoHead.biWidth = biWidth;
    72. infoHead.biHeight = biHeight;
    73. infoHead.biPlanes = 1;
    74. infoHead.biBitCount = biBitCount;
    75. infoHead.biCompression = BI_RGB;
    76. infoHead.biSizeImage = lineByte * biHeight;
    77. infoHead.biXPelsPerMeter = 0;
    78. infoHead.biYPelsPerMeter = 0;
    79. infoHead.biClrUsed = 0;
    80. infoHead.biClrImportant = 0;
    81. //写入
    82. fwrite(&infoHead,sizeof(BITMAPINFOHEADER),1,fp);
    83. fwrite(pBmpBuf,sizeof(char),lineByte * biHeight,fp);
    84. fclose(fp);    //关闭文件
    85. return TRUE;
    86. }
    87. /**
    88. * 函数名: work
    89. * 功  能: 处理位图信息,并将位图数据保存到ImageData文件中
    90. */
    91. void work()
    92. {
    93. char readFileName[] = "nv.BMP";   //定义要读入的文件名
    94. if(FALSE == readBmp(readFileName))
    95. cout<<"readfile error!"<<endl;
    96. //输出图像的信息
    97. cout<<"Width = "<<biWidth<<" Height = "<<biHeight<<" biBitCount="<<biBitCount<<endl;
    98. ofstream outfile("ImageData.txt",ios::in | ios::trunc);
    99. if(!outfile)
    100. {
    101. cout<<"open error"<<endl;
    102. return ;
    103. }
    104. int count = 0;
    105. //图像数据信息是从左下角按行开始存储的
    106. for(int i = 0; i < biHeight; i++ )
    107. {
    108. for(int j = 0; j < biWidth; j++ )
    109. {
    110. for(int k = 0; k < 3; k++ )
    111. {
    112. int temp = *(pBmpBuf + i * lineByte + j + k);
    113. count++;
    114. outfile<<temp<<" ";
    115. if(count % 8 == 0)
    116. {
    117. outfile<<endl;
    118. }
    119. }
    120. }
    121. }
    122. cout<<"总的像素数:"<<count / 3<<endl;
    123. char writeBmpName[] = "nvcpy.BMP";
    124. saveBmp(writeBmpName);
    125. delete []pBmpBuf;  //释放内存
    126. }
    127. int main()
    128. {
    129. work();
    130. return 0;
    131. }
    132. from:http://blog.csdn.net/sun1956/article/details/8648460

图像编程学习笔记1——bmp文件结构处理与显示的更多相关文章

  1. 图像编程学习笔记2——bmp位图平移

    以下文字内容copy于<<数字图像处理编程入门>>,code为自己实现,是win32控制台程序. 2.1 平移 平移(translation)变换大概是几何变换中最简单的一种了 ...

  2. 图像处理笔记(1): bmp文件结构处理与显示

    1.1图和调色板的概念 如今Windows(3.x以及95,98,NT)系列已经成为绝大多数用户使用的操作系统,它比DOS成功的一个重要因素是它可视化的漂亮界面.那么Windows是如何显示图象的呢? ...

  3. 【Visual C++】游戏编程学习笔记之四:透明动画实现

    本系列文章由@二货梦想家张程 所写,转载请注明出处. 本文章链接:http://blog.csdn.net/terence1212/article/details/44224963 作者:ZeeCod ...

  4. 【Visual C++】游戏编程学习笔记之八:鼠标输入消息(小demo)

     本系列文章由@二货梦想家张程 所写,转载请注明出处. 作者:ZeeCoder  微博链接:http://weibo.com/zc463717263 我的邮箱:michealfloyd@126.c ...

  5. 【Visual C++】游戏编程学习笔记之七:键盘输入消息

     本系列文章由@二货梦想家张程 所写,转载请注明出处. 作者:ZeeCoder  微博链接:http://weibo.com/zc463717263 我的邮箱:michealfloyd@126.c ...

  6. 【Visual C++】游戏编程学习笔记之六:多背景循环动画

    本系列文章由@二货梦想家张程 所写,转载请注明出处. 本文章链接:http://blog.csdn.net/terence1212/article/details/44264153 作者:ZeeCod ...

  7. DirectX 11游戏编程学习笔记之6: 第5章The Rendering Pipeline(渲染管线)

            本文由哈利_蜘蛛侠原创,转载请注明出处.有问题欢迎联系2024958085@qq.com         注:我给的电子版是700多页,而实体书是800多页,所以我在提到相关概念的时候 ...

  8. JAVA GUI编程学习笔记目录

    2014年暑假JAVA GUI编程学习笔记目录 1.JAVA之GUI编程概述 2.JAVA之GUI编程布局 3.JAVA之GUI编程Frame窗口 4.JAVA之GUI编程事件监听机制 5.JAVA之 ...

  9. Linux Shell编程学习笔记——目录(附笔记资源下载)

    LinuxShell编程学习笔记目录附笔记资源下载 目录(?)[-] 写在前面 第一部分 Shell基础编程 第二部分 Linux Shell高级编程技巧 资源下载 写在前面 最近花了些时间学习She ...

随机推荐

  1. ESRI Shapefiles (SHP)

    ESRI Shapefiles (SHP) Also known as ESRI ArcView Shapefiles or ESRI Shapefiles. ESRI is the company ...

  2. 统一横轴墨卡托投影(UTM)

    UTM 坐标系统使用基于网格的方法表示坐标.UTM 系统将地球分为 60 个区,每一个区基于横轴墨卡托投影.画图法中的地图投影方法能够在平面中表示一个两维的曲面,比如一个标准地图.图 1 展示了一个横 ...

  3. 动态字符串 Stringbuilder类

    StringBuilder动态字符串 string 类型的修改会浪费资源,如果要修改字符串而不创建新的对象,则可以使用 System.Text.StringBuilder 类, stringbuild ...

  4. ActiveMQ持久化方式(转)

    消息持久性对于可靠消息传递来说应该是一种比较好的方法,有了消息持久化,即使发送者和接受者不是同时在线或者消息中心在发送者发送消息后宕机了,在消息 中心重新启动后仍然可以将消息发送出去,如果把这种持久化 ...

  5. C++-struct类的新特性当class用

    #include <iostream> #include <iomanip> #include <string> using namespace std; stru ...

  6. php的var关键字

    public和var的作用差不多 因为 var定义的变量如果没有加protected 或 private则默认为public php4 中一般是用 varphp5 中就一般是用 public了 现在基 ...

  7. Caffe —— Deep learning in Practice

    因工作交接须要. 要将caffe用法及总体结构描写叙述清楚. 鉴于也有同学问过我相关内容, 决定在本文中写个简单的tutorial, 方便大家參考. 本文简单的讲几个事情: Caffe能做什么? 为什 ...

  8. JQuery AJAX Demo

    JQuery AJAX Demo APP发展集团:347072638(HTML5,APP) 1.先看一个JQuery AJAX Demo HTML端: <!DOCTYPE html PUBLIC ...

  9. PYTHON学习第二天[脑图][2]

    控制流语句:if , for ,while , break , continue

  10. 一些Windows API导致的Crash以及使用问题总结

    RegQueryValueEx gethostbyname/getaddrinfo _localtime64 FindFirstFile/FindNextFile VerQueryValue Crea ...