见招拆招《Windows程序设计》(十三)
相关的例子:下载>>> 作者:Zoologist 于2009-3-17上传 

与设备无关的位图

 

在上一期我们了解到Windows GDI位图对象(也称为与设备相关的位图,或DDB)有许多程序设计用途。但是我并没有展示把这些位图储存到磁盘文件或把它们加载内存的方法。这是以前在Windows中使用的方法,现在根本不用了。因为位图的位格式相当依赖于设备,所以DDB不适用于图像交换。DDB内没有色彩对照表来指定位图的位与色彩之间的联系。DDB只有在Windows开机到关机的生命期内被建立和清除时才有意义。

在Windows 3.0中发表了与设备无关的位图(DIB),提供了适用于交换的图像文件格式。正如您所知的,像.GIF或.JPEG之类的其它图像文件格式在Internet上比DIB文件更常见。这主要是因为.GIF和.JPEG格式进行了压缩,明显地减少了下载的时间。尽管有一个用于DIB的压缩方案,但极少使用。DIB内的位图几乎都没有被压缩。如果您想在程序中操作位图,这实际上是一个优点。DIB不像.GIF和.JPEG文件,Windows API直接支持DIB。如果在内存中有DIB,您就可以提供指向该DIB的指标作为某些函数的参数,来显示DIB或把DIB转化为DDB。

DIB 文件格式

有意思的是,DIB格式并不是源自于Windows。它首先定义在OS/2的1.1版中,该操作系统最初由IBM和Microsoft在八十年代中期开始开发。OS/2 1.1在1988年发布,并且是第一个包含了类似Windows的图形使用者接口的OS/2版本,该图形使用者接口被称之为「Presentation Manager(PM)」。「Presentation Manager」包含了定义位图格式的「图形程序接口」(GPI)。

额外提一句:前几天我还遇到了一个OS2的问题,安装了一个OS/2 wrap,如果以当时的眼光看,这样的设计是非常优秀的。不过因为商业上的原因(应该是被MS忽悠了)和一些其他的原因(比如:IBM的软件用起来总感觉是把客户当作开发人员,操作起来非常复杂),OS/2最终没能发展下去。

然后在Windows 3.0中(发布于1990)使用了OS/2位图格式,这时称之为DIB。Windows 3.0也包含了原始DIB格式的变体,并在Windows下成为标准。在Windows 95(以及Windows NT 4.0)和Windows 98(以及Windows NT 5.0)下也定义了一些其它的增强能力,我会在本章讨论它们。

DIB首先作为一种文件格式,它的扩展名为.BMP,在极少情况下为.DIB。Windows应用程序使用的位图图像被当做DIB文件建立,并作为只读资源储存在程序的可执行文件中。图标和鼠标光标也是形式稍有不同的DIB文件。

程序能将DIB文件减去前14个字节加载连续的内存块中。这时就可以称它为「packed DIB(packed-DIB)格式的位图」。在Windows下执行的应用程序能使用packed DIB格式,通过Windows剪贴簿来交换图像或建立画刷。程序也可以完全存取DIB的内容并以任意方式修改DIB。

程序也能在内存中建立自己的DIB然后把它们存入文件。程序使用GDI函数呼叫就能「绘制」这些DIB内的图像,也能在程序中利用别的内存DIB直接设定和操作图素位。

在内存中加载了DIB后,程序也能通过几个Windows API函数呼叫来使用DIB数据。与DIB相关的API呼叫是很少的,并且主要与显示器或打印机页面上显示DIB相关,还与转换GDI位图对象有关。

除了这些内容以外,还有许多应用程序需要完成的DIB任务,而这些任务Windows操作系统并不支持。例如,程序可能存取了24位DIB并且想把它转化为带有最佳化的256色调色盘的8位DIB,而Windows不会为您执行这些操作。但是在本章和下一章将向您显示Windows API之外的操作DIB的方式。

OS/2样式的DIB

先不要陷入太多的细节,让我们看一下与首先在OS/2 1.1中出现的位图格式兼容的Windows DIB格式。

DIB文件有四个主要部分:

您可以把前两部分看成是C的数据结构,把第三部分看成是数据结构的数组。在Windows表头文件WINGDI.H中说明了这些结构(对于汇编语言用户,可以在Windows.inc中找到对应定义)。在内存中的packed DIB格式内有三个部分:

除了没有文件表头外,其它部分与储存在文件内的DIB相同。

DIB文件(不是内存中的packed DIB)以定义为如下结构的14个字节的文件表头开始:

 

typedef struct tagBITMAPFILEHEADER  // bmfh
        
{
        
           WORD          bfType ;        // signature word "BM" or 0x4D42
        
           DWORD         bfSize ;        // entire size of file
        
           WORD          bfReserved1 ;   // must be zero
        
           WORD          bfReserved2 ;   // must be zero
        
           DWORD        bfOffsetBits ;  // offset in file of DIB pixel bits
        
}
        
BITMAPFILEHEADER, * PBITMAPFILEHEADER ;
        

在WINGDI.H内定义的结构可能与这不完全相同,但在功能上是相同的。第一个注释(就是文字「bmfh」)指出了给这种数据型态的数据变量命名时推荐的缩写。如果在我的程序内看到了名为pbmfh的变量,这可能是一个指向BITMAPFILEHEADER型态结构的指针或指向PBITMAPFILEHEADER型态变量的指针。

结构的长度为14字节,它以两个字母「BM」开头以指明是位图文件。这是一个WORD值0x4D42。紧跟在「BM」后的DWORD以字节为单位指出了包括文件表头在内的文件大小。下两个WORD字段设定为0。(在与DIB文件格式相似的鼠标光标文件内,这两个字段指出光标的「热点(hot spot)」)。结构还包含一个DWORD字段,它指出了文件中图素位开始位置的字节偏移量。此数值来自DIB信息表头中的信息,为了使用的方便提供在这里。

在OS/2样式的DIB内,BITMAPFILEHEADER结构后紧跟了BITMAPCOREHEADER结构,它提供了关于DIB图像的基本信息。紧缩的DIB(Packed DIB)开始于BITMAPCOREHEADER:

typedef struct tagBITMAPCOREHEADER  // bmch
        
{
        
           DWORD         bcSize ;              // size of the structure = 12
        
           WORD          bcWidth ;             // width of image in pixels
        
           WORD          bcHeight ;           // height of image in pixels
        
           WORD          bcPlanes ;            // = 1
        
           WORD          bcBitCount ;          // bits per pixel (1, 4, 8, or 24)
        
}
        
BITMAPCOREHEADER, * PBITMAPCOREHEADER ;
        

「core(核心)」用在这里看起来有点奇特,它是指这种格式是其它由它所衍生的位图格式的基础。

BITMAPCOREHEADER结构中的bcSize字段指出了数据结构的大小,在这种情况下是12字节。

bcWidth和bcHeight字段包含了以图素为单位的位图大小。尽管这些字段使用WORD意味着一个DIB可能为65,535图素高和宽,但是我们几乎不会用到那么大的单位。

bcPlanes字段的值始终是1。这个字段是我们在上一章中遇到的早期Windows GDI位图对象的残留物。

bcBitCount字段指出了每图素的位数。对于OS/2样式的DIB,这可能是1、4、8或24。DIB图像中的颜色数等于2 ^ bmch.bcBitCount,或用C的语法表示为:

1 << bmch.bcBitCount
        
c语言中 A << B,意思是“将A左移B位”,反之是“右移”。上面的表达式用汇编的语言描述就是:

mov eax,1

mov ecx,bmch.bcBitCount

shl eax,ecx

这样,bcBitCount字段等于:

当我提到「8位DIB」时,就是说每图素占8位的DIB。

对于前三种情况(也就是位数为1、4和8时),BITMAPCOREHEADER后紧跟色彩对照表,24位DIB没有色彩对照表。色彩对照表是一个3字节RGBTRIPLE结构的数组,数组中的每个元素代表图像中的每种颜色:

typedef struct tagRGBTRIPLE  // rgbt
        
{
        
           BYTE rgbtBlue ;       // blue level
        
           BYTE rgbtGreen ;      // green level
        
           BYTE rgbtRed ;        // red level
        
}
        
RGBTRIPLE ;
        

这样排列色彩对照表以便DIB中最重要的颜色首先显示,我们将在下一期中说明原因。

WINGDI.H表头文件也定义了下面的结构:

typedef struct tagBITMAPCOREINFO  // bmci
        
{
        
           BITMAPCOREHEADER      bmciHeader ;                  // core-header structure
        
           RGBTRIPLE             bmciColors[1] ;               // color table array
        
}
        
BITMAPCOREINFO, * PBITMAPCOREINFO ;
        

这个结构把信息表头与色彩对照表结合起来。虽然在这个结构中RGBTRIPLE结构的数量等于1,但在DIB文件内您绝对不会发现只有一个RGBTRIPLE。根据每个图素的位数,色彩对照表的大小始终是2、16或256个RGBTRIPLE结构。如果需要为8位DIB配置PBITMAPCOREINFO结构,您可以这样做:

pbmci = malloc (sizeof (BITMAPCOREINFO) + 255 * sizeof (RGBTRIPLE)) ;
        

然后可以这样存取RGBTRIPLE结构:

pbmci->bmciColors[i]
        

因为RGBTRIPLE结构的长度是3字节,许多RGBTRIPLE结构可能在DIB中以奇数地址开始。然而,因为在DIB文件内始终有偶数个的RGBTRIPLE结构,所以紧跟在色彩对照表数组后的数据块总是以WORD地址边界开始。

紧跟在色彩对照表(24位DIB中是信息表头)后的数据是图素位本身。

由下而上

像大多数位图格式一样,DIB中的图素位是以水平行组织的,用显示器硬件的术语称作「扫描线」。行数等于BITMAPCOREHEADER结构的bcHeight字段。然而,与大多数位图格式不同的是,DIB从图像的底行开始,往上表示图像。

在此应定义一些术语,当我们说「顶行」和「底行」时,指的是当其正确显示在显示器或打印机的页面上时出现在虚拟图像的顶部和底部。就好像肖像的顶行是头发,底行是下巴,在DIB文件中的「第一行」指的是DIB文件的色彩对照表后的图素行,「最后行」指的是文件最末端的图素行。

因此,在DIB中,图像的底行是文件的第一行,图像的顶行是文件的最后一行。这称之为由下而上的组织。因为这种组织和直觉相反,您可能会问:为什么要这么做?

好,现在我们回到OS/2的Presentation Manager。IBM的人认为PM内的坐标系统-包括窗口、图形和位图-应该是一致的。这引起了争论:大多数人,包括在全画面文字方式下编程和窗口环境下工作的程序写作者认为应使用垂直坐标在屏幕上向下增加的坐标。然而,计算机图形程序写作者认为应使用解析几何的数学方法进行视频显示,这是一个垂直坐标在空间中向上增加的直角(或笛卡尔)坐标系。

简而言之,数学方法赢了。PM内的所有事物都以左下角为原点(包括窗口坐标),因此DIB也就有了那种方式。

DIB图素位

DIB文件的最后部分(在大多数情况下是DIB文件的主体)由实际的DIB的图素字节成。图素位是由从图像的底行开始并沿着图像向上增长的水平行组织的。

DIB中的行数等于BITMAPCOREHEADER结构的bcHeight字段。每一行的图素数等于该结构的bcWidth字段。每一行从最左边的图素开始,直到图像的右边。每个图素的位数可以从bcBitCount字段取得,为1、4、8或24。

以字节为单位的每行长度始终是4的倍数。行的长度可以计算为:

RowLength = 4 * ((bmch.bcWidth * bmch.bcBitCount + 31) / 32) ;
        

或者在C内用更有效的方法:

RowLength = ((bmch.bcWidth * bmch.bcBitCount + 31) & ~31) >> 3 ;
        

如果需要,可通过在右边补充行(通常是用零)来完成长度。图素数据的总字节数等于RowLength和bmch.bcHeight的乘积。

要了解图素编码的方式,让我们分别考虑四种情况。在下面的图表中,每个字节的位显示在框内并且编了号,7表示最高位,0表示最低位。图素也从行的最左端从0开始编号。

对于每图素1位的DIB,每字节对应为8图素。最左边的图素是第一个字节的最高位:


 

每个图素可以是0或1。0表示该图素的颜色由色彩对照表中第一个RGBTRIPLE项目给出。1表示图素的颜色由色彩对照表的第二个项目给出。

对于每图素4位的DIB,每个字节对应两个图素。最左边的图素是第一个字节的高4位,以此类推:  

每图素4位的值的范围从0到15。此值是指向色彩对照表中16个项目的索引。

对于每图素8位的DIB,每个字节为1个图素:


 

字节的值从0到255。同样,这也是指向色彩对照表中256个项目的索引。

对于每图素24位的DIB,每个图素需要3个字节来代表红、绿和蓝的颜色值。图素位的每一行,基本上就是RGBTRIPLE结构的数组,可能需要在每行的末端补0以便该行为4字节的倍数:


 

每图素24位的DIB没有色彩对照表。

扩展的Windows DIB

现在我们掌握了Windows 3.0中介绍的与OS/2兼容的DIB,同时也看一看Windows中DIB的扩展版本。

这种DIB形式跟前面的格式一样,以BITMAPFILEHEADER结构开始,但是接着是BITMAPINFOHEADER结构,而不是BITMAPCOREHEADER结构:

typedef struct tagBITMAPINFOHEADER  // bmih
        
{
        
           DWORD biSize ;              // size of the structure = 40
        
          LONG  biWidth ;             // width of the image in pixels
        
           LONG  biHeight ;            // height of the image in pixels
        
           WORD  biPlanes ;            // = 1
        
           WORD  biBitCount ;          // bits per pixel (1, 4, 8, 16, 24, or 32)
        
           DWORD biCompression ;       // compression code
        
           DWORD biSizeImage ;         // number of bytes in image
        
           LONG  biXPelsPerMeter ;     // horizontal resolution
        
           LONG  biYPelsPerMeter ;     // vertical resolution
        
           DWORD biClrUsed ;           // number of colors used
        
           DWORD biClrImportant ;      // number of important colors
        
}
        
BITMAPINFOHEADER, * PBITMAPINFOHEADER ;
        

您可以通过检查结构的第一字段区分与OS/2兼容的DIB和Windows DIB,前者为12,后者为40。

您将注意到,在这个结构内有六个附加的字段,但是BITMAPINFOHEADER不是简单地由BITMAPCOREHEADER加上一些新字段而成。仔细看一下:在BITMAPCOREHEADER结构中,bcWidth和bcHeight字段是16位WORD值;而在BITMAPINFOHEADER结构中它们是32位LONG值。这是一个令人讨厌的小变化,当心它会给您带来麻烦。

另一个变化是:对于使用BITMAPINFOHEADER结构的1位、4位和8位DIB,色彩对照表不是RGBTRIPLE结构的数组。相反,BITMAPINFOHEADER结构紧跟着一个RGBQUAD结构的数组:

typedef struct tagRGBQUAD  // rgb
        
{
        
           BYTE rgbBlue ;     // blue level
        
           BYTE rgbGreen ;    // green level
        
           BYTE rgbRed ;      // red level
        
           BYTE rgbReserved ; // = 0
        
}
        
RGBQUAD ;
        

除了包括总是设定为0的第四个字段外,与RGBTRIPLE结构相同。 WINGDI.H表头文件也定义了以下结构:

typedef struct tagBITMAPINFO              // bmi
        
{
        
           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字段指出了它的大小。

总结如下:

另一个警告:原先使用早期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压缩:

如果值是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 ;
        

           while (!(dwMask & 1))
        
                          dwMask >>= 1 ;
        

           for (iShift = 0 ; dwMask & 1 ; iShift++)
        
                          dwMask >>= 1 ;
        

           return 8 - iShift ;
        
}
        

然后呼叫MaskToRShift函数三次来获得右移值:

iRShift[0] = MaskToRShift (dwMask[0]) ;
        
iRShift[1] = MaskToRShift (dwMask[1]) ;
        
iRShift[2] = MaskToRShift (dwMask[2]) ;
        

分别得到值11、5和0。然后呼叫MaskToLShift:

iLShift[0] = MaskToLShift (dwMask[0]) ;
        
iLShift[1] = MaskToLShift (dwMask[1]) ;
        
iLShift[2] = MaskToLShift (dwMask[2]) ;
        

分别得到值3、2和3。现在能从图素中提取每种颜色:

Red                = ((dwMask[0] & wPixel) >> iRShift[0]) << iLShift[0] ;
        
Green              = ((dwMask[1] & wPixel) >> iRShift[1]) << iLShift[1] ;
        
Blue               = ((dwMask[2] & wPixel) >> iRShift[2]) << iLShift[2] ;
        

除了颜色标记能大于0x0000FFFF(这是16位DIB的最大屏蔽值)之外,程序与32位DIB一样。


注意:

对于16位或32位DIB,红色、绿色和蓝色值能大于255。实际上,在32位DIB中,如果屏蔽中有两个为0,第三个应为32位颜色值0xFFFFFFFF。当然,这有点荒唐,但不用担心这个问题。


不像Windows NT,Windows 95和Windows 98在使用颜色屏蔽时有许多的限制。可用的值显示在表15-2中。

表15-2

16位DIB

16位DIB

32位DIB

红色屏蔽

0x00007C00

0x0000F800

0x00FF0000

绿色屏蔽

0x000003E0

0x000007E0

0x0000FF00

蓝色屏蔽

0x0000001F

0x0000001F

0x000000FF

速记为

5-5-5

5-6-5

8-8-8

换句话说,就是当biCompression是BI_RGB时,您能使用内定的两组屏蔽,包括前面例子中显示的屏蔽组。表格底行显示了一个速记符号来指出每图素红色、绿色和蓝色的位数。

第4版本的Header

我说过,Windows 95更改了一些原始BITMAPINFOHEADER字段的定义。Windows 95也包括了一个称为BITMAPV4HEADER的新扩展的信息表头。如果您知道Windows 95曾经称作Windows 4.0,则就会明白此结构的名称了,Windows NT 4.0也支持此结构。

typedef struct
        
{
        
DWORD              bV4Size ;             // size of the structure = 120
        
LONG       bV4Width ;            // width of the image in pixels
        
LONG               bV4Height ;           // height of the image in pixels
        
WORD               bV4Planes ;           // = 1
        
WORD               bV4BitCount ;         // bits per pixel (1, 4, 8, 16, 24, or 32)
        
DWORD             bV4Compression ;      // compression code
        
DWORD              bV4SizeImage ;        // number of bytes in image
        
LONG               bV4XPelsPerMeter ;    // horizontal resolution
        
LONG               bV4YPelsPerMeter ;    // vertical resolution
        
DWORD              bV4ClrUsed ;         // number of colors used
        
DWORD              bV4ClrImportant ;        // number of important colors
        
DWORD              bV4RedMask ;          // Red color mask
        
DWORD              bV4GreenMask ;        // Green color mask
        
DWORD              bV4BlueMask ;         // Blue color mask
        
DWORD              bV4AlphaMask ;        // Alpha mask
        
DWORD              bV4CSType ;           // color space type
        
CIEXYZTRIPLE       bV4Endpoints ;        // XYZ values
        
DWORD              bV4GammaRed ;         // Red gamma value
        
DWORD              bV4GammaGreen ;       // Green gamma value
        
DWORD              bV4GammaBlue ;        // Blue gamma value
        
}
        
BITMAPV4HEADER, * PBITMAPV4HEADER ;
        

注意前11个字段与BITMAPINFOHEADER结构中的相同,后5个字段支持Windows 95和Windows NT 4.0的图像颜色调配技术。除非使用BITMAPV4HEADER结构的后四个字段,否则您应该使用BITMAPINFOHEADER(或BITMAPV5HEADER)。

当bV4Compression字段等于BI_BITFIELDS时,bV4RedMask、bV4GreenMask和bV4BlueMask可以用于16位和32位DIB。它们作为定义在BITMAPINFOHEADER结构中的颜色屏蔽用于相同的函数,并且当使用除了明确的结构字段之外的原始结构时,它们实际上出现在DIB文件的相同位置。就我所知,bV4AlphaMask字段不被使用。

BITMAPV5HEADER结构剩余的字段包括「Windows颜色管理(Image Color Management)」,它的内容超越了本书的范围,但是了解一些背景会对您有益。

为色彩使用RGB方案的问题在于,它依赖于显示器、彩色照相机和彩色扫描仪的显示技术。如果颜色指定为RGB值(255,0,0),意味着最大的电压应该加到阴极射线管内的红色电子枪上,RGB值(128,0,0)表示使用一半电压。不同显示器会产生不同的效果。而且,打印机使用了不同的颜色表示方法,以青色、洋红色、黄色和黑色的组合表示颜色。这些方法称之为CMY(cyan-magenta-yellow:青色-洋红色-黄色)和CMYK(cyan-magenta-yellow-black:青色-洋红色-黄色-黑色)。数学公式能把RGB值转化为CMY和CMYK,但不能保证打印机颜色与显示器颜色相符合。「色彩调配技术」是把颜色与对设备无关的标准联系起来的一种尝试。

颜色的现象与可见光的波长有关,波长的范围从380nm(蓝)到780nm(红)之间。一切我们能察觉的光线是可见光谱内不同波长的组合。1931年,Commission Internationale de L'Eclairage (International Commission on Illumination)或CIE开发了一种科学度量颜色的方法。这包括使用三个颜色调配函数(名称为x、y和z),它们以其省略的形式(带有每5nm的值)发表在CIE Publication 15.2-1986,「Colorimetry,Second Edition」的表2.1中。

颜色的光谱(S)是一组指出每个波长强度的值。如果知道光谱,就能够将与颜色相关的函数应用到光谱来计算X、Y和Z:


 

这些值称为大X、大Y和大 Z。y颜色匹配函数等于肉眼对范围在可见光谱内光线的反应。(他看上去像一条由380nm和780nm到0的时钟形曲线)。Y称之为CIE亮度,因为它指出了光线的总体强度。

如果使用BITMAPV5HEADER结构,bV4CSType字段就必须设定为LCS_CALIBRATED_RGB,其值为0。后四个字节必须设定为有效值。

CIEXYZTRIPLE结构按照如下方式定义:

typedef struct tagCIEXYZTRIPLE
        
{
        
           CIEXYZ  ciexyzRed ;
        
           CIEXYZ  ciexyzGreen ;
        
           CIEXYZ  ciexyzBlue ;
        
}
        
CIEXYZTRIPLE, * LPCIEXYZTRIPLE ;
        

而CIEXYZ结构定义如下:

typedef struct tagCIEXYZ
        
{
        
           FXPT2DOT30 ciexyzX ;
        
           FXPT2DOT30 ciexyzY ;
        
           FXPT2DOT30 ciexyzZ ;
        
}
        
CIEXYZ, * LPCIEXYZ ;
        

这三个字段定义为FXPT2DOT30值,意味着它们是带有2位整数部分和30位小数部分的定点值。这样,0x40000000是1.0,0x48000000是1.5。最大值0xFFFFFFFF仅比4.0小一点点。

bV4Endpoints字段提供了三个与RGB颜色(255,0,0)、(0,255,0)和(0,0,255)相关的X、Y和Z值。这些值应该由建立DIB的应用程序插入以指明这些RGB颜色的设备无关的意义。

BITMAPV4HEADER剩余的三个字段指「伽马值」(希腊的小写字母γ),它指出颜色等级规格内的非线性。在DIB内,红、绿、蓝的范围从0到225。在显示卡上,这三个数值被转化为显示器使用的三个模拟电压,电压决定了每个图素的强度。然而,由于阴极射线管中电子枪的电子特性,图素的强度(I)并不与电压(V)线性相关,它们的关系为:


 

ε是由显示器的「亮度」控制设定的黑色等级(理想值为0)。指数γ由显示器的「图像」或「对比度」控制设定的。对于大多数显示器,γ大约在2.5左右。

为了对此非线性作出补偿,摄影机在线路内包含了「伽马修正」。指数0.45修正了进入摄影机的光线,这意味着视频显示器的伽马为2.2。(视频显示器的高伽马值增加了对比度,这通常是不需要的,因为周围的光线更适合于低对比度。)

视频显示器的这个非线性反应实际上是很适当的,这是因为人类对光线的反应也是非线性的。我曾提过,Y被称为CIE亮度,这是线性的光线度量。CIE也定义了一个接近于人类感觉的亮度值。亮度是L* (发音为"ell star") ,通过使用如下公式从Y计算得到的:


 

在此Yn是白色等级。公式的第一部分是一个小的线性部分。一般,人类的亮度感觉是与线性亮度的立方根相关的,这由第二个公式指出。L* 的范围从0到100,每次L* 的增加都假定是人类能感觉到的亮度的最小变化。

根据知觉亮度而不是线性亮度对光线强度编码要更好一些。这使得位的数量减少到一个合理的程度并且在模拟线路上也降低了噪声。

让我们来看一下整个程序。图素值(P)范围从0到255,它被线性转化成电压等级,我们假定标准化为0.0到1.0之间的值。假设显示器的黑色级设定为0,则图素的强度为:


 

这里γ大约为2.5。人类感觉的亮度(L*)依赖于此强度的立方根和变化从0到100的范围,因此大约是:


 

指数值大约为0.85。如果指数值为1,那么CIE亮度与图素值完全匹配。当然不完全是那种情况,但是如果图素值指出了线性亮度就非常接近。

BITMAPV4HEADER的最后三个字段为建立DIB的程序提供了一种为图素值指出假设的伽马值的方法。这些值由16位整数值和16位的小数值说明。例如,0x10000为1.0。如果DIB是捕捉实际影像而建立的,影像捕捉硬件就可能包含这个伽马值,并且可能是2.2(编码为0x23333)。如果DIB是由程序通过算法产生的,程序会使用一个函数将它使用的任何线性亮度转化为CIE亮度。

第5版的Header

为Windows 98和Windows NT 5.0(即Windows 2000)编写的程序能使用拥有新的BITMAPV5HEADER信息结构的DIB:

typedef struct
        
{
        
DWORD              bV5Size ;             // size of the structure = 120
        
LONG               bV5Width ;            // width of the image in pixels
        
LONG               bV5Height ;           // height of the image in pixels
        
WORD               bV5Planes ;           // = 1
        
WORD               bV5BitCount ;         // bits per pixel (1,4,8,16,24,or32)
        
DWORD              bV5Compression ;      // compression code
        
DWORD              bV5SizeImage ;        // number of bytes in image
        
LONG              bV5XPelsPerMeter ;    // horizontal resolution
        
LONG               bV5YPelsPerMeter ;    // vertical resolution
        
DWORD              bV5ClrUsed ;          // number of colors used
        
DWORD              bV5ClrImportant ;     // number of important colors
        
DWORD              bV5RedMask ;          // Red color mask
        
DWORD              bV5GreenMask ;        // Green color mask
        
DWORD              bV5BlueMask ;         // Blue color mask
        
DWORD              bV5AlphaMask ;        // Alpha mask
        
DWORD              bV5CSType ;           // color space type
        
CIEXYZTRIPLE       bV5Endpoints ;        // XYZ values
        
DWORD              bV5GammaRed ;         // Red gamma value
        
DWORD              bV5GammaGreen ;       // Green gamma value
        
DWORD             bV5GammaBlue ;        // Blue gamma value
        
DWORD              bV5Intent ;           // rendering intent
        
DWORD              bV5ProfileData ;      // profile data or filename
        
DWORD              bV5ProfileSize ;      // size of embedded data or filename
        
DWORD              bV5Reserved ;
        
}
        
BITMAPV5HEADER, * PBITMAPV5HEADER ;
        

这里有四个新字段,只有其中三个有用。这些字段支持ICC Profile Format Specification,这是由「国际色彩协会(International Color Consortium)」(由Adobe、Agfa、Apple、Kodak、Microsoft、Silicon Graphics、Sun Microsystems及其它公司组成)建立的。您能在http://www.icc.org上取得这个标准的副本。基本上,每个输入(扫描仪和摄影机)、输出(打印机和胶片记录器)以及显示(显示器)设备与将原始设备相关颜色(一般为RGB或CMYK)联系到设备无关颜色规格的设定文件有关,最终依据CIE XYZ值来修正颜色。这些设定文件的扩展名是.ICM(指「图像颜色管理:image color management」)。设定文件能嵌入DIB文件中或从DIB文件连结以指出建立DIB的方式。您能在/Platform SDK/Graphics and Multimedia Services/Color Management中取得有关Windows「图像颜色管理」的详细信息。

BITMAPV5HEADER的bV5CSType字段能拥有几个不同的值。如果是LCS_CALIBRATED_RGB,那么它就与BITMAPV4HEADER结构兼容。bV5Endpoints字段和伽马字段必须有效。

如果bV5CSType字段是LCS_sRGB,就不用设定剩余的字段。预设的颜色空间是「标准」的RGB颜色空间,这是由Microsoft和Hewlett-Packard主要为Internet设计的,它包含设备无关的内容而不需要大量的设定文件。此文件位于http://www.color.org/contrib/sRGB.html。

如果bV5CSType字段是LCS_Windows_COLOR_SPACE,就不用设定剩余的字段。Windows通过API函数呼叫使用预设的颜色空间显示位图。

如果bV5CSType字段是PROFILE_EMBEDDED,则DIB文件包含一个ICC设定文件。如果字段是PROFILE_LINKED,DIB文件就包含了ICC设定文件的完整路径和文件名称。在这两种情况下,bV5ProfileData都是从BITMAPV5HEADER开始到设定文件数据或文件名称起始位置的偏移量。bV5ProfileSize字段给出了数据或文件名的大小。不必设定bV5Endpoints和伽马字段。

显示DIB信息

现在让我们来看一些程序代码。实际上我们并不未充分了解显示DIB的知识,但至少能表从头结构上显示有关DIB的信息。如程序15-1 DIBHEADS所示。

程序15-1 DIBHEADS

(因为下面的程序就是一个查表输出的程序,汇编语言做起来非常麻烦,因此没有转化为汇编语言)

        
DIBHEADS.C
        
/*---------------------------------------------------------------------------
        
  DIBHEADS.C -- Displays DIB Header Information
        
                          (c) Charles Petzold, 1998
        
---------------------------------------------------------------------------*/
        
#include <windows.h>
        
#include "resource.h"
        

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
        
TCHAR szAppName[] = TEXT ("DibHeads") ;
        
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
        
                   PSTR szCmdLine, int iCmdShow)
        
{
        
           HACCEL                hAccel ;
        
           HWND                  hwnd ;
        
           MSG                           msg ;
        
   WNDCLASS              wndclass ;
        

           wndclass.style                               = CS_HREDRAW | CS_VREDRAW ;
        
           wndclass.lpfnWndProc                 = WndProc ;
        
           wndclass.cbClsExtra                  = 0 ;
        
           wndclass.cbWndExtra                  = 0 ;
        
           wndclass.hInstance                   = hInstance ;
        
           wndclass.hIcon                       = LoadIcon (NULL, IDI_APPLICATION) ;
        
           wndclass.hCursor                     = LoadCursor (NULL, IDC_ARROW) ;
        
           wndclass.hbrBackground              = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
        
           wndclass.lpszMenuName                = szAppName ;
        
           wndclass.lpszClassName               = szAppName ;
        

           if (!RegisterClass (&wndclass))
        
           {
        
                  MessageBox (NULL, TEXT ("This program requires Windows NT!"),
        
                                  szAppName, MB_ICONERROR) ;
        
                  return 0 ;
        
           }
        
   
        
           hwnd = CreateWindow (szAppName, TEXT ("DIB Headers"),
        
                                  WS_OVERLAPPEDWINDOW,
        
                                  CW_USEDEFAULT, CW_USEDEFAULT,
        
                                  CW_USEDEFAULT, CW_USEDEFAULT,
        
                                  NULL, NULL, hInstance, NULL) ;
        
   
        
           ShowWindow (hwnd, iCmdShow) ;
        
           UpdateWindow (hwnd) ;
        

           hAccel = LoadAccelerators (hInstance, szAppName) ;
        
           while (GetMessage (&msg, NULL, 0, 0))
        
           {
        
                  if (!TranslateAccelerator (hwnd, hAccel, &msg))
        
                  {
        
                                  TranslateMessage (&msg) ;
        
                                 DispatchMessage (&msg) ;
        
                  }
        
           }
        
           return msg.wParam ;
        
}
        

void Printf (HWND hwnd, TCHAR * szFormat, ...)
        
{
        
           TCHAR                 szBuffer [1024] ;
        
           va_list       pArgList ;
        

           va_start (pArgList, szFormat) ;
        
           wvsprintf (szBuffer, szFormat, pArgList) ;
        
           va_end (pArgList) ;
        

           SendMessage (hwnd, EM_SETSEL, (WPARAM) -1, (LPARAM) -1) ;
        
           SendMessage (hwnd, EM_REPLACESEL, FALSE, (LPARAM) szBuffer) ;
        
           SendMessage (hwnd, EM_SCROLLCARET, 0, 0) ;
        
}
        
void DisplayDibHeaders (HWND hwnd, TCHAR * szFileName)
        
{
        
static TCHAR   * szInfoName []= { TEXT ("BITMAPCOREHEADER"),
        
                                   TEXT ("BITMAPINFOHEADER"),
        
                                   TEXT ("BITMAPV4HEADER"),
        
                                   TEXT ("BITMAPV5HEADER") } ;
        
Static TCHAR  * szCompression []={TEXT ("BI_RGB"),
        
TEXT    ("BI_RLE8"),
        
                                   TEXT ("BI_RLE4"),
        
                                   TEXT ("BI_BITFIELDS"),
        
                                   TEXT ("unknown") } ;
        
    BITMAPCOREHEADER      *      pbmch ;
        
    BITMAPFILEHEADER      *      pbmfh ;
        
    BITMAPV5HEADER*      pbmih ;
        
    BOOL                          bSuccess ;
        
    DWORD                 dwFileSize, dwHighSize, dwBytesRead ;
        
    HANDLE                hFile ;
        
  int                 i ;
        
  PBYTE               pFile ;
        
  TCHAR             * szV ;
        

                          // Display the file name
        

           Printf (hwnd, TEXT ("File: %s\r\n\r\n"), szFileName) ;
        
                          // Open the file
        
           hFile = CreateFile (szFileName, GENERIC_READ, FILE_SHARE_READ, NULL,
        
       OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL) ;
        
           if (hFile == INVALID_HANDLE_VALUE)
        
           {
        
                          Printf (hwnd, TEXT ("Cannot open file.\r\n\r\n")) ;
        
                          return ;
        
    }
        
        
        
                          // Get the size of the file
        
           dwFileSize = GetFileSize (hFile, &dwHighSize) ;
        
           if (dwHighSize)
        
           {
        
                  Printf (hwnd, TEXT ("Cannot deal with >4G files.\r\n\r\n")) ;
        
                  CloseHandle (hFile) ;
        
                  return ;
        
    }
        
                          // Allocate memory for the file
        
           pFile = malloc (dwFileSize) ;
        
           if (!pFile)
        
           {
        
                  Printf (hwnd, TEXT ("Cannot allocate memory.\r\n\r\n")) ;
        
                  CloseHandle (hFile) ;
        
                  return ;
        
           }
        

                                  // Read the file
        
           SetCursor (LoadCursor (NULL, IDC_WAIT)) ;
        
           ShowCursor (TRUE) ;
        

           bSuccess = ReadFile (hFile, pFile, dwFileSize, &dwBytesRead, NULL) ;
        
           ShowCursor (FALSE) ;
        
           SetCursor (LoadCursor (NULL, IDC_ARROW)) ;
        

           if (!bSuccess || (dwBytesRead != dwFileSize))
        
           {
        
                         Printf (hwnd, TEXT ("Could not read file.\r\n\r\n")) ;
        
                          CloseHandle (hFile) ;
        
                   free (pFile) ;
        
                  return ;
        
           }
        

                                  // Close the file
        
           CloseHandle (hFile) ;
        
                                  // Display file size
        
           Printf (hwnd, TEXT ("File size = %u bytes\r\n\r\n"), dwFileSize) ;
        
                                  // Display BITMAPFILEHEADER structure
        
           pbmfh = (BITMAPFILEHEADER *) pFile ;
        
           Printf (hwnd, TEXT   ("BITMAPFILEHEADER\r\n")) ;
        
           Printf (hwnd, TEXT   ("\t.bfType = 0x%X\r\n"), pbmfh->bfType) ;
        
           Printf (hwnd, TEXT   ("\t.bfSize = %u\r\n"), pbmfh->bfSize) ;
        
           Printf (hwnd, TEXT   ("\t.bfReserved1 = %u\r\n"), pbmfh->bfReserved1) ;
        
           Printf (hwnd, TEXT   ("\t.bfReserved2 = %u\r\n"), pbmfh->bfReserved2) ;
        
           Printf (hwnd, TEXT   ("\t.bfOffBits = %u\r\n\r\n"), pbmfh->bfOffBits) ;
        

                                  // Determine which information structure we have
        

           pbmih = (BITMAPV5HEADER *) (pFile + sizeof (BITMAPFILEHEADER)) ;
        
           switch (pbmih->bV5Size)
        
           {
        
          case   sizeof (BITMAPCOREHEADER):i= 0 ; break ;
        
           case   sizeof (BITMAPINFOHEADER): i= 1 ; szV=
        
TEXT ("i") ;       break ;
        
           case   sizeof (BITMAPV4HEADER):i= 2 ; szV=
        
TEXT ("V4") ;      break ;
        
           case   sizeof (BITMAPV5HEADER):i= 3 ; szV=
        
TEXT ("V5") ;      break ;
        
           default:
        
Printf (hwnd,      TEXT ("Unknown header size of %u.\r\n\r\n"),
        
                          pbmih->bV5Size) ;
        
                  free (pFile) ;
        
                  return ;
        
           }
        

           Printf (hwnd, TEXT ("%s\r\n"), szInfoName[i]) ;
        
                                        // Display the BITMAPCOREHEADER fields
        
           if (pbmih->bV5Size == sizeof (BITMAPCOREHEADER))
        
           {
        
                  pbmch = (BITMAPCOREHEADER *) pbmih ;
        
Printf(hwnd,TEXT("\t.bcSize = %u\r\n"), pbmch->bcSize) ;
        
Printf(hwnd,TEXT("\t.bcWidth = %u\r\n"), pbmch->bcWidth) ;
        
Printf(hwnd,TEXT("\t.bcHeight = %u\r\n"), pbmch->bcHeight) ;
        
Printf(hwnd,TEXT("\t.bcPlanes = %u\r\n"), pbmch->bcPlanes) ;
        
Printf(hwnd,TEXT("\t.bcBitCount = %u\r\n\r\n"), pbmch->bcBitCount) ;
        
     free (pFile) ;
        
     return ;
        
    }
        

                         // Display the BITMAPINFOHEADER fields
        
Printf(hwnd,TEXT("\t.b%sSize = %u\r\n"), szV, pbmih->bV5Size) ;
        
Printf(hwnd,TEXT("\t.b%sWidth = %i\r\n"), szV, pbmih->bV5Width) ;
        
Printf(hwnd,TEXT("\t.b%sHeight = %i\r\n"), szV, pbmih->bV5Height) ;
        
Printf(hwnd,TEXT("\t.b%sPlanes = %u\r\n"), szV, pbmih->bV5Planes) ;
        
Printf(hwnd,TEXT("\t.b%sBitCount=%u\r\n"),szV, pbmih->bV5BitCount) ;
        
Printf(hwnd,TEXT("\t.b%sCompression = %s\r\n"), szV,
        
                        szCompression [min (4, pbmih->bV5Compression)]) ;
        
Printf(hwnd,TEXT("\t.b%sSizeImage= %u\r\n"),szV,
        
pbmih->bV5SizeImage) ;
        
Printf(hwnd,TEXT ("\t.b%sXPelsPerMeter = %i\r\n"), szV,
        
               pbmih->bV5XPelsPerMeter) ;
        
Printf(hwnd,TEXT ("\t.b%sYPelsPerMeter = %i\r\n"), szV,
        
                        pbmih->bV5YPelsPerMeter) ;
        
Printf      (hwnd, TEXT ("\t.b%sClrUsed = %i\r\n"), szV,
        
pbmih->bV5ClrUsed) ;
        
Printf      (hwnd, TEXT ("\t.b%sClrImportant = %i\r\n\r\n"), szV,
        
                 pbmih->bV5ClrImportant) ;
        

           if (pbmih->bV5Size == sizeof (BITMAPINFOHEADER))
        
    {
        
                          if (pbmih->bV5Compression == BI_BITFIELDS)
        
         {
        
Printf (hwnd,TEXT("Red Mask = %08X\r\n"), pbmih->bV5RedMask) ;
        
Printf (hwnd,TEXT ("Green Mask = %08X\r\n"), pbmih->bV5GreenMask) ;
        
Printf (hwnd,TEXT ("Blue Mask  = %08X\r\n\r\n"), pbmih->bV5BlueMask) ;
        
         }
        
                  free (pFile) ;
        
                  return ;
        
    }
        

                                  // Display additional BITMAPV4HEADER fields
        
           Printf (hwnd, TEXT ("\t.b%sRedMask   = %08X\r\n"), szV,
        
                                                        pbmih->bV5RedMask) ;
        
           Printf (hwnd, TEXT ("\t.b%sGreenMask = %08X\r\n"), szV,
        
                                                         pbmih->bV5GreenMask) ;
        
           Printf (hwnd, TEXT ("\t.b%sBlueMask  = %08X\r\n"), szV,
        
                                                         pbmih->bV5BlueMask) ;
        
           Printf (hwnd, TEXT ("\t.b%sAlphaMask = %08X\r\n"), szV,
        
                                                         pbmih->bV5AlphaMask) ;
        
           Printf (hwnd, TEXT ("\t.b%sCSType = %u\r\n"), szV,
        
                                                         pbmih->bV5CSType) ;
        
   Printf (hwnd, TEXT ("\t.b%sEndpoints.ciexyzRed.ciexyzX = %08X\r\n"),
        
                                                 szV, pbmih->bV5Endpoints.ciexyzRed.ciexyzX) ;
        
    Printf (hwnd, TEXT ("\t.b%sEndpoints.ciexyzRed.ciexyzY = %08X\r\n"),
        
                                                 szV, pbmih->bV5Endpoints.ciexyzRed.ciexyzY) ;
        
    Printf (hwnd, TEXT ("\t.b%sEndpoints.ciexyzRed.ciexyzZ = %08X\r\n"),
        
                                                 szV, pbmih->bV5Endpoints.ciexyzRed.ciexyzZ) ;
        
    Printf (hwnd, TEXT ("\t.b%sEndpoints.ciexyzGreen.ciexyzX = %08X\r\n"),
        
                                         szV, pbmih->bV5Endpoints.ciexyzGreen.ciexyzX) ;
        
    Printf (hwnd, TEXT ("\t.b%sEndpoints.ciexyzGreen.ciexyzY = %08X\r\n"),
        
                                                 szV, pbmih->bV5Endpoints.ciexyzGreen.ciexyzY) ;
        
   Printf (hwnd, TEXT ("\t.b%sEndpoints.ciexyzGreen.ciexyzZ = %08X\r\n"),
        
                                                 szV, pbmih->bV5Endpoints.ciexyzGreen.ciexyzZ) ;
        
    Printf (hwnd, TEXT ("\t.b%sEndpoints.ciexyzBlue.ciexyzX = %08X\r\n"),
        
                                          szV, pbmih->bV5Endpoints.ciexyzBlue.ciexyzX) ;
        
    Printf (hwnd, TEXT ("\t.b%sEndpoints.ciexyzBlue.ciexyzY = %08X\r\n"),
        
                                          szV, pbmih->bV5Endpoints.ciexyzBlue.ciexyzY) ;
        
    Printf (hwnd, TEXT ("\t.b%sEndpoints.ciexyzBlue.ciexyzZ = %08X\r\n"),
        
                                          szV, pbmih->bV5Endpoints.ciexyzBlue.ciexyzZ) ;
        
    Printf (hwnd, TEXT ("\t.b%sGammaRed = %08X\r\n"), szV,
        
                                          pbmih->bV5GammaRed) ;
        
    Printf (hwnd, TEXT ("\t.b%sGammaGreen = %08X\r\n"), szV,
        
                                          pbmih->bV5GammaGreen) ;
        
    Printf (hwnd, TEXT ("\t.b%sGammaBlue  = %08X\r\n\r\n"), szV,
        
                                          pbmih->bV5GammaBlue) ;
        

    if (pbmih->bV5Size == sizeof (BITMAPV4HEADER))
        
    {
        
    free (pFile) ;
        
    return ;
        
    }
        

            // Display additional BITMAPV5HEADER fields
        
    Printf        (hwnd,       TEXT ("\t.b%sIntent = %u\r\n"), szV, pbmih->bV5Intent) ;
        
    Printf (hwnd, TEXT ("\t.b%sProfileData = %u\r\n"), szV,
        
                                                  pbmih->bV5ProfileData) ;
        
    Printf (hwnd, TEXT ("\t.b%sProfileSize = %u\r\n"), szV,
        
                                                  pbmih->bV5ProfileSize) ;
        
    Printf (hwnd, TEXT ("\t.b%sReserved = %u\r\n\r\n"), szV,
        
                                                  pbmih->bV5Reserved) ;
        

   free (pFile) ;
        
  return ;
        
}
        

LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
        
{
        
    static HWND                   hwndEdit ;
        
    static OPENFILENAME ofn ;
        
    static TCHAR                  szFileName [MAX_PATH], szTitleName [MAX_PATH] ;
        
    static TCHAR                  szFilter[]= TEXT("Bitmap Files (*.BMP)\0*.bmp\0")
        
                                          TEXT("All Files (*.*)\0*.*\0\0") ;
        
   
        
    switch (message)
        
    {
        
    case   WM_CREATE:
        
                   hwndEdit = CreateWindow (TEXT ("edit"), NULL,
        
                                   WS_CHILD | WS_VISIBLE | WS_BORDER |
        
                                   WS_VSCROLL | WS_HSCROLL |
        
                                  ES_MULTILINE | ES_AUTOVSCROLL | ES_READONLY,
        
                                   0, 0, 0, 0, hwnd, (HMENU) 1,
        
                                   ((LPCREATESTRUCT) lParam)->hInstance, NULL) ;
        

            ofn.lStructSize               = sizeof (OPENFILENAME) ;
        
            ofn.hwndOwner         = hwnd ;
        
            ofn.hInstance                 = NULL ;
        
            ofn.lpstrFilter               = szFilter ;
        
                  ofn.lpstrCustomFilter = NULL ;
        
                  ofn.nMaxCustFilter    = 0 ;
        
                  ofn.nFilterIndex              = 0 ;
        
                  ofn.lpstrFile                 = szFileName ;
        
                  ofn.nMaxFile                  = MAX_PATH ;
        
                 ofn.lpstrFileTitle            = szTitleName ;
        
                  ofn.nMaxFileTitle             = MAX_PATH ;
        
                  ofn.lpstrInitialDir           = NULL ;
        
                  ofn.lpstrTitle               = NULL ;
        
                  ofn.Flags                     = 0 ;
        
                  ofn.nFileOffset              = 0 ;
        
                  ofn.nFileExtension            = 0 ;
        
                          ofn.lpstrDefExt       = TEXT ("bmp") ;
        
                          ofn.lCustData         = 0 ;
        
                          ofn.lpfnHook          = NULL ;
        
                          ofn.lpTemplateName = NULL ;
        
                          return 0 ;
        

    case   WM_SIZE:
        
                   MoveWindow (hwndEdit, 0, 0, LOWORD (lParam), HIWORD (lParam), TRUE) ;
        
                          return 0 ;
        

           case   WM_COMMAND:
        
                          switch (LOWORD (wParam))
        
                          {
        
                          case   IDM_FILE_OPEN:
        
                                                 if (GetOpenFileName (&ofn))
        
                   DisplayDibHeaders (hwndEdit, szFileName) ;
        

                                                 return 0 ;
        
                          }
        
                  break ;
        
   
        
           case   WM_DESTROY:
        
                          PostQuitMessage (0) ;
        
                          return 0 ;
        
    }
        
           return DefWindowProc (hwnd, message, wParam, lParam) ;
        
}
        
DIBHEADS.RC (摘录)
        
//Microsoft Developer Studio generated resource script.
        
#include "resource.h"
        
#include "afxres.h"
        
///////////////////////////////////////////////////////////////////////////
        
// Accelerator
        
DIBHEADS ACCELERATORS DISCARDABLE
        
BEGIN
        
  "O",            IDM_FILE_OPEN,               VIRTKEY, CONTROL, NOINVERT
        
END
        

///////////////////////////////////////////////////////////////////////////
        
// Menu
        
DIBHEADS MENU DISCARDABLE
        
BEGIN
        
   POPUP "&File"
        
   BEGIN
        
                  MENUITEM "&Open\tCtrl+O",     IDM_FILE_OPEN
        
   END
        
END
        
RESOURCE.H (摘录)
        
// Microsoft Developer Studio generated include file.
        
// Used by DibHeads.rc
        
#define IDM_FILE_OPEN                40001
        

此程序有一个简短的WndProc函数,它建立了一个只读的编辑窗口来填满它的显示区域,它也处理菜单上的「File Open」命令。它通过呼叫GetOpenFileName函数使用标准的「File Open」对话框,然后呼叫DisplayDibHeaders函数。此函数把整个DIB文件读入内存并逐栏地显示所有的表头信息。



<<<上一篇
欢迎访问AoGo汇编小站:http://www.aogosoft.com
下一篇>>>