见招拆招《Windows程序设计》(十二)
相关的例子:下载>>> 作者:Zoologist 于2008-12-15上传 

位图和Bitblt


位图是一个二维的位数组,它与图像的像素一一对应。当现实世界的图像被扫描成位图以后,图像被分割成网格,并以像素作为取样单位。在位图中的每个像素值指明了一个单位网格内图像的平均颜色。单色位图每个像素只需要一位,灰色或彩色位图中每个像素需要多个位。

位图代表了Windows程序内储存图像信息的两种方法之一。储存图像信息的另一种形式是metafile,我将在后面讨论。Metafile储存的就是对图像如何生成的描述,而不是将图像以数字化的图标代表。

以后我将更详细地讨论,Microsoft Windows 3.0定义了一种称为设备无关位图(DIB:device-independent bitmap)。我将在下一期讨论DIB。本章主要讨论GDI位图对象,这是一种在Windows中比DIB更早支持的位图形数据。如同本章大量的范例程序所说明的,这种比DIB位图更早被Windows支持的图形格式仍然有其利用价值。

位图入门

位图和metafile在计算机图形处理世界中都占有一席之地。位图经常用来表示来自真实世界的复杂图像,例如数字化的照片或者视频图像。Metafile更适合于描述由人或者机器产生的图像,比如建筑蓝图。位图和metafile都能存于内存或作为文件存于磁盘上,并且都能通过剪贴簿在Windows应用程序之间传输。

位图和metafile的区别在于位映像图像和向量图像之间的差别。位映像图像用离散的像素来处理输出设备;而向量图像用笛卡尔坐标系统来处理输出设备,其线条和填充对象能被个别拖移。现在大多数的图像输出设备是位映像设备,这包括视频显示、点阵打印机、激光打印机和喷墨打印机。而笔式绘图机则是向量输出设备。

位图有两个主要的缺点。第一个问题是容易受设备依赖性的影响。最明显的就是对颜色的依赖性,在单色设备上显示彩色位图的效果总是不能令人满意的。另一个问题是位图经常暗示了特定的显示分辨率和图像纵横比。尽管位图能被拉伸和缩小,但是这样的处理通常包括复制或删除像素的某些行和列,这样会破坏图像的大小。而metafile在放大缩小后仍然能保持图形样貌不受破坏。

位图的第二个缺点是需要很大的储存空间。例如,描述完整的640×480像素,16色的视频图形数组(VGA:Video Graphics Array)屏幕的一幅位图需要大于150 KB的空间;一幅1024×768,并且每个像素为24位颜色的图像则需要大于2 MB的空间。Metafile需要通常比位图来得少的空间。位图的储存空间由图像的大小及其包含的颜色决定,而metafile的储存空间则由图像的复杂程度和它所包含的GDI指令数决定。

然而,位图优于metafile之处在于速度。将位图复制给视频显示器通常比复制基本图形文件的速度要快。最近几年,压缩技术允许压缩位图的文件大小,以使它能有效地通过电话线传输并广泛地用于Internet的网页上。

位图的来源

位图可以手工建立,例如,使用Windows 98附带的「画图」程序。一些人宁愿使用位映像绘图软件也不使用向量绘图软件。他们假定:图形最后一定会复杂到不能用线条跟填充区域来表达。

位图图像也能由计算机程序计算生成。尽管大多数计算生成的图像能按向量图形metafile储存,但是高清晰度的画面或碎形图样通常还是需要位图。

现在,位图通常用于描述真实世界的图像,并且有许多硬设备能让您把现实世界的图像输入到计算机。这类硬件通常使用 电荷耦合设备(CCD:charge-coupled device),这种设备接触到光就释放电荷。有时这些CCD单元能排列成一组,一个像素对应一个CCD;为节约开支,只用一行CCD扫描图像。

在这些计算机CCD设备中,扫描仪是最古老的。它用一行CCD沿着纸上图像(例如照片)的表面扫描。CCD根据光的强度产生电荷。模拟数字转换器(ADC:Analog-to-digital converters)把电荷转换为数字讯号,然后排列成位图。

便携式摄像机也利用CCD单元组来捕捉影像。通常,这些影像是记录到录像带上。不过,这些视频输出也能直接进入 影像捕捉器(frame grabber),该设备能把模拟视频信号转换为一组像素值。这些影像捕捉器与任何兼容的视频信号来源都能同时使用,例如VCR、光盘、DVD播放机或有线电视译码器。

位图尺寸

位图呈矩形,并有空间尺寸,图像的高度和宽度都以像素为单位。例如,此网格可描述一个很小的位图:宽度为9像素,高度为6像素,或者更简单地计为9×6:


 

习惯上,位图的速记尺寸是先给出宽度。位图总数为9×6或者54像素。我将经常使用符号cx和cy来表示位图的宽度和高度。c表示计数,因此cx和cy是沿着x轴(水平)和y轴(垂直)的像素数。

我们能根据x和y坐标来描述位图上具体的像素。一般(并不都是这样),在网格内计算像素时,位图开始于图像的左上角。这样,在此位图右下角的像素坐标就是(8, 5)。因为从0开始计数,所以此值比图像的宽度和高度小1。

位图的空间尺寸通常也指定了分辨率,但这是一个有争议的词。我们说我们的视频显示有640×480的分辨率,但是激光打印机的分辨率只有每英寸300点。我喜欢用后一种情况中分辨率的意思作为每单位像素的数量。位图在这种意义上的分辨率指的是位图在特定测量单位中的像素数。不管怎样,当我使用分辨率这个词语时,其定义的内容应该是明确的。

位图是矩形的,但是计算机内存空间是线性的。通常(但并不都是这样)位图按列储存在内存中,且从顶列像素开始到底列结束。(DIB是此规则的一个主要例外)。每一列,像素都从最左边的像素开始依次向右储存。这就好像储存几列文字中的各个字符。

颜色和位图

除空间尺寸以外,位图还有颜色尺寸。这里指的是每个像素所需要的位数,有时也称为位图的 颜色深度(color depth)、位数(bit-count)或 位/像素(bpp:bits per pixel)数。位图中的每个像素都有相同数量的颜色位。

每像素1位的位图称为二阶(bilevel)、 二色(bicolor)或者单色 (monochrome)位图。每像素可以是0或1,0表示黑色,1可以表示白色,但并不总是这样。对于其它颜色,一个像素就需要有多个位。可能的颜色值等于2位数值。用2位可以得到4种颜色,用4位可以得16种颜色,8位可得到256种颜色,16位可得到65,536种颜色,而24位可得到16,777,216种颜色。

如何将颜色位的组合与人们所熟悉的颜色相对应是目前处理位图时经常碰到(而且常常是灾难)的问题。

实际的设备

位图可按其颜色位数来分类;在Windows的发展过程中,不同的位图颜色格式取决于常用视频显示卡的功能。实际上,我们可把视频显示内存看作是一幅巨大的位图-我们从显示器上就可以看见。

Windows 1.0多数采用的显示卡是IBM的彩色图像适配器(CGA:Color Graphics Adapter)和单色图形卡(HGC:Hercules Graphics Card)。HGC是单色设备,而CGA也只能在Windows以单色图形模式使用。单色位图现在还很常用(例如,鼠标的光标一般为单色),而且单色位图除显示图像以外还有其它用途。

随着增强型图形显示卡(EGA:Enhanced Graphics Adapter)的出现,Windows使用者开始接触16色的图形。每个像素需要4个颜色位。(实际上,EGA比这里所讲的更复杂,它还包括一个64种颜色的调色盘,应用程序可以从中选择任意的16种颜色,但Windows只按较简单的方法使用EGA)。在EGA中使用的16种颜色是黑、白、两种灰色、高低亮度的红色、绿和蓝(三原色)、青色(蓝和绿组合的颜色)。现在认为这16种颜色是Windows的最低颜色标准。同样,其它16色位图也可以在Windows中显示。大多数的图示都是16色的位图。通常,简单的卡通图像也可以用这16种颜色制作。

在16色位图中的颜色编码有时称为IRGB(高亮红绿蓝:Intensity-Red-Green-Blue),并且实际上是源自IBM CGA文字模式下最初使用的十六种颜色。每个像素所用的4个IRGB颜色位都映像为表14-1所示的Windows十六进制RGB颜色。

表14-1

IRGB

RGB颜色

颜色名称

0000

00-00-00

0001

00-00-80

暗蓝

0010

00-80-00

暗绿

0011

00-80-80

暗青

0100

80-00-00

暗红

0101

80-00-80

暗洋红

0110

80-80-00

暗黄

0111

C0-C0-C0

亮灰

1000

80-80-80

暗灰

1001

00-00-FF

1010

00-FF-00

绿

1011

00-FF-FF

1100

FF-00-00

1101

FF-00-FF

洋红

1110

FF-FF-00

1111

FF-FF-FF

EGA的内存组成了四个「颜色面」,也就是说,定义每个像素颜色的四位在内存中是不连续的。然而,这样组织显示内存便于使所有的亮度位都排列在一起、所有的红色位都排在一起,等等。这样听起来就好像一种设备依赖特性,即Windows程序写作者不需要了解所有细节,但这时应或多或少地知道一些。不过,这些颜色面会出现在一些API呼叫中,例如GetDeviceCaps和CreateBitmap。

Windows 98和Microsoft Windows NT/2000/XP需要VGA或分辨率更高的图形卡。这是目前公认的显示卡的最低标准。

1987年,IBM最早发表视频图像数组(Video Graphics Array:VGA)以及PS/2系列的个人计算机。它提供了许多不同的显示模式,但最好的图像模式(Windows也使用其中之一)是水平显示640个像素,垂直显示480个像素,带有16种颜色。要显示256种颜色,最初的VGA必须切换到320×240的图形模式,这种像素数不适合Windows的正常工作。

一般人们已经忘记了最初VGA卡的颜色限制,因为其它硬件制造商很快就开发了「Super-VGA」(SVGA)显示卡,它包括更多的视频内存,可显示256种颜色并有多于640×480的模式。这是现在的标准,而且也是一件好事,因为对于现实世界中的图像来说,16种颜色过于简单,有些不适合。

显示256种颜色的显示卡模式采用每像素8位。不过,这些8位值都不必与实际的颜色相符。事实上,显示卡提供了「调色盘对照表(palette lookup table)」,该表允许软件指定这8位的颜色值,以便与实际颜色相符合。在Windows中,应用程序不能直接存取调色盘对照表。实际上,Windows储存了256种颜色中的20种,而应用程序可以通过「Windows调色盘管理器」来自订其余的236种颜色。关于这些内容,我将在 第十六章详细介绍。调色盘管理器允许应用程序在256色显示器上显示实际位图。Windows所储存的20种颜色如表14-2所示。

表14-2

IRGB

RGB颜色

颜色名称

00000000

00-00-00

00000001

80-00-00

暗红

00000010

00-80-00

暗绿

00000011

80-80-00

暗黄

00000100

00-00-80

暗蓝

00000101

80-00-80

暗洋红

00000110

00-80-80

暗青

00000111

C0-C0-C0

亮灰

00001000

C0-DC-C0

美元绿

00001001

A6-CA-F0

天蓝

11110110

FF-FB-F0

乳白

11110111

A0-A0-A4

中性灰

11111000

80-80-80

暗灰

11111001

FF-00-00

11111010

00-FF-00

绿

11111011

FF-FF-00

11111100

00-00-FF

11111101

FF-00-FF

洋红

11111110

00-FF-FF

11111111

FF-FF-FF

最近几年,True-Color显示卡很普遍,它们在每像素使用16位或24位。有时每像素虽然用了16位,其中有1位不用,而其它15位主要近似于红、绿和蓝。这样红、绿和蓝每种都有32色阶,组合起来就可以达到32,768种颜色。更普遍的是,6位用于绿色(人类对此颜色最敏感),这样就可得到65,536种颜色。对于非技术性的PC使用者来说,他们并不喜欢看到诸如32,768或65,536之类的数字,因此通常将这种视频显示卡称为Hi-Color显示卡,它能提供数以千计的颜色。

到了每个像素24位时,我们总共有了16,777,216种颜色(或者True Color、数百万的颜色),每个像素使用3字节。这与今后的标准很相似,因为它大致代表了人类感官的极限而且也很方便。

在呼叫GetDeviceCaps时,您能利用BITSPIXEL和PLANES常数来获得显示卡的颜色单位,这些值显示如表14-3所示

表14-3

BITSPIXEL

PLANES

颜色数

1

1

2

1

4

16

8

1

256

15或16

1

32,768或65 536

24或32

1

16 777 216

最近,您应该不会再碰到单色显示器了,但即便碰到了,您的应用程序也应该不会发生问题。

GDI支援的位图

Windows图形设备接口(GDI:Graphics Device Interface)从1.0版开始支持位图。不过,一直到Windows 3.0以前,Windows下唯一支持GDI对象的只有位图,以位图句柄来使用。这些GDI位图对象是单色的,或者与实际的图像输出设备(例如视频显示器)有相同的颜色单位。例如,与16色VGA兼容的位图有四个颜色面。问题是这些颜色位图不能储存,也不能用于颜色单位不同的图像输出设备(如每像素占8位就可以产生256种颜色的设备)上。

从Windows 3.0开始,定义了一种新的位图格式,我们称之为设备无关位图(device-independent bitmap),或者DIB。DIB包括了自己的调色盘,其中显示了与RGB颜色相对应的像素位。DIB能显示在任何位映像输出设备上。这里唯一的问题是DIB的颜色通常一定会转换成设备实际表现出来的颜色。

与DIB同时,Windows 3.0还介绍了「Windows调色盘管理器」,它让程序能够从显示的256种颜色中自订颜色。就像我们在后面几期将要看到的那样,应用程序通常在显示DIB时使用「调色盘管理器」。

Microsoft在Windows 95(和Windows NT 4.0)中扩展了DIB的定义,并且在Windows 98(和Windows NT 5.0)中再次扩展。这些扩展增加了所谓的「图像颜色管理器(ICM:Image Color Management),并允许DIB更精确地指定图像所需要的颜色。我将在后面一期中简要讨论ICM。

不论DIB多么重要,在处理位图时,早期的GDI位图对象依然扮演了重要的角色。掌握位图使用方式的最好方法是按各种用法在演进发展的时间顺序来学习,先从GDI位图对象和位块传输的概念开始。

位块传输

我前面提到过,您可以把整个视频显示器看作是一幅大位图。您在屏幕上见到的像素由储存在视频显示卡上内存中的位来描述。任何视频显示的矩形区域也都是一个位图,其大小是它所包含的行列数。

让我们从将图像从视频显示的一个区域复制到另一个区域,开始我们在位图世界的旅行吧!这个是强大的BitBlt函数的工作。

Bitblt(读作「bit blit」)代表「位块传输(bit-block transfer)」。BLT起源于一条汇编语言指令,该指令在DEC PDP-10上用来传输内存块。术语「bitblt」第一次用在图像上与Xerox Palo Alto Research Center(PARC)设计的SmallTalk系统有关。在SmallTalk中,所有的图形输出操作都使用bitblt。程序写作者有时将blt用作动词,例如:「Then I wrote some code to blt the happy face to the screen and play a wave file.」

BitBlt函数移动的是像素,或者(更明确地)是一个位映像图块。您将看到,术语「传输(transfer)」与BitBlt函数不尽相同。此函数实际上对像素执行了一次位操作,而且可以产生一些有趣的结果。

简单的BitBlt

程序14-1所示的BITBLT程序用BitBlt函数将程序系统的菜单图标(位于程序Windows的左上角)复制到它的显示区域。

程序14-1 BITBLT
        
BITBLT.ASM

;MASMPlus 代码模板 - 普通的 Windows 程序代码

.386
.Model Flat, StdCall
Option Casemap :None

Include windows.inc
Include user32.inc
Include kernel32.inc
Include gdi32.inc

includelib gdi32.lib
IncludeLib user32.lib
IncludeLib kernel32.lib
include macro.asm

WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD

.DATA
szAppName TCHAR "BitBlt",0

.DATA?
hInstance HINSTANCE ?
cxClient DWORD ?
cyClient DWORD ?
cxSource DWORD ?
cySource DWORD ?

.CODE
START:
invoke GetModuleHandle,NULL
invoke WinMain,eax,NULL,NULL,SW_SHOWDEFAULT
invoke ExitProcess,0
WinMain proc hInst:DWORD,hPrevInst:DWORD,szCmdLine:DWORD,iCmdShow:DWORD
LOCAL wndclass :WNDCLASSEX
LOCAL msg :MSG
LOCAL hWnd :HWND

mov wndclass.cbSize,sizeof WNDCLASSEX
mov wndclass.style,CS_HREDRAW or CS_VREDRAW
mov wndclass.lpfnWndProc,offset WndProc

mov wndclass.cbClsExtra,0
mov wndclass.cbWndExtra,0

push hInst
pop wndclass.hInstance

invoke LoadIcon,NULL, IDI_INFORMATION
mov wndclass.hIcon,eax

invoke LoadCursor,NULL,IDC_ARROW
mov wndclass.hCursor,eax

invoke GetStockObject,WHITE_BRUSH
mov wndclass.hbrBackground,EAX

lea eax,szAppName
mov wndclass.lpszMenuName,eax
mov wndclass.lpszClassName,eax

mov wndclass.hIconSm,0

invoke RegisterClassEx, ADDR wndclass
.if (eax==0)
invoke MessageBox,NULL,CTXT("This program requires Windows NT!"),szAppName, MB_ICONERROR
ret
.endif


invoke CreateWindowEx,NULL,
ADDR szAppName, ;window class name
CTXT("BitBlt Demo"),
WS_OVERLAPPEDWINDOW, ;window style
CW_USEDEFAULT, ;initial x position
CW_USEDEFAULT, ;initial y position
CW_USEDEFAULT, ;initial x size
CW_USEDEFAULT, ;initial y size
NULL, ;parent window handle
NULL, ;window menu handle
hInst, ;program instance handle
NULL ;creation parameters
mov hWnd,eax

invoke ShowWindow,hWnd,iCmdShow
invoke UpdateWindow,hWnd

StartLoop:
invoke GetMessage,ADDR msg,NULL,0,0
cmp eax, 0
je ExitLoop
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
jmp StartLoop
ExitLoop:

mov eax,msg.wParam
ret
WinMain endp

WndProc proc hwnd:DWORD,uMsg:DWORD,wParam :DWORD,lParam :DWORD
LOCAL hdcClient,hdcWindow:HDC
LOCAL x,y :DWORD
LOCAL ps :PAINTSTRUCT

.if uMsg == WM_CREATE
invoke GetSystemMetrics,SM_CXSIZEFRAME
mov cxSource,eax
invoke GetSystemMetrics,SM_CXSIZE
add cxSource,eax

invoke GetSystemMetrics,SM_CYSIZEFRAME
mov cySource,eax
invoke GetSystemMetrics,SM_CYCAPTION
add cySource,eax

xor eax,eax
ret
.elseif uMsg == WM_SIZE
mov eax,lParam
and eax,0FFFFh
mov cxClient,eax

mov eax,lParam
shr eax,16
mov cyClient,eax

xor eax,eax
ret

.elseif uMsg == WM_PAINT
invoke BeginPaint,hwnd,addr ps
mov hdcClient,eax
invoke GetWindowDC,hwnd
mov hdcWindow,eax

xor eax,eax
mov y,eax
loopy:
xor eax,eax
mov x,eax
loopx:
invoke BitBlt,hdcClient, x, y, cxSource, cySource,hdcWindow, 0, 0, SRCCOPY
mov eax,cxSource
add x,eax
mov eax,x
.if eax jmp loopx
.endif
mov eax,cySource
add y,eax
mov eax,y
.if eax jmp loopy
.endif
invoke ReleaseDC,hwnd, hdcWindow
invoke EndPaint,hwnd,addr ps
xor eax,eax
ret
.elseif uMsg == WM_DESTROY
invoke PostQuitMessage,NULL
xor eax,eax
ret
.endif

invoke DefWindowProc,hwnd,uMsg,wParam,lParam
ret
WndProc endp
END START

但为什么只用了一个BitBlt呢?实际上,那个BITBLT用系统菜单图标的多个副本来填满显示区域(在此情况下是信息方块中普遍使用的IDI_INFORMATION图示),如图14-1所示。


 

图14-1 BITBLT的屏幕显示

BitBlt函数从称为「来源」的设备内容中将一个矩形区的像素传输到称为「目的(destination)」的另一个设备内容中相同大小的矩形区。此函数的语法如下:

BitBlt (hdcDst, xDst, yDst, cx, cy, hdcSrc, xSrc, ySrc, dwROP) ;
        

来源和目的设备内容可以相同。

在BITBLT程序中,目的设备内容是窗口的显示区域,设备内容句柄从BeginPaint函数获得。来源设备内容是应用程序的整个窗口,此设备内容句柄从GetWindowDC获得的。很明显地,这两个设备内容指的是同一个实际设备(视频显示器)。不过,这两个设备内容的坐标原点不同。

xSrc和ySrc参数指明了来源图像左上角的坐标位置。在BITBLT中,这两个参数设为0,表示图像从来源设备内容(也就是整个窗口)的左上角开始,cx和cy参数是图像的宽度和高度。BITBLT根据从GetSytemMetrics函数获得的信息来计算这些值。

xDst和yDst参数表示了复制图像位置左上角的坐标位置。在BITBLT中,这两个参数设定为不同的值以便多次复制图像。对于第一次BitBlt呼叫,这两个参数设制为0,将图像复制到显示区域的左上角位置。

BitBlt的最后一个参数是位映像操作型态。我将简短地讨论一下这个值。

请注意,BitBlt是从实际视频显示内存传输像素,而不是从系统菜单图标的其它图像传输。如果您移动BITBLT窗口以使部分系统菜单图标移出屏幕,然后调整BITBLT窗口的尺寸使其重画,这时您将发现BITBLT显示区域中显示的是菜单图示的一部分。BitBlt函数不再存取整个图像。

在BitBlt函数中,来源和目的设备内容可以相同。您可以重新编写BITBLT以使WM_PAINT处理执行以下内容:

BitBlt (hdcClient, 0, 0, cxSource, cySource,
        
                  hdcWindow, 0, 0, SRCCOPY) ;
        
for (y = 0 ; y < cyClient ; y += cySource)
        
for (x = 0 ; x < cxClient ; x += cxSource)
        
{
        
           if (x > 0 || y > 0)
        
                          BitBlt (hdcClient, x, y, cxSource, cySource,
        
                hdcClient, 0, 0, SRCCOPY) ;
        
}
        

这将与前面显示的BITBLT一样产生相同的效果,只是显示区域左上角比较模糊。

在BitBlt内的最大限制是两个设备内容必须是兼容的。这意味着或者其中之一必须是单色的,或者两者的每个像素都相同的位数。总而言之,您不能用此方法将屏幕上的某些图形复制到打印机。

拉伸位图

在BitBlt函数中,目的图像与来源图像的尺寸是相同的,因为函数只有两个参数来说明宽度和高度。如果您想在复制时拉伸或者压缩图像尺寸,可以使用StretchBlt函数。StretchBlt函数的语法如下:

StretchBlt (hdcDst, xDst, yDst, cxDst, cyDst,
        
                                  hdcSrc, xSrc, ySrc, cxSrc, cySrc, dwROP) ;
        

此函数添加了两个参数。现在的函数就分别包含了目的和来源各自的宽度和高度。STRETCH程序展示了StretchBlt函数,如程序14-2所示。

程序14-2  STRETCH
        
STRETCH.ASM
        

此程序只有呼叫了StretchBlt函数一次,但是利用此函数以系统菜单图标填充了整个显示区域,如图14-2所示。


 

图14-2 STRETCH的屏幕显示

BitBlt和StretchBlt函数中所有的坐标与大小都是依据逻辑单位的。但是当您在BitBlt函数中定义了两个不同的设备内容,而这两个设备内容虽然参考同一个实际设备,却各自有着不同的映像模式,这时将发生什么结果呢?如果出现这种情况,呼叫BitBlt产生的结果就显得不明确了:cx和cy参数都是逻辑单位,而它们同样应用于来源设备内容和目的设备内容中的矩形区。所有的坐标和尺寸必须在实际的位传输之前转换为设备坐标。因为cx和cy值同时用于来源和目的设备内容,所以此值必须转换为设备内容自己的单位。

当来源和目的设备内容相同,或者两个设备内容都使用MM_TEXT图像模式时,设备单位下的矩形尺寸在两个设备内容中会是相同的,然后才由Windows进行像素对像素的转换。不过,如果设备单位下的矩形尺寸在两个设备内容中不同时,则Windows就把此工作转交给更通用的StretchBlt函数。

StretchBlt也允许水平或垂直翻转图像。如果cxSrc和cxDst标记(转换成设备单位以后)不同,那么StretchBlt就建立一个镜像:左右翻转。在STRETCH程序中,通过将xDst参数改为cxClient并将cxDst参数改成-cxClient,您就可以做到这一点。如果cySrc和cyDst不同,则StretchBlt会上下翻转图像。要在STRETCH程序中测试这一点,可将yDst参数改为cyClient并将cyDst参数改成-cyClient。

StretchBlt模式

使用StretchBlt会碰到一些与位图大小缩放相关的一些根本问题。在扩展一个位图时,StretchBlt必须复制像素行或列。如果放大倍数不是原图的整数倍,那么此操作会造成产生的图像有些失真。

如果目的矩形比来源矩形小,那么StretchBlt在缩小图像时就必须把两行(或列)或者多行(或列)的像素合并到一行(或列)。完成此操作有四种方法,它根据设备内容伸展模式属性来选择其中一种方法。您可使用SetStretchBltMode函数来修改这个属性。

SetStretchBltMode (hdc, iMode) ;
        

iMode可取下列值:

Windows还包括用于取得目前伸展模式的GetStretchBltMode函数。

位映像操作

BITBLT和STRETCH程序简单地将来源位图复制给了目的位图,在过程中也可能进行了缩放。这是把SRCCOPY作为BitBlt和StretchBlt函数最后一个参数的结果。SRCCOPY只是您能在这些函数中使用的256个位映像操作中的一个。让我们先在STRETCH程序中做一个别的实验,然后再系统地研究位映像操作。

尽量用NOTSRCCOPY来代替SRCCOPY。与它们名称一样,位映像操作在复制位图时转换其颜色。在显示区域窗口,所有的颜色转换:黑色变成白色、白色变成黑色,蓝色变成黄色。现在试一下SRCINVERT,您将得到同样效果。如果试一下BLACKNESS,正如其名称一样,整个显示区域都将变成黑色,而WHITENESS则使其变成白色。

现在试一试用下列三条叙述来代替StretchBlt呼叫:

SelectObject (hdcClient, CreateHatchBrush (HS_DIAGCROSS, RGB (0, 0, 0)));
        
StretchBlt (hdcClient, 0, 0, cxClient, cyClient,
        
                                  hdcWindow, 0, 0, cxSource, cySource, MERGECOPY) ;
        
DeleteObject (hdcClient, GetStockObject (WHITE_BRUSH)) ;
        

这次,您将在图像上看到一个菱形的画刷,这是什么?

我在前面说过,BitBlt和StretchBlt函数不是简单的位块传输。此函数实际在下面三种图像间执行位操作。

结果是复制到了目的矩形中。

位映像操作与我们在前面遇到的绘图模式在概念上相似。绘图模式采用图像对象的控件方式,例如一条线就组合成一个目的。我们知道有16种绘图模式-也就是说,对象中的0和1画出时,唯一结果就是目的中0和1的组合。

使用BitBlt和StretchBlt的位映像操作包含了三个对象的组合,这将产生256种位映像操作。有256种方法来组合来源位图、目的位图和图案。有15种位映像操作已经命名-其中一些名称其实还不能够清楚清楚说明其意义-它们定义在WINGDI.H里头,其余的都有数值,列在/Platform SDK/Graphics and Multimedia Services/GDI/Raster Operation Codes/Ternary Raster Operations之中。

有名称的15种ROP代码见表14-4。

表14-4

ā??嵨?嬐????湰??8含?吉@吜?吉@呞?吉@含?吉@呅?吉@呅?吉@呜?吉@吪ň吉@吮?吉@呃?吉@吤?吉@吭?吉@吁
Pattern (P): 1 1 1 1 0 0 0 0
Source (S): 1 1 0 0 1 1 0 0
Destination (D): 1 0 1 0 1 0 1 0 Boolean Operation ROP Code Name
Result: 0 0 0 0 0 0 0 0 0 0x000042 BLACKNESS
0 0 0 1 0 0 0 1 ~ (S ¦ D) 0x1100A6 NOTSRCERASE
0 0 1 1 0 0 1 1 ~S 0x330008 NOTSRCCOPY
0 1 0 0 0 1 0 0 S & ~D 0x440328 SRCERASE
0 1 0 1 0 1 0 1 ~D 0x550009 DSTINVERT
0 1 0 1 1 0 1 0 P ^ D 0x5A0049 PATINVERT
0 1 1 0 0 1 1 0 S ^ D 0x660046 SRCINVERT
1 0 0 0 1 0 0 0 S & D0x8800C6 SRCAND
1 0 1 1 1 0 1 1 ~S ¦ D 0xBB0226 MERGEPAINT
1 1 0 0 0 0 0 0 P & S 0xC000CA MERGECOPY
1 1 0 0 1 1 0 0 S 0xCC0020 SRCCOPY
1 1 1 0 1 1 1 0 S ¦ D 0xEE0086 SRCPAINT
1 1 1 1 0 0 0 0 P 0xF00021 PATCOPY
1 1 1 1 1 0 1 1 P ¦ ~S ¦ D 0xFB0A09 PATPAINT
1 1 1 1 1 1 1 1 1 0xFF0062 WHITENESS

此表格对于理解和使用位映像操作非常重要,因此我们应花点时间来研究。

在这个表格中,「ROP代码」行的值将传递给BitBlt或StretchBlt的最后一个参数;在「名称」行中的值在WINDOWS.INC定义。ROP代码的低字组协助设备驱动程序传输位映像操作。高字组是0到255之间的数值。此数值与第2列的图案的位相同,这是在图案、来源和显示在顶部的目的之间进行位操作的结果。「布尔运算」列按C语法显示图案、来源和目的的组合方式。

要开始了解此表,最简单的办法是假定您正处理一个单色系统(每像素1位)其中0代表黑色,1代表白色。BLACKNESS操作的结果是不管是来源、目的和图案是什么,全部为零,因此目的将显示黑色。类似地,WHITENESS总导致目的呈白色。

现在假定您使用位映像操作PATCOPY。这导致结果位与图案位相同,而忽略了来源和目的位图。换句话说,PATCOPY简单地将目前图案复制给了目的矩形。

PATPAINT位映像操作包含一个更复杂的操作。其结果相同于在图案、目的和反转的来源之间进行位或操作。当来源位图是黑色(0)时,其结果总是白色(1);当来源是白色(1)时,只要图案或目的为白色,则结果就是白色。换句话说,只有来源为白色而图案和目的都是黑色时,结果才是黑色。

彩色显示时每个像素都使用了多个位。BitBlt和StretchBlt函数对每个颜色位都分别提供了位操作。例如,如果目的是红色而来源为蓝色,SRCPAINT位映像操作把目的变成洋红色。注意,操作实际是按显示卡内储存的位执行的。这些位所对应的颜色取决于显示卡的调色盘的设定。Windows完成了此操作,以便位映像操作能达到您预计的结果。不过,如果您修改了调色盘,位映像操作将产生无法预料的结果。

图案Blt

除了BitBlt和StretchBlt以外,Windows还包括一个称为PatBlt (「pattern block transfer:图案块传输」)的函数。这是三个「blt」函数中最简单的。与BitBlt和StretchBlt不同,它只使用一个目的设备内容。PatBlt语法是:

PatBlt (hdc, x, y, cx, cy, dwROP) ;
        

x、y、cx和cy参数字于逻辑单位。逻辑点(x,y)指定了矩形的左上角。矩形宽为cx单位,高为cy单位。这是PatBlt修改的矩形区域。PatBlt在画刷与目的设备内容上执行的逻辑操作由dwROP参数决定,此参数是ROP代码的子集-也就是说,您可以只使用那些不包括来源目的设备内容的ROP代码。下表列出了PatBlt支持的16个位映像操作:

表14-5

Pattern (P): 1 1 0 0
Destination (D): 1 0 1 0 Boolean Operation ROP Code Name
Result: 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。

更现实的情况下,您只会在第一种情况下呼叫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位)没有用的原因。

现在情况是这样:SelectObject呼叫以后,DDB就是内存设备内容的显示平面。处理实际设备内容的每项操作,您几乎都可以用于内存设备内容。例如,如果用GDI画图函数在内存设备内容中画图,那么图像将画在位图上。这是非常有用的。还可以将内存设备内容作为来源,把视频设备内容作为目的来呼叫BitBlt。这就是在显示器上绘制位图的方法。如果把视频设备内容作为来源,把内存设备内容作为目的,那么呼叫BitBlt可将屏幕上的一些内容复制给位图。我们将看到这些都是可能的。

载入位图资源

除了各种各样的位图建立函数以外,获得GDI位图对象句柄的另一个方法就是呼叫LoadBitmap函数。使用此函数,您不必担心位图格式。在程序中,您只需简单地按资源来建立位图,这与建立图标或者鼠标光标的方法类似。LoadBitmap函数的语法与LoadIcon和LoadCursor相同:

hBitmap = LoadBitmap (hInstance, szBitmapName) ;
        

如果想加载系统位图,那么将第一个参数设为NULL。这些不同的位图是Windows视觉接口(例如关闭方块和勾选标记)的一小部分,它们的标识符以字母OBM开始。如果位图与整数标识符而不是与名称有联系,那么第二个参数就可以使用MAKEINTRESOURCE宏。由LoadBitmap加载的所有位图最终应用DeleteObject清除。

如果位图资源是单色的,那么从LoadBitmap传回的句柄将指向一个单色的位图对象。如果位图资源不是单色,那么从LoadBitmap传回的句柄将指向一个GDI位图对象,该对象与执行程序的视频显示器有相同的色彩组织。因此,位图始终与视频显示器兼容,并且总是选进与视频显示器兼容的内存设备内容中。采用LoadBitmap呼叫后,就不用担心任何色彩转换的问题了。在下一章中,我们就知道LoadBitmap的具体运作方式了。

程序14-3所示的BRICKS1程序示范了加载一小张单色位图资源的方法。此位图本身不像砖块,但当它水平和垂直重复时,就与砖墙相似了。

程序14-3  BRICKS1
        
BRICKS1.ASM


;MASMPlus 代码模板 - 普通的 Windows 程序代码

.386
.Model Flat, StdCall
Option Casemap :None

Include windows.inc
Include user32.inc
Include kernel32.inc
Include gdi32.inc

includelib gdi32.lib
IncludeLib user32.lib
IncludeLib kernel32.lib
include macro.asm

WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD

.DATA
szAppName TCHAR "BRICKS1",0

.DATA?
hInstance HINSTANCE ?
cxClient DWORD ?
cyClient DWORD ?
cxSource DWORD ?
cySource DWORD ?
hBitmap HBITMAP ?

.CODE
START:
invoke GetModuleHandle,NULL
invoke WinMain,eax,NULL,NULL,SW_SHOWDEFAULT
invoke ExitProcess,0
WinMain proc hInst:DWORD,hPrevInst:DWORD,szCmdLine:DWORD,iCmdShow:DWORD
LOCAL wndclass :WNDCLASSEX
LOCAL msg :MSG
LOCAL hWnd :HWND

mov wndclass.cbSize,sizeof WNDCLASSEX
mov wndclass.style,CS_HREDRAW or CS_VREDRAW
mov wndclass.lpfnWndProc,offset WndProc

mov wndclass.cbClsExtra,0
mov wndclass.cbWndExtra,0

push hInst
pop wndclass.hInstance

invoke LoadIcon,NULL, IDI_INFORMATION
mov wndclass.hIcon,eax

invoke LoadCursor,NULL,IDC_ARROW
mov wndclass.hCursor,eax

invoke GetStockObject,WHITE_BRUSH
mov wndclass.hbrBackground,EAX

lea eax,szAppName
mov wndclass.lpszMenuName,eax
mov wndclass.lpszClassName,eax

mov wndclass.hIconSm,0

invoke RegisterClassEx, ADDR wndclass
.if (eax==0)
invoke MessageBox,NULL,CTXT("This program requires Windows NT!"),szAppName, MB_ICONERROR
ret
.endif


invoke CreateWindowEx,NULL,
ADDR szAppName, ;window class name
CTXT("LoadBitmap Demo"),
WS_OVERLAPPEDWINDOW, ;window style
CW_USEDEFAULT, ;initial x position
CW_USEDEFAULT, ;initial y position
CW_USEDEFAULT, ;initial x size
CW_USEDEFAULT, ;initial y size
NULL, ;parent window handle
NULL, ;window menu handle
hInst, ;program instance handle
NULL ;creation parameters
mov hWnd,eax

invoke ShowWindow,hWnd,iCmdShow
invoke UpdateWindow,hWnd

StartLoop:
invoke GetMessage,ADDR msg,NULL,0,0
cmp eax, 0
je ExitLoop
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
jmp StartLoop
ExitLoop:

mov eax,msg.wParam
ret
WinMain endp

WndProc proc hwnd:DWORD,uMsg:DWORD,wParam :DWORD,lParam :DWORD
LOCAL bitmap:BITMAP
LOCAL hdc, hdcMem:HDC
LOCAL hInstanceWnd:HINSTANCE
LOCAL x,y :DWORD
LOCAL ps :PAINTSTRUCT

.if uMsg == WM_CREATE
;LPCREATESTRUCT不是结构,只是一个指向结构CREATESTRUCT的指针.以下是该结构的信息:
;The CREATESTRUCT structure defines the initialization parameters passed to the window procedure of an application.
;typedef struct tagCREATESTRUCT { // cs
; LPVOID lpCreateParams;
; HINSTANCE hInstance;
; HMENU hMenu;
; HWND hwndParent;
; int cy;
; int cx;
; int y;
; int x;
; LONG style;
; LPCTSTR lpszName;
; LPCTSTR lpszClass;
; DWORD dwExStyle;
;} CREATESTRUCT
mov esi,lParam
mov eax,[esi+4]
mov hInstanceWnd,eax
invoke LoadBitmap,hInstanceWnd, CTEXT("Bricks")
mov hBitmap,eax
invoke GetObject,hBitmap, sizeof (BITMAP),addr bitmap

mov eax,bitmap.bmWidth
mov cxSource,eax

mov eax,bitmap.bmHeight
mov cySource,eax

xor eax,eax
ret
.elseif uMsg == WM_SIZE
mov eax,lParam
and eax,0FFFFh
mov cxClient,eax

mov eax,lParam
shr eax,16
mov cyClient,eax

xor eax,eax
ret

.elseif uMsg == WM_PAINT
invoke BeginPaint,hwnd,addr ps
mov hdc,eax

invoke CreateCompatibleDC,hdc
mov hdcMem,eax
invoke SelectObject,hdcMem, hBitmap

xor eax,eax
mov y,eax
loopy:
xor eax,eax
mov x,eax
loopx:
invoke BitBlt,hdc, x, y, cxSource, cySource,hdcMem, 0, 0, SRCCOPY
mov eax,cxSource
add x,eax
mov eax,x
.if eax jmp loopx
.endif
mov eax,cySource
add y,eax
mov eax,y
.if eax jmp loopy
.endif
invoke DeleteDC,hdcMem

invoke EndPaint,hwnd,addr ps
xor eax,eax
ret
.elseif uMsg == WM_DESTROY
invoke PostQuitMessage,NULL
xor eax,eax
ret
.endif

invoke DefWindowProc,hwnd,uMsg,wParam,lParam
ret
WndProc endp
END START

         

BRICKS.BMP

 

在Visual C++ Developer Studio中建立位图时,应指明位图的高度和宽度都是8个像素,是单色,名称是「Bricks」。BRICKS1程序在WM_CREATE消息处理期间加载了位图并用GetObject来确定位图的像素尺寸(以便当位图不是8像素见方时程序仍能继续工作)。以后,BRICKS1将在WM_DESTROY消息中删除此位图。

在WM_PAINT消息处理期间,BRICKS1建立了一个与显示器兼容的内存设备内容,并且选进了位图。然后是从内存设备内容到显示区域设备内容一系列的BitBlt函数呼叫,再删除内存设备内容。图14-3显示了程序的执行结果。

顺便说一下,Developer Studio建立的BRICKS.BMP文件是一个设备无关位图。您可能想在Developer Studio内建立一个彩色的BRICKS.BMP文件(您可自己选定颜色),并且保证一切工作正常。

我们看到DIB能转换成与视频显示器兼容的GDI位图对象。我们将在下一章看到这是如何操作的。


 

图14-3 BRICKS1的屏幕显示

单色位图格式

如果您在处理小块单色图像,那么您不必把它们当成资源来建立。与彩色位图对象不同,单色位的格式相对简单一些,而且几乎能全部从您要建立的图像中分离出来。例如,假定您要建立下图所示的位图:


 

您能写下一系列的位(0代表黑色,1代表白色),这些位直接对应于网格。从左到右读这些位,您能给每8字节配置一个十六进制元的字节值。如果位图的宽度不是16的倍数,在字节的右边用零填充,以得到偶数个字节:

0 1 0 1 0 0 0 1 0 1 1 1 0 1 1 1 0 0 0 1 = 51 77 10 00
        
0 1 0 1 0 1 1 1 0 1 1 1 0 1 1 1 0 1 0 1 = 57 77 50 00
        
0 0 0 1 0 0 1 1 0 1 1 1 0 1 1 1 0 1 0 1 = 13 77 50 00
        
0 1 0 1 0 1 1 1 0 1 1 1 0 1 1 1 0 1 0 1 = 57 77 50 00
        
0 1 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 = 51 11 10 00
        

像素宽为20,扫描线高为5,字节宽为4。您可以用下面的叙述来设定此位图的BITMAP结构:

static BITMAP bitmap               = { 0, 20, 5, 4, 1, 1 } ;
        

并且可以将位储存在BYTE数组中:

static BYTE  bits []       = {    0x51, 0x77, 0x10, 0x00,
        
                                  0x57, 0x77, 0x50, 0x00,
        
                                         0x13, 0x77, 0x50, 0x00,
        
                             0x57, 0x77, 0x50, 0x00,
        
                               0x51, 0x11, 0x10, 0x00 } ;
        

用CreateBitmapIndirect来建立位图需要下面两条叙述:

bitmap.bmBits = (PSTR) bits ;
        
hBitmap = CreateBitmapIndirect (&bitmap) ;
        

另一种方法是:

hBitmap = CreateBitmapIndirect (&bitmap) ;
        
SetBitmapBits (hBitmap, sizeof bits, bits) ;
        

您也可以用一道叙述来建立位图:

hBitmap = CreateBitmap (20, 5, 1, 1, bits) ;
        

在程序14-4显示的BRICKS2程序利用此技术直接建立了砖块位图,而没有使用资源。

程序14-4 BRICKS2
        
BRICKS2.ASM
        
;MASMPlus 代码模板 - 普通的 Windows 程序代码

.386
.Model Flat, StdCall
Option Casemap :None

Include windows.inc
Include user32.inc
Include kernel32.inc
Include gdi32.inc

includelib gdi32.lib
IncludeLib user32.lib
IncludeLib kernel32.lib
include macro.asm

WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD

.DATA
szAppName TCHAR "BRICKS2",0
bitmap BITMAP <0, 8, 8, 2, 1, 1,0>
bits BYTE 0FFh, 0, 0Ch, 0, 0Ch, 0, 0Ch, 0, 0FFh, 0, 0C0h, 0, 0C0h, 0, 0C0h, 0

.DATA?
hInstance HINSTANCE ?
cxClient DWORD ?
cyClient DWORD ?
cxSource DWORD ?
cySource DWORD ?
hBitmap HBITMAP ?

.CODE
START:
invoke GetModuleHandle,NULL
invoke WinMain,eax,NULL,NULL,SW_SHOWDEFAULT
invoke ExitProcess,0
WinMain proc hInst:DWORD,hPrevInst:DWORD,szCmdLine:DWORD,iCmdShow:DWORD
LOCAL wndclass :WNDCLASSEX
LOCAL msg :MSG
LOCAL hWnd :HWND

mov wndclass.cbSize,sizeof WNDCLASSEX
mov wndclass.style,CS_HREDRAW or CS_VREDRAW
mov wndclass.lpfnWndProc,offset WndProc

mov wndclass.cbClsExtra,0
mov wndclass.cbWndExtra,0

push hInst
pop wndclass.hInstance

invoke LoadIcon,NULL, IDI_INFORMATION
mov wndclass.hIcon,eax

invoke LoadCursor,NULL,IDC_ARROW
mov wndclass.hCursor,eax

invoke GetStockObject,WHITE_BRUSH
mov wndclass.hbrBackground,EAX

lea eax,szAppName
mov wndclass.lpszMenuName,eax
mov wndclass.lpszClassName,eax

mov wndclass.hIconSm,0

invoke RegisterClassEx, ADDR wndclass
.if (eax==0)
invoke MessageBox,NULL,CTXT("This program requires Windows NT!"),szAppName, MB_ICONERROR
ret
.endif


invoke CreateWindowEx,NULL,
ADDR szAppName, ;window class name
CTXT("CreateBitmap Demo"),
WS_OVERLAPPEDWINDOW, ;window style
CW_USEDEFAULT, ;initial x position
CW_USEDEFAULT, ;initial y position
CW_USEDEFAULT, ;initial x size
CW_USEDEFAULT, ;initial y size
NULL, ;parent window handle
NULL, ;window menu handle
hInst, ;program instance handle
NULL ;creation parameters
mov hWnd,eax

invoke ShowWindow,hWnd,iCmdShow
invoke UpdateWindow,hWnd

StartLoop:
invoke GetMessage,ADDR msg,NULL,0,0
cmp eax, 0
je ExitLoop
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
jmp StartLoop
ExitLoop:

mov eax,msg.wParam
ret
WinMain endp

WndProc proc hwnd:DWORD,uMsg:DWORD,wParam :DWORD,lParam :DWORD
LOCAL hdc, hdcMem:HDC
LOCAL x,y :DWORD
LOCAL ps :PAINTSTRUCT

.if uMsg == WM_CREATE

lea eax,bits
mov bitmap.bmBits,eax
mov eax,offset bitmap
invoke CreateBitmapIndirect,eax
mov hBitmap,eax
mov eax,bitmap.bmWidth
mov cxSource,eax
mov eax,bitmap.bmHeight
mov cySource,eax

xor eax,eax
ret
.elseif uMsg == WM_SIZE
mov eax,lParam
and eax,0FFFFh
mov cxClient,eax

mov eax,lParam
shr eax,16
mov cyClient,eax

xor eax,eax
ret

.elseif uMsg == WM_PAINT
invoke BeginPaint,hwnd,addr ps
mov hdc,eax

invoke CreateCompatibleDC,hdc
mov hdcMem,eax
invoke SelectObject,hdcMem, hBitmap

xor eax,eax
mov y,eax
loopy:
xor eax,eax
mov x,eax
loopx:
invoke BitBlt,hdc, x, y, cxSource, cySource,hdcMem, 0, 0, SRCCOPY
mov eax,cxSource
add x,eax
mov eax,x
.if eax jmp loopx
.endif
mov eax,cySource
add y,eax
mov eax,y
.if eax jmp loopy
.endif
invoke DeleteDC,hdcMem

invoke EndPaint,hwnd,addr ps
xor eax,eax
ret
.elseif uMsg == WM_DESTROY
invoke PostQuitMessage,NULL
xor eax,eax
ret
.endif

invoke DefWindowProc,hwnd,uMsg,wParam,lParam
ret
WndProc endp
END START

您可以尝试一下与彩色位图相似的对象。例如,如果您的视频显示器执行在256色模式下,那么您可以根据表14-2来定义彩色砖的每个像素。不过,当程序执行在其它显示模式下时,此程序代码不起作用。以设备无关方式处理彩色位图需要使用下一期将要介绍的DIB。



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