位图中的画刷
BRICKS系列的最后一个项目是BRICKS3,如程序14-5所示。乍看此程序,您可能会有这种感觉:程序代码哪里去了呢?
程序14-5 BRICKS3
BRICKS3.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 "BRICKS3",0
.DATA? hInstance HINSTANCE ?
.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 LOCAL hBitmap:HBITMAP LOCAL hBrush:HBRUSH
invoke LoadBitmap,hInst, CTEXT ("Bricks") mov hBitmap,eax invoke CreatePatternBrush,hBitmap mov hBrush,eax invoke DeleteObject,hBitmap
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 mov eax,hBrush 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("CreatePatternBrush 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
.if uMsg == WM_DESTROY invoke PostQuitMessage,NULL xor eax,eax ret .endif
invoke DefWindowProc,hwnd,uMsg,wParam,lParam ret WndProc endp END START
此程序与BRICKS1使用同一个BRICKS.BMP文件,而且窗口看上去也相同。
正如您看到的一样,窗口消息处理程序没有更多的内容。BRICKS3实际上使用砖块图案作为窗口类别背景画刷,它在WNDCLASS结构的hbrBackground字段中定义。
您现在可能猜想GDI画刷是很小的位图,通常是8个像素见方。如果将LOGBRUSH结构的lbStyle字段设定为BS_PATTERN,然后呼叫CreatePatternBrush或CreateBrushIndirect,您就可以在位图外面 来建立画刷了。此位图至少是宽高各8个像素。如果再大,Windows 98将只使用位图的左上角作为画刷。而Windows NT不受此限制,它会使用整个位图。
请记住,画刷和位图都是GDI对象,而且您应该在程序终止前删除您在程序中建立画刷和位图。如果您依据位图建立画刷,那么在用画刷画图时,Windows将复制位图位到画刷所绘制的区域内。呼 叫CreatePatternBrush(或者CreateBrushIndirect)之后,您可以立即删除位图而不会影响到画笔。类似地,您也可以删除画刷而不会影响到您选进的原始位图。注意,BRICKS3在建立画刷后删除了位 图,并在程序终止前删除了画刷。
绘制位图
在窗口中绘图时,我们已经将位图当成绘图来源使用过了。这要求先将位图选进内存设备内容,并呼叫BitBlt或者StretchBlt。您也可以用内存设备内容句柄作为所有实际呼叫的GDI函数中的第一 参数。内存设备内容的动作与实际的设备内容相同,除非显示平面是位图。
程序14-6所示的HELLOBIT程序展示了此项技术。程序在一个小位图上显示了字符串「Hello, world!」,然后从位图到程序显示区域执行BitBlt或StretchBlt(依照选择的菜单选项而定)。
程序14-6 HELLOBIT
HELLOBIT.C
;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 IDM_BIG equ 40001 IDM_SMALL equ 40002
.DATA szAppName TCHAR "HelloBit",0 iSize DWORD IDM_BIG szTxt TCHAR " Hello, world! ",0
.DATA? hInstance HINSTANCE ? cxBitmap DWORD ? cyBitmap DWORD ? cxClient DWORD ? cyClient DWORD ? hBitmap HBITMAP ? hdcMem HDC ?
.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 hdc:HDC LOCAL hMenu:HMENU LOCAL x,y :DWORD LOCAL ps :PAINTSTRUCT LOCAL sizel :SIZEL
.if uMsg == WM_CREATE invoke GetDC,hwnd mov hdc,eax invoke CreateCompatibleDC,hdc mov hdcMem,eax invoke lstrlen,addr szTxt mov ebx,eax invoke GetTextExtentPoint32,hdc,addr szTxt, ebx ,addr sizel lea esi,sizel mov eax,[esi] ;sizel.cx mov cxBitmap,eax mov eax,[esi+4] ;sizel.cy mov cyBitmap,eax invoke CreateCompatibleBitmap,hdc, cxBitmap, cyBitmap mov hBitmap,eax invoke ReleaseDC,hwnd, hdc invoke SelectObject,hdcMem, hBitmap invoke lstrlen,addr szTxt mov ebx,eax invoke TextOut,hdcMem, 0, 0, addr szTxt, ebx 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_COMMAND invoke GetMenu,hwnd mov hMenu,eax mov eax,wParam and eax,0FFFFh .if (eax==IDM_BIG)||(eax==IDM_SMALL) invoke CheckMenuItem,hMenu, iSize, MF_UNCHECKED mov eax,wParam and eax,0FFFFh mov iSize,eax invoke CheckMenuItem,hMenu, iSize, MF_CHECKED invoke InvalidateRect,hwnd, NULL, TRUE .endif xor eax,eax ret .elseif uMsg == WM_PAINT invoke BeginPaint,hwnd,addr ps mov hdc,eax
mov eax,iSize .if eax==IDM_BIG invoke StretchBlt,hdc, 0, 0, cxClient, cyClient, hdcMem, 0, 0, cxBitmap, cyBitmap, SRCCOPY .elseif eax==IDM_SMALL xor eax,eax mov y,eax loopy: xor eax,eax mov x,eax loopx: invoke BitBlt,hdc, x, y, cxBitmap, cyBitmap,hdcMem, 0, 0, SRCCOPY mov eax,cxBitmap add x,eax mov eax,x .if eax<cxClient jmp loopx .endif mov eax,cyBitmap add y,eax mov eax,y .if eax<cyClient jmp loopy .endif .endif invoke EndPaint,hwnd,addr ps xor eax,eax ret .elseif uMsg == WM_DESTROY invoke DeleteDC,hdcMem invoke DeleteObject,hBitmap invoke PostQuitMessage,NULL xor eax,eax ret .endif
invoke DefWindowProc,hwnd,uMsg,wParam,lParam ret WndProc endp END START
程序从呼叫GetTextExtentPoint32确定字符串的像素尺寸开始。这些尺寸将成为与视频显示兼容的位图尺寸。当此位图被选进内存设备内容(也与视频显示兼容)后,再呼叫TextOut将文字显示在 位图上。内存设备内容在程序执行期间保留。在处理WM_DESTROY信息期间,HELLOBIT删除了位图和内存设备内容。
HELLOBIT中的一条菜单选项允许您显示位图尺寸,此尺寸或者是显示区域中水平和垂直方向平铺的实际尺寸,或者是缩放成显示区域大小的尺寸,如图14-4所示。正与您所见到的一样,这不是显 示大尺寸字符的好方法!它只是小字体的放大版,并带有放大时产生的锯齿线。
您可能想知道一个程序,例如HELLOBIT,是否需要处理WM_DISPLAYCHANGE消息。只要使用者(或者其它应用程序)修改了视频显示大小或者颜色深度,应用程序就接收到此讯息。其中颜色深度的 改变会导致内存设备内容和视频设备内容不兼容。但这并不会发生,因为当显示模式修改后,Windows自动修改了内存设备内容的颜色分辨率。选进内存设备内容的位图仍然保持原样,但不会造成任何问 题。
阴影位图
在内存设备内容绘图(也就是位图)的技术是执行「阴影位图(shadow bitmap)」的关键。此位图包含窗口显示区域中显示的所有内容。这样,对WM_PAINT消息的处理就简化到简单的BitBlt。
阴影位图在绘画程序中最有用。程序14-7所示的SKETCH程序并不是一个最完美的绘画程序,但它是一个开始。
程序14-7 SKETCH
SKETCH.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 "Sketch",0
.DATA? fLeftButtonDown BOOL ? fRightButtonDown BOOL ? hBitmap HBITMAP ? hdcMem HDC ? cxBitmap DWORD ? cyBitmap DWORD ? cxClient DWORD ? cyClient DWORD ? xMouse DWORD ? yMouse 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_APPLICATION mov wndclass.hIcon,eax invoke LoadCursor,NULL,IDC_ARROW mov wndclass.hCursor,eax invoke GetStockObject,WHITE_BRUSH mov wndclass.hbrBackground,EAX mov wndclass.lpszMenuName,NULL lea eax,szAppName 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("Sketch"), 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 .if (hWnd == NULL) invoke MessageBox,NULL, CTEXT ("Not enough memory to create bitmap!"),addr szAppName, MB_ICONERROR xor eax,eax ret .endif 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
GetLargestDisplayMode proc pcxBitmap:DWORD,pcyBitmap:DWORD LOCAL devmode:DEVMODE LOCAL iModeNum:DWORD
xor eax,eax mov iModeNum,eax mov esi,pcxBitmap mov [esi],eax mov esi,pcyBitmap mov [esi],eax invoke RtlZeroMemory,addr devmode,sizeof (DEVMODE) mov ax,sizeof DEVMODE mov devmode.dmSize,ax @@: invoke EnumDisplaySettings,NULL, iModeNum,addr devmode .if eax==0 jmp @f .endif inc iModeNum mov esi,pcxBitmap mov eax,[esi] .if eax<devmode.dmPelsWidth mov eax,devmode.dmPelsWidth .endif mov esi,pcxBitmap mov [esi],eax
mov esi,pcyBitmap mov eax,[esi] .if eax<devmode.dmPelsHeight mov eax,devmode.dmPelsHeight .endif mov esi,pcyBitmap mov [esi],eax jmp @b @@: ret GetLargestDisplayMode endp
WndProc proc hwnd:DWORD,uMsg:DWORD,wParam :DWORD,lParam :DWORD LOCAL hdc:HDC LOCAL ps :PAINTSTRUCT LOCAL szBuffer[64 + MAX_PATH]:TCHAR .if uMsg == WM_CREATE invoke GetLargestDisplayMode,addr cxBitmap,addr cyBitmap invoke GetDC,hwnd mov hdc,eax invoke wsprintf,addr szBuffer, CTXT ("%d??%d"),cxBitmap,cyBitmap ;这个地方是我设计用来debug的 invoke SetWindowText,hwnd,addr szBuffer invoke CreateCompatibleBitmap,hdc, cxBitmap, cyBitmap mov hBitmap,eax invoke CreateCompatibleDC,hdc mov hdcMem,eax invoke ReleaseDC,hwnd, hdc
.if (hBitmap==0);no memory for bitmap invoke DeleteDC,hdcMem mov eax,-1 ret .endif invoke SelectObject,hdcMem, hBitmap invoke PatBlt,hdcMem, 0, 0, cxBitmap, cyBitmap, WHITENESS 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_LBUTTONDOWN
.if fRightButtonDown==0 invoke SetCapture,hwnd .endif invoke SetWindowText,hwnd,CTXT("DOWN") mov eax,lParam and eax,0FFFFh mov xMouse,eax mov eax,lParam shr eax,16 mov yMouse,eax mov fLeftButtonDown,TRUE xor eax,eax ret .elseif uMsg ==WM_LBUTTONUP .if fLeftButtonDown!=0 invoke SetCapture,NULL .endif invoke SetWindowText,hwnd,CTXT("UPUP") mov fLeftButtonDown,FALSE xor eax,eax ret .elseif uMsg == WM_RBUTTONDOWN .if fLeftButtonDown==0 invoke SetCapture,hwnd .endif mov eax,lParam and eax,0FFFFh mov xMouse,eax mov eax,lParam shr eax,16 mov yMouse,eax mov fRightButtonDown,TRUE xor eax,eax ret .elseif uMsg == WM_RBUTTONUP .if fRightButtonDown!=0 invoke SetCapture,NULL .endif mov fRightButtonDown,FALSE xor eax,eax ret .elseif uMsg == WM_MOUSEMOVE .if (fLeftButtonDown==0) && (fRightButtonDown==0) xor eax,eax ret .endif invoke GetDC,hwnd mov hdc,eax .if fLeftButtonDown==0 mov eax,WHITE_PEN .else mov eax,BLACK_PEN .endif invoke GetStockObject,eax invoke SelectObject,hdc,eax
.if fLeftButtonDown==0 mov eax,WHITE_PEN .else mov eax,BLACK_PEN .endif invoke GetStockObject,eax invoke SelectObject,hdcMem,eax invoke MoveToEx,hdc, xMouse, yMouse, NULL invoke MoveToEx,hdcMem, xMouse, yMouse, NULL mov eax,lParam and eax,0FFFFh movsx ecx,ax mov xMouse,ecx mov eax,lParam shr eax,16 movsx ecx,ax mov yMouse,ecx invoke LineTo,hdc, xMouse, yMouse invoke LineTo,hdcMem, xMouse, yMouse invoke ReleaseDC,hwnd, hdc xor eax,eax ret .elseif uMsg == WM_PAINT invoke BeginPaint,hwnd,addr ps mov hdc,eax
invoke BitBlt,hdc, 0, 0, cxClient, cyClient, hdcMem, 0, 0, SRCCOPY
invoke EndPaint,hwnd,addr ps xor eax,eax ret .elseif uMsg == WM_DESTROY invoke DeleteDC,hdcMem invoke DeleteObject,hBitmap invoke PostQuitMessage,NULL xor eax,eax ret .endif
invoke DefWindowProc,hwnd,uMsg,wParam,lParam ret WndProc endp END START
要想在SKETCH中画线,请按下鼠标左键并拖动鼠标。要擦掉画过的东西(更确切地说,是画白线),请按下鼠标右键并拖动鼠标。要清空整个窗口,请…结束程序,然后重新加载,一切从头再来 。图14-5中显示的SKETCH程序图样表达了对频果公司的麦金塔计算机早期广告的敬意。
此阴影位图应多大?在本程序中,它应该大到能包含最大化窗口的整个显示区域。这一问题很容易根据GetSystemMetrics信息计算得出,但如果使用者修改了显示设定后再显示,进而扩大了最大 化时窗口的尺寸,这时将发生什么呢?SKETCH程序在EnumDisplaySettings函数的帮助下解决了此问题。此函数使用DEVMODE结构来传回全部有效视频显示模式的信息。第一次呼叫此函数时,应将 EnumDisplaySettings的第二参数设为0,以后每次呼叫此值都增加。EnumDisplaySettings传回FALSE时完成。
与此同时,SKETCH将建立一个阴影位图,它比目前视频显示模式的表面还多四倍,而且需要几兆字节的内存。由于如此,SKETCH将检查位图是否建立成功了,如果没有建立,就从WM_CREATE传回-1 ,以表示错误。
在WM_MOUSEMOVE消息处理期间,按下鼠标左键或者右键,并在内存设备内容和显示区域设备内容中画线时,SKETCH拦截鼠标。如果画线方式更复杂的话,您可能想在一个函数中实作,程序将呼叫 此函数两次-一次画在视频设备内容上,一次画在内存设备内容上。
下面是一个有趣的实验:使SKETCH窗口小于全画面尺寸。随着鼠标左键的按下,将鼠标拖出窗口的右下角。因为SKETCH拦截鼠标,所以它继续接收并处理WM_MOUSEMOVE消息。现在扩大窗口,您将 看到阴影位图包含您在SKETCH窗口外所画的内容。
在菜单中使用位图
您也可以用位图在菜单上显示选项。如果您联想起菜单中文件夹、剪贴簿和资源回收筒的图片,那么不要再想那些图片了。您应该考虑一下,菜单上显示位图对画图程序用途有多大,想象一下在 菜单中使用不同字体和字体大小、线宽、阴影图案以及颜色。
GRAFMENU是展示图形菜单选项的范例程序。此程序顶层菜单如图14-6所示。放大的字母来自于40×16像素的单色位图文件,该文件在Visual C++ Developer Studio建立。从菜单上选择「FONT」将弹出三个选择项-「Courier New」、「 Arial」和「Times New Roman」。它们是标准的Windows TrueType字体,并且每一个都按其相关的字体显示,如图14-7所示。这些位图在程序中用内存设备内容建立。
图14-7 GRAFMENU程序弹出的「FONT」菜单
最后,在拉下系统菜单时,您将获得一些「辅助」信息,用「HELP」表示了新使用者的在线求助项目(参见图14-8)。此64×64像素的单色位图是在Developer Studio中建立的。
GRAFMENU程序,包括四个Developer Studio中建立的位图,如程序14-8所示。
程序14-8 GRAFMENU
GRAFMENU.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 IDM_FONT_COUR equ 101 IDM_FONT_ARIAL equ 102 IDM_FONT_TIMES equ 103 IDM_HELP equ 104 IDM_EDIT_UNDO equ 40005 IDM_EDIT_CUT equ 40006 IDM_EDIT_COPY equ 40007 IDM_EDIT_PASTE equ 40008 IDM_EDIT_CLEAR equ 40009 IDM_FILE_NEW equ 40010 IDM_FILE_OPEN equ 40011 IDM_FILE_SAVE equ 40012 IDM_FILE_SAVE_AS equ 40013
.DATA szAppName TCHAR "GrafMenu",0 Font1 db "Courier New",0 Font2 db "Arial",0 Font3 db "Times New Roman",0 szFaceName DWORD offset Font1 DWORD offset Font2 DWORD offset Font3 iCurrentFont DWORD IDM_FONT_COUR ; .DATA?
.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("Bitmap Menu Demonstration"), 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
CopyMemory proc Dest:DWORD, Source:DWORD, mlength:DWORD cld ;Work upwards mov esi, Source ;Source address mov edi, Dest ;Destination address mov ecx, mlength ;Get size in bytes rep movsb ; repeat copy util all done ret CopyMemory endp
;StretchBitmap: Scales bitmap to display resolution StretchBitmap proc hBitmap1:HBITMAP LOCAL bm1:BITMAP LOCAL bm2:BITMAP LOCAL hBitmap2:HBITMAP LOCAL hdc, hdcMem1, hdcMem2:HDC LOCAL cxChar, cyChar:DWORD ;Get the width and height of a system font character invoke GetDialogBaseUnits mov cxChar,eax and cxChar,0FFFFh shr eax,16 mov cyChar,eax
;Create 2 memory DCs compatible with the display invoke CreateIC,CTEXT ("DISPLAY"), NULL, NULL, NULL mov hdc,eax invoke CreateCompatibleDC,hdc mov hdcMem1,eax invoke CreateCompatibleDC,hdc mov hdcMem2,eax invoke DeleteDC,hdc
;Get the dimensions of the bitmap to be stretched invoke GetObject,hBitmap1, sizeof (BITMAP), addr bm1 ;Scale these dimensions based on the system font size invoke CopyMemory,addr bm2,addr bm1,sizeof(BITMAP) mov eax,cxChar mov ecx,bm2.bmWidth mul ecx shr eax,2 mov bm2.bmWidth,eax mov eax,cyChar mov ecx,bm2.bmHeight mul ecx shr eax,3 mov bm2.bmHeight,eax
mov eax,bm2.bmWidth add eax,15 shr eax,4 shl eax,2 mov bm2.bmWidthBytes,eax ;Create a new bitmap of larger size invoke CreateBitmapIndirect,addr bm2 mov hBitmap2,eax ;Select the bitmaps in the memory DCs and do a StretchBlt invoke SelectObject,hdcMem1, hBitmap1 invoke SelectObject,hdcMem2, hBitmap2 invoke StretchBlt,hdcMem2, 0, 0, bm2.bmWidth, \ bm2.bmHeight,hdcMem1, 0, 0, \ bm1.bmWidth, bm1.bmHeight, SRCCOPY ;Clean up invoke DeleteDC,hdcMem1 invoke DeleteDC,hdcMem2 invoke DeleteObject,hBitmap1 mov eax,hBitmap2 ret StretchBitmap endp
; AddHelpToSys: Adds bitmap Help item to system menu AddHelpToSys proc hInstance:HINSTANCE, hwnd:HWND LOCAL hBitmap:HBITMAP LOCAL hMenu:HMENU invoke GetSystemMenu,hwnd, FALSE mov hMenu,eax invoke LoadBitmap,hInstance, CTEXT ("BitmapHelp") invoke StretchBitmap,eax mov hBitmap,eax invoke AppendMenu,hMenu, MF_SEPARATOR, 0, NULL invoke AppendMenu,hMenu, MF_BITMAP, IDM_HELP,hBitmap mov eax,eax ret AddHelpToSys endp
;GetBitmapFont: Creates bitmaps with font names GetBitmapFont proc i:DWORD LOCAL hBitmap:HBITMAP LOCAL hdc, hdcMem:HDC LOCAL hFont:HFONT LOCAL sizeGB:SIZEL LOCAL tm:TEXTMETRIC invoke CreateIC,CTEXT ("DISPLAY"), NULL, NULL, NULL mov hdc,eax invoke GetTextMetrics,hdc,addr tm invoke CreateCompatibleDC,hdc mov hdcMem,eax mov ebx,tm.tmHeight shl ebx,1 mov eax,i shl eax,2 mov eax,[szFaceName+eax] invoke CreateFont,ebx, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,eax mov hFont,eax invoke SelectObject,hdcMem, hFont mov hFont,eax mov eax,i shl eax,2 mov eax,[szFaceName+eax] push eax invoke lstrlen,eax mov ecx,eax pop ebx invoke GetTextExtentPoint32,hdcMem, ebx,ecx, addr sizeGB invoke CreateBitmap,sizeGB.x, sizeGB.y, 1, 1, NULL mov hBitmap,eax invoke SelectObject,hdcMem, hBitmap mov eax,i shl eax,2 mov eax,[szFaceName+eax] push eax invoke lstrlen,eax pop ebx invoke TextOut,hdcMem, 0, 0, ebx,eax invoke SelectObject,hdcMem, hFont invoke DeleteObject,eax invoke DeleteDC,hdcMem invoke DeleteDC,hdc mov eax,hBitmap ret GetBitmapFont endp
;CreateMyMenu: Assembles menu from components CreateMyMenu proc hInstance:HINSTANCE LOCAL hBitmap:HBITMAP LOCAL hMenu, hMenuPopup:HMENU LOCAL i:DWORD invoke CreateMenu mov hMenu,eax invoke LoadMenu,hInstance, CTEXT ("MenuFile") mov hMenuPopup,eax invoke LoadBitmap,hInstance, CTEXT ("BitmapFile") invoke StretchBitmap,eax mov hBitmap,eax invoke AppendMenu,hMenu, MF_BITMAP or MF_POPUP, hMenuPopup,hBitmap invoke LoadMenu,hInstance, CTEXT ("MenuEdit") mov hMenuPopup,eax invoke LoadBitmap,hInstance, CTEXT ("BitmapEdit") invoke StretchBitmap,eax mov hBitmap,eax invoke AppendMenu,hMenu, MF_BITMAP or MF_POPUP,hMenuPopup,hBitmap invoke CreateMenu mov hMenuPopup,eax mov i,0 @@: invoke GetBitmapFont,i mov hBitmap,eax invoke AppendMenu,hMenuPopup, MF_BITMAP, IDM_FONT_COUR + i,hBitmap inc i cmp i,3 jNz @b invoke LoadBitmap,hInstance, CTEXT ("BitmapFont") invoke StretchBitmap,eax mov hBitmap,eax invoke AppendMenu,hMenu, MF_BITMAP or MF_POPUP, hMenuPopup,hBitmap mov eax,hMenu ret CreateMyMenu endp
;DeleteAllBitmaps: Deletes all the bitmaps in the menu DeleteAllBitmaps proc hwnd:HWND LOCAL hMenu:HMENU LOCAL i:DWORD LOCAL mii:MENUITEMINFO ;MENUITEMINFOA STRUCT ; cbSize DWORD ? ; fMask DWORD ? ; fType DWORD ? ; fState DWORD ? ; wID DWORD ? ; hSubMenu DWORD ? ; hbmpChecked DWORD ? ; hbmpUnchecked DWORD ? ; dwItemData DWORD ? ; dwTypeData DWORD ? ; cch DWORD ? ;MENUITEMINFOA ENDS
mov eax,sizeof (MENUITEMINFO) mov mii.cbSize,eax mov mii.fMask,MIIM_SUBMENU or MIIM_TYPE ;Delete Help bitmap on system menu invoke GetSystemMenu,hwnd, FALSE mov hMenu,eax invoke GetMenuItemInfo,hMenu, IDM_HELP, FALSE,addr mii invoke DeleteObject,mii.dwTypeData ;Delete top-level menu bitmaps invoke GetMenu,hwnd mov hMenu,eax
mov i,0 @@: invoke GetMenuItemInfo,hMenu, i, TRUE,addr mii invoke DeleteObject,mii.dwTypeData inc i cmp i,3 jNz @b ;Delete bitmap items on Font menu mov eax,mii.hSubMenu mov hMenu,eax
mov i,0 @@: invoke GetMenuItemInfo,hMenu, i, TRUE,addr mii invoke DeleteObject,mii.dwTypeData inc i cmp i,3 jNz @b ret DeleteAllBitmaps endp
WndProc proc hwnd:DWORD,uMsg:DWORD,wParam :DWORD,lParam :DWORD LOCAL hMenu:HMENU
.if uMsg == WM_CREATE mov esi,lParam mov eax,[esi+4] invoke AddHelpToSys,eax,hwnd mov esi,lParam mov eax,[esi+4] invoke CreateMyMenu,eax mov hMenu,eax invoke SetMenu,hwnd, hMenu invoke CheckMenuItem,hMenu, iCurrentFont, MF_CHECKED xor eax,eax ret .elseif uMsg == WM_SYSCOMMAND mov eax,wParam and eax,0FFFFh .if eax==IDM_HELP invoke MessageBox,hwnd,CTEXT ("Help not yet implemented!"),\ addr szAppName, MB_OK or MB_ICONEXCLAMATION xor eax,eax ret .endif .elseif uMsg == WM_COMMAND mov eax,wParam and eax,0FFFFh .if (eax==IDM_FILE_NEW)||\ (eax==IDM_FILE_OPEN)||\ (eax==IDM_FILE_SAVE)||\ (eax==IDM_FILE_SAVE_AS)||\ (eax==IDM_EDIT_UNDO)||\ (eax==IDM_EDIT_CUT)||\ (eax==IDM_EDIT_COPY)||\ (eax==IDM_EDIT_PASTE)||\ (eax==IDM_EDIT_CLEAR) invoke MessageBeep,0 xor eax,eax ret .elseif (eax==IDM_FONT_COUR)||\ (eax==IDM_FONT_ARIAL)||\ (eax==IDM_FONT_TIMES) invoke GetMenu,hwnd mov hMenu,eax invoke CheckMenuItem,hMenu, iCurrentFont, MF_UNCHECKED mov eax,wParam and eax,0FFFFh mov iCurrentFont,eax invoke CheckMenuItem,hMenu, iCurrentFont, MF_CHECKED xor eax,eax ret .endif
.elseif uMsg == WM_DESTROY invoke DeleteAllBitmaps,hwnd invoke PostQuitMessage,NULL xor eax,eax ret .endif
invoke DefWindowProc,hwnd,uMsg,wParam,lParam ret WndProc endp END START
EDITLABL.BMP
FILELABL.BMP
FONTLABL.BMP
BIGHELP.BMP
要将位图插入菜单,可以利用AppendMenu或InsertMenu。位图有两个来源:可以在Visual C++ Developer Studio建立位图,包括资源脚本中的位图文件,并在程序使用LoadBitmap时将位图资源加载到内存,然后呼叫AppendMenu或InsertMenu将位图附加到菜单上。但是用这种方法会有一些问题:位图 不适于所有显示模式的分辨率和纵横比;有时您需要缩放加载的位图以解决此问题。另一种方法是:在程序内部建立位图,并将它选进内存设备内容,画出来,然后再附加到菜单中。
GRAFMENU中的GetBitmapFont函数的参数为0、1或2,传回一个位图句柄。此位图包含字符串「Courier New」、「Arial」或「Times New Roman」,而且字体是各自对应的字体,大小是正常系统字体的两倍。让我们看看GetBitmapFont是怎么做的。(下面的程序代码与GRAFMENU.C文件中的有些不同。为了清楚起见,我用「Arial」字 体相应的值代替了引用szFaceName数组。)
第一步是用TEXTMETRIC结构来确定目前系统字体的大小,并建立一个与目前屏幕兼容的内存设备内容:
hdc = CreateIC (TEXT ("DISPLAY"), NULL, NULL, NULL) ;
GetTextMetrics (hdc, &tm) ;
hdcMem = CreateCompatibleDC (hdc) ;
CreateFont函数建立了一种逻辑字体,该字体高是系统字体的两倍,而且逻辑名称为「Arial」:
hFont = CreateFont (2 * tm.tmHeight, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
TEXT ("Arial")) ;
从内存设备内容中选择该字体,然后储存内定字体句柄:
hFont = (HFONT) SelectObject (hdcMem, hFont) ;
现在,当我们向内存设备内容写一些文字时,Windows就会使用选进设备内容的TrueType Arial字体了。
但这个内存设备内容最初只有一个单像素单色设备平面。我们必须建立一个足够大的位图以容纳我们所要显示的文字。通过GetTextExtentPoint32函数,可以取得文字的大小,而用CreateBitmap 可以根据这些尺寸来建立位图:
GetTextExtentPoint32 (hdcMem, TEXT ("Arial"), 5, &size) ;
hBitmap = CreateBitmap (size.cx, size.cy, 1, 1, NULL) ;
SelectObject (hdcMem, hBitmap) ;
现在这个设备内容是一个单色的显示平面,大小也是严格的文字尺寸。我们现在要做的就是书写文字:
TextOut (hdcMem, 0, 0, TEXT ("Arial"), 5) ;
除了清除,所有的工作都完成了。要清除,我们可以用SelectObject将系统字体(带有句柄hFont)重新选进设备内容,然后删除SelectObject传回的前一个字体句柄,也就是Arial字体句柄:
DeleteObject (SelectObject (hdcMem, hFont)) ;
现在可以删除两个设备内容:
DeleteDC (hdcMem) ;
DeleteDC (hdc) ;
这样,我们就获得了一个位图,该位图上有Arial字体的字符串「Arial」。
当我们需要缩放字体以适应不同显示分辨率或纵横比时,内存设备内容也能解决问题。在GRAFMENU程序中,我建立了四个位图,这些位图只适用于系统字体高8像素、宽4像素的显示。对于其它尺 寸的系统字体,只能缩放位图。GRAFMENU中的StretchBitmap函数完成此功能。
第一步是获得显示的设备内容,然后取得系统字体的文字规格,接下来建立两个内存设备内容:
hdc = CreateIC (TEXT ("DISPLAY"), NULL, NULL, NULL) ;
GetTextMetrics (hdc, &tm) ;
hdcMem1 = CreateCompatibleDC (hdc) ;
hdcMem2 = CreateCompatibleDC (hdc) ;
DeleteDC (hdc) ;
传递给函数的位图句柄是hBitmap1。程序能用GetObject获得位图的大小:
GetObject (hBitmap1, sizeof (BITMAP), (PSTR) &bm1) ;
此操作将尺寸复制到BITMAP型态的结构bm1中。结构bm2等于结构bm1,然后根据系统字体大小来修改某些字段:
bm2 = bm1 ;
bm2.bmWidth = (tm.tmAveCharWidth * bm2.bmWidth) / 4 ;
bm2.bmHeight = (tm.tmHeight * bm2.bmHeight) / 8 ;
bm2.bmWidthBytes = ((bm2.bmWidth + 15) / 16) * 2 ;
下一个位图带有句柄hBitmap2,可以根据动态的尺寸建立:
hBitmap2 = CreateBitmapIndirect (&bm2) ;
然后将这两个位图选进两个内存设备内容中:
SelectObject (hdcMem1, hBitmap1) ;
SelectObject (hdcMem2, hBitmap2) ;
我们想把第一个位图复制给第二个位图,并在此程序中进行拉伸。这包括StretchBlt呼叫:
StretchBlt (hdcMem2, 0, 0, bm2.bmWidth, bm2.bmHeight,
hdcMem1, 0, 0, bm1.bmWidth, bm1.bmHeight, SRCCOPY) ;
现在第二幅图适当地缩放了,我们可将其用到菜单中。剩下的清除工作很简单:
DeleteDC (hdcMem1) ;
DeleteDC (hdcMem2) ;
DeleteObject (hBitmap1) ;
在建造菜单时,GRAFMENU中的CreateMyMenu函数呼叫了StretchBitmap和GetBitmapFont函数。GRAFMENU在资源文件中定义了两个菜单,在选择「File」和「Edit」选项时会弹出这两个菜单。函数 开始先取得一个空菜单的句柄:
从资源文件加载「File」的弹出式菜单(包括四个选项:「New」、「Open」、「Save」和「Save as」):
hMenuPopup = LoadMenu (hInstance, TEXT ("MenuFile")) ;
从资源文件还加载了包含「FILE」的位图,并用StretchBitmap进行了拉伸:
hBitmapFile = StretchBitmap (LoadBitmap (hInstance, TEXT ("BitmapFile"))) ;
位图句柄和弹出式菜单句柄都是AppendMenu呼叫的参数:
AppendMenu (hMenu, MF_BITMAP | MF_POPUP, hMenuPopup, (PTSTR) (LONG)
hBitmapFile) ;
「Edit」菜单类似程序如下:
hMenuPopup = LoadMenu (hInstance, TEXT ("MenuEdit")) ;
hBitmapEdit = StretchBitmap (LoadBitmap (hInstance, TEXT ("BitmapEdit"))) ;
AppendMenu (hMenu, MF_BITMAP | MF_POPUP, hMenuPopup, (PTSTR)(LONG) hBitmapEdit) ;
呼叫GetBitmapFont函数可以构造这三种不同字体的弹出式菜单:
hMenuPopup = CreateMenu () ;
for (i = 0 ; i < 3 ; i++)
{
hBitmapPopFont [i] = GetBitmapFont (i) ;
AppendMenu (hMenuPopup, MF_BITMAP, IDM_FONT_COUR + i,
(PTSTR) (LONG) hMenuPopupFont [i]) ;
}
然后将弹出式菜单添加到菜单中:
hBitmapFont = StretchBitmap (LoadBitmap (hInstance, "BitmapFont")) ;
AppendMenu (hMenu, MF_BITMAP | MF_POPUP, hMenuPopup, (PTSTR) (LONG) hBitmapFont) ;
WndProc通过呼叫SetMenu,完成了窗口菜单的建立工作。
GRAFMENU还改变了AddHelpToSys函数中的系统菜单。此函数首先获得一个系统菜单句柄:
hMenu = GetSystemMenu (hwnd, FALSE) ;
这将载入「HELP」位图,并将其拉伸到适当尺寸:
hBitmapHelp = StretchBitmap (LoadBitmap (hInstance, TEXT ("BitmapHelp"))) ;
这将给系统菜单添加一条分隔线和拉伸的位图:
AppendMenu (hMenu, MF_SEPARATOR, 0, NULL) ;
AppendMenu (hMenu, MF_BITMAP, IDM_HELP, (PTSTR)(LONG) hBitmapHelp) ;
GRAFMENU在退出之前呼叫一个函数来清除并删除所有位图。
下面是在菜单中使用位图的一些注意事项。
在顶层菜单中,Windows调整菜单列的高度以适应最高的位图。其它位图(或字符串)是根据菜单列的顶端对齐的。如果在顶层菜单中使用了位图,那么从使用常数SM_CYMENU的GetSystemMetrics得 到的菜单列大小将不再有效。
执行GRAFMENU期间可以看到:在弹出式菜单中,您可使用带有位图菜单项的勾选标记,但勾选标记是正常尺寸。如果不满意,您可以建立一个自订的勾选标记,并使用SetMenuItemBitmaps。
在菜单中使用非文字(或者使用非系统字体的文字)的另一种方法是「拥有者绘制」菜单。
菜单的键盘接口是另一个问题。当菜单含有文字时,Windows会自动添加键盘接口。要选择一个菜单项,可以使用Alt与字符串中的一个字母的组合键。而一旦在菜单中放置了位图,就删除了键盘 接口。即使位图表达了一定的含义,但Windows并不知道。
目前我们可以使用WM_MENUCHAR消息。当您按下Alt和与菜单项不相符的一个字符键的组合键时,Windows将向您的窗口消息处理程序发送一个WM_MENUCHAR消息。GRAFMENU需要截取WM_MENUCHAR消息 并检查wParam的值(即按键的ASCII码)。如果这个值对应一个菜单项,那么向Windows传回双字组:其中高字组为2,低字组是与该键相关的菜单项索引值。然后由Windows处理余下的事。
非矩形位图图像
位图都是矩形,但不需要都显示成矩形。例如,假定您有一个矩形位图图像,但您却想将它显示成椭圆形。
首先,这听起来很简单。您只需将图像加载Visual C++ Developer Studio或者Windows的「画图」程序,然后用白色的画笔将图像四周画上白色。这时将获得一幅椭圆形的图像,而椭圆的外面就 成了白色。只有当背景色为白色时此位图才能正确显示,如果在其它背景色上显示,您就会发现椭圆形的图像和背景之间有一个白色的矩形。这种效果不好。
有一种非常通用的技术可解决此类问题。这种技术包括「屏蔽(mask)」位图和一些位映像操作。屏蔽是一种单色位图,它与您要显示的矩形位图图像尺寸相同。每个屏蔽的像素都对应位图图像 的一个像素。屏蔽像素是1(白色),对应着位图像素显示;是0(黑色),则显示背景色。(或者屏蔽位图与此相反,这根据您使用的位映像操作而有一些相对应的变化。)
让我们看看BITMASK程序是如何实作这一技术的。如程序14-9所示。
程序14-9 BITMASK
BITMASK.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 "BitMask",0
.DATA? hInstance HINSTANCE ? hBitmapImag HBITMAP ? hBitmapMask HBITMAP ? cxClient DWORD ? cyClient DWORD ? cxBitmap DWORD ? cyBitmap 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,LTGRAY_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("Bitmap Masking 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, hdcMemImag, hdcMemMask:HDC LOCAL x, y:DWORD LOCAL ps:PAINTSTRUCT
.if uMsg == WM_CREATE mov esi,lParam mov eax,[esi+4] mov hInstance,eax ; Load the original image and get its size invoke LoadBitmap,hInstance, CTEXT ("Matthew") mov hBitmapImag,eax invoke GetObject,hBitmapImag, sizeof (BITMAP),addr bitmap mov eax,bitmap.bmWidth mov cxBitmap,eax mov eax,bitmap.bmHeight mov cyBitmap,eax
;Select the original image into a memory DC invoke CreateCompatibleDC,NULL mov hdcMemImag,eax invoke SelectObject,hdcMemImag, hBitmapImag ;Create the monochrome mask bitmap and memory DC invoke CreateBitmap,cxBitmap, cyBitmap, 1, 1, NULL mov hBitmapMask,eax invoke CreateCompatibleDC,NULL mov hdcMemMask,eax invoke SelectObject,hdcMemMask, hBitmapMask ;Color the mask bitmap black with a white ellipse invoke GetStockObject,BLACK_BRUSH invoke SelectObject,hdcMemMask, eax invoke Rectangle,hdcMemMask, 0, 0, cxBitmap, cyBitmap invoke GetStockObject,WHITE_BRUSH invoke SelectObject,hdcMemMask,eax invoke Ellipse,hdcMemMask, 0, 0, cxBitmap, cyBitmap
;Mask the original image invoke BitBlt,hdcMemImag, 0, 0, cxBitmap, cyBitmap, hdcMemMask, 0, 0, SRCAND invoke DeleteDC,hdcMemImag invoke DeleteDC,hdcMemMask 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 ;Select bitmaps into memory DCs invoke CreateCompatibleDC,hdc mov hdcMemImag,eax invoke SelectObject,hdcMemImag, hBitmapImag invoke CreateCompatibleDC,hdc mov hdcMemMask,eax invoke SelectObject,hdcMemMask, hBitmapMask ;Center image mov eax,cxClient sub eax,cxBitmap shr eax,1 mov x,eax
mov eax,cyClient sub eax,cyBitmap shr eax,1 mov y,eax ;Do the bitblts invoke BitBlt,hdc, x, y, cxBitmap, cyBitmap, hdcMemMask, 0, 0, 220326h invoke BitBlt,hdc, x, y, cxBitmap, cyBitmap, hdcMemImag, 0, 0, SRCPAINT invoke DeleteDC,hdcMemImag invoke DeleteDC,hdcMemMask invoke EndPaint,hwnd,addr ps xor eax,eax ret
.elseif uMsg == WM_DESTROY invoke DeleteObject,hBitmapImag invoke DeleteObject,hBitmapMask invoke PostQuitMessage,NULL xor eax,eax ret .endif
invoke DefWindowProc,hwnd,uMsg,wParam,lParam ret WndProc endp END START
资源文件中的MATTHEW.BMP文件是原作者侄子的一幅黑白数字照片,宽200像素,高320像素,每像素8位。不过,另外制作个BITMASK只是因为此文件的内容是任何东西都可以。
注意,BITMASK将窗口背景设为亮灰色。这样就确保我们能正确地屏蔽位图,而不只是将其涂成白色。
下面让我们看一下WM_CREATE的处理程序:BITMASK用LoadBitmap函数获得hBitmapImag变量中原始图像的句柄。用GetObject函数可取得位图的宽度高度。然后将位图句柄选进句柄为hdcMemImag的 内存设备内容中。
程序建立的下一个单色位图与原来的图大小相同,其句柄储存在hBitmapMask,并选进句柄为hdcMemMask的内存设备内容中。在内存设备内容中,使用GDI函数,屏蔽位图就涂成了黑色背景和一个 白色的椭圆:
SelectObject (hdcMemMask, GetStockObject (BLACK_BRUSH)) ;
Rectangle (hdcMemMask, 0, 0, cxBitmap, cyBitmap) ;
SelectObject (hdcMemMask, GetStockObject (WHITE_BRUSH)) ;
Ellipse (hdcMemMask, 0, 0, cxBitmap, cyBitmap) ;
因为这是一个单色的位图,所以黑色区域的位是0,而白色区域的位是1。
然后BitBlt呼叫就按此屏蔽修改了原图像:
BitBlt (hdcMemImag, 0, 0, cxBitmap, cyBitmap,
hdcMemMask, 0, 0, SRCAND) ;
SRCAND位映像操作在来源位(屏蔽位图)和目的位(原图像)之间执行了位AND操作。只要屏蔽位图是白色,就显示目的;只要屏蔽是黑色,则目的就也是黑色。现在原图像中就形成了一个黑色包 围的椭圆区域。
现在让我们看一下WM_PAINT处理程序。此程序同时改变了选进内存设备内容中的图像位图和屏蔽位图。两次BitBlt呼叫完成了这个魔术,第一次在窗口上执行屏蔽位图的BitBlt:
BitBlt (hdc, x, y, cxBitmap, cyBitmap, hdcMemMask, 0, 0, 0x220326) ;
这里使用了一个没有名称的位映像操作。逻辑运算子是D & ~S。回忆来源-即屏蔽位图-是黑色(位值0)包围的一个白色(位值1)椭圆。位映像操作首先将来源反色,也就是改成白色包围的黑色椭圆。然后位操作在这个已转换的来源和目的(即窗口上 )之间执行位AND操作。当目的和位值1「AND」时保持不变;与位值0「AND」时,目的将变黑。因此,BitBlt操作将在窗口上画一个黑色的椭圆。
第二次的BitBlt呼叫则在窗口中绘制图像位图:
BitBlt (hdc, x, y, cxBitmap, cyBitmap, hdcMemImag, 0, 0, SRCPAINT) ;
位映像操作在来源和目的之间执行位「OR」操作。由于来源位图的外面是黑色,因此保持目的不变;而在椭圆区域内,目的是黑色,因此图像就原封不动地复制了过来。执行结果如图14-9所示。
注意事项:
有时您需要一个很复杂的屏蔽-例如,抹去原始图像的整个背景。您将需要在画图程序中手工建立然后将其储存到成文件。
如果正在为Windows NT/XP 编写类似的应用程序,那么您可以使用与MASKBIT程序类似的MaskBlt函数,而只需要更少的函数呼叫。Windows NT还包括另一个类似BitBlt的函数,Windows 98不支持该函数。此函数是PlgBlt(「平行四边形位块移动:parallelogram blt」)。这个函数可以对图像进行旋转或者倾斜位图图像。
最后,如果在您的机器上执行BITMASK程序,您就只会看见黑色、白色和两个灰色的阴影,这是因为您执行的显示模式是16色或256色。对于16色模式,显示效果无法改进,但在256色模式下可以 改变调色盘以显示灰阶。
简单的动画
小张的位图显示起来非常快,因此可以将位图和Windows定时器联合使用,来完成一些基本的动画。
现在开始这个弹球程序。
BOUNCE程序,如程序14-10所示,产生了一个在窗口显示区域弹来弹去的小球。该程序利用定时器来控制小球的行进速度。小球本身是一幅位图,程序首先通过建立位图来建立小球,将其选进内存 设备内容,然后呼叫一些简单的GDI函数。程序用BitBlt从一个内存设备内容将这个位图小球画到显示器上。
程序14-10 BOUNCE
BOUNCE.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 ID_TIMER equ 1 .DATA szAppName TCHAR "Bounce",0 dbg DWORD 0 .DATA? hInstance HINSTANCE ? hBitmap HBITMAP ? cxClient DWORD ? cyClient DWORD ? xCenter DWORD ? yCenter DWORD ? cxTotal DWORD ? cyTotal DWORD ? cxRadius DWORD ? cyRadius DWORD ? cxMove DWORD ? cyMove DWORD ? xPixel DWORD ? yPixel 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("Bouncing Ball"), 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 hBrush :HBRUSH LOCAL hdc, hdcMem :HDC LOCAL iScale :DWORD
.if uMsg == WM_CREATE invoke GetDC,hwnd mov hdc,eax invoke GetDeviceCaps,hdc, ASPECTX mov xPixel,eax invoke GetDeviceCaps,hdc, ASPECTY mov yPixel,eax invoke ReleaseDC,hwnd, hdc invoke SetTimer,hwnd, ID_TIMER, 30, NULL xor eax,eax ret .elseif uMsg == WM_SIZE mov eax,lParam and eax,0FFFFh mov cxClient,eax shr eax,1 mov xCenter,eax
mov eax,lParam shr eax,16 mov cyClient,eax shr eax,1 mov yCenter,eax
mov eax,cxClient mov ecx,xPixel mul ecx mov ebx,eax mov eax,cyClient mov ecx,yPixel mul ecx .if eax>ebx mov eax,ebx .endif shr eax,4 mov iScale,eax
xor edx,edx mov eax,iScale mov ecx,xPixel div ecx mov cxRadius,eax xor edx,edx mov eax,iScale mov ecx,yPixel div ecx mov cyRadius,eax
mov eax,cxRadius shr eax,1 .if eax<1 mov eax,1 .endif mov cxMove,eax mov eax,cyRadius shr eax,1 .if eax<1 mov eax,1 .endif mov cyMove,eax mov eax,cxRadius add eax,cxMove shl eax,1 mov cxTotal,eax mov eax,cyRadius add eax,cyMove shl eax,1 mov cyTotal,eax .if hBitmap!=0 invoke DeleteObject,hBitmap .endif invoke GetDC,hwnd mov hdc,eax invoke CreateCompatibleDC,hdc mov hdcMem,eax invoke CreateCompatibleBitmap,hdc, cxTotal, cyTotal mov hBitmap,eax invoke ReleaseDC,hwnd, hdc invoke SelectObject,hdcMem, hBitmap mov eax,cxTotal inc eax mov ebx,cyTotal inc ebx invoke Rectangle,hdcMem, -1, -1, eax,ebx invoke CreateHatchBrush,HS_DIAGCROSS, 0 mov hBrush,eax invoke SelectObject,hdcMem, hBrush mov eax,0FF0FFh invoke SetBkColor,hdcMem, eax mov eax,cxTotal sub eax,cxMove mov ebx,cyTotal sub ebx,cyMove invoke Ellipse,hdcMem, cxMove, cyMove,eax,ebx invoke DeleteDC,hdcMem invoke DeleteObject,hBrush xor eax,eax ret
.elseif uMsg == WM_TIMER .if (hBitmap==0) jmp Go_Ret .endif invoke GetDC,hwnd mov hdc,eax invoke CreateCompatibleDC,hdc mov hdcMem,eax invoke SelectObject,hdcMem, hBitmap mov eax,xCenter mov ebx,cxTotal shr ebx,1 sub eax,ebx mov ecx,yCenter mov edx,cyTotal shr edx,1 sub ecx,edx
invoke BitBlt,hdc, eax,ecx, cxTotal, cyTotal,hdcMem, 0, 0, SRCCOPY invoke ReleaseDC,hwnd, hdc invoke DeleteDC,hdcMem mov eax,cxMove add xCenter,eax mov eax,cyMove add yCenter,eax mov eax,xCenter sub eax,cxRadius cmp eax,0 jl @f mov eax,xCenter add eax,cxRadius .if (eax >= cxClient) @@: mov eax,cxMove neg eax mov cxMove,eax .endif mov eax,yCenter sub eax,cyRadius cmp eax,0 jl @f mov eax,yCenter add eax,cyRadius .if (eax >= cyClient) @@: mov eax,cyMove neg eax mov cyMove,eax .endif
Go_Ret: ;invoke KillTimer,hwnd, ID_TIMER xor eax,eax ret
.elseif uMsg == WM_DESTROY .if hBitmap!=0 invoke DeleteObject,hBitmap .endif invoke KillTimer,hwnd, ID_TIMER invoke PostQuitMessage,NULL xor eax,eax ret .endif
invoke DefWindowProc,hwnd,uMsg,wParam,lParam ret WndProc endp END START
BOUNCE每次收到一个WM_SIZE消息时都重画小球。这就需要与视频显示器兼容的内存设备内容:
hdcMem = CreateCompatibleDC (hdc) ;
小球的直径设为窗口显示区域高度或宽度中较短者的十六分之一。不过,程序构造的位图却比小球大:从位图中心到位图四个边的距离是小球半径的1.5倍:
hBitmap = CreateCompatibleBitmap (hdc, cxTotal, cyTotal) ;
将位图选进内存设备内容后,整个位图背景设成白色:
Rectangle (hdcMem, -1, -1, xTotal + 1, yTotal + 1) ;
那些不固定的坐标使矩形边框在位图之外着色。一个对角线开口的画刷选进内存设备内容,并将小球画在位图的中央:
Ellipse (hdcMem, xMove, yMove, xTotal - xMove, yTotal - yMove) ;
当小球移动时,小球边界的空白会有效地删除前一时刻的小球图像。在另一个位置重画小球只需在BitBlt呼叫中使用SRCCOPY的ROP代码:
BitBlt (hdc, xCenter - cxTotal / 2, yCenter - cyTotal / 2, cxTotal, cyTotal,
hdcMem, 0, 0, SRCCOPY) ;
BOUNCE程序只是展示了在显示器上移动图像的最简单的方法。在一般情况下,这种方法并不能令人满意。如果您对动画感兴趣,那么除了在来源和目的之间执行或操作以外,您还应该研究其它的 ROP代码(例如SRCINVERT)。其它动画技术包括Windows调色盘(以及AnimatePalette函数)和CreateDIBSection函数。对于更高级的动画您只好放弃GDI而使用DirectX接口了。
窗口外的位图
SCRAMBLE程序,如程序14-11所示,编写非常粗糙,我本来不应该展示这个程序,但它示范了一些有趣的技术,而且在交换两个显示矩形内容的BitBlt操作的程序中,用内存设备内容作为临时储存 空间。
程序14-11 SCRAMBLE
SCRAMBLE.ASM
;MASMPlus 代码模板 - 普通的 Windows 程序代码 .386 .Model Flat, StdCall Option Casemap :None
Include windows.inc Include user32.inc Include kernel32.inc Include gdi32.inc Include libc.inc
includelib gdi32.lib IncludeLib user32.lib IncludeLib kernel32.lib IncludeLib Libc.lib include macro.asm WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD NUM equ 300 .DATA szAppName TCHAR "SCRAMBLE",0 .DATA? hInstance HINSTANCE ? iKeep DWORD NUM*4 dup(?) .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 hdcScr, hdcMem:HDC LOCAL cXX, cYY:DWORD LOCAL hBitmap:HBITMAP LOCAL hwnd :HWND LOCAL i, j, x1, y1, x2, y2 :DWORD LOCAL tmp:SYSTEMTIME invoke GetDesktopWindow mov hwnd,eax invoke LockWindowUpdate,eax .if eax!=0 invoke GetDCEx,hwnd, NULL, DCX_CACHE or DCX_LOCKWINDOWUPDATE mov hdcScr,eax invoke CreateCompatibleDC,hdcScr mov hdcMem,eax invoke GetSystemMetrics,SM_CXSCREEN xor edx,edx mov ecx,10 div ecx mov cXX,eax
invoke GetSystemMetrics,SM_CYSCREEN xor edx,edx mov ecx,10 div ecx mov cYY,eax invoke CreateCompatibleBitmap,hdcScr, cXX, cYY mov hBitmap,eax invoke SelectObject,hdcMem, hBitmap invoke GetSystemTime,addr tmp movsx eax,tmp.wMilliseconds ;只取后面的DWORD invoke srand,eax mov i,0 Loopi: mov j,0 Loopj: .if i==0 mov eax,j shl eax,4 ;数组和DWORD lea esi,iKeep add esi,eax push esi invoke rand pop esi xor edx,edx mov ecx,10 div ecx mov eax,edx mov ecx,cXX mul ecx mov x1,eax mov [esi],eax
push esi invoke rand pop esi xor edx,edx mov ecx,10 div ecx mov eax,edx mov ecx,cYY mul ecx mov y1,eax mov [esi+4],eax push esi invoke rand pop esi xor edx,edx mov ecx,10 div ecx mov eax,edx mov ecx,cXX mul ecx mov x2,eax mov [esi+8],eax
push esi invoke rand pop esi xor edx,edx mov ecx,10 div ecx mov eax,edx mov ecx,cYY mul ecx mov y2,eax mov [esi+12],eax .else mov eax,j shl eax,4 ;数组和DWORD lea esi,iKeep add esi,(NUM-1)*16 sub esi,eax mov eax,[esi] mov x1,eax mov eax,[esi+4] mov y1,eax mov eax,[esi+8] mov x2,eax mov eax,[esi+12] mov y2,eax .endif invoke BitBlt,hdcMem, 0, 0, cXX, cYY, hdcScr, x1, y1, SRCCOPY invoke BitBlt,hdcScr, x1, y1, cXX, cYY, hdcScr, x2, y2, SRCCOPY invoke BitBlt,hdcScr, x2, y2, cXX, cYY, hdcMem, 0, 0, SRCCOPY invoke Sleep,1 invoke MessageBeep,0 inc j .if j!=NUM jmp Loopj .endif inc i .if i!=2 jmp Loopi .endif invoke DeleteDC,hdcMem invoke ReleaseDC,hwnd, hdcScr invoke DeleteObject,hBitmap invoke LockWindowUpdate,NULL .endif
ret WinMain endp END START
SCRAMBLE没有窗口消息处理程序。在WinMain中,它首先呼叫带有桌面窗口句柄的LockWindowUpdate。此函数暂时防止其它程序更新屏幕。然后SCRAMBLE通过呼叫带有参数DCX_LOCKWINDOWUPDATE的 GetDCEx来获得整个屏幕的设备内容。这样就只有SCRAMBLE可以更新屏幕了。
然后SCRAMBLE确定全屏幕的尺寸,并将长宽分别除以10。程序用这个尺寸(名称是cx和cy)来建立一个位图,并将该位图选进内存设备内容。
使用C语言的rand函数,SCRAMBLE计算出四个随机值(两个坐标点)作为cx和cy的倍数。程序透过三次呼叫BitBlt函数来交换两个矩形块中显示的内容。第一次将从第一个坐标点开始的矩形复制到 内存设备内容。第二次BitBlt将从第二坐标点开始的矩形复制到第一点开始的位置。第三次将内存设备内容中的矩形复制到第二个坐标点开始的区域。
此程序将有效地交换显示器上两个矩形中的内容。SCRAMBLE执行300次交换,这时的屏幕显示肯定是一团糟。但不用担心,因为SCRAMBLE记得是怎么把显示弄得这样一团糟的,接着在退出前它会按 相反的次序恢复原来的桌面显示(锁定屏幕前的画面)!
您也可以用内存设备内容将一个位图复制给另一个位图。例如,假定您要建立一个位图,该位图只包含另一个位图左上角的图形。如果原来的图像句柄为hBitmap,那么您可以将其尺寸复制到一个 BITMAP型态的结构中:
GetObject (hBitmap, sizeof (BITMAP), &bm) ;
然后建立一个未初始化的新位图,该位图的尺寸是原来图的1/4:
hBitmap2 = CreateBitmap ( bm.bmWidth / 2, bm.bmHeight / 2,
bm.bmPlanes, bm.bmBitsPixel, NULL) ;
现在建立两个内存设备内容,并将原来位图和新位图选分别进这两个内存设备内容:
hdcMem1 = CreateCompatibleDC (hdc) ;
hdcMem2 = CreateCompatibleDC (hdc) ;
SelectObject (hdcMem1, hBitmap) ;
SelectObject (hdcMem2, hBitmap2) ;
最后,将第一个位图的左上角复制给第二个:
BitBlt ( hdcMem2, 0, 0, bm.bmWidth / 2, bm.bmHeight / 2,
hdcMem1, 0, 0, SRCCOPY) ;
剩下的只是清除工作:
DeleteDC (hdcMem1) ;
DeleteDC (hdcMem2) ;
DeleteObject (hBitmap) ;
BLOWUP.C程序,如图14-21所示,也用窗口更新锁定来在程序窗口之外显示一个捕捉的矩形。此程序允许使用者用鼠标圈选屏幕上的矩形区域,然后BLOWUP将该区域的内容复制到位图。在WM_PAINT 消息处理期间,位图复制到程序的显示区域,必要时将拉伸或压缩。(参见程序14-12。)
程序14-12 BLOWUP
BLOWUP.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 IDM_EDIT_CUT equ 40001 IDM_EDIT_COPY equ 40002 IDM_EDIT_PASTE equ 40003 IDM_EDIT_DELETE equ 40004
.DATA szAppName TCHAR "BLOWUP",0,0 ptBeg POINT <0,0> ptEnd POINT <0,0> bCapturing BOOL 0 bBlocking BOOL 0 .DATA?
hBitmap HBITMAP ? hwndScr HWND ?
szBuffer TCHAR 100 dup(?) .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 LOCAL hAccel:HACCEL
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_APPLICATION 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("Blow-Up Mouse 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 .if (hWnd == NULL) invoke MessageBox,NULL, CTEXT ("Not enough memory to create bitmap!"),addr szAppName, MB_ICONERROR xor eax,eax ret .endif invoke ShowWindow,hWnd,iCmdShow invoke UpdateWindow,hWnd
lea eax,szAppName invoke LoadAccelerators,hInst,eax mov hAccel,eax
StartLoop: invoke GetMessage,ADDR msg,NULL,0,0 cmp eax, 0 je ExitLoop invoke TranslateAccelerator,hInst, hAccel,addr msg .if eax==0 invoke TranslateMessage, ADDR msg invoke DispatchMessage, ADDR msg .endif jmp StartLoop ExitLoop: mov eax,msg.wParam ret WinMain endp
InvertBlock proc hwndScrIB:HWND,hwnd:HWND,ptBegAddr:DWORD,ptEndAddr:DWORD LOCAL hdc:HDC LOCAL aa,bb,cc,ff:DWORD invoke GetDCEx,hwndScrIB, NULL, DCX_CACHE or DCX_LOCKWINDOWUPDATE mov hdc,eax
invoke ClientToScreen,hwnd,ptBegAddr ;将用户坐标转换成屏幕坐标 invoke ClientToScreen,hwnd,ptEndAddr mov esi,ptBegAddr mov edi,ptEndAddr push DSTINVERT mov eax,[edi+4] sub eax,[esi+4] push eax mov aa,eax mov eax,[edi] sub eax,[esi] push eax mov bb,eax mov eax,[esi+4] push eax mov cc,eax mov eax,[esi] push eax mov ff,eax push hdc call PatBlt invoke wsprintf,addr szBuffer, CTXT ("[%d??%d] [%d??%d]"),aa,bb,cc,ff invoke SetWindowText,hwnd,addr szBuffer invoke ScreenToClient,hwnd,ptBegAddr invoke ScreenToClient,hwnd,ptEndAddr invoke ReleaseDC,hwndScrIB, hdc ret InvertBlock endp CopyBitmap proc hBitmapSrc:HBITMAP LOCAL bitmap:BITMAP LOCAL hBitmapDst:HBITMAP LOCAL hdcSrc, hdcDst:HDC invoke GetObject,hBitmapSrc, sizeof (BITMAP),addr bitmap invoke CreateBitmapIndirect,addr bitmap mov hBitmapDst,eax invoke CreateCompatibleDC,NULL mov hdcSrc,eax invoke CreateCompatibleDC,NULL mov hdcDst,eax invoke SelectObject,hdcSrc, hBitmapSrc invoke SelectObject,hdcDst, hBitmapDst invoke BitBlt,hdcDst, 0, 0, bitmap.bmWidth, bitmap.bmHeight,hdcSrc, 0, 0, SRCCOPY invoke DeleteDC,hdcSrc invoke DeleteDC,hdcDst mov eax,hBitmapDst ret CopyBitmap endp
GetABS proc value:DWORD cmp value,0 jl @f mov eax,value jmp GoEnd @@: mov eax,value neg eax GoEnd: ret GetABS endp
WndProc proc hwnd:DWORD,uMsg:DWORD,wParam :DWORD,lParam :DWORD LOCAL bm:BITMAP LOCAL hBitmapClip:HBITMAP LOCAL hdc, hdcMem:HDC LOCAL iEnable:DWORD LOCAL ps:PAINTSTRUCT LOCAL rect:RECT
.if uMsg == WM_LBUTTONDOWN ;按下左键 .if bCapturing==0 ;如果此时还没有开始抓图 invoke GetDesktopWindow ;则取得整个窗口的handle mov hwndScr,eax ;invoke LockWindowUpdate,eax ;锁定指定窗口,禁止它更新 .if eax!=0 mov bCapturing,TRUE ;标记开始抓图 invoke SetCapture,hwnd ;在指定窗口里设置鼠标捕获 invoke LoadCursor,NULL, IDC_CROSS invoke SetCursor,eax .else invoke MessageBeep,0 .endif .endif xor eax,eax ret .elseif uMsg == WM_RBUTTONDOWN ;按下右键 .if bCapturing!=0 ;如果已经开始抓图 mov bBlocking,TRUE ;设置抓图区域标志 mov eax,lParam and eax,0FFFFh mov ptBeg.x,eax
mov eax,lParam shr eax,16 mov ptBeg.y,eax ;鼠标的坐标 mov esi,offset ptBeg mov edi,offset ptEnd mov eax,[esi] mov [edi],eax mov eax,[esi+4] mov [edi+4],eax
invoke InvertBlock,hwndScr, hwnd,addr ptBeg,addr ptEnd .endif xor eax,eax ret .elseif uMsg == WM_MOUSEMOVE .if bBlocking!=0 invoke InvertBlock,hwndScr, hwnd,addr ptBeg,addr ptEnd mov eax,lParam and eax,0FFFFh mov ptEnd.x,eax
mov eax,lParam shr eax,16 mov ptEnd.y,eax invoke InvertBlock,hwndScr, hwnd,addr ptBeg,addr ptEnd .endif xor eax,eax ret .elseif (uMsg == WM_LBUTTONUP)||(uMsg == WM_RBUTTONUP) .if bBlocking!=0 invoke InvertBlock,hwndScr, hwnd,addr ptBeg,addr ptEnd mov eax,lParam and eax,0FFFFh mov ptEnd.x,eax
mov eax,lParam shr eax,16 mov ptEnd.y,eax .if hBitmap!=0 invoke DeleteObject,hBitmap mov hBitmap,NULL .endif invoke GetDC,hwnd mov hdc,eax invoke CreateCompatibleDC,hdc mov hdcMem,eax mov esi,offset ptBeg mov edi,offset ptEnd mov eax,[edi+4] sub eax,[esi+4] invoke GetABS,eax push eax mov eax,[edi] sub eax,[esi] invoke GetABS,eax push eax push hdc call CreateCompatibleBitmap ;invoke CreateCompatibleBitmap (hdc,abs (ptEnd.x - ptBeg.x),abs (ptEnd.y - ptBeg.y)) ; mov hBitmap,eax invoke SelectObject,hdcMem, hBitmap
mov esi,offset ptBeg mov edi,offset ptEnd push SRCCOPY mov eax,[edi+4] sub eax,[esi+4] push eax mov eax,[edi] sub eax,[esi] push eax push [esi+4] push [esi] push hdc mov eax,[edi+4] sub eax,[esi+4] invoke GetABS,eax push eax mov eax,[edi] sub eax,[esi] invoke GetABS,eax push eax push 0 push 0 push hdcMem call StretchBlt ;invoke StretchBlt (hdcMem, 0, 0,abs (ptEnd.x - ptBeg.x),abs (ptEnd.y - ptBeg.y), ; hdc, ptBeg.x, ptBeg.y, ptEnd.x - ptBeg.x,ptEnd.y - ptBeg.y, SRCCOPY) ; invoke DeleteDC,hdcMem invoke ReleaseDC,hwnd, hdc invoke InvalidateRect,hwnd, NULL, TRUE .endif .if (bBlocking!=0) || (bCapturing!=0) mov eax,FALSE mov bBlocking,eax mov bCapturing,eax invoke LoadCursor,NULL, IDC_ARROW invoke SetCursor,eax invoke ReleaseCapture invoke LockWindowUpdate,NULL .endif xor eax,eax ret .elseif (uMsg == WM_INITMENUPOPUP) invoke IsClipboardFormatAvailable,CF_BITMAP .if eax==0 mov eax,MF_GRAYED .else mov eax,MF_ENABLED .endif invoke EnableMenuItem,wParam, IDM_EDIT_PASTE, iEnable .if hBitmap==0 mov eax,MF_GRAYED .else mov eax,MF_ENABLED .endif mov iEnable,eax invoke EnableMenuItem,wParam, IDM_EDIT_CUT, iEnable invoke EnableMenuItem,wParam, IDM_EDIT_COPY, iEnable invoke EnableMenuItem,wParam, IDM_EDIT_DELETE, iEnable xor eax,eax ret .elseif (uMsg == WM_COMMAND) mov eax,lParam and eax,0FFFFh .if (eax==IDM_EDIT_CUT)||(eax==IDM_EDIT_COPY) .if hBitmap!=0 invoke CopyBitmap,hBitmap mov hBitmapClip,eax invoke OpenClipboard,hwnd invoke EmptyClipboard invoke SetClipboardData,CF_BITMAP, hBitmapClip .endif mov eax,lParam and eax,0FFFFh .if eax==IDM_EDIT_COPY xor eax,eax ret .endif jmp @f ;fall through for IDM_EDIT_CUT .elseif eax==IDM_EDIT_DELETE @@: .if hBitmap!=0 invoke DeleteObject,hBitmap mov hBitmap,NULL .endif invoke InvalidateRect,hwnd, NULL, TRUE xor eax,eax ret .elseif eax== IDM_EDIT_PASTE .if (hBitmap!=0) invoke DeleteObject,hBitmap mov hBitmap,NULL .endif invoke OpenClipboard,hwnd invoke GetClipboardData,CF_BITMAP mov hBitmapClip,eax .if (hBitmapClip!=0) invoke CopyBitmap,hBitmapClip mov hBitmap,eax .endif invoke CloseClipboard invoke InvalidateRect,hwnd, NULL, TRUE xor eax,eax ret .endif .elseif (uMsg == WM_PAINT) invoke BeginPaint,hwnd,addr ps mov hdc,eax .if (hBitmap!=0) invoke GetClientRect,hwnd,addr rect invoke CreateCompatibleDC,hdc mov hdcMem,eax invoke SelectObject,hdcMem, hBitmap invoke GetObject,hBitmap, sizeof (BITMAP), addr bm invoke SetStretchBltMode,hdc, COLORONCOLOR invoke StretchBlt,hdc, 0, 0, rect.right, rect.bottom,hdcMem, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY invoke DeleteDC,hdcMem .endif invoke EndPaint,hwnd,addr ps xor eax,eax ret .elseif (uMsg == WM_DESTROY) .if (hBitmap!=0) invoke DeleteObject,hBitmap .endif invoke PostQuitMessage,0 xor eax,eax ret .endif
invoke DefWindowProc,hwnd,uMsg,wParam,lParam ret WndProc endp END START
由于鼠标拦截的限制,所以开始使用BLOWUP时会有些困难,需要逐渐适应。下面是使用本程序的方法:
在BLOWUP显示区域按下鼠标左键不放,鼠标指针会变成「+」字型。 继续按住左键,将鼠标移到屏幕上的任何其它位置。鼠标光标的位置就是您要圈选的矩形区域的左上角。 继续按住左键,按下鼠标右键,然后拖动鼠标到您要圈选的矩形区域的右下角。释放鼠标左键和右键。(释放鼠标左、右键次序无关紧要。) 鼠标光标恢复成箭头状,这时您圈选的矩形区域已复制到了BLOWUP的显示区域,并作了适当的压缩或拉伸变化。
如果您从右上角到左下角选取的话,BLOWUP将显示矩形区域的镜像。如果从左下到右上角选取,BLOWUP将显示颠倒的图像。如果从右上角至左上角选取,程序将综合两种效果。
BLOWUP还 包含将位图复制到剪贴簿,以及将剪贴簿中的位图复制到程序的处理功能。BLOWUP处理WM_INITMENUPOPUP消息来启用或禁用「Edit」菜单中的不同选项,并通过WM_COMMAND消息来处理这些菜单项。您应 该对这些程序代码的结构比较熟悉,因为它们与第十二章 中的复制和粘贴文字项目的处理方式在本质上是一样的。
不过,对于位图,剪贴簿对象不是整体句柄而是位图句柄。当您使用CF_BITMAP时, GetClipboardData函数传回一个HBITMAP对象,而且SetClipboardData函数接收一个HBITMAP对象。如果您想将位图传送给剪贴簿又想保留副本以供程序本身使用,那么您必须复制位图。同样,如 果您从剪贴簿上粘贴了一幅位图,也应该做一个副本。BLOWUP中的CopyBitmap函数是通过取得现存位图的BITMAP结构,并在CreateBitmapIndirect函数中用这个结构建立一个新位图来完成此项操作的。( 变量名的后缀Src和Dst分别代表「来源」和「目的」。) 两个位图都被选进内存设备内容,而且通过呼叫BitBlt来复制位图内容。(另一种复制位的方法,可以先按位图大小配置一块内存,然后为来源位图呼叫GetBitmapBits,为目的位图呼叫 SetBitmapBits。)
我发现BLOWUP对于检查Windows及其应用程序中大量分散的小位图和图片非常有用