📄 15. 与设备无关的位图.txt
字号:
与设备无关的位图
智能中国——游戏组 整理编译
--------------------------------------------------------------------------------
在上一章我们了解到Windows GDI位图对象(也称为与设备相关的位图,或DDB)有许多程序设计用途。但是我并没有展示把这些位图储存到磁盘文件或把它们加载内存的方法。这是以前在Windows中使用的方法,现在根本不用了。因为位图的位格式相当依赖于设备,所以DDB不适用于图像交换。DDB内没有色彩对照表来指定位图的位与色彩之间的联系。DDB只有在Windows开机到关机的生命期内被建立和清除时才有意义。
在Windows 3.0中发表了与设备无关的位图(DIB),提供了适用于交换的图像文件格式。正如您所知的,像.GIF或.JPEG之类的其它图像文件格式在Internet上比DIB文件更常见。这主要是因为.GIF和.JPEG格式进行了压缩,明显地减少了下载的时间。尽管有一个用于DIB的压缩方案,但极少使用。DIB内的位图几乎都没有被压缩。如果您想在程序中操作位图,这实际上是一个优点。DIB不像.GIF和.JPEG文件,Windows API直接支持DIB。如果在内存中有DIB,您就可以提供指向该DIB的指标作为某些函数的参数,来显示DIB或把DIB转化为DDB。
DIB 文件格式
有意思的是,DIB格式并不是源自于Windows。它首先定义在OS/2的1.1版中,该操作系统最初由IBM和Microsoft在八十年代中期开始开发。OS/2 1.1在1988年发布,并且是第一个包含了类似Windows的图形使用者接口的OS/2版本,该图形使用者接口被称之为「Presentation Manager(PM)」。「Presentation Manager」包含了定义位图格式的「图形程序接口」(GPI)。
然后在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
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -