📄 15. 与设备无关的位图.txt
字号:
{
BITMAPINFOHEADER bmiHeader ; // info-header structure
RGBQUAD bmiColors[1] ; // color table array
}
BITMAPINFO, * PBITMAPINFO ;
注意,如果BITMAPINFO结构以32位的地址边界开始,因为BITMAPINFOHEADER结构的长度是40字节,所以RGBQUAD数组内的每一个项目也以32位边界开始。这样就确保通过32位微处理器能更有效地对色彩对照表数据寻址。
尽管BITMAPINFOHEADER最初是在Windows 3.0中定义的,但是许多字段在Windows 95和Windows NT 4.0中又重新定义了,并且被带入Windows 98和Windows NT 5.0中。比如现在的文件中说:「如果biHeight是负数,则位图是由上而下的DIB,原点在左上角」。这很好,但是在1990年刚开始定义DIB格式时,如果有人做了这个决定,那会更好。我的建议是避免建立由上而下的DIB。有一些程序在编写时没有考虑这种新「特性」,在遇到负的biHeight字段时会当掉。还有如Microsoft Word 97带有的Microsoft Photo Editor在遇到由上而下的DIB时会报告「图像高度不合法」(虽然Word 97本身不会出错)。
biPlanes字段始终是1,但biBitCount字段现在可以是16或32以及1、4、8或24。这也是在Windows 95和Windows NT 4.0中的新特性。一会儿我将介绍这些附加格式工作的方式。
现在让我们先跳过biCompression和biSizeImage字段,一会儿再讨论它们。
biXPelsPerMeter和biYPelsPerMeter字段以每公尺多少图素这种笨拙的单位指出图像的实际尺寸。(「pel」--picture element(图像元素)--是IBM对图素的称呼。)Windows在内部不使用此类信息。然而,应用程序能够利用它以准确的大小显示DIB。如果DIB来源于没有方图素的设备,这些字段是很有用的。在大多数DIB内,这些字段设定为0,这表示没有建议的实际大小。每英寸72点的分辨率(有时用于视讯显示器,尽管实际分辨率依赖于显示器的大小)大约相当于每公尺2835个图素,300 DPI的普通打印机的分辨率是每公尺11,811个图素。
biClrUsed是非常重要的字段,因为它影响色彩对照表中项目的数量。对于4位和8位DIB,它能分别指出色彩对照表中包含了小于16或256个项目。虽然并不常用,但这是一种缩小DIB大小的方法。例如,假设DIB图像仅包括64个灰阶,biClrUsed字段设定为64,并且色彩对照表为256个字节大小的色彩对照表包含了64个RGBQUAD结构。图素值的范围从0x00到0x3F。DIB仍然每图素需要1字节,但每个图素字节的高2位为零。如果biClrUsed字段设定为0,意味着色彩对照表包含了由biBitCount字段表示的全部项目数。
从Windows 95开始,biClrUsed字段对于16位、24位或32位DIB可以为非零。在这些情况下,Windows不使用色彩对照表解释图素位。相反地,它指出DIB中色彩对照表的大小,程序使用该信息来设定调色盘在256色视讯显示器上显示DIB。您可能想起在OS/2兼容格式中,24位DIB没有色彩对照表。在Windows 3.0中的扩展格式中,也与这一样。而在Windows 95中,24位DIB有色彩对照表,biClrUsed字段指出了它的大小。
总结如下:
对于1位DIB,biClrUsed始终是0或2。色彩对照表始终有两个项目。
对于4位DIB,如果biClrUsed字段是0或16,则色彩对照表有16个项目。如果是从2到15的数,则指的是色彩对照表中的项目数。每个图素的最大值是小于该数的1。
对于8位DIB,如果biClrUsed字段是0或256,则色彩对照表有256个项目。如果是从2到225的数,则指的是色彩对照表中的项目数。每个图素的最大值是小于该数的1。
对于16位、24位或32位DIB,biClrUsed字段通常为0。如果它不为0,则指的是色彩对照表中的项目数。执行于256色显示卡的应用程序能使用这些项目来为DIB设定调色盘。
另一个警告:原先使用早期DIB文件编写的程序不支持24位DIB中的色彩对照表,如果在程序使用24位DIB的色彩对照表的话,就要冒一定的风险。
biClrImportant字段实际上没有biClrUsed字段重要,它通常被设定为0以指出色彩对照表中所有的颜色都是重要的,或者它与biClrUsed有相同的值。两种方法意味着同一件事,如果它被设定为0与biClrUsed之间的值,就意味着DIB图像能仅仅通过色彩对照表中第一个biClrImportant项目合理地取得。当在256色显示卡上并排显示两个或更多8位DIB时,这是很有用的。
对于1位、4位、8位和24位的DIB,图素位的组织和OS/2兼容的DIB是相同的,一会儿我将讨论16位和32位DIB。
真实检查
当遇到一个由其它程序或别人建立的DIB时,您希望从中发现什么内容呢?
尽管在Windows3.0首次推出时,OS/2样式的DIB已经很普遍了,但最近这种格式却已经很少出现了。许多程序写作者在实际编写快速DIB例程时忽略了它们。您遇到的任何4位DIB可能是Windows的「小画家」程序使用16色视讯显示器建立的,在这些显示器上色彩对照表具有标准的16种颜色。
最普遍的DIB可能是每图素8位。8位DIB分为两类:灰阶DIB和混色DIB。不幸的是,表头信息中并没有指出8位DIB的型态。
许多灰阶DIB有一个等于64的biClrUsed字段,指出色彩对照表中的64个项目。这些项目通常以上升的灰阶层排列,也就是说色彩对照表以00-00-00、04-04-04、08-08-08、0C-0C-0C的RGB值开始,并包括F0-F0-F0、F4-F4-F4、F8-F8-F8和FC-FC-FC的RGB值。此类色彩对照表可用下列公式计算:
rgb[i].rgbRed = rgb[i].rgbGreen = rgb[i].rgbBlue = i * 256 / 64 ;
在这里rgb是RGBQUAD结构的数组,i的范围从0到63。灰阶色彩对照表可用下列公式计算:
rgb[i].rgbRed = rgb[i].rgbGreen = rgb[i].rgbBlue = i * 255 / 63 ;
因而此表以FF-FF-FF结尾。
实际上使用哪个计算公式并没有什么区别。许多视讯显示卡和显示器没有比6位更大的色彩精确度。第一个公式承认了这个事实。然而当产生小于64的灰阶时-可能是16或32(在此情况下公式的除数分别是15和31)-使用第二个公式更适合,因为它确保了色彩对照表的最后一个项目是FF-FF-FF,也就是白色。
当某些8位灰阶DIB在色彩对照表内有64个项目时,其它灰阶的DIB会有256个项目。biClrUsed字段实际上可以为0(指出色彩对照表中有256个项目)或者从2到256的数。当然,biClrUsed值是2的话就没有任何意义(因为这样的8位DIB能当作1位DIB被重新编码)或者小于或等于16的值也没意义(因为它能当作4位DIB被重新编码)。任何情况下,色彩对照表中的项目数必须与biClrUsed字段相同(如果biClrUsed是0,则是256),并且图素值不能超过色彩对照表项目数减1的值。这是因为图素值是指向色彩对照表数组的索引。对于biClrUsed值为64的8位DIB,图素值的范围从0x00到0x3F。
在这里应记住一件重要的事情:当8位DIB具有由整个灰阶组成的色彩对照表(也就是说,当红色、绿色和蓝色程度相等时),或当这些灰阶层在色彩对照表中递增(像上面描述的那样)时,图素值自身就代表了灰色的程度。也就是说,如果biClrUsed是64,那么0x00图素值为黑色,0x20的图素值是50%的灰阶,0x3F的图素值为白色。
这对于一些图像处理作业是很重要的,因为您可以完全忽略色彩对照表,仅需处理图素值。这是很有用的,如果让我回溯时光去对BITMAPINFOHEADER结构做一个简单的更改,我会添加一个旗标指出DIB映像是不是灰阶的,如果是,DIB就没有色彩对照表,并且图素值直接代表灰阶。
混色的8位DIB一般使用整个色彩对照表,它的biClrUsed字段为0或256。然而您也可能遇到较小的颜色数,如236。我们应承认一个事实:程序通常只能在Windows颜色面内更改236个项目以正确显示这些DIB,我将在 下章讨论这些内容。
biXPelsPerMeter和biYPelsPerMeter很少为非零值,biClrImportant字段不为0或biClrUsed值的情况也很少。
DIB压缩
前面我没有讨论BITMAPINFOHEADER中的biCompression和biSizeImage字段,现在我们讨论一下这些值。
biCompression字段可以为四个常数之一,它们是:BI_RGB、BI_RLE8、BI_RLE4或BI_BITFIELDS。它们定义在WINGDI.H表头文件中,值分别为0到3。此字段有两个用途:对于4位和8位DIB,它指出图素位被用一种运行长度(run-length)编码方式压缩了。对于16位和32位DIB,它指出了颜色屏蔽(color masking)是否用于对图素位进行编码。这两个特性都是在Windows 95中发表的。
首先让我们看一下RLE压缩:
对于1位DIB,biCompression字段始终是BI_RGB。
对于4位DIB,biCompression字段可以是BI_RGB或BI_RLE4。
对于8位DIB,biCompression字段可以是BI_RGB或BI_RLE8。
对于24位DIB,biCompression字段始终是BI_RGB。
如果值是BI_RGB,图素位储存的方式和OS/2兼容的DIB一样,否则就使用运行长度编码压缩图素位。
运行长度编码(RLE)是一种最简单的数据压缩形式,它是根据DIB映射在一列内经常有相同的图素字符串这个事实进行的。RLE通过对重复图素的值及重复的次数编码来节省空间,而用于DIB的RLE方案只定义了很少的矩形DIB图像,也就是说,矩形的某些区域是未定义的,这能被用于表示非矩形图像。
8位DIB的运行长度编码在概念上更简单一些,因此让我们从这里入手。表15-1会帮助您理解当biCompression字段等于BI_RGB8时,图素位的编码方式。
表15-1
字节1
字节2
字节3
字节4
含义
00
00
行尾
00
01
映射尾
00
02
dx
dy
移到(x+dx,y+dy)
00
n = 03到FF
使用下面n个图素
n = 01到FF
图素
重复图素n次
当对压缩的DIB译码时,可成对查看DIB数据字节,例如此表内的「字节1」和「字节2」。表格以这些字节值的递增方式排列,但由下而上讨论这个表格会更有意义。
如果第一个字节非零(表格最后一行的情况),那么它就是运行长度的重复因子。下面的图素值被重复多次,例如,字节对
0x05 0x27
解碼后的图素值为:
0x27 0x27 0x27 0x27 0x27
当然DIB会有许多数据不是图素到图素的重复,表格倒数第二行处理这种情况,它指出紧跟着的图素数应逐个使用。例如:考虑序列
0x00 0x06 0x45 0x32 0x77 0x34 0x59 0x90
解碼后的图素值为:
0x45 0x32 0x77 0x34 0x59 0x90
这些序列总是以2字节的界限排列。如果第二个字节是奇数,那么序列内就有一个未使用的多余字节。例如,序列
0x00 0x05 0x45 0x32 0x77 0x34 0x59 0x00
解碼后的图素值为:
0x45 0x32 0x77 0x34 0x59
这就是运行长度编码的工作方式。很明显地,如果在DIB图像内没有重复的图素,使用此压缩技术实际上会增加了DIB文件的大小。
上面表格的前三行指出了矩形DIB图像的某些部分可以不被定义的方法。想象一下,您写的程序对已压缩的DIB进行解压缩,在这个解压缩的例程中,您将保持一对数字(x,y),开始为(0,0)。每对一个图素译码,x的值就增加1,每完成一行就将x重新设为0并且增加y的值。
当遇到跟着0x02的字节0x00时,您读取下两个字节并把它们作为无正负号的增量添加到目前的x和y值中,然后继续解碼。当遇到跟着0x00的0x00时,您就解完了一行,应将x设0并增加y值。当遇到跟着0x01的0x00时,您就完成译码了。这些代码准许DIB包含那些未定义的区域,它们用于对非矩形图像编码或在制作数字动画和电影时非常有用(因为几乎每一格影像都有来自前一格的信息而不需重新编码)。
对于4位DIB,编码一般是相同的,但更复杂,因为字节和图素之间不是一对一的关系。
如果读取的第一个字节非零,那就是一个重复因子n。第二个字节(被重复的)包含2个图素,在n个图素的被解碼的序列中交替出现。例如,字节对
0x07 0x35
被解碼为:
0x35 0x35 0x35 0x3?
其中的问号指出图素还未知,如果是上面显示的0x07 0x35对紧跟着下面的字节对:
0x05 0x24
则整个解碼的序列为:
0x35 0x35 0x35 0x32 0x42 0x42
如果字节对中的第一字节是0x00 ,第二个字节是0x03或更大,则使用第二字节指出的图素数。例如,序列
0x00 0x05 0x23 0x57 0x10 0x00
解碼为:
0x23 0x57 0x1?
注意必须填补解碼的序列使其成为偶数字节。
无论biCompression字段是BI_RLE4或BI_RLE8,biSizeImage字段都指出了字节内DIB图素数据的大小。如果biCompression字段是BI_RGB,则biSizeImage通常为0,但是它能被设定为行内位组长度的biHeight倍,就像在本章前面计算的那样。
目前文件说「由上而下的DIB不能被压缩」。由上而下的DIB是在biHeight字段为负数的情况下出现的。
颜色掩码(Color Masking)
biCompression字段也用于连结Windows 95中新出现的16位和32位DIB。对于这些DIB,biCompression字段可以是BI_RGB或BI_BITFIELDS(均定义为值3)。
让我们看一下24位DIB的图素格式,它始终有一个等于BI_RGB的biCompression字段:
也就是说,每一行基本上都是RGBTRIPLE结构的数组,在每行末端有可能补充值以使行内的字节是4的倍数。
对于具有biCompression字段等于BI_RGB的16位DIB,每个图素需要两个字节。颜色是这样来编码的:
每种颜色使用5位。对于行内的第一个图素,蓝色值是第一个字节的最低五位。绿色值在第一和第二个字节中都有位:绿色值的两个最高位是第二个字节中的两个最低位,绿色值的三个最低位是第一个字节中的三个最高位。红色值是第二个字节中的2到6位。第二个字节的最高位是0。
当以16位字组存取图素值时,这会更加有意义。因为多个字节值的最低位首先被储存,图素字组如下:
假设在wPixel内储存了16位图素,您能用下列公式计算红色、绿色和蓝色值:
Red = ((0x7C00 & wPixel) >> 10) << 3 ;
Green = ((0x03E0 & wPixel) >> 5) << 3 ;
Blue = ((0x001F & wPixel) >> 0) << 3 ;
首先,使用屏蔽值与图素进行了位AND运算。此结果是:红色向右移动10位,绿色向右移动5位,蓝色向右移动0位。(这些移动值我称之为「右移值」)。这就产生了从0x00和0x1F的颜色值,这些值必须向左移动3位以合成从0x00到0xF8的颜色值。(这些移动值我称之为「左移值」。)
请记住:如果16位DIB的图素宽度是奇数,每行会在末端补充多余的2字节以使字节宽度能被4整除。
对于32位DIB,如果biCompression等于BI_RGB,每个图素需要4字节。蓝色值是第一个字节,绿色为第二个,红色为第三个,第四字节等于0。也可这么说,图素是RGBQUAD结构的数组。因为每个图素的长度是4字节,在列末端就不需填补字节。
若想以32位双字组存取每个图素,它就像这样:
如果dwPixel是32位双字组,
Red = ((0x00FF0000 & dwPixel) >> 16) << 0 ;
Green = ((0x0000FF00 & dwPixel) >> 8) << 0 ;
Blue = ((0x000000FF & dwPixel) >> 0) << 0 ;
左移值全为零,因为颜色值在0xFF已是最大。注意这个双字组与Windows GDI函数呼叫中用于指定RGB颜色的32位COLORREF值不一致。在COLORREF值中,红色占最低位的字节。
到目前为止,我们讨论了当biCompression字段为BI_RGB时,16位和32位DIB的内定情况。如果biCompression字段为BI_BITFIELDS,则紧跟着DIB的BITMAPINFOHEADER结构的是三个32位颜色屏蔽,第一个用于红色,第二个用于绿色,第三个用于蓝色。可以使用C的位AND运算子(&)把这些屏蔽应用于16位或32位的图素值上。然后通过右移值向右移动结果,这些值只有检查完屏蔽后才能知道。颜色屏蔽的规则应该很明确:每个颜色屏蔽位串内的1必须是连续的,并且1不能在三个屏蔽位串中重迭。
让我们来举个例子,如果您有一个16位DIB,并且biCompression字段为BI_BITFIELDS。您应该检查BITMAPINFOHEADER结构之后的前三个双字组:
0x0000F800
0x000007E0
0x0000001F
注意,因为这是16位DIB,所以只有位于底部16位的位值才能被设定为1。您可以把变量dwMask[0]、dwMask[1]和dwMask[2]设定为这些值。现在可以编写从掩码中计算右移和左移值的一些例程了:
int MaskToRShift (DWORD dwMask)
{
int iShift ;
if ( dwMask == 0)
return 0 ;
for ( iShift = 0 ; !(dwMask & 1) ; iShift++)
dwMask >>= 1 ;
return iShift ;
}
int MaskToLShift (DWORD dwMask)
{
int iShift ;
if ( dwMask == 0)
return 0 ;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -