⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 15. 与设备无关的位图.txt

📁 本书介绍了在Microsoft Windows 98、Microsoft Windows NT 4.0和Windows NT 5.0下程序写作的方法
💻 TXT
📖 第 1 页 / 共 5 页
字号:
        
{
        
           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 + -