与设备无关的位图 |
|
在上一章我们了解到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格式并不是源自于Windows。它首先定义在OS/2的1.1版中,该操作系统最初由IBM和Microsoft在八十年代中期开始开发。OS/2 1.1在1988年发布,并且是第一个包含了类似Windows的图形使用者接口的OS/2版本,该图形使用者接口被称之为「Presentation Manager(PM)」。「Presentation Manager」包含了定义位图格式的「图形程序接口」(GPI)。
然后在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文件有四个主要部分:
- 文件表头
- 信息表头
- RGB色彩对照表(不一定有)
- 位图图素位
您可以把前两部分看成是C的数据结构,把第三部分看成是数据结构的数组。在Windows表头文件WINGDI.H中说明了这些结构。在内存中的packed DIB格式内有三个部分:
- 信息表头
- RGB色彩对照表(不一定有)
- 位图图素位
除了没有文件表头外,其它部分与储存在文件内的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图像中的颜色数等于2bmch.bcBitCount,或用C的语法表示为:
1 << bmch.bcBitCount
这样,bcBitCount字段等于:
- 1代表2色DIB
- 4代表16色DIB
- 8代表256色DIB
- 24代表full -Color DIB
当我提到「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字段指出了它的大小。
总结如下:
-
对于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字段为负数的情况下出现的。
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所示。
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文件读入内存并逐栏地显示所有的表头信息。
位图是用来看的。在这一节中,我们看一看Windows在视讯显示器上或打印页面上支持显示DIB的两个函数。要得到更好的性能,您可以使用一种兜圈子的方法来显示位图,我会在本章的后面讨论该方法,但先研究这两个函数会好一些。
这两个函数称为SetDIBitsToDevice(发音为「set dee eye bits to device」)和StretchDIBits (发音为「stretch dee eye bits」)。每个函数都使用储存在内存中的DIB并能显示整个DIB或它的矩形部分。当使用SetDIBitsToDevice时,以图素为单位所显示映像的大小与DIB的图素大小相同。例如,一个640×480的DIB会占据整个标准的VGA屏幕,但在300dpi的激光打印机上它只有约2.1×1.6英寸。StretchDIBits能延伸和缩小DIB尺寸的行和列从而在输出设备上显示一个特定的大小。
了解DIB
当呼叫两个函数之一来显示DIB时,您需要几个关于图像的信息。正如我前面说过的,DIB文件包含下列部分:
DIB文件能被加载内存。如果除了文件表头外,整个文件被储存在内存的连续区块中,指向该内存块开始处(也就是信息表头的开头)的指标被称为指向packed DIB的指标(见下图)。
这是通过剪贴簿传输DIB时所用的格式,并且也是您从DIB建立画刷时所用的格式。因为整个DIB由单个指标(如pPackedDib)引用,所以packed DIB是在内存中储存DIB的方便方法,您可以把指标定义为指向BYTE的指标。使用本章前面所示的结构定义,能得到所有储存在DIB内的信息,包括色彩对照表和个别图素位。
然而,要想得到这么多信息,还需要一些程序代码。例如,您不能通过以下叙述简单地取得DIB的图素宽度:
iWidth = ((PBITMAPINFOHEADER) pPackedDib)->biWidth ;
DIB有可能是OS/2兼容格式的。在那种格式中,packed DIB以BITMAPCOREHEADER结构开始,并且DIB的图素宽度和高度以16位WORD,而不是32位LONG储存。因此,首先必须检查DIB是否为旧的格式,然后进行相对应的操作:
if (((PBITMAPCOREHEADER) pPackedDib)->bcSize == sizeof (BITMAPCOREHEADER)) iWidth = ((PBITMAPCOREHEADER) pPackedDib)->bcWidth ; else iWidth = ((PBITMAPINFOHEADER) pPackedDib)->biWidth ;
当然,这不很糟,但它不如我们所喜好的清晰。
现在有一个很有趣的实验:给定一个指向packed DIB的指标,我们要找出位于坐标(5,27)的图素值。即使假定DIB不是OS/2兼容的格式,您也需要了解DIB的宽度、高度和位数。您需要计算每一列图素的位组长度,确定色彩对照表内的项目数,以及色彩对照表是否包括三个32位的颜色屏蔽。您还需检查DIB是否被压缩,在这种情况下图素是不能直接由地址得到的。
如果您需要直接存取所有的DIB图素(就像许多图形处理工作一样),这可能会增加一点处理时间。由于这个原因,储存一个指向packed DIB的指标就很方便了,不过这并不是一种有效率的解决方式。另一个漂亮的解决方法是为DIB定义一个包含足够成员数据的C++类别,从而允许快速随机地存取DIB图素。然而,我曾经答应读者在本书内无需了解C++,我将在下一章说明一个C的解决方法。
对于SetDIBitsToDevice和StretchDIBits函数,需要的信息包括一个指向DIB的BITMAPINFO结构的指针。您应回想起,BITMAPINFO结构由BITMAPINFOHEADER结构和色彩对照表组成。因此这仅是一个指向packed DIB的指标。
函数也需要一个指向图素位的指针。尽管程序代码写得很不漂亮,但这个指针还是可以从信息表头内的信息推出。注意,当您存取BITMAPFILEHEADER结构的bfOffBits字段时,这个指标能很容易地计算出。bfOffBits字段指出了从DIB文件的开头到图素位的偏移量。您可以简单地把此偏移量加到BITMAPINFO指标中,然后减去BITMAPFILEHEADER结构的大小。然而,当您从剪贴簿上得到指向packed DIB的指标时,这并不起作用,因为没有BITMAPFILEHEADER结构。
此图表显示了两个所需的指标:
SetDIBitsToDevice和StretchDIBits函数需要两个指向DIB的指标,因为这两个部分不在连续的内存块内。您可能有如下所示的两块内存:
确实,把DIB分成两个内存块是很有用的,只是我们更喜欢与整个DIB储存在单个内存块的packed DIB打交道。
除了这两个指标,SetDIBitsToDevice和StretchDIBits函数通常也需要DIB的图素宽度和高度。如只想显示DIB的一部分,就不必明确地知道这些值,但它们会定义您在DIB图素位数组内定义的矩形的上限。
点对点图素显示
SetDIBitsToDevice函数显示没有延伸和缩小的DIB。DIB的每个图素对应到输出设备的一个图素上,而且DIB中的图像一定会被正确显示出来-也就是说,图像的顶列在上方。任何会影响设备内容的坐标转换都影响了显示DIB的开始位置,但不影响显示出来的图片大小和方向。该函数如下:
iLines = SetDIBitsToDevice ( hdc, // device context handle xDst, // x destination coordinate yDst, // y destination coordinate cxSrc, // source rectangle width cySrc, // source rectangle height xSrc, // x source coordinate ySrc, // y source coordinate yScan, // first scan line to draw cyScans, // number of scan lines to draw pBits, // pointer to DIB pixel bits pInfo, // pointer to DIB information fClrUse) ; // color use flag
不要对参数的数量感到厌烦,在多数情况下,函数用起来比看起来要简单。不过在其它用途上来说,它的用法真的是乱七八糟,不过我们将学会怎么用它。
和GDI显示函数一样,SetDIBitsToDevice的第一个参数是设备内容句柄,它指出显示DIB的设备。下面两个参数xDst和yDst,是输出设备的逻辑坐标,并指出了显示DIB图像左上角的坐标(「上端」指的是视觉上的上方,并不是DIB图素的第一行)。注意,这些都是逻辑坐标,因此它们附属于实际上起作用的任何坐标转换方式或-在Windows NT的情况下-设定的任何空间转换。在内定的MM_TEXT映像方式下,可以把这些参数设为0,从显示平面上向左向上显示DIB图像。
您可以显示整个DIB图像或仅显示其中的一部分,这就是后四个参数的作用。但是DIB图素数据的由上而下的方向产生了许多误解,待会儿会谈到这些。现在应该清楚当显示整个DIB时,应把xSrc和ySrc设定为0,并且cxSrc和cySrc应分别等于DIB的图素宽度和高度。注意,因为BITMAPINFOHEADER结构的biHeight字段对于由上而下的DIB来说是负的,cySrc应设定为biHeight字段的绝对值。
此函数的文件(/Platform SDK/Graphics and Multimedia Services/GDI/Bitmaps/Bitmap Reference/Bitmap Functions/SetDIBitsToDevice)中说xSrc、ySrc、cxSrc和cySrc参数是逻辑单位。这是不正确的,它们是图素的坐标和尺寸。对于DIB内的图素,拥有逻辑坐标和单位是没有什么意义的。而且,不管是什么映像方式,在输出设备上显示的DIB始终是cxSrc图素宽和cySrc图素高。
现在先不详细讨论这两个参数yScan和cyScan。这些参数在您从磁盘文件或通过调制解调器读取数据时,透过每次显示DIB的一小部分减少对内存的需求。通常,yScan设定为0,cyScan设定为DIB的高度。
pBits参数是指向DIB图素位的指针。pInfo参数是指向DIB的BITMAPINFO结构的指针。虽然BITMAPINFO结构的地址与BITMAPINFOHEADER结构的地址相同,但是SetDIBitsToDevice结构被定义为使用BITMAPINFO结构,暗示着:对于1位、4位和8位DIB,位图信息表头后必须跟着色彩对照表。尽管pInfo参数被定义为指向BITMAPINFO结构的指针,它也是指向BITMAPCOREINFO、BITMAPV4HEADER或BITMAPV5HEADER结构的指针。
最后一个参数是DIB_RGB_COLORS或DIB_PAL_COLORS,在WINGDI.H内分别定义为0和1。如果您使用DIB_RGB_COLORS,这意味着DIB包含了色彩对照表。DIB_PAL_COLORS旗标指出,DIB内的色彩对照表已经被指向在设备内容内选定并识别的调色盘的16位索引代替。在下一章我们将学习这个选项。现在先使用DIB_RGB_COLORS,或者是0。
SetDIBitsToDevice函数传回所显示的扫描行的数目。
因此,要呼叫SetDIBitsToDevice来显示整个DIB图像,您需要下列信息:
- hdc目的表面的设备内容句柄
-
xDst和yDst图像左上角的目的坐标
-
cxDib和cyDibDIB的图素宽度和高度,在这里,cyDib是BITMAPINFOHEADER结构内biHeight字段的绝对值。
-
pInfo和pBits指向位图信息部分和图素位的指针
然后用下列方法呼叫SetDIBitsToDevice:
SetDIBitsToDevice ( hdc, // device context handle xDst, // x destination coordinate yDst, // y destination coordinate cxDib, // source rectangle width cyDib, // source rectangle height 0, // x source coordinate 0, // y source coordinate 0, // first scan line to draw cyDib, // number of scan lines to draw pBits, // pointer to DIB pixel bits pInfo, // pointer to DIB information 0) ; // color use flag
因此,在DIB的12个参数中,四个设定为0,一个是重复的。
程序15-2 SHOWDIB1通过使用SetDIBitsToDevice函数显示DIB。
SHOWDIB1.C /*--------------------------------------------------------------------- SHOWDIB1.C -- Shows a DIB in the client area (c) Charles Petzold, 1998 ---------------------------------------------------------------------*/ #include <windows.h> #include "dibfile.h" #include "resource.h" LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; TCHAR szAppName[] = TEXT ("ShowDib1") ; 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 ("Show DIB #1"), 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 ; } LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam,LPARAM lParam) { static BITMAPFILEHEADER * pbmfh ; static BITMAPINFO * pbmi ; static BYTE * pBits ; static int cxClient, cyClient, cxDib, cyDib ; static TCHAR szFileName [MAX_PATH], szTitleName [MAX_PATH] ; BOOL bSuccess ; HDC hdc ; PAINTSTRUCT ps ; switch (message) { case WM_CREATE: DibFileInitialize (hwnd) ; return 0 ; case WM_SIZE: cxClient = LOWORD (lParam) ; cyClient = HIWORD (lParam) ; return 0 ; case WM_INITMENUPOPUP: EnableMenuItem ((HMENU) wParam, IDM_FILE_SAVE, pbmfh ? MF_ENABLED : MF_GRAYED) ; return 0 ; case WM_COMMAND: switch (LOWORD (wParam)) { case IDM_FILE_OPEN: // Show the File Open dialog box if (!DibFileOpenDlg (hwnd, szFileName, szTitleName)) return 0 ; // If there's an existing DIB, free the memory if (pbmfh) { free (pbmfh) ; pbmfh = NULL ; } // Load the entire DIB into memory SetCursor (LoadCursor (NULL, IDC_WAIT)) ; ShowCursor (TRUE) ; pbmfh = DibLoadImage (szFileName) ; ShowCursor (FALSE) ; SetCursor (LoadCursor (NULL, IDC_ARROW)) ; // Invalidate the client area for later update InvalidateRect (hwnd, NULL, TRUE) ; if (pbmfh == NULL) { MessageBox ( hwnd, TEXT ("Cannot load DIB file"), szAppName, 0) ; return 0 ; } // Get pointers to the info structure & the bits pbmi = (BITMAPINFO *) (pbmfh + 1) ; pBits = (BYTE *) pbmfh + pbmfh->bfOffBits ; // Get the DIB width and height if (pbmi->bmiHeader.biSize == sizeof (BITMAPCOREHEADER)) { cxDib = ((BITMAPCOREHEADER *) pbmi)->bcWidth ; cyDib = ((BITMAPCOREHEADER *) pbmi)->bcHeight ; } else { cxDib = pbmi->bmiHeader.biWidth ; cyDib = abs(pbmi->bmiHeader.biHeight) ; } return 0 ; case IDM_FILE_SAVE: // Show the File Save dialog box if (!DibFileSaveDlg (hwnd, szFileName, szTitleName)) return 0 ; // Save the DIB to memory SetCursor (LoadCursor (NULL, IDC_WAIT)) ; ShowCursor (TRUE) ; bSuccess = DibSaveImage (szFileName, pbmfh) ; ShowCursor (FALSE) ; SetCursor (LoadCursor (NULL, IDC_ARROW)) ; if (!bSuccess) MessageBox ( hwnd, TEXT ("Cannot save DIB file"), szAppName, 0) ; return 0 ; } break ; case WM_PAINT: hdc = BeginPaint (hwnd, &ps) ; if (pbmfh) SetDIBitsToDevice (hdc, 0, // xDst 0, // yDst cxDib, // cxSrc cyDib, // cySrc 0, // xSrc 0, // ySrc 0, // first scan line cyDib, // number of scan lines pBits, pbmi, DIB_RGB_COLORS) ; EndPaint (hwnd, &ps) ; return 0 ; case WM_DESTROY: if (pbmfh) free (pbmfh) ; PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ; }
DIBFILE.H /*------------------------------------------------------------------------- DIBFILE.H -- Header File for DIBFILE.C -----------------------------------------------------------------------*/ void DibFileInitialize (HWND hwnd) ; BOOL DibFileOpenDlg(HWND hwnd, PTSTR pstrFileName, PTSTR pstrTitleName) ; BOOL DibFileSaveDlg(HWND hwnd, PTSTR pstrFileName, PTSTR pstrTitleName) ; BITMAPFILEHEADER * DibLoadImage (PTSTR pstrFileName) ; BOOL DibSaveImage(PTSTR pstrFileName, BITMAPFILEHEADER *) ;
DIBFILE.C /*--------------------------------------------------------------------------- DIBFILE.C -- DIB File Functions ----------------------------------------------------------------------------*/ #include <windows.h> #include <commdlg.h> #include "dibfile.h" static OPENFILENAME ofn ; void DibFileInitialize (HWND hwnd) { static TCHAR szFilter[] = TEXT ("Bitmap Files (*.BMP)\0*.bmp\0") \ TEXT ("All Files (*.*)\0*.*\0\0") ; ofn.lStructSize = sizeof (OPENFILENAME) ; ofn.hwndOwner = hwnd ; ofn.hInstance = NULL ; ofn.lpstrFilter = szFilter ; ofn.lpstrCustomFilter = NULL ; ofn.nMaxCustFilter = 0 ; ofn.nFilterIndex = 0 ; ofn.lpstrFile = NULL ; // Set in Open and Close functions ofn.nMaxFile = MAX_PATH ; ofn.lpstrFileTitle = NULL ; // Set in Open and Close functions ofn.nMaxFileTitle = MAX_PATH ; ofn.lpstrInitialDir = NULL ; ofn.lpstrTitle = NULL ; ofn.Flags = 0 ; // Set in Open and Close functions ofn.nFileOffset = 0 ; ofn.nFileExtension = 0 ; ofn.lpstrDefExt = TEXT ("bmp") ; ofn.lCustData = 0 ; ofn.lpfnHook = NULL ; ofn.lpTemplateName = NULL ; } BOOL DibFileOpenDlg (HWND hwnd, PTSTR pstrFileName, PTSTR pstrTitleName) { ofn.hwndOwner = hwnd ; ofn.lpstrFile = pstrFileName ; ofn.lpstrFileTitle = pstrTitleName ; ofn.Flags = 0 ; return GetOpenFileName (&ofn) ; } BOOL DibFileSaveDlg (HWND hwnd, PTSTR pstrFileName, PTSTR pstrTitleName) { ofn.hwndOwner = hwnd ; ofn.lpstrFile = pstrFileName ; ofn.lpstrFileTitle = pstrTitleName ; ofn.Flags = OFN_OVERWRITEPROMPT ; return GetSaveFileName (&ofn) ; } BITMAPFILEHEADER * DibLoadImage (PTSTR pstrFileName) { BOOL bSuccess ; DWORD dwFileSize, dwHighSize, dwBytesRead ; HANDLE hFile ; BITMAPFILEHEADER * pbmfh ; hFile = CreateFile ( pstrFileName, GENERIC_READ, FILE_SHARE_READ,NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN,NULL) ; if ( hFile == INVALID_HANDLE_VALUE) return NULL ; dwFileSize = GetFileSize (hFile, &dwHighSize) ; if (dwHighSize) { CloseHandle (hFile) ; return NULL ; } pbmfh = malloc (dwFileSize) ; if (!pbmfh) { CloseHandle (hFile) ; return NULL ; } bSuccess = ReadFile (hFile, pbmfh, dwFileSize, &dwBytesRead, NULL) ; CloseHandle (hFile) ; if (!bSuccess || (dwBytesRead != dwFileSize) || (pbmfh->bfType != * (WORD *) "BM") || (pbmfh->bfSize != dwFileSize)) { free (pbmfh) ; return NULL ; } return pbmfh ; } BOOL DibSaveImage (PTSTR pstrFileName, BITMAPFILEHEADER * pbmfh) { BOOL bSuccess ; DWORD dwBytesWritten ; HANDLE hFile ; hFile = CreateFile ( pstrFileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL) ; if (hFile == INVALID_HANDLE_VALUE) return FALSE ; bSuccess = WriteFile (hFile, pbmfh, pbmfh->bfSize, &dwBytesWritten, NULL) ; CloseHandle (hFile) ; if (!bSuccess || (dwBytesWritten != pbmfh->bfSize)) { DeleteFile (pstrFileName) ; return FALSE ; } return TRUE ; }
SHOWDIB1.RC (摘录) //Microsoft Developer Studio generated resource script. #include "resource.h" #include "afxres.h" ///////////////////////////////////////////////////////////////////////////// // Menu SHOWDIB1 MENU DISCARDABLE BEGIN POPUP "&File" BEGIN MENUITEM "&Open...", IDM_FILE_OPEN MENUITEM "&Save...", IDM_FILE_SAVE END END
RESOURCE.H (摘录) // Microsoft Developer Studio generated include file. // Used by ShowDib1.rc #define IDM_FILE_OPEN 40001 #define IDM_FILE_SAVE 40002
DIBFILE.C文件包含了显示「File Open」和「File Save」对话框的例程,以及把整个DIB文件(拥有BITMAPFILEHEADER结构)加载单个内存块的例程。程序也会将这样一个内存区写出到文件。
当在SHOWDIB1.C内执行「File Open」命令加载DIB文件后,程序计算内存块中BITMAPINFOHEADER结构和图素位的偏移量,程序也获得DIB的图素宽度和高度。所有信息都储存在静态变量中。在处理WM_PAINT消息处理期间,程序通过呼叫SetDIBitsToDevice显示DIB。
当然,SHOWDIB1还缺少一些功能。例如,如果DIB对显示区域来说太大,则没有滚动条可用来移动查看。在 下一章的末尾将修改这些缺陷。
DIB的颠倒世界
我们将得到一个重要的教训,它不仅在生活中重要,而且在操作系统的应用程序接口的设计中也重要。这个教训是:覆水难收。
回到OS/2 Presentation Manager那由下而上的DIB图素位的定义处,这样的定义是有点道理的,因为PM内的任何坐标系都有一个内定的左下角原点。例如:在PM窗口内,内定的(0,0)原点是窗口的左下角。(如果您觉得这很古怪,很多人和您的感觉一样。如果您不觉得古怪,那您可能是位数学家。)位图的绘制函数也根据左下角指定目的地。
因此,在OS/2内如果给位图指定了目的坐标(0,0),则图像将从窗口的左下角向上向右显示,如图15-1所示。
图15-1 在OS/2中以(0,0)为目的点显示的位图 |
在够慢的机器上,您能实际看到计算机由下而上绘制位图。
尽管OS/2坐标系统显得很古怪,但它的优点是高度的一致。位图的 (0,0)原点是位图文件中第一行的第一个图素,并且此图素被映像到在位图绘制函数中指定的目的坐标上。
Windows存在的问题是不能保持内部的一致性。当您只要显示整个DIB图像中的一小块矩形时,就要使用参数xSrc、ySrc、cxSrc和cySrc。这些来源坐标和大小与DIB数据的第一行(图像的最后一行)相关。这方面与OS/2相似,与OS/2不同的是,Windows在目的坐标上显示图像的顶列。因此,如果显示整个DIB图像,显示在(xDst,yDst)的图素是位于坐标(0,cyDib - 1)处的图素。DIB数据的最后一列就是图形的顶列。如果仅显示图像的一部分,则在(xDst,yDst)显示的图素是位于坐标(xSrc, ySrc + cySrc - 1)处的DIB图素。
图15-2显示的图表将帮助您理解这方面的内容。您可以把下面显示的DIB当成是储存在内存中的-就是说,上下颠倒。坐标的原点与DIB图素数据的第一个位是一致的。SetDIBitsToDevice的xSrc参数是以DIB的左边为基准,并且cxSrc是xSrc右边的图像宽度,这很直观。ySrc参数以DIB数据的首列(也就是图像的底部)为基准,并且cySrc是从ySrc到数据的末列(图像的顶端)的图像高度。
图15-2 正常DIB(由下而上)的坐标 |
如果目的设备内容具有使用MM_TEXT映像方式的内定图素坐标,来源矩形和目的矩形角落坐标之间的关系显示在表15-3中。
表15-3 |
来源矩形 |
目的矩形 |
(xSrc, ySrc) |
(xDst, yDst + cySrc - 1) |
(xSrc + cxSrc - 1, ySrc) |
(xDst + cxSrc - 1, yDst + cySrc - 1) |
(xSrc, ySrc + cySrc - 1) |
(xDst, yDst) |
(xSrc + cxSrc - 1, ySrc + cySrc - 1) |
(xDst + cxSrc - 1, yDst) |
(xSrc,ySrc)不映射到(xDst,yDst),使得表格显得很混乱。在其它映像方式中,点(xSrc,ySrc + cySrc - 1)总是映像到逻辑点(xDst,yDst),图像也与MM_TEXT所显示的一样。
到目前为止,我们讨论了当BITMAPINFOHEADER结构的biHeight字段是正值时的正常情况。如果biHeight字段是负值,则DIB数据会以合理的由上而下的方式排列。您可能会认为这样将解决所有问题,如果您真地这样认为,那您就错了。
很明显地,有人会认为如果把DIB上下倒置,旋转每一行,然后给biHeight设定一个正值,它将像正常的由下而上的DIB一样操作,所有与DIB矩形相关的现存程序代码就不必修改。我认为这是一个合理的目的,但它忘记了一个事实,程序需要修改以处理由上而下的DIB,这样就不会使用一个负高度。
而且,此决定的结果意味着由上而下的DIB的来源坐标在DIB数据的最后一列有一个原点,它也是图像的底列。这与我们遇到的情况完全不同。位于(0,0)原点的DIB图素不再是pBits指标引用的第一个图素,也不是DIB文件的最后一个图素,它位于两者之间。
图15-3显示的图表说明了在由上而下的DIB中指定矩形的方法,也是它储存在文件或内存中的样子。
图15-3 指定由上而下的DIB的坐标 |
无论如何,这个方案的实际优点是SetDIBitsToDevice函数的参数与DIB数据的方向无关。如果有显示了同一图像的两个DIB(一个由下而上,另一个由上而下。表示在两个DIB文件内的列顺序相反),您可以使用相同的参数呼叫SetDIBitsToDevice来选择显示图像的相同部分。
如程序15-3 APOLLO11中所示。
APOLLO11.C /*------------------------------------------------------------------------- APOLLO11.C -- Program for screen captures (c) Charles Petzold, 1998 ----------------------------------------------------------------------*/ #include <windows.h> #include "dibfile.h" LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; TCHAR szAppName[] = TEXT ("Apollo11") ; int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { HWND hwnd ; MSG msg ; WNDCLAS 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 = NULL ; wndclass.lpszClassName = szAppName ; if (!RegisterClass (&wndclass)) { MessageBox (NULL, TEXT ("This program requires Windows NT!"), szAppName, MB_ICONERROR) ; return 0 ; } hwnd = CreateWindow (szAppName, TEXT ("Apollo 11"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL) ; ShowWindow (hwnd, iCmdShow) ; UpdateWindow (hwnd) ; while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg) ; DispatchMessage (&msg) ; } return msg.wParam ; } LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam,LPARAM lParam) { static BITMAPFILEHEADER * pbmfh [2] ; static BITMAPINFO * pbmi [2] ; static BYTE * pBits [2] ; static int cxClient, cyClient, cxDib[2], cyDib[2] ; HDC hdc ; PAINTSTRUCT ps ; switch (message) { case WM_CREATE: pbmfh[0] = DibLoadImage (TEXT ("Apollo11.bmp")) ; pbmfh[1] = DibLoadImage (TEXT ("ApolloTD.bmp")) ; if (pbmfh[0] == NULL || pbmfh[1] == NULL) { MessageBox (hwnd, TEXT ("Cannot load DIB file"), szAppName, 0) ; return 0 ; } // Get pointers to the info structure & the bits pbmi [0] = (BITMAPINFO *) (pbmfh[0] + 1) ; pbmi [1] = (BITMAPINFO *) (pbmfh[1] + 1) ; pBits [0] = (BYTE *) pbmfh[0] + pbmfh[0]->bfOffBits ; pBits [1] = (BYTE *) pbmfh[1] + pbmfh[1]->bfOffBits ; // Get the DIB width and height (assume BITMAPINFOHEADER) // Note that cyDib is the absolute value of the header value!!! cxDib [0] = pbmi[0]->bmiHeader.biWidth ; cxDib [1] = pbmi[1]->bmiHeader.biWidth ; cyDib [0] = abs (pbmi[0]->bmiHeader.biHeight) ; cyDib [1] = abs (pbmi[1]->bmiHeader.biHeight) ; return 0 ; case WM_SIZE: cxClient = LOWORD (lParam) ; cyClient = HIWORD (lParam) ; return 0 ; case WM_PAINT: hdc = BeginPaint (hwnd, &ps) ; // Bottom-up DIB full size SetDIBitsToDevice (hdc, 0, // xDst cyClient / 4, // yDst cxDib[0], // cxSrc cyDib[0], // cySrc 0, // xSrc 0, // ySrc 0, // first scan line cyDib[0], // number of scan lines pBits[0], pbmi[0], DIB_RGB_COLORS) ; // Bottom-up DIB partial SetDIBitsToDevice (hdc, 240, // xDst cyClient / 4, // yDst 80, // cxSrc 166, // cySrc 80, // xSrc 60, // ySrc 0, // first scan line cyDib[0], // number of scan lines pBits[0], pbmi[0], DIB_RGB_COLORS) ; // Top-down DIB full size SetDIBitsToDevice (hdc, 340, // xDst cyClient / 4, // yDst cxDib[0], // cxSrc cyDib[0], // cySrc 0, // xSrc 0, // ySrc 0, // first scan line cyDib[0], // number of scan lines pBits[0], pbmi[0], DIB_RGB_COLORS) ; // Top-down DIB partial SetDIBitsToDevice (hdc, 580, // xDst cyClient / 4, // yDst 80, // cxSrc 166, // cySrc 80, // xSrc 60, // ySrc 0, // first scan line cyDib[1], // number of scan lines pBits[1], pbmi[1], DIB_RGB_COLORS) ; EndPaint (hwnd, &ps) ; return 0 ; case WM_DESTROY: if (pbmfh[0]) free (pbmfh[0]) ; if (pbmfh[1]) free (pbmfh[1]) ; PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ; }
程序加载了名为APOLLO11.BMP(由下而上版本)和APOLLOTD.BMP(由上而下版本)的两个DIB。它们都是220图素宽和240图素高。注意,在程序从表头信息结构中确定DIB的宽度和高度时,它使用abs函数得到biHeight字段的绝对值。当以全部大小或范围显示DIB时,不管显示位图的种类,xSrc、ySrc、cxSrc和cySrc坐标都是相同的。结果如图15-4所示。
图15-4 APOLLO11的屏幕显示 |
注意,「第一条扫描线」和「扫描线数目」参数保持不变,我将在以后简短说明。pBits参数也不变,不要只为了使它指向您需要显示的区域而试图更改pBits。
我在这个问题上花了这么多时间,并不是因为要让那些试图跟API定义中有问题的部分妥协的Windows程序写作者难堪,而是想让您不至于因为这个令人混淆的问题而紧张起来。这个问题之所以令人困惑,是因为它本身早就被搞混了。
我也想让您留意Windows文件中的某些叙述,例如对SetDIBitsToDevice,文件说:「由下而上DIB的原点是位图的左下角;由上而下DIB的原点是左上角」。这不仅模糊,而且是错误的。我可以用更好的方式来讲述:由下而上DIB的原点是位图图像的左下角,它是位图资料的第一列的第一个图素。由上而下DIB的原点也是位图图像的左下角,但在这种情况下,左下角是位图数据的最后一列的第一个图素。
如果要撰写存取DIB个别位的函数,问题会变的更糟。这应该与您为显示部分DIB映像而指定的坐标一致,我的解决方法是(我将在第十六章的DIB链接库中使用)以统一的手法参考DIB图素和坐标,就像在图像被正确显示时(0,0)原点所指的是DIB图像顶行的最左边的图素一样。
循序显示
拥有海量存储器能确保程序更容易地执行。要显示磁盘文件内的DIB,可以分为两个独立的工作:将DIB加载内存,然后显示它。
然而,您也可能在不把整个文件加载内存的情况下显示DIB。即使有足够的物理内存提供给DIB,把DIB移入内存也会迫使Windows的虚拟内存系统把内存中别的数据和程序代码移到磁盘上。如果DIB仅用于显示并立即从内存中消除,这就非常讨厌。
还有另一个问题:假设DIB位于例如软盘的慢速储存媒体上,或由调制解调器传输过来,或者来自扫描仪或视频截取程序取得图素数据的转换例程。您是否得等到整个DIB被加载内存后才显示它?还是从磁盘或电话线或扫描仪上得到DIB时,就开始显示它?
解决这些问题是SetDIBitsToDevice函数中yScan和cyScans参数的目的。要使用这个功能,需要多次呼叫SetDIBitsToDevice,大多数情况下使用同样的参数。然而对于每次呼叫,pBits参数指向位图图素总体排列的不同部分。yScans参数指出了pBits指向图素资料的行,cyScans参数是被pBits引用的行数。这大量地减少了内存需求。您仅需要为储存DIB的信息部分(BITMAPINFOHEADER结构和色彩对照表)和至少一行图素数据配置足够的内存。
例如,假设DIB有23行图素,您希望每次最多5行的分段显示这个DIB。您可能想配置一个由变量pInfo引用的内存块来储存DIB的BITMAPINFO部分,然后从文件中读取该DIB。在检查完此结构的字段后,能够计算出一行的位组长度。乘以5并配置该大小的另一个内存块(pBits)。现在读取前5行,呼叫您正常使用的函数,把yScan设定为0,把cyScans设定为5。现在从文件中读取下5行,这一次将yScan设定为5,继续将yScan设定为10,然后为15。最后,将最后3行读入pBits指向的内存块,并将yScan设定为20,将cyScans设定为3,以呼叫SetDIBitsToDevice。
现在有一个不好的消息。首先,使用SetDIBitsToDevice的这个功能要求程序的数据取得和数据显示元素之间结合得相当紧密。这通常是不理想的,因为您必须在获得数据和显示数据之间切换。首先,您将延缓整个程序;第二,SetDIBitsToDevice是唯一具有这个功能的位图显示函数。StretchDIBits函数不包括这个功能,因此您不能使用它以不同图素大小显示发表的DIB。您必须呼叫StretchDIBits多次,每次更改BITMAPINFOHEADER结构中的信息,并在屏幕的不同区域显示结果。
程序15-4 SEQDISP展示了这个功能的使用方法。
SEQDISP.C /*---------------------------------------------------------------------------- SEQDISP.C -- Sequential Display of DIBs (c) Charles Petzold, 1998 -----------------------------------------------------------------------------*/ #include <windows.h> #include "resource.h" LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; TCHAR szAppName[] = TEXT ("SeqDisp") ; 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 Sequential Display"), 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 ; } LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { static BITMAPINFO * pbmi ; static BYTE * pBits ; static int cxDib, cyDib, cBits ; 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") ; BITMAPFILEHEADER bmfh ; BOOL bSuccess, bTopDown ; DWORD dwBytesRead ; HANDLE hFile ; HDC hdc ; HMENU hMenu ; int iInfoSize, iBitsSize, iRowLength, y ; PAINTSTRUCT ps ; switch (message) { case WM_CREATE: 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_COMMAND: hMenu = GetMenu (hwnd) ; switch (LOWORD (wParam)) { case IDM_FILE_OPEN: // Display File Open dialog if (!GetOpenFileName (&ofn)) return 0 ; // Get rid of old DIB if (pbmi) { free (pbmi) ; pbmi = NULL ; } if (pBits) { free (pBits) ; pBits = NULL ; } // Generate WM_PAINT message to erase background InvalidateRect (hwnd, NULL, TRUE) ; UpdateWindow (hwnd) ; // Open the file hFile = CreateFile (szFileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL) ; if (hFile == INVALID_HANDLE_VALUE) { MessageBox ( hwnd, TEXT ("Cannot open file."), szAppName, MB_ICONWARNING | MB_OK) ; return 0 ; } // Read in the BITMAPFILEHEADER bSuccess = ReadFile (hFile,&bmfh, sizeof (BITMAPFILEHEADER), &dwBytesRead, NULL) ; if (!bSuccess || dwBytesRead != sizeof (BITMAPFILEHEADER)) { MessageBox (hwnd, TEXT ("Cannot read file."), szAppName, MB_ICONWARNING | MB_OK) ; CloseHandle (hFile) ; return 0 ; } // Check that it's a bitmap if (bmfh.bfType != * (WORD *) "BM") { MessageBox (hwnd, TEXT ("File is not a bitmap."), szAppName, MB_ICONWARNING | MB_OK) ; CloseHandle (hFile) ; return 0 ; } // Allocate memory for header and bits iInfoSize = bmfh.bfOffBits - sizeof (BITMAPFILEHEADER) ; iBitsSize = bmfh.bfSize - bmfh.bfOffBits ; pbmi = malloc (iInfoSize) ; pBits = malloc (iBitsSize) ; if (pbmi == NULL || pBits == NULL) { MessageBox (hwnd, TEXT ("Cannot allocate memory."), szAppName, MB_ICONWARNING | MB_OK) ; if (pbmi) free (pbmi) ; if (pBits) free (pBits) ; CloseHandle (hFile) ; return 0 ; } // Read in the Information Header bSuccess = ReadFile (hFile, pbmi, iInfoSize, &dwBytesRead, NULL) ; if (!bSuccess || (int) dwBytesRead != iInfoSize) { MessageBox (hwnd, TEXT ("Cannot read file."), szAppName, MB_ICONWARNING | MB_OK) ; if (pbmi) free (pbmi) ; if (pBits) free (pBits) ; CloseHandle (hFile) ; return 0 ; } // Get the DIB width and height bTopDown = FALSE ; if (pbmi->bmiHeader.biSize == sizeof (BITMAPCOREHEADER)) { cxDib = ((BITMAPCOREHEADER *) pbmi)->bcWidth ; cyDib = ((BITMAPCOREHEADER *) pbmi)->bcHeight ; cBits = ((BITMAPCOREHEADER *) pbmi)->bcBitCount ; } else { if (pbmi->bmiHeader.biHeight < 0) bTopDown = TRUE ; cxDib = pbmi->bmiHeader.biWidth ; cyDib = abs (pbmi->bmiHeader.biHeight) ; cBits = pbmi->bmiHeader.biBitCount ; if (pbmi->bmiHeader.biCompression != BI_RGB && pbmi->bmiHeader.biCompression != BI_BITFIELDS) { MessageBox (hwnd, TEXT ("File is compressed."), szAppName, MB_ICONWARNING | MB_OK) ; if (pbmi) free (pbmi) ; if (pBits) free (pBits) ; CloseHandle (hFile) ; return 0 ; } } // Get the row length iRowLength = ((cxDib * cBits + 31) & ~31) >> 3 ; // Read and display SetCursor (LoadCursor (NULL, IDC_WAIT)) ; ShowCursor (TRUE) ; hdc = GetDC (hwnd) ; for (y = 0 ; y < cyDib ; y++) { ReadFile (hFile, pBits + y * iRowLength,iRowLength,&dwBytesRead, NULL) ; SetDIBitsToDevice (hdc, 0, // xDst 0, // yDst cxDib, // cxSrc cyDib, // cySrc 0, // xSrc 0, // ySrc bTopDown ? cyDib - y - 1 : y, // first scan line 1, // number of scan lines pBits + y * iRowLength, pbmi, DIB_RGB_COLORS) ; } ReleaseDC (hwnd, hdc) ; CloseHandle (hFile) ; ShowCursor (FALSE) ; SetCursor (LoadCursor (NULL, IDC_ARROW)) ; return 0 ; } break ; case WM_PAINT: hdc = BeginPaint (hwnd, &ps) ; if (pbmi && pBits) SetDIBitsToDevice (hdc, 0, // xDst 0, // yDst cxDib, // cxSrc cyDib, // cySrc 0, // xSrc 0, // ySrc 0, // first scan line cyDib, // number of scan lines pBits, DIB_RGB_COLORS) ; EndPaint (hwnd, &ps) ; return 0 ; case WM_DESTROY: if (pbmi) free (pbmi) ; if (pBits) free (pBits) ; PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ; }
SEQDISP.RC (摘录) //Microsoft Developer Studio generated resource script. #include "resource.h" #include "afxres.h" ///////////////////////////////////////////////////////////////////////////// // Accelerator SEQDISP ACCELERATORS DISCARDABLE BEGIN "O", IDM_FILE_OPEN, VIRTKEY, CONTROL, NOINVERT END ///////////////////////////////////////////////////////////////////////////// // Menu SEQDISP 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 SeqDisp.rc #define IDM_FILE_OPEN 40001
在处理「File Open」菜单命令期间,在SEQDISP.C内的所有文件I/O都会发生。在处理WM_COMMAND的最后,程序进入读取单行图素并用SetDIBitsToDevice显示该行图素的循环。整个DIB储存在内存中以便在处理WM_PAINT期间也能显示它。
缩放到合适尺寸
SetDIBitsToDevice完成了将DIB的图素对点送入输出设备的显示程序。这对于打印DIB用处不大。打印机的分辨率越高,得到的图像就越小,您最终会得到如邮票大小的图像。
要通过缩小或放大DIB,在输出设备上以特定的大小显示它,可以使用StretchDIBits:
iLines = StretchDIBits ( hdc, // device context handle xDst, // x destination coordinate yDst, // y destination coordinate cxDst, // destination rectangle width cyDst, // destination rectangle height xSrc, // x source coordinate ySrc, // y source coordinate cxSrc, // source rectangle width cySrc, // source rectangle height pBits, // pointer to DIB pixel bits pInfo, // pointer to DIB information fClrUse, // color use flag dwRop) ; // raster operation
函数参数除了下列三个方面,均与SetDIBitsToDevice相同。
- 目的坐标包括逻辑宽度(cxDst)和高度(cyDst),以及开始点。
- 不能通过持续显示DIB来减少内存需求。
-
最后一个参数是位映像操作方式,它指出了DIB图素与输出设备图素结合的方式,在最后一章将学到这些内容。现在我们为此参数设定为SRCCOPY。
还有另一个更细微的差别。如果查看SetDIBitsToDevice的声明,您会发现cxSrc和cySrc是DWORD,这是32位无正负号长整数型态。在StretchDIBits中,cxSrc和cySrc(以及cxDst和cyDst)定义为带正负号的整数型态,这意味着它们可以为负数,实际上等一下就会看到,它们确实能为负数。如果您已经开始检查是否别的参数也可以为负数,就让我声明一下:在两个函数中,xSrc和ySrc均定义为int,但这是错的,这些值始终是非负数。
DIB内的来源矩形被映射到目的矩形的坐标显示如表15-4所示。
来源矩形 |
目的矩形 |
(xSrc, ySrc) |
(xDst, yDst + cyDst - 1) |
(xSrc + cxSrc - 1, ySrc) |
(xDst + cxDst - 1, yDst + cyDst - 1) |
(xSrc, ySrc + cySrc - 1) |
(xDst, yDst) |
(xSrc + cxSrc - 1, ySrc + cySrc - 1) |
(xDst + cxDst - 1, yDst) |
右列中的-1项是不精确的,因为放大的程度(以及映像方式和其它变换)能产生略微不同的结果。
例如,考虑一个2×2的DIB,这里StretchDIBits的xSrc和ySrc参数均为0,cxSrc和cySrc均为2。假定我们显示到的设备内容具有MM_TEXT映像方式并且不进行变换。如果xDst和yDst均为0,cxDst和cyDst均为4,那么我们将以倍数2放大DIB。每个来源图素(x,y)将映射到下面所示的四个目的图素上:
(0,0) --> (0,2) and (1,2) and (0,3) and (1,3) (1,0) --> (2,2) and (3,2) and (2,3) and (3,3) (0,1) --> (0,0) and (1,0) and (0,1) and (1,1) (1,1) --> (2,0) and (3,0) and (2,1) and (3,1)
上表正确地指出了目的的角,(0,3)、(3,3)、(0,0)和(3,0)。在其它情况下,坐标可能是个大概值。
目的设备内容的映像方式对SetDIBitsToDevice的影响仅是由于xDst和yDst是逻辑坐标。StretchDIBits完全受映像方式的影响。例如,如果您设定了y值向上递增的一种度量映像方式,DIB就会颠倒显示。
您可以通过把cyDst设定为负数来弥补这种情况。实际上,您可以将任何参数的宽度和高度变为负值来水平或垂直翻转DIB。在MM_TEXT映像方式下,如果cySrc和cyDst符号相反,DIB会沿着水平轴翻转并颠倒显示。如果cxSrc和cxDst符号相反,DIB会沿着垂直轴翻转并显示它的镜面图像。
下面是总结这些内容的表达式,xMM和yMM指出映像方式的方向,如果x值向右增长,则xMM值为1;如果x值向左增长,则值为-1。同样,如果y值向下增长,则yMM值为1;如果y值向上增长,则值为-1。Sign函数对于正值传回TURE,对于负值传回FALSE。
if (!Sign (xMM × cxSrc × cxDst)) DIB is flipped on its vertical axis (mirror image) if (!Sign (yMM × cySrc × cyDst)) DIB is flipped on its horizontal axis (upside down)
若有疑问,请查阅表15-4。
程序15-5 SHOWDIB以实际尺寸显示DIB、放大至显示区域窗口的大小、打印DIB以及把DIB传输到剪贴簿。
SHOWDIB2.C /*-------------------------------------------------------------------------- SHOWDIB2.C -- Shows a DIB in the client area (c) Charles Petzold, 1998 ---------------------------------------------------------------------------*/ #include <windows.h> #include "dibfile.h" #include "resource.h" LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; TCHAR szAppName[] = TEXT ("ShowDib2") ; 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 ("Show DIB #2"), 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 ; } int ShowDib (HDC hdc, BITMAPINFO * pbmi, BYTE * pBits, int cxDib, int cyDib, int cxClient, int cyClient, WORD wShow) { switch (wShow) { case IDM_SHOW_NORMAL: return SetDIBitsToDevice (hdc, 0, 0, cxDib, cyDib, 0, 0, 0, cyDib, pBits, pbmi, DIB_RGB_COLORS) ; case IDM_SHOW_CENTER: return SetDIBitsToDevice (hdc, (cxClient - cxDib) / 2, (cyClient - cyDib) / 2, cxDib, cyDib, 0, 0, 0, cyDib, pBits, pbmi, DIB_RGB_COLORS) ; case IDM_SHOW_STRETCH: SetStretchBltMode (hdc, COLORONCOLOR) ; return StretchDIBits(hdc,0, 0, cxClient, cyClient, 0, 0, cxDib, cyDib, pBits, pbmi, DIB_RGB_COLORS, SRCCOPY) ; case IDM_SHOW_ISOSTRETCH: SetStretchBltMode (hdc, COLORONCOLOR) ; SetMapMode (hdc, MM_ISOTROPIC) ; SetWindowExtEx (hdc, cxDib, cyDib, NULL) ; SetViewportExtEx (hdc, cxClient, cyClient, NULL) ; SetWindowOrgEx (hdc, cxDib / 2, cyDib / 2, NULL) ; SetViewportOrgEx (hdc, cxClient / 2, cyClient / 2, NULL) ; return StretchDIBits(hdc,0, 0, cxDib, cyDib, 0, 0, cxDib, cyDib, pBits, pbmi, DIB_RGB_COLORS, SRCCOPY) ; } } LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam,LPARAM lParam) { static BITMAPFILEHEADER * pbmfh ; static BITMAPINFO * pbmi ; static BYTE * pBits ; static DOCINFO di = {sizeof (DOCINFO), TEXT ("ShowDib2: Printing") } ; static int cxClient, cyClient, cxDib, cyDib ; static PRINTDLG printdlg = { sizeof (PRINTDLG) } ; static TCHAR szFileName [MAX_PATH], szTitleName [MAX_PATH] ; static WORD wShow = IDM_SHOW_NORMAL ; BOOL bSuccess ; HDC hdc, hdcPrn ; HGLOBAL hGlobal ; HMENU hMenu ; int cxPage, cyPage, iEnable ; PAINTSTRUCT ps ; BYTE * pGlobal ; switch (message) { case WM_CREATE: DibFileInitialize (hwnd) ; return 0 ; case WM_SIZE: cxClient = LOWORD (lParam) ; cyClient = HIWORD (lParam) ; return 0 ; case WM_INITMENUPOPUP: hMenu = GetMenu (hwnd) ; if (pbmfh) iEnable = MF_ENABLED ; else iEnable = MF_GRAYED ; EnableMenuItem (hMenu, IDM_FILE_SAVE, iEnable) ; EnableMenuItem (hMenu, IDM_FILE_PRINT, iEnable) ; EnableMenuItem (hMenu, IDM_EDIT_CUT, iEnable) ; EnableMenuItem (hMenu, IDM_EDIT_COPY, iEnable) ; EnableMenuItem (hMenu, IDM_EDIT_DELETE, iEnable) ; return 0 ; case WM_COMMAND: hMenu = GetMenu (hwnd) ; switch (LOWORD (wParam)) { case IDM_FILE_OPEN: // Show the File Open dialog box if (!DibFileOpenDlg (hwnd, szFileName, szTitleName)) return 0 ; // If there's an existing DIB, free the memory if (pbmfh) { free (pbmfh) ; pbmfh = NULL ; } // Load the entire DIB into memory SetCursor (LoadCursor (NULL, IDC_WAIT)) ; ShowCursor (TRUE) ; pbmfh = DibLoadImage (szFileName) ; ShowCursor (FALSE) ; SetCursor (LoadCursor (NULL, IDC_ARROW)) ; // Invalidate the client area for later update InvalidateRect (hwnd, NULL, TRUE) ; if (pbmfh == NULL) { MessageBox ( hwnd, TEXT ("Cannot load DIB file"), szAppName, MB_ICONEXCLAMATION | MB_OK) ; return 0 ; } // Get pointers to the info structure & the bits pbmi = (BITMAPINFO *) (pbmfh + 1) ; pBits = (BYTE *) pbmfh + pbmfh->bfOffBits ; // Get the DIB width and height if (pbmi->bmiHeader.biSize == sizeof (BITMAPCOREHEADER)) { cxDib = ((BITMAPCOREHEADER *) pbmi)->bcWidth ; cyDib = ((BITMAPCOREHEADER *) pbmi)->bcHeight ; } else { cxDib = pbmi->bmiHeader.biWidth ; cyDib = abs (pbmi->bmiHeader.biHeight) ; } return 0 ; case IDM_FILE_SAVE: // Show the File Save dialog box if (!DibFileSaveDlg (hwnd, szFileName, szTitleName)) return 0 ; // Save the DIB to a disk file SetCursor (LoadCursor (NULL, IDC_WAIT)) ; ShowCursor (TRUE) ; bSuccess = DibSaveImage (szFileName, pbmfh) ; ShowCursor (FALSE) ; SetCursor (LoadCursor (NULL, IDC_ARROW)) ; if (!bSuccess) MessageBox ( hwnd, TEXT ("Cannot save DIB file"), szAppName, MB_ICONEXCLAMATION | MB_OK) ; return 0 ; case IDM_FILE_PRINT: if (!pbmfh) return 0 ; // Get printer DC printdlg.Flags = PD_RETURNDC | PD_NOPAGENUMS | PD_NOSELECTION ; if (!PrintDlg (&printdlg)) return 0 ; if (NULL == (hdcPrn = printdlg.hDC)) { MessageBox (hwnd, TEXT ("Cannot obtain Printer DC"), szAppName, MB_ICONEXCLAMATION | MB_OK) ; return 0 ; } // Check whether the printer can print bitmaps if (!(RC_BITBLT & GetDeviceCaps (hdcPrn, RASTERCAPS))) { DeleteDC (hdcPrn) ; MessageBox ( hwnd, TEXT ("Printer cannot print bitmaps"), szAppName, MB_ICONEXCLAMATION | MB_OK) ; return 0 ; } // Get size of printable area of page cxPage = GetDeviceCaps (hdcPrn, HORZRES) ; cyPage = GetDeviceCaps (hdcPrn, VERTRES) ; bSuccess = FALSE ; // Send the DIB to the printer SetCursor (LoadCursor (NULL, IDC_WAIT)) ; ShowCursor (TRUE) ; if ((StartDoc (hdcPrn, &di) > 0) && (StartPage (hdcPrn) > 0)) { ShowDib ( hdcPrn, pbmi, pBits, cxDib, cyDib, cxPage, cyPage, wShow) ; if (EndPage (hdcPrn) > 0) { bSuccess = TRUE ; EndDoc (hdcPrn) ; } } ShowCursor (FALSE) ; SetCursor (LoadCursor (NULL, IDC_ARROW)) ; DeleteDC (hdcPrn) ; if (!bSuccess) MessageBox (hwnd, TEXT ("Could not print bitmap"), szAppName, MB_ICONEXCLAMATION | MB_OK) ; return 0 ; case IDM_EDIT_COPY: case IDM_EDIT_CUT: if (!pbmfh) return 0 ; // Make a copy of the packed DIB hGlobal = GlobalAlloc (GHND | GMEM_SHARE, pbmfh->bfSize - sizeof (BITMAPFILEHEADER)) ; pGlobal = GlobalLock (hGlobal) ; CopyMemory ( pGlobal, (BYTE *) pbmfh + sizeof (BITMAPFILEHEADER), pbmfh->bfSize - sizeof (BITMAPFILEHEADER)) ; GlobalUnlock (hGlobal) ; // Transfer it to the clipboard OpenClipboard (hwnd) ; EmptyClipboard () ; SetClipboardData (CF_DIB, hGlobal) ; CloseClipboard () ; if (LOWORD (wParam) == IDM_EDIT_COPY) return 0 ; // fall through if IDM_EDIT_CUT case IDM_EDIT_DELETE: if (pbmfh) { free (pbmfh) ; pbmfh = NULL ; InvalidateRect (hwnd, NULL, TRUE) ; } return 0 ; case IDM_SHOW_NORMAL: case IDM_SHOW_CENTER: case IDM_SHOW_STRETCH: case IDM_SHOW_ISOSTRETCH: CheckMenuItem (hMenu, wShow, MF_UNCHECKED) ; wShow = LOWORD (wParam) ; CheckMenuItem (hMenu, wShow, MF_CHECKED) ; InvalidateRect (hwnd, NULL, TRUE) ; return 0 ; } break ; case WM_PAINT: hdc = BeginPaint (hwnd, &ps) ; if (pbmfh) ShowDib ( hdc, pbmi, pBits, cxDib, cyDib, cxClient, cyClient, wShow) ; EndPaint (hwnd, &ps) ; return 0 ; case WM_DESTROY: if (pbmfh) free (pbmfh) ; PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ; }
SHOWDIB2.RC (摘录) //Microsoft Developer Studio generated resource script. #include "resource.h" #include "afxres.h" ///////////////////////////////////////////////////////////////////////////// // Menu SHOWDIB2 MENU DISCARDABLE BEGIN POPUP "&File" BEGIN MENUITEM "&Open...\tCtrl+O",IDM_FILE_OPEN MENUITEM "&Save...\tCtrl+S", IDM_FILE_SAVE MENUITEM SEPARATOR MENUITEM "&Print\tCtrl+P", IDM_FILE_PRINT END POPUP "&Edit" BEGIN MENUITEM "Cu&t\tCtrl+X", IDM_EDIT_CUT MENUITEM "&Copy\tCtrl+C", IDM_EDIT_COPY MENUITEM "&Delete\tDelete", IDM_EDIT_DELETE END POPUP "&Show" BEGIN MENUITEM "&Actual Size", IDM_SHOW_NORMAL, CHECKED MENUITEM "&Center", IDM_SHOW_CENTER MENUITEM "&Stretch to Window", IDM_SHOW_STRETCH MENUITEM "Stretch &Isotropically", IDM_SHOW_ISOSTRETCH END END ///////////////////////////////////////////////////////////////////////////// // Accelerator SHOWDIB2 ACCELERATORS DISCARDABLE BEGIN "C", IDM_EDIT_COPY, VIRTKEY, CONTROL, NOINVERT "O", IDM_FILE_OPEN, VIRTKEY, CONTROL, NOINVERT "P", IDM_FILE_PRINT, VIRTKEY, CONTROL, NOINVERT "S", IDM_FILE_SAVE, VIRTKEY, CONTROL, NOINVERT VK_DELETE, IDM_EDIT_DELETE, VIRTKEY, NOINVERT "X", IDM_EDIT_CUT, VIRTKEY, CONTROL, NOINVERT END
RESOURCE.H (摘录) // Microsoft Developer Studio generated include file. // Used by ShowDib2.rc #define IDM_FILE_OPEN 40001 #define IDM_SHOW_NORMAL 40002 #define IDM_SHOW_CENTER 40003 #define IDM_SHOW_STRETCH 40004 #define IDM_SHOW_ISOSTRETCH 40005 #define IDM_FILE_PRINT 40006 #define IDM_EDIT_COPY 40007 #define IDM_EDIT_CUT 40008 #define IDM_EDIT_DELETE 40009 #define IDM_FILE_SAVE 40010
有意思的是ShowDib函数,它依赖于菜单选择以四种不同的方式之一在程序的显示区域显示DIB。可以使用SetDIBitsToDevice从显示区域的左上角或在显示区域的中心显示DIB。程序也有两个使用StretchDIBits的选项,DIB能放大填充整个显示区域。在此情况下它可能会变形,或它能等比例显示,也就是说不会变形。
把DIB复制到剪贴簿包括:在整体共享内存中制作packed DIB内存块的副本。剪贴簿数据型态为CF_DIB。程序没有列出从剪贴簿复制DIB的方法,因为在仅有指向packed DIB的指标的情况下这样做需要更多步骤来确定图素位的偏移量。我将在下一章的末尾示范如何做到这点的方法。
您可能注意到了SHOWDIB2中的一些不足之处。如果您以256色显示模式执行Windows,就会看到显示除了单色或4位DIB以外的其它图形出现的问题,您看不到真正的颜色。存取那些颜色需要使用调色盘,在下一章会做这些工作。您也可能注意到速度问题,尤其在Windows NT下执行SHOWDIB2时。在下一章packed DIB和位图时,我会展示处理的方法。我也给DIB显示添加滚动条,这样也能以实际尺寸查看大于屏幕的DIB。
色彩转换、调色盘和显示效能
记得在虎豹小霸王编剧William Goldman的另一出电影剧本《All the President's Men》中,Deep Throat告诉Bob Woodward揭开水门秘密的关键是「跟着钱走」。那么在位图显示中获得高级性能的关键就是「跟着图素位走」以及理解色彩转换发生的时机。DIB是设备无关的格式,视讯显示器内存几乎总是与图素格式不同。在SetDIBitsToDevice或StretchDIBits函数呼叫期间,每个图素(可能有几百万个)必须从设备无关的格式转换成设备相关格式。
在许多情况下,这种转换是很繁琐的。例如,在24位视讯显示器上显示24位DIB,显示驱动程序最多是切换红、绿、蓝的字节顺序而已。在24位设备上显示16位DIB就需要位的搬移和修剪了。在24位设备上显示4位或8位DIB要求在DIB色彩对照表内查找DIB图素位,然后对字节重新排列。
但是要在4位或8位视讯显示器上显示16位、24位或32位DIB时,会发生什么事情呢?一种完全不一样的颜色转换发生了。对于DIB内的每个图素,设备驱动程序必须在图素和显示器上可用的颜色之间「找寻最接近的色彩」,这包括循环和计算。(GDI函数GetNearestColor进行「最接近色彩搜寻」。)
整个RGB色彩的三维数组可用立方体表示。曲线内任意两点之间的距离是:
在这里两个颜色是R1G1B1和R2G2B2。执行最接近色彩搜寻包括从一种颜色到其它颜色集合中找寻最短距离。幸运的是,在RGB颜色立方体中「比较」距离时,并不需要计算平方根部分。但是需转换的每个图素必须与设备的所有颜色相比较以发现最接近的颜色。这是个工作量相当大的工作。(尽管在8位设备上显示8位DIB也得进行最接近色彩搜寻,但它不必对每个图素都进行,它仅需对DIB色彩对照表中的每种颜色进行寻找。)
正是由于以上原因,应该避免使用SetDIBitsToDevice或StretchDIBits在8位视讯显示卡上显示16位、24位或32位DIB。DIB应转换为8位DIB,或者8位DDB,以求得更好的显示效能。实际上,您可以经由将DIB转换为DDB并使用BitBlt和StretchBlt显示图像,来加快显示任何DIB的速度。
如果在8位视讯显示器上执行Windows(或仅仅切换到8位模式来观察在显示True-ColorDIB时的效能变化),您可能会注意到另一个问题:DIB不会使用所有颜色来显示。任何在8位视讯显示器上的DIB刚好限制在以20种颜色显示。如何获得多于20种颜色是「调色盘管理器」的任务,这将在下一章提到。
最后,如果在同一台机器上执行Windows 98和Windows NT,您可能会注意到:对于同样的显示模式,Windows NT显示大型DIB花费的时间较长。这是Windows NT的客户/服务器体系结构的结果,它使大量数据在传输给API函数时耗费更多时间。解决方法是将DIB转换为DDB。而我等一下将谈到的CreateDIBSection函数对这种情况特别有用。
您可以做许多事情去发掘DIB的格式,并呼叫两个DIB绘图函数:SetDIBitsToDevice和StretchDIBits。您可以直接存取DIB中的各个位、字节和图素,且一旦您有了一堆能让您以结构化的方式检查和更改数据的函数,您要怎么处理DIB就没人管了。
实际上,我们发现还是有一些限制。在上一章,我们了解了使用GDI函数在DDB上绘制图像的方法。到目前为止,还没有在DIB上绘图的方法。另一个问题是SetDIBitsToDevice和StretchDIBits没有BitBlt和StretchBlt速度快,尤其在Windows NT环境下以及执行许多最接近颜色搜寻(例如,在8位视频卡上显示24位DIB)时。
因此,在DIB和DDB之间进行转换是有好处的。例如,如果我们有一个需要在屏幕上显示许多次的DIB,那么把DIB转换为DDB就很有意义,这样我们就能够使用快速的BitBlt和StretchBlt函数来显示它了。
从DIB建立DDB
从DIB中建立GDI位图对象可能吗?基本上我们已经知道了方法:如果有DIB,您就能够使用CreateCompatibleBitmap来建立与DIB大小相同并与视讯显示器兼容的GDI位图对象。然后将该位图对象选入内存设备内容并呼叫SetDIBitsToDevice在那个内存DC上绘图。结果就是DDB具有与DIB相同的图像,但具有与视讯显示器兼容的颜色组织。
您也可以通过呼叫CreateDIBitmap用几个步骤完成上述工作。函数的语法为:
hBitmap = CreateDIBitmap ( hdc, // device context handle pInfoHdr, // pointer to DIB information header fInit, // 0 or CBM_INIT pBits, // pointer to DIB pixel bits pInfo, // pointer to DIB information fClrUse) ; // color use flag
请注意pInfoHdr和pInfo这两个参数,它们分别定义为指向BITMAPINFOHEADER结构和BITMAPINFO结构的指针。正如我们所知,BITMAPINFO结构是后面紧跟色彩对照表的BITMAPINFOHEADER结构。我们一会儿会看到这种区别所起的作用。最后一个参数是DIB_RGB_ COLORS(等于0)或DIB_PAL_COLORS,它们在SetDIBitsToDevice函数中使用。下一章我将讨论更多这方面的内容。
理解Windows中位图函数的作用是很重要的。不要考虑CreateDIBitmap函数的名称,它不建立与「设备无关的位图」,它从设备无关的规格中建立「设备相关的位图」。注意该函数传回GDI位图对象的句柄,CreateBitmap、CreateBitmapIndirect和CreateCompatibleBitmap也与它一样。
呼叫CreateDIBitmap函数最简单的方法是:
hBitmap = CreateDIBitmap (NULL, pbmih, 0, NULL, NULL, 0) ;
唯一的参数是指向BITMAPINFOHEADER结构(不带色彩对照表)的指标。在这个形式中,函数建立单色GDI位图对象。第二种简单的方法是:
hBitmap = CreateDIBitmap (hdc, pbmih, 0, NULL, NULL, 0) ;
在这个形式中,函数建立了与设备内容兼容的DDB,该设备内容由hdc参数指出。到目前为止,我们都是透过CreateBitmap(建立单色位图)或CreateCompatibleBitmap(建立与视讯显示器兼容的位图)来完成一些工作。
在CreateDIBitmap的这两个简化模式中,图素还未被初始化。如果CreateDIBitmap的第三个参数是CBM_INIT,Windows就会建立DDB并使用最后三个参数初始化位图位。pInfo参数是指向包括色彩对照表的BITMAPINFO结构的指针。pBits参数是指向由BITMAPINFO结构指出的色彩格式中的位数组的指针,根据色彩对照表这些位被转换为设备的颜色格式,这与SetDIBitsToDevice的情况相同。实际上,整个CreateDIBitmap函数可以用下列程序代码来实作:
HBITMAP CreateDIBitmap ( HDC hdc, CONST BITMAPINFOHEADER * pbmih, DWORD fInit, CONST VOID * pBits, CONST BITMAPINFO * pbmi, UINT fUsage) { HBITMAP hBitmap ; HDC hdc ; int cx, cy, iBitCount ; if (pbmih->biSize == sizeof (BITMAPCOREHEADER)) { cx = ((PBITMAPCOREHEADER) pbmih)->bcWidth ; cy = ((PBITMAPCOREHEADER) pbmih)->bcHeight ; iBitCount = ((PBITMAPCOREHEADER) pbmih)->bcBitCount ; } else { cx = pbmih->biWidth ; cy = pbmih->biHeight ; iBitCount = pbmih->biBitCount ; } if (hdc) hBitmap = CreateCompatibleBitmap (hdc, cx, cy) ; else hBitmap = CreateBitmap (cx, cy, 1, 1, NULL) ; if (fInit == CBM_INIT) { hdcMem = CreateCompatibleDC (hdc) ; SelectObject (hdcMem, hBitmap) ; SetDIBitsToDevice ( hdcMem, 0, 0, cx, cy, 0, 0, 0 cy, pBits, pbmi, fUsage) ; DeleteDC (hdcMem) ; } return hBitmap ; }
如果仅需显示DIB一次,并担心SetDIBitsToDevice显示太慢,则呼叫CreateDIBitmap并使用BitBlt或StretchBlt来显示DDB就没有什么意义。因为SetDIBitsToDevice和CreateDIBitmap都执行颜色转换,这两个工作会占用同样长的时间。只有在多次显示DIB时(例如在处理WM_PAINT消息时)进行这种转换才有意义。
程序15-6 DIBCONV展示了利用SetDIBitsToDevice把DIB文件转换为DDB的方法。
DIBCONV.C /*-------------------------------------------------------------------------- DIBCONV.C -- Converts a DIB to a DDB (c) Charles Petzold, 1998 ----------------------------------------------------------------------------*/ #include <windows.h> #include <commdlg.h> #include "resource.h" LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; TCHAR szAppName[] = TEXT ("DibConv") ; int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { 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 to DDB Conversion"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL) ; ShowWindow (hwnd, iCmdShow) ; UpdateWindow (hwnd) ; while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg) ; DispatchMessage (&msg) ; } return msg.wParam ; } HBITMAP CreateBitmapObjectFromDibFile (HDC hdc, PTSTR szFileName) { BITMAPFILEHEADER * pbmfh ; BOOL bSuccess ; DWORD dwFileSize, dwHighSize, dwBytesRead ; HANDLE hFile ; HBITMAP hBitmap ; // Open the file: read access, prohibit write access hFile = CreateFile (szFileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL) ; if (hFile == INVALID_HANDLE_VALUE) return NULL ; // Read in the whole file dwFileSize = GetFileSize (hFile, &dwHighSize) ; if (dwHighSize) { CloseHandle (hFile) ; return NULL ; } pbmfh = malloc (dwFileSize) ; if (!pbmfh) { CloseHandle (hFile) ; return NULL ; } bSuccess = ReadFile (hFile, pbmfh, dwFileSize, &dwBytesRead, NULL) ; CloseHandle (hFile) ; // Verify the file if (!bSuccess || (dwBytesRead != dwFileSize) || (pbmfh->bfType != * (WORD *) "BM") || (pbmfh->bfSize != dwFileSize)) { free (pbmfh) ; return NULL ; } // Create the DDB hBitmap = CreateDIBitmap (hdc, (BITMAPINFOHEADER *) (pbmfh + 1), CBM_INIT, (BYTE *) pbmfh + pbmfh->bfOffBits, (BITMAPINFO *) (pbmfh + 1), DIB_RGB_COLORS) ; free (pbmfh) ; return hBitmap ; } LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam,LPARAM lParam) { static HBITMAP hBitmap ; static int cxClient, cyClient ; 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") ; BITMAP bitmap ; HDC hdc, hdcMem ; PAINTSTRUCT ps ; switch (message) { case WM_CREATE: 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: cxClient = LOWORD (lParam) ; cyClient = HIWORD (lParam) ; return 0 ; case WM_COMMAND: switch (LOWORD (wParam)) { case IDM_FILE_OPEN: // Show the File Open dialog box if (!GetOpenFileName (&ofn)) return 0 ; // If there's an existing DIB, delete it if (hBitmap) { DeleteObject (hBitmap) ; hBitmap = NULL ; } // Create the DDB from the DIB SetCursor (LoadCursor (NULL, IDC_WAIT)) ; ShowCursor (TRUE) ; hdc = GetDC (hwnd) ; hBitmap = CreateBitmapObjectFromDibFile (hdc, szFileName) ; ReleaseDC (hwnd, hdc) ; ShowCursor (FALSE) ; SetCursor (LoadCursor (NULL, IDC_ARROW)) ; // Invalidate the client area for later update InvalidateRect (hwnd, NULL, TRUE) ; if (hBitmap == NULL) { MessageBox (hwnd, TEXT ("Cannot load DIB file"), szAppName, MB_OK | MB_ICONEXCLAMATION) ; } return 0 ; } break ; case WM_PAINT: hdc = BeginPaint (hwnd, &ps) ; if (hBitmap) { GetObject (hBitmap, sizeof (BITMAP), &bitmap) ; hdcMem = CreateCompatibleDC (hdc) ; SelectObject (hdcMem, hBitmap) ; BitBlt (hdc,0, 0, bitmap.bmWidth, bitmap.bmHeight, hdcMem, 0, 0, SRCCOPY) ; DeleteDC (hdcMem) ; } EndPaint (hwnd, &ps) ; return 0 ; case WM_DESTROY: if (hBitmap) DeleteObject (hBitmap) ; PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ; }
DIBCONV.RC (摘录) //Microsoft Developer Studio generated resource script. #include "resource.h" #include "afxres.h" ///////////////////////////////////////////////////////////////////////////// // Menu DIBCONV MENU DISCARDABLE BEGIN POPUP "&File" BEGIN MENUITEM "&Open", IDM_FILE_OPEN END END
RESOURCE.H (摘录) // Microsoft Developer Studio generated include file. // Used by DibConv.rc #define IDM_FILE_OPEN 40001
DIBCONV.C本身就是完整的,并不需要前面的文件。在对它仅有的菜单命令(「File Open」)的响应中,WndProc呼叫程序的CreateBitmapObjectFromDibFile函数。此函数将整个文件读入内存并将指向内存块的指针传递给CreateDIBitmap函数,函数传回位图的句柄,然后包含DIB的内存块被释放。在WM_PAINT消息处理期间,WndProc将位图选入兼容的内存设备内容并使用BitBlt(不是SetDIBitsToDevice)在显示区域显示位图。它通过使用位图句柄呼叫带有BITMAP结构的GetObject函数来取得位图的宽度和高度。
在从CreateDIBitmap建立位图时不必初始化DDB图素位,之后您可以呼叫SetDIBits初始化图素位。该函数的语法如下:
iLines = SetDIBits ( hdc, // device context handle hBitmap, // bitmap handle yScan, // first scan line to convert cyScans, // number of scan lines to convert pBits, // pointer to pixel bits pInfo, // pointer to DIB information fClrUse) ; // color use flag
函数使用了BITMAPINFO结构中的色彩对照表把位转换为设备相关的格式。只有在最后一个参数设定为DIB_PAL_COLORS时,才需要设备内容句柄。
从DDB到DIB
与SetDIBits函数相似的函数是GetDIBits,您可以使用此函数把DDB转化为DIB:
int WINAPI GetDIBits ( hdc, // device context handle hBitmap, // bitmap handle yScan, // first scan line to convert cyScans, // number of scan lines to convert pBits, // pointer to pixel bits (out) pInfo, // pointer to DIB information (out) fClrUse) ; // color use flag
然而,此函数产生的恐怕不是SetDIBits的反运算结果。在一般情况下,如果使用CreateDIBitmap和SetDIBits将DIB转换为DDB,然后使用GetDIBits把DDB转换回DIB,您就不会得到原来的图像。这是因为在DIB被转换为设备相关的格式时,有一些信息遗失了。遗失的信息数量取决于进行转换时Windows所执行的显示模式。
您可能会发现没有使用GetDIBits的必要性。考虑一下:在什么环境下您的程序发现自身带有位图句柄,但没有用于在起始的位置建立位图的数据?剪贴簿?但是剪贴簿为DIB提供了自动的转换。GetDIBits函数的一个例子是在捕捉屏幕显示内容的情况下,例如第十四章中BLOWUP程序所做的。我不示范这个函数,但在Microsoft网站的Knowledge Base文章Q80080中有一些信息。
DIB区块
我希望您已经对设备相关和设备无关位图的区别有了清晰的概念。DIB能拥有几种色彩组织中的一种,DDB必须是单色的或是与真实输出设备相同的格式。DIB是一个文件或内存块;DDB是GDI位图对象并由位图句柄表示。DIB能被显示或转换为DDB并转换回DIB,但是这里包含了设备无关位和设备相关位之间的转换程序。
现在您将遇到一个函数,它打破了这些规则。该函数在32位Windows版本中发表,称为CreateDIBSection,语法为:
hBitmap = CreateDIBSection ( hdc, // device context handle pInfo, // pointer to DIB information fClrUse, // color use flag ppBits, // pointer to pointer variable hSection, // file-mapping object handle dwOffset) ; // offset to bits in file-mapping object
CreateDIBSection是Windows API中最重要的函数之一(至少在使用位图时),然而您会发现它很深奥并难以理解。
让我们从它的名称开始,我们知道DIB是什么,但「DIB section」到底是什么呢?当您第一次检查CreateDIBSection时,可能会寻找该函数与DIB区块工作的方式。这是正确的,CreateDIBSection所做的就是建立了DIB的一部分(位图图素位的内存块)。
现在我们看一下传回值,它是GDI位图对象的句柄,这个传回值可能是该函数呼叫最会拐人的部分。传回值似乎暗示着CreateDIBSection在功能上与CreateDIBitmap相同。事实上,它只是相似但完全不同。实际上,从CreateDIBSection传回的位图句柄与我们在本章和 上一章遇到的所有位图建立函数传回的位图句柄在本质上不同。
一旦理解了CreateDIBSection的真实特性,您可能觉得奇怪为什么不把传回值定义得有所区别。您也可能得出结论:CreateDIBSection应该称之为CreateDIBitmap,并且如同我前面所指出的CreateDIBitmap应该称之为CreateDDBitmap。
首先让我们检查一下如何简化CreateDIBSection,并正确地使用它。首先,把最后两个参数hSection和dwOffset,分别设定为NULL和0,我将在本章最后讨论这些参数的用法。第二,仅在fColorUse参数设定为DIB_ PAL_COLORS时,才使用hdc参数,如果fColorUse为DIB_RGB_COLORS(或0),hdc将被忽略(这与CreateDIBitmap不同,hdc参数用于取得与DDB兼容的设备的色彩格式)。
因此,CreateDIBSection最简单的形式仅需要第二和第四个参数。第二个参数是指向BITMAPINFO结构的指针,我们以前曾使用过。我希望指向第四个参数的指标定义的指标不会使您困惑,它实际上很简单。
假设要建立每图素24位的384×256位DIB,24位格式不需要色彩对照表,因此它是最简单的,所以我们可以为BITMAPINFO参数使用BITMAPINFOHEADER结构。
您需要定义三个变量:BITMAPINFOHEADER结构、BYTE指针和位图句柄:
BITMAPINFOHEADER bmih ; BYTE * pBits ; HBITMAP hBitmap ;
现在初始化BITMAPINFOHEADER结构的字段
bmih->biSize = sizeof (BITMAPINFOHEADER) ; bmih->biWidth = 384 ; bmih->biHeight = 256 ; bmih->biPlanes = 1 ; bmih->biBitCount = 24 ; bmih->biCompression = BI_RGB ; bmih->biSizeImage = 0 ; bmih->biXPelsPerMeter = 0 ; bmih->biYPelsPerMeter = 0 ; bmih->biClrUsed = 0 ; bmih->biClrImportant = 0 ;
在基本准备后,我们呼叫该函数:
hBitmap = CreateDIBSection (NULL, (BITMAPINFO *) &bmih, 0, &pBits, NULL, 0) ;
注意,我们为第二个参数赋予BITMAPINFOHEADER结构的地址。这是常见的,但一个BYIE指针pBits的地址,就不常见了。这样,第四个参数是函数需要的指向指标的指标。
这是函数呼叫所做的:CreateDIBSection检查BITMAPINFOHEADER结构并配置足够的内存块来加载DIB图素位。(在这个例子里,内存块的大小为384×256×3字节。)它在您提供的pBits参数中储存了指向此内存块的指针。函数传回位图句柄,正如我说的,它与CreateDIBitmap和其它位图建立函数传回的句柄不一样。
然而,我们还没有做完,位图图素是未初始化的。如果正在读取DIB文件,可以简单地把pBits参数传递给ReadFile函数并读取它们。或者可以使用一些程序代码「人工」设定。
程序15-7 DIBSECT除了呼叫CreateDIBSection而不是CreateDIBitmap之外,与DIBCONV程序相似。
DIBSECT.C /*---------------------------------------------------------------------------- DIBSECT.C -- Displays a DIB Section in the client area (c) Charles Petzold, 1998 -----------------------------------------------------------------------------*/ #include <windows.h> #include <commdlg.h> #include "resource.h" LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; TCHAR szAppName[] = TEXT ("DIBsect") ; int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { 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 Section Display"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL) ; ShowWindow (hwnd, iCmdShow) ; UpdateWindow (hwnd) ; while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg) ; DispatchMessage (&msg) ; } return msg.wParam ; } HBITMAP CreateDIBsectionFromDibFile (PTSTR szFileName) { BITMAPFILEHEADER bmfh ; BITMAPINFO * pbmi ; BYTE * pBits ; BOOL bSuccess ; DWORD dwInfoSize, dwBytesRead ; HANDLE hFile ; HBITMAP hBitmap ; // Open the file: read access, prohibit write access hFile = CreateFile (szFileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL) ; if (hFile == INVALID_HANDLE_VALUE) return NULL ; // Read in the BITMAPFILEHEADER bSuccess = ReadFile (hFile, &bmfh, sizeof (BITMAPFILEHEADER), &dwBytesRead, NULL) ; if (!bSuccess || (dwBytesRead != sizeof (BITMAPFILEHEADER)) || (bmfh.bfType != * (WORD *) "BM")) { CloseHandle (hFile) ; return NULL ; } // Allocate memory for the BITMAPINFO structure & read it in dwInfoSize = bmfh.bfOffBits - sizeof (BITMAPFILEHEADER) ; pbmi = malloc (dwInfoSize) ; bSuccess = ReadFile (hFile, pbmi, dwInfoSize, &dwBytesRead, NULL) ; if (!bSuccess || (dwBytesRead != dwInfoSize)) { free (pbmi) ; CloseHandle (hFile) ; return NULL ; } // Create the DIB Section hBitmap = CreateDIBSection (NULL, pbmi, DIB_RGB_COLORS, &pBits, NULL, 0) ; if (hBitmap == NULL) { free (pbmi) ; CloseHandle (hFile) ; return NULL ; } // Read in the bitmap bits ReadFile (hFile, pBits, bmfh.bfSize - bmfh.bfOffBits, &dwBytesRead, NULL) ; free (pbmi) ; CloseHandle (hFile) ; return hBitmap ; } LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam,LPARAM lParam) { static HBITMAP hBitmap ; static int cxClient, cyClient ; 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") ; BITMAP bitmap ; HDC hdc, hdcMem ; PAINTSTRUCT ps ; switch (message) { case WM_CREATE: 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: cxClient = LOWORD (lParam) ; cyClient = HIWORD (lParam) ; return 0 ; case WM_COMMAND: switch (LOWORD (wParam)) { case IDM_FILE_OPEN: // Show the File Open dialog box if (!GetOpenFileName (&ofn)) return 0 ; // If there's an existing bitmap, delete it if (hBitmap) { DeleteObject (hBitmap) ; hBitmap = NULL ; } // Create the DIB Section from the DIB file SetCursor (LoadCursor (NULL, IDC_WAIT)) ; ShowCursor (TRUE) ; hBitmap = CreateDIBsectionFromDibFile (szFileName) ; ShowCursor (FALSE) ; SetCursor (LoadCursor (NULL, IDC_ARROW)) ; // Invalidate the client area for later update InvalidateRect (hwnd, NULL, TRUE) ; if (hBitmap == NULL) { MessageBox ( hwnd, TEXT ("Cannot load DIB file"), szAppName, MB_OK | MB_ICONEXCLAMATION) ; } return 0 ; } break ; case WM_PAINT: hdc = BeginPaint (hwnd, &ps) ; if (hBitmap) { GetObject (hBitmap, sizeof (BITMAP), &bitmap) ; hdcMem = CreateCompatibleDC (hdc) ; SelectObject (hdcMem, hBitmap) ; BitBlt ( hdc, 0, 0, bitmap.bmWidth, bitmap.bmHeight, hdcMem, 0, 0, SRCCOPY) ; DeleteDC (hdcMem) ; } EndPaint (hwnd, &ps) ; return 0 ; case WM_DESTROY: if (hBitmap) DeleteObject (hBitmap) ; PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ; }
DIBSECT.RC(摘录) //Microsoft Developer Studio generated resource script. #include "resource.h" #include "afxres.h" ///////////////////////////////////////////////////////////////////////////// // Menu DIBSECT MENU DISCARDABLE BEGIN POPUP "&File" BEGIN MENUITEM "&Open", IDM_FILE_OPEN END END
RESOURCE.H(摘录) // Microsoft Developer Studio generated include file. // Used by DIBsect.rc #define IDM_FILE_OPEN 40001
注意DIBCONV中的CreateBitmapObjectFromDibFile函数和DIBSECT中的CreateDIbsectionFromDibFile函数之间的区别。DIBCONV读入整个文件,然后把指向DIB内存块的指针传递给CreateDIBitmap函数。DIBSECT首先读取BITMAPFILEHEADER结构中的信息,然后确定BITMAPINFO结构的大小,为此配置内存,并在第二个ReadFile呼叫中将它读入内存。然后,函数把指向BITMAPINFO结构和指针变量pBits的指针传递给CreateDIBSection。函数传回位图句柄并设定pBits指向函数将要读取DIB图素位的内存块。
pBits指向的内存块归系统所有。当通过呼叫DeleteObject删除位图时,内存会被自动释放。然而,程序能利用该指针直接改变DIB位。当应用程序透过API传递海量存储器块时,只要系统拥有这些内存块,在WINDOWS NT下就不会影响速度。
我之前曾说过,当在视讯显示器上显示DIB时,某些时候必须进行从设备无关图素到设备相关图素的转换,有时这些格式转换可能相当费时。来看一看三种用于显示DIB的方法:
-
当使用SetDIBitsToDevice或StretchDIBits来把DIB直接显示在屏幕上,格式转换在SetDIBitsToDevice或StretchDIBits呼叫期间发生。
-
当使用CreateDIBitmap和(可能是)SetDIBits把DIB转换为DDB,然后使用BitBlt或StretchBlt来显示它时,如果设定了CBM_INIT旗标,格式转换在CreateDIBitmap或SetDIBits期间发生。
-
当使用CreateDIBSection建立DIB区块,然后使用BitBlt或StretchBlt显示它时,格式转换在BitBlt对StretchBlt的呼叫期间发生。
再读一下上面这些叙述,确定您不会误解它的意思。这是从CreateDIBSection传回的位图句柄不同于我们所遇到的其它位图句柄的一个地方。此位图句柄实际上指向储存在内存中由系统维护但应用程序能存取的DIB。在需要的时候,DIB会转化为特定的色彩格式,通常是在用BitBlt或StretchBlt显示位图时。
您也可以将位图句柄选入内存设备内容并使用GDI函数来绘制。在 pBits 变量指向的DIB图素内将反映出结果。因为Windows NT下的GDI函数分批呼叫,在内存设备背景上绘制之后和「人为」的存取位之前会呼叫GdiFlush。
在DIBSECT,我们清除pBits变量,因为程序不再需要这个变量了。您会使用CreateDIBSection的主要原因在于您有需要直接更改位值。在CreateDIBSection呼叫之后似乎就没有别的方法来取得位指针了。
DIB区块的其它区别
从CreateDIBitmap传回的位图句柄与函数的hdc参数引用的设备有相同的平面和图素字节织。您能通过具有BITMAP结构的GetObject呼叫来检验这一点。
CreateDIBSection就不同了。如果以该函数传回的位图句柄的BITMAP结构呼叫GetObject,您会发现位图具有的色彩组织与BITMAPINFOHEADER结构的字段指出的色彩组织相同。您能将这个句柄选入与视讯显示器兼容的内存设备内容。这与 上一章关于DDB的内容相矛盾,但这也就是我说此DIB区块位图句柄不同的原因。
另一个奇妙之处是:你可能还记得,DIB中图素数据行的位组长度始终是4的倍数。GDI位图对象中行的位组长度,就是使用GetObject从BITMAP结构的bmWidthBytes字段中得到的长度,始终是2的倍数。如果用每图素24位和宽度2图素设定BITMAPINFOHEADER结构并随后呼叫GetObject,您就会发现bmWidthBytes字段是8而不是6。
使用从CreateDIBSection传回的位图句柄,也可以使用DIBSECTION结构呼叫GetObject:
GetObject (hBitmap, sizeof (DIBSECTION), &dibsection) ;
此函数不能处理其它位图建立函数传回的位图句柄。DIBSECTION结构定义如下:
typedef struct tagDIBSECTION // ds { BITMAP dsBm ; // BITMAP structure BITMAPINFOHEADER dsBmih ; // DIB information header DWORD dsBitfields [3] ; // color masks HANDLE dshSection ; // file-mapping object handle DWORD dsOffset ; // offset to bitmap bits } DIBSECTION, * PDIBSECTION ;
此结构包含BITMAP结构和BITMAPINFOHEADER结构。最后两个字段是传递给CreateDIBSection的最后两个参数,等一下将会讨论它们。
DIBSECTION结构中包含除了色彩对照表以外有关位图的许多内容。当把DIB区块位图句柄选入内存设备内容时,可以通过呼叫GetDIBColorTable来得到色彩对照表:
hdcMem = CreateCompatibleDC (NULL) ; SelectObject (hdcMem, hBitmap) ; GetDIBColorTable (hdcMem, uFirstIndex, uNumEntries, &rgb) ; DeleteDC (hdcMem) ;
同样,您可以通过呼叫SetDIBColorTable来设定色彩对照表中的项目。
文件映像选项
我们还没有讨论CreateDIBSection的最后两个参数,它们是文件映像对象的句柄和文件中位图位开始的偏移量。文件映像对象使您能够像文件位于内存中一样处理文件。也就是说,可以通过使用内存指针来存取文件,但文件不需要整个加载内存中。
在大型DIB的情况下,此技术对于减少内存需求是很有帮助的。DIB图素位能够储存在磁盘上,但仍然可以当作位于内存中一样进行存取,虽然会影响程序执行效能。问题是,当图素位实际上储存在磁盘上时,它们不可能是实际DIB文件的一部分。它们必须位于其它的文件内。
为了展示这个程序,下面显示的函数除了不把图素位读入内存以外,与DIBSECT中建立DIB区块的函数很相似。然而,它提供了文件映像对象和传递给CreateDIBSection函数的偏移量:
HBITMAP CreateDIBsectionMappingFromFile (PTSTR szFileName) { BITMAPFILEHEADER bmfh ; BITMAPINFO * pbmi ; BYTE * pBits ; BOOL bSuccess ; DWORD dwInfoSize, dwBytesRead ; HANDLE hFile, hFileMap ; HBITMAP hBitmap ; hFile = CreateFile (szFileName, GENERIC_READ | GENERIC_WRITE, 0, // No sharing! NULL, OPEN_EXISTING, 0, NULL) ; if (hFile == INVALID_HANDLE_VALUE) return NULL ; bSuccess = ReadFile ( hFile, &bmfh, sizeof (BITMAPFILEHEADER), &dwBytesRead, NULL) ; if (!bSuccess || (dwBytesRead != sizeof (BITMAPFILEHEADER)) || (bmfh.bfType != * (WORD *) "BM")) { CloseHandle (hFile) ; return NULL ; } dwInfoSize = bmfh.bfOffBits - sizeof (BITMAPFILEHEADER) ; pbmi = malloc (dwInfoSize) ; bSuccess = ReadFile (hFile, pbmi, dwInfoSize, &dwBytesRead, NULL) ; if (!bSuccess || (dwBytesRead != dwInfoSize)) { free (pbmi) ; CloseHandle (hFile) ; return NULL ; } hFileMap = CreateFileMapping (hFile, NULL, PAGE_READWRITE, 0, 0, NULL) ; hBitmap = CreateDIBSection ( NULL, pbmi, DIB_RGB_COLORS, &pBits,hFileMap, bmfh.bfOffBits) ; free (pbmi) ; return hBitmap ; }
啊哈!这个程序不会动。CreateDIBSection的文件指出「dwOffset [函数的最后一个参数]必须是DWORD大小的倍数」。尽管信息表头的大小始终是4的倍数并且色彩对照表的大小也始终是4的倍数,但位图文件表头却不是,它是14字节。因此bmfh.bfOffBits永远不会是4的倍数。
总结
如果您有小型的DIB并且需要频繁地操作图素位,您可以使用SetDIBitsToDevice和StretchDIBits来显示它们。然而,对于大型的DIB,此技术会遇到显示效能的问题,尤其在8位视讯显示器上和Windows NT环境下。
您可以使用CreateDIBitmap和SetDIBits把DIB转化为DDB。现在,显示位图可以使用快速的BitBlt和StretchBlt函数来进行了。然而,您不能直接存取这些与设备无关的图素位。
CreateDIBSection是一个很好的折衷方案。在Windows NT下通过BitBlt和StretchBlt使用位图句柄比使用SetDIBitsToDevice和StretchDIBits(但没有DDB的缺陷)会得到更好的效能。您仍然可以存取DIB图素位。
下一章,在讨论「Windows调色盘管理器」之后会进入位图的探索。