📄 14. 位图和bitblt.txt
字号:
图案(P):1 1 0 0
目的(D):1 0 1 0
布尔操作
ROP代码
名称
结果:
0 0 0 0
0
0x000042
BLACKNESS
0 0 0 1
~(P | D)
0x0500A9
0 0 1 0
~P & D
0x0A0329
0 0 1 1
~P
0x0F0001
0 1 0 0
P & ~D
0x500325
0 1 0 1
~D
0x550009
DSTINVERT
0 1 1 0
P ^ D
0x5A0049
PATINVERT
0 1 1 1
~(P & D)
0x5F00E9
1 0 0 0
P & D
0xA000C9
1 0 0 1
~(P ^ D)
0xA50065
1 0 1 0
D
0xAA0029
1 0 1 1
~P | D
0xAF0229
1 1 0 0
P
0xF00021
PATCOPY
1 1 0 1
P | ~D
0xF50225
1 1 1 0
P | D
0xFA0089
1 1 1 1
1
0xFF0062
WHITENESS
下面列出了PatBlt一些更常见用途。如果想画一个黑色矩形,您可呼叫
PatBlt (hdc, x, y, cx, cy, BLACKNESS) ;
要画一个白色矩形,请用
PatBlt (hdc, x, y, cx, cy, WHITENESS) ;
函数
PatBlt (hdc, x, y, cx, cy, DSTINVERT) ;
用于改变矩形的颜色。如果目前设备内容中选择了WHITE_BRUSH,那么函数
PatBlt (hdc, x, y, cx, cy, PATINVERT) ;
也改变矩形。
您可以再次呼叫FillRect函数来用画笔充满一个矩形区域:
FillRect (hdc, &rect, hBrush) ;
FillRect函数相同于下列代码:
hBrush = SelectObject (hdc, hBrush) ;
PatBlt (hdc, rect.left, rect.top,
rect.right - rect.left,
rect.bottom - rect.top, PATCOPY) ;
SelectObject (hdc, hBrush) ;
实际上,此程序代码是Windows用于执行FillRect函数的动作。如果您呼叫
InvertRect (hdc, &rect) ;
Windows将其转换成函数:
PatBlt (hdc, rect.left, rect.top,
rect.right - rect.left,
rect.bottom - rect.top, DSTINVERT) ;
在介绍PatBlt函数的语法时,我说过点(x,y)指出了矩形的左上角,而且此矩形宽度为cx单位,高度为cy单位。此叙述并不完全正确。BitBlt、PatBlt和StretchBlt是最合适的GDI画图函数,它们根据从一个角测得的逻辑宽度和高度来指定逻辑直角坐标。矩形边框用到的其它所有GDI画图函数都要求根据左上角和右下角的坐标来指定坐标。对于MM_TEXT映像模式,上面讲述的PatBlt参数就是正确的。然而对于公制的映像模式来说,就不正确。如果您使用一的cx和cy值,那么点(x,y)将是矩形的左下角。如果希望点(x,y)是矩形的左上角,那么cy参数必须设为矩形的负高度。
如果想更精确,用PatBlt修改颜色的矩形将通过cx的绝对值获得逻辑宽度,通过cy的绝对值获得逻辑高度。这两个参数可以是负值。由逻辑点(x, y)和(x + cx, y + cy)给定的两个角定义了矩形。矩形的左上角通常属于PatBlt修改的区域。右上角则超出了矩形的范围。根据映像模式和cx、cy参数的符号,矩形左上角的点应为(x, y)、(x, y + cy)、(x + cx, y)或者(x + cx, y + cy)。
如果给MM_LOENGLISH设定了映像模式,并且您想在显示区域左上角的一小块正方形上使用PatBlt,您可以使用
PatBlt (hdc, 0, 0, 100, -100, dwROP) ;
或
PatBlt (hdc, 0, -100, 100, 100, dwROP) ;
或
PatBlt (hdc, 100, 0, -100, -100, dwROP) ;
或
PatBlt (hdc, 100, -100, -100, 100, dwROP) ;
给PatBlt设定正确参数最容易的方法是将x和y设为矩形左上角。如果映像模式定义y坐标随着向上卷动显示而增加,那么请使用负的cy参数。如果映像模式定义x坐标向左增加(很少有人用),则需要使用负的cx参数。
GDI 位图对象
我在本章前面已提到过Windows从1.0开始就支持GDI位图对象。因为在Windows 3.0发表了设备无关位图,GDI位图对象有时也称为设备相关位图,或者DDB。我尽量不全部引用device-dependent bitmap的全文,因为它看上去与device-independent bitmap类似。缩写DDB会好一些,因为我们很容易把它与DIB区别开来。
对程序写作者来说,现存的两种不同型态的位图从Windows 3.0开始就更为混乱。许多有经验的Windows程序写作者都不能准确地理解DIB和DDB之间的关系。(恐怕本书的Windows 3.0版本不能澄清这个问题)。诚然,DIB和DDB在许多方面是相关的:DIB与DDB能相互转换(尽管转换程序中会丢失一些信息)。然而DIB和DDB是不可以相互替换的,并且不能简单地选择一种方法来表示同一个可视数据。
如果我们能假设说DIB一定会替代DDB,那以后就会很方便了。但现实并不是如此,DDB还在Windows中扮演着很重要角色,尤其是您在乎程序执行表现好坏时。
建立DDB
DDB是Windows图形设备接口的图形对象之一(其中还包括绘图笔、画刷、字体、metafile和调色盘)。这些图形对象储存在GDI模块内部,由应用程序软件以句柄数字的方式引用。您可以将DDB句柄储存在一个HBITMAP(「handle to a bitmap:位图句柄」)型态的变量中,例如:
HBITMAP hBitmap ;
然后通过呼叫DDB建立的一个函数来获得句柄,例如:CreateBitmap。这些函数配置并初始化GDI内存中的一些内存来储存关于位图的信息,以及实际位图位的信息。应用程序不能直接存取这段内存。位图与设备内容无关。当程序使用完位图以后,就要清除这段内存:
DeleteObject (hBitmap) ;
如果程序执行时您使用了DDB,那么程序终止时,您可以完成上面的操作。
CreateBitmap函数用法如下:
hBitmap = CreateBitmap (cx, cy, cPlanes, cBitsPixel, bits) ;
前两个参数是位图的宽度和高度(以图素为单位),第三个参数是颜色面的数目,第四个参数是每图素的位数,第五个参数是指向一个以特定颜色格式存放的位数组的指针,数组内存放有用来初始化该DDB的图像。如果您不想用一张现有的图像来初始化DDB,可以将最后一个参数设为NULL。以后您还是可以设定该DDB内图素的内容。
使用此函数时,Windows也允许建立您喜欢的特定型态GDI位图对象。例如,假设您希望位图宽7个图素、高9个图素、5个?色位面,并且每个图素占3位,您只需要执行下面的操作:
hBitmap = CreateBitmap (7, 9, 5, 3, NULL) ;
这时Windows会好好给您一个有效的位图句柄。
在此函数呼叫期间,Windows将储存您传递给函数的信息,并为图素位配置内存。粗略的计算是此位图需要7×9×5×3,即945位,这需要比118个字节还多几个位。
然而,Windows为位图配置好内存以后,每行图素都占用许多连贯的字节,这样
iWidthBytes = 2 * ((cx * cBitsPixel + 15) / 16) ;
或者C程序写作者更倾向于写成:
iWidthBytes = (cx * cBitsPixel + 15) & ~15) >> 3 ;
因此,为DDB配置的内存就是:
iBitmapBytes = cy * cPlanes * iWidthBytes ;
本例中,iWidthBytes占4字节,iBitmapBytes占180字节。
现在,知道一张位图有5个颜色位面,每图素占3个颜色位有什么意义吗?真是见鬼了,这甚至不能把它称作一个习题作业。虽然您让GDI内部配置了些内存,并且让这些内存有一定结构的内容,但是您这张位图完全作不出任何有用的事情来。
实际上,您将用两种型态的参数来呼叫CreateBitmap。
cPlanes和cBitsPixel都等于1(表示单色位图);或者
cPlanes和cBitsPixel都等于某个特定设备内容的值,您可以使用PLANES和BITSPIXEL索引来从GetDeviceCaps函数获得。
更现实的情况下,您只会在第一种情况下呼叫CreateBitmap。对于第二种情况,您可以用CreateCompatibleBitmap来简化问题:
hBitmap = CreateCompatibleBitmap (hdc, cx, cy) ;
此函数建立了一个与设备兼容的位图,此设备的设备内容句柄由第一个参数给出。CreateCompatibleBitmap用设备内容句柄来获得GetDeviceCaps信息,然后将此信息传递给CreateBitmap。除了与实际的设备内容有相同的内存组织之外,DDB与设备内容没有其它联系。
CreateDiscardableBitmap函数与CreateCompatibleBitmap的参数相同,并且功能上相同。在早期的Windows版本中,CreateDiscardableBitmap建立的位图可以在内存减少时由Windows将其从内存中清除,然后程序再重建位图数据。
第三个位图建立函数是CreateBitmapIndirect:
hBitmap CreateBitmapIndirect (&bitmap) ;
其中bitmap是BITMAP型态的结构。BITMAP结构定义如下:
typedef struct _tagBITMAP
{
LONG bmType ; // set to 0
LONG bmWidth ; // width in pixels
LONG bmHeight ; // height in pixels
LONG bmWidthBytes ; // width of row in bytes
WORD bmPlanes ; // number of color planes
WORD bmBitsPixel ; // number of bits per pixel
LPVOIDbmBits ; // pointer to pixel bits
}
BITMAP, * PBITMAP ;
在呼叫CreateBitmapIndirect函数时,您不需要设定bmWidthBytes字段。Windows将为您计算,您也可以将bmBits字段设定为NULL,或者设定为初始化位图时用的图素位地址。
GetObject函数内也使用BITMAP结构,首先定义一个BITMAP型态的结构。
BITMAP bitmap ;
并呼叫函数如下:
GetObject (hBitmap, sizeof (BITMAP), &bitmap) ;
Windows将用位图信息填充BITMAP结构的字段,不过,bmBits字段等于NULL。
您最后应呼叫DeleteObject来清除程序内建立的所有位图。
位图位
用CreateBitmap或CreateBitmapIndirect来建立设备相关GDI位图对象时,您可以给位图图素位指定一个指针。或者您也可以让位图维持未初始化的状态。在建立位图以后,Windows还提供两个函数来获得并设定图素位。
要设定图素位,请呼叫:
SetBitmapBits (hBitmap, cBytes, &bits) ;
GetBitmapBits函数有相同的语法:
GetBitmapBits (hBitmap, cBytes, &bits) ;
在这两个函数中,cBytes指明要复制的字节数,bits是最少cBytes大小的缓冲区。
DDB中的图素位从顶列开始排列。我在前面说过,每列的字节数都是偶数。除此之外,没什么好说明的了。如果位图是单色的,也就是说它有1个位面并且每个图素占1位,则每个图素不是1就是0。每列最左边的图素是本列第一个字节最高位的位。我们在本章的后面讲完如何显示单色DDB之后,将做一个单色的DDB。
对于非单色位图,应避免出现您需要知道图素位含义的状况。例如,假定在8位颜色的VGA上执行Windows,您可以呼叫CreateCompatibleBitmap。通过GetDeviceCaps,您能够确定您正处理一个有1个颜色位面和每图素8位的设备。一个字节储存一个图素。但是图素值0x37是什么意思呢?很明显是某种颜色,但到底是什么颜色呢?
图素实际上并不涉及任何固定的颜色,它只是一个值。DDB没有颜色表。问题的关键在于:当DDB显示在屏幕上时,图素的颜色是什么。它肯定是某种颜色,但具体是什么颜色呢?显示的图素将与在显示卡上的调色盘查看表里的0x37索引值代表的RGB颜色有关。这就是您现在碰到的设备依赖性。
不过,不要只因为我们不知道图素值的含义,就假定非单色DDB没用。我们将简要看一下它们的用途。 下一章,我们将看到SetBitmapBits和GetBitmapBits函数是如何被更有用的SetDIBits和GetDIBits函数所取代的。
因此,基本的规则是这样的:不要用CreateBitmap、CreateBitmapIndirect或SetBitmapBits来设定彩色DDB的位,您只能安全地使用这些函数来设定单色DDB的位。(如果您在呼叫GetBitmapBits期间,从其它相同格式的DDB中获得位,那么这些规则例外。)
在继续之前,让我再讨论一下SetBitmapDimensionEx和GetBitmapDimensionEx函数。这些函数让您设定(和获得)位图的测量尺寸(以0.1毫米为单位)。这些信息与位图分辨率一起储存在GDI中,但不用于任何操作。它只是您与DDB联系的一个测量尺寸标识。
内存设备内容
我们必须解决的下一个概念是内存设备内容。您需要用内存设备内容来处理GDI位图对象。
通常,设备内容指的是特殊的图形输出设备(例如视讯显示器或者打印机)及其设备驱动程序。内存设备内容只位于内存中,它不是真正的图形输出设备,但可以说与指定的真正设备「兼容」。
要建立一个内存设备内容,您必须首先有实际设备的设备内容句柄。如果是hdc,那么您可以像下面那样建立内存设备内容:
hdcMem = CreateCompatibleDC (hdc) ;
通常,函数的呼叫比这更简单。如果您将参数设为NULL,那么Windows将建立一个与视讯显示器相兼容的内存设备内容。应用程序建立的任何内存设备内容最终都通过呼叫DeleteDC来清除。
内存设备内容有一个与实际位映像设备相同的显示平面。不过,最初此显示平面非常小-单色、1图素宽、1图素高。显示平面就是单独1位。
当然,用1位的显示平面,您不能做更多的工作,因此下一步就是扩大显示平面。您可以通过将一个GDI位图对象选进内存设备内容来完成这项工作,例如:
SelectObject (hdcMem, hBitmap) ;
此函数与您将画笔、画刷、字体、区域和调色盘选进设备内容的函数相同。然而,内存设备内容是您可以选进位图的唯一一种设备内容型态。(如果需要,您也可以将其它GDI对象选进内存设备内容。)
只有选进内存设备内容的位图是单色的,或者与内存设备内容兼容设备有相同的色彩组织时,SelectObject才会起作用。这也是建立特殊的DDB(例如有5个位面,且每图素3位)没有用的原因。
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -