青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

luqingfei@C++

為中華之崛起而崛起!
兼聽則明,偏聽則暗。

Win32匯編--圖形操作--GDI原理

 

Win32匯編--圖形操作--GDI原理

 

Windows是基于圖形界面的,所以在Win32編程中,圖形操作是最常用的操作。GDI的意義在于將程序對圖形界面的操作和硬件設備隔絕開來,在程序中可以將所有的圖形設備都看成是虛擬設備,包括視頻顯示器和打印機等,然后通過GDI函數(shù)用同樣的方法去操作它們,由Windows負責將函數(shù)調(diào)用轉化成針對具體硬件的操作。只要一個設備提供了和Windows兼容的驅動程序,它就可以被看做是一個標準的設備。以前在DOS系統(tǒng)下寫應用程序的時候,如果要進行圖形操作,那么就要考慮到市場上每種顯示卡的不同,否則在裝配某種顯卡的計算機上就可能無法正常運行,對匯編程序員來說,這真是一個惡夢。在Win32編程中,正是GDI函數(shù)讓這個惡夢成為歷史。

 

GDI函數(shù)全部包括在GDI32.DLL中,在編程的時候,注意要在源程序的開頭加上相應的包含語句:

include          gdi32.inc

includelib       gdi32.lib

 

GDI相關的內(nèi)容真是太龐大了,只要查看一下gdi32.inc文件就可以發(fā)現(xiàn),函數(shù)的總數(shù)達到了300多個,和GDI相關的數(shù)據(jù)結構也非常多,為了能了解GDI的原理和基本的使用方法:

歸納起來,GDI操作可以從3個方面去了解——When, WhereHow

When——指的是進行圖形操作的時機,究竟什么時刻最適合程序進行圖形操作呢?——“GDI程序的結構”

Where——指的是圖形該往哪里畫,既然Windows隔離了硬件圖形設備,那么該把什么地方當做“下筆”的地方呢?——“設備環(huán)境”

How——了解了上面兩個問題后,最后還要知道“如何畫”,這就涉及如何使用大部分GDI函數(shù)的問題了。

 

 

一、GDI程序的結構

1、客戶區(qū)的刷新

正如上面所說的,這里討論的是“When”的問題,讀者可能會問:為什么會有這個問題,如果要向窗口輸出圖形,程序想在什么時候輸出那就是什么時候,難道這個時刻還有規(guī)定不成?

 

DOS操作系統(tǒng)中編程的時候,程序把文字或圖形輸出到屏幕,在輸出新的內(nèi)容之前,這些內(nèi)容總是保留在屏幕原處,這些內(nèi)容會被意外覆蓋的唯一情況是激活一個TSR程序,但TSR程序在退出之前有義務恢復原來的屏幕,如果它無法恢復屏幕的內(nèi)容,那么這是它的責任,我們不會在自己的程序中去考慮屏幕內(nèi)容會無緣無故消失這種情況,所以可以把屏幕看成是應用程序私有的。

 

如果程序輸出的內(nèi)容過多,如用dir顯示一個含有很多文件的目錄,用戶根本無法看清快速上翻的屏幕,這時程序可以設計一個參數(shù)來暫停一下,如dir/p。這已經(jīng)是DOS程序最“體貼”的做法了,如果用戶想回過頭去看已經(jīng)滾出屏幕的內(nèi)容,那可對不起,只能再執(zhí)行一遍了!

 

所以對DOS程序來說,程序想在什么時候輸出信息那就是什么時候,根本不存在When這個問題。

 

但在Windows操作系統(tǒng)中,屏幕是多個程序“公用”的,用戶程序不要指望輸出到窗口中的內(nèi)容經(jīng)過一段時間后還會保留在那里,它們可能被別的東西覆蓋,如其他窗口、鼠標箭頭或下拉的菜單等。在Windows中,恢復被覆蓋內(nèi)容的責任大部分屬于用戶程序自己,理由很簡單:Windows是個多任務的操作系統(tǒng),假如程序B覆蓋了程序A的窗口內(nèi)容,覆蓋掉的內(nèi)容由程序B負責恢復的話,它就必須保存它覆蓋掉的內(nèi)容,但是在它將保存的內(nèi)容恢復之前,程序A也在運行,并可能在程序B恢復以前已經(jīng)向它自己的窗口輸出新的內(nèi)容,結果當程序B恢復它保存的窗口內(nèi)容時,保存的內(nèi)容可能是過時的(而DOS的情況就不同,TSR程序激活的時候,用戶程序是被掛起的),所以最好的辦法就是讓程序A自己來決定如何恢復。

 

Windows系統(tǒng)采用的方法是:當Windows檢測到窗口被覆蓋的地方需要恢復的時候,它會向用戶程序發(fā)送一個WM_PAINT消息,消息中包括了需要恢復的區(qū)域,然后由用戶程序來決定如何恢復被覆蓋的內(nèi)容。

 

如果程序因為忙于處理其他事務以至于無法及時響應WM_PAINT消息,那么窗口客戶區(qū)原先被覆蓋的地方可能會被Windows暫時畫成一塊白色(或者背景色)的矩形,或者根本就是保留被覆蓋時的情形,直到程序有時間去響應WM_PAINT消息為止。我們常常可以看到這種情況發(fā)生在死鎖程序的客戶區(qū)內(nèi),這就是因為死鎖的程序無法響應WM_PAINT消息來恢復客戶區(qū)造成的。

 

所以對于“When”這個問題,答案是:程序應該在Windows要求的時候繪畫客戶區(qū),也就是在收到WM_PAINT消息的時候。如果程序需要主動刷新客戶區(qū),那么可以通過調(diào)用InvalidateRect等函數(shù)引發(fā)一條WM_PAINT消息,因為在WM_PAINT消息中刷新客戶區(qū)的代碼是必須存在的,所以用這種看似“舍近求遠”的辦法實際上可以節(jié)省一份重復的代碼。即使是在游戲程序這種“主動刷新”遠遠多于“被動刷新”的程序中,只要窗口有被其他東西覆蓋的可能,那么這個原則就是適用的。

 

2GDI程序的結構

對于Win32程序來說,WM_PAINT消息隨時可能發(fā)生,這就意味著,程序再也不能像在DOS下一樣輸出結果后就不管了,反過來,程序在任何時刻都應該知道如何恢復整個或局部客戶區(qū)中以前輸出的內(nèi)容。

 

如果程序的功能比較簡單,可以將計算及刷新整個客戶區(qū)的代碼全部安排在WM_PAINT消息中完成,這樣,每次當客戶區(qū)的全部或部分需要被更新的時候,程序重新執(zhí)行整個生成客戶區(qū)屏幕數(shù)據(jù)的功能模塊并刷新客戶區(qū)。這種結構適用于功能模塊很短小且執(zhí)行速度很快的情況,整個過程的時間最好不超過幾百ms,否則,用戶會在一個明顯的等待時間后才看到程序把客戶區(qū)中的“空洞”補上。

 

當生成屏幕數(shù)據(jù)的功能模塊有些復雜的時候,就應該考慮采用如下結構,即功能模塊和客戶區(qū)刷新模塊分別在不同的子程序中實現(xiàn),功能模塊單獨用一個子程序完成,這個子程序可以由用戶通過選擇菜單項在WM_COMMAND消息中執(zhí)行,也可以新建另外一個線和來完成,總之,它最后把計算結果放到一個緩沖區(qū)中,而每當客戶區(qū)需要刷新時,程序在WM_PAINT消息中調(diào)用客戶區(qū)刷新子程序,這個子程序從計算好的緩沖區(qū)中取出數(shù)據(jù)并輸出到客戶區(qū)中,由于單純的屏幕刷新過程是很快的,所以用戶根本來不及看到客戶區(qū)中的空洞。

 

3、探討WM_PAINT消息

當客戶區(qū)被覆蓋并重新顯示的時候,Windows并不是在所有的的下都發(fā)送WM_PAINT消息,下面是幾種不同的情況:

l         當鼠標光標移過窗口客戶區(qū)以及圖標拖過客戶區(qū)這兩種情況,Windows總是自己保存被覆蓋的區(qū)域并恢復它,并不需要發(fā)送WM_PAINT消息通知用戶程序。

l         當窗口客戶區(qū)被自己的下拉式菜單覆蓋,或者被自己彈出的對話框覆蓋后,Windows會嘗試保存被覆蓋的區(qū)域并在以后恢復它,如果因為某種原因無法保存并恢復的話,Windows會發(fā)送一個WM_PAINT消息通知程序。

l         別的情況造成窗口的一部分從不可見變到可見,如程序從最小化的狀態(tài)恢復,其他的窗口覆蓋客戶區(qū)后移開,用戶改變了窗口的大小不一和用戶按動滾動條等,在這些情況下,Windows會向窗口發(fā)送WM_PAINT消息。

l         一些函數(shù)會引發(fā)WM_PAINT消息,如UpdateWindowInvalidateRect以及InvalidateRgn函數(shù)等。

 

窗口過程收到WM_PAINT消息后,并不代表整個客戶區(qū)都需要被刷新,有可能客戶區(qū)被覆蓋的區(qū)域只有一小塊,這個區(qū)域就叫做“無效區(qū)域”,程序只需要更新這個區(qū)域。

 

WM_TIMER消息類似,WM_PAINT消息也是一個低級別的消息,雖然它不會像WM_TIMER消息一樣被丟棄,但Windows總是在消息循環(huán)空的時候才把WM_PAINT放入其中,實際上,Windows為每個窗口維護一個“繪圖信息結構”,無效區(qū)域的坐標就在其中,每當消息循環(huán)空的時候,如果Windows發(fā)現(xiàn)存在一個無效區(qū)域,就會放入一個WM_PAINT消息。

 

無效區(qū)域的坐標并不附帶在WM_PAINT消息的參數(shù)中,在程序中有其他方法可以獲取,WM_PAINT消息只是通知程序有個區(qū)域需要更新而已,所以Windows也不會同時將兩條WM_PAINT消息放入消息循環(huán),當Windows要放入一條WM_PAINT消息的時候,如果發(fā)現(xiàn)已存在一個無效區(qū)域了,那么它只需要把新舊兩個無效區(qū)域合并計算出一個新的無效區(qū)域就可以了,消息循環(huán)中還是只需要一條WM_PAINT消息。

 

由于存在“無效區(qū)域”這樣一個東西,所以程序在WM_PAINT消息中對客戶區(qū)刷新完畢后工作并沒有結束,如果不使無效區(qū)域變得有效,Windows會在下一輪消息循環(huán)中繼續(xù)放入一個WM_PAINT消息。當Windows檢查“繪圖信息結構”的時候發(fā)現(xiàn)沒有了無效區(qū)域,也就不會繼續(xù)發(fā)送WM_PAINT消息了。

 

WM_PAINT消息的處理流程一般是:

.if    eax == WM_PAINT      ;eaxuMsg

       invoke BeginPaint, hWnd, addr stPS

       ;刷新客戶區(qū)的代碼

       invoke EndPaint, hWnd, addr stPS

       xor eax, eax

       ret

 

讀者可以發(fā)現(xiàn)中間并沒有調(diào)用ValidateRect來使無效區(qū)域變得有效,這是因為BeginPaint函數(shù)和EndPaint函數(shù)隱含有這個功能,如果不是以BeginPaint/EndPaint當做消息處理代碼的頭尾的話,那么在WM_PAINT消息返回的時候就必須調(diào)用ValidateRect函數(shù)。

 

BeginPaint函數(shù)的第二個參數(shù)是一個繪圖信息結構的緩沖區(qū)地址,Windows會在這里返回繪圖信息結構,結構中包含了無效區(qū)域的位置和大小,繪圖信息結構的定義如下:

PAINTSTRUCT STRUCT

       hdc                DWORD        ?

       fErase            DWORD        ?

       rcPaint           RECT            <>

       fRestore         DWORD        ?

       fIncUpdate     DWORD        ?

       rgbReserved   BYTE 32 dup(?)

PAINTSTRUCT ENDS

 

其中hdc字段是窗口的設備環(huán)境句柄,rcPaint字段是一個RECT結構,它指定了無效區(qū)域矩形的對角頂點,fErase字段如果為非零值,表示Windows在發(fā)送WM_PAINT消息前已經(jīng)用背景色擦除了無效區(qū)域,后面3個字段是Windows內(nèi)部使用的,應用程序不必去理會它們。

 

 

二、設備環(huán)境

解決了“When”的總是后,再考慮一下“Where”的問題。

DOS操作系統(tǒng)中,向屏幕輸出數(shù)據(jù)實際上是把輸出內(nèi)容拷貝到視頻緩沖區(qū)中,如果在文本模式下顯示信息,只需要把內(nèi)容拷貝到B8000h處的內(nèi)存中;顯示圖形信息,可以把圖形數(shù)據(jù)拷貝到A0000h處的內(nèi)存中。

 

Windows中,GDI接口把程序和硬件分隔出來,在Win32編程中,再也不能通過直接向視頻緩沖區(qū)拷貝數(shù)據(jù)的辦法來顯示信息了,那么,究竟該往哪里輸出圖形呢——這就是“Where”的問題。答案是:通過“設備環(huán)境”來輸出圖形。

1、什么是設備環(huán)境

Windows中,所有與圖形相關的操作都是用統(tǒng)一的方法來完成的(不然就不能稱為“圖形設備接口”了)。不管是繪畫屏幕上的一個窗口,還是把圖形輸出到打印機,或者對一幅位圖進行繪畫,使用的繪圖函數(shù)都是相同的,為了實現(xiàn)方法上的統(tǒng)一,必須將所有的圖形對象看成是一個虛擬的設備,這些設備可能有不同的屬性,如黑白打印機和彩色屏幕的顏色深度是不同的,不同打印機的尺寸和分辨率可能是不同的,繪圖儀只支持矢量而不支持位圖等。不同設備的不同屬性就構成了一個繪圖的“環(huán)境”,就像DOS操作系統(tǒng)中把視頻緩沖區(qū)當做圖形操作的對象一樣,這個繪圖的“環(huán)境”就是Win32編程中圖形操作的對象,把它叫做“設備環(huán)境”。設備環(huán)境實際上是一個數(shù)據(jù)結構,結構中保存的就是設備的屬性,當對設備環(huán)境進行圖形操作的時候,Windows可以根據(jù)這些屬性找到對應的設備進行相關的操作。

 

在實際使用中,通過“設備環(huán)境”可以操作的對象很廣泛,除了可以是打印機或繪圖儀等硬件設備外,也可以是窗口的客戶區(qū),包括大大小小的所有可以被稱為窗口的按鈕與控件等的客戶區(qū),也可以是一個位圖。總之,任何需要用到圖形操作的東西都可以通過“設備環(huán)境”進行繪圖。

 

為了更好地理解“設備環(huán)境”是什么,先來看一個例子:

//DcCopy.asm

                .386

                .model flat, stdcall

                option casemap:none

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

; Include 文件定義

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

include         windows.inc

include         gdi32.inc

includelib      gdi32.lib

include         user32.inc

includelib      user32.lib

include         kernel32.inc

includelib      kernel32.lib

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

ID_TIMER        equ     1

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

; 數(shù)據(jù)段

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

                .data?

hInstance       dd      ?

hWin1           dd      ?

hWin2           dd      ?

 

                .const

szClass1        db      'SourceWindow',0

szClass2        db     'DestWindow',0

szCaption1      db      '請嘗試用別的窗口覆蓋本窗口!',0

szCaption2      db      '本窗口圖像拷貝自另一窗口',0

szText          db      'Win32 Assembly, Simple and powerful!',0

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

                .code

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

; 定時器過程

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

_ProcTimer      proc    _hWnd, uMsg, _idEvent, _dwTime

                local   @hDc1, @hDc2

                local   @stRect:RECT

               

                invoke GetDC, hWin1

                mov     @hDc1, eax

               

                invoke GetDC, hWin2

                mov     @hDc2,eax

               

                invoke GetClientRect, hWin1, addr @stRect

                invoke BitBlt, @hDc2, 0, 0, @stRect.right, @stRect.bottom, @hDc1, 0, 0, SRCCOPY

                invoke ReleaseDC, hWin1, @hDc1

                invoke ReleaseDC, hWin2, @hDc2

                ret

               

_ProcTimer      endp

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

; 窗口過程

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

_ProcWinMain    proc    uses ebx edi esi, hWnd, uMsg, wParam, lParam

                local   @stPs:PAINTSTRUCT

                local   @stRect:RECT

                local   @hDc

               

                mov     eax, uMsg

                mov     ecx, hWnd

;****************************************************************

                .if     eax == WM_PAINT && ecx == hWin1

                        invoke BeginPaint, hWnd, addr @stPs

                        mov     @hDc, eax

                        invoke GetClientRect, hWnd, addr @stRect

                        invoke DrawText, @hDc, addr szText, -1, addr @stRect, DT_SINGLELINE or DT_CENTER or DT_VCENTER

                        invoke EndPaint, hWnd, addr @stPs

;****************************************************************

                .elseif eax == WM_CLOSE

                        invoke PostQuitMessage, NULL

                        invoke DestroyWindow, hWin1

                        invoke DestroyWindow, hWin2

;****************************************************************

                .else

                        invoke DefWindowProc, hWnd, uMsg, wParam, lParam

                        ret

                .endif

;****************************************************************

                xor eax, eax

                ret

_ProcWinMain    endp

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

_WinMain proc

        local   @stWndClass:WNDCLASSEX

        local   @stMsg:MSG

        local   @hTimer

       

        invoke GetModuleHandle, NULL

        mov     hInstance, eax

        invoke RtlZeroMemory, addr @stWndClass, sizeof @stWndClass

;****************************************************************

        invoke   LoadCursor, 0, IDC_ARROW

        mov      @stWndClass.hCursor, eax

        push     hInstance

        pop      @stWndClass.hInstance

        mov      @stWndClass.cbSize, sizeof WNDCLASSEX

        mov      @stWndClass.style, CS_HREDRAW or CS_VREDRAW

        mov      @stWndClass.lpfnWndProc, offset _ProcWinMain

        mov      @stWndClass.hbrBackground, COLOR_WINDOW + 1

        mov      @stWndClass.lpszClassName, offset szClass1

        invoke   RegisterClassEx, addr @stWndClass

        invoke   CreateWindowEx, WS_EX_CLIENTEDGE, offset szClass1, offset szCaption1, WS_OVERLAPPEDWINDOW, 450, 100, 300, 300, NULL, NULL, hInstance, NULL

        mov      hWin1, eax

        invoke   ShowWindow, hWin1, SW_SHOWNORMAL

        invoke   UpdateWindow, hWin1

;****************************************************************

        mov     @stWndClass.lpszClassName, offset szClass2

        invoke RegisterClassEx, addr @stWndClass

        invoke CreateWindowEx, WS_EX_CLIENTEDGE, offset szClass2, offset szCaption2, WS_OVERLAPPEDWINDOW, 100, 100, 300, 300, NULL, NULL, hInstance, NULL

        mov     hWin2, eax

        invoke ShowWindow, hWin2, SW_SHOWNORMAL

        invoke UpdateWindow, hWin2

;****************************************************************

; 設置定時器

;****************************************************************

        invoke SetTimer, NULL, NULL, 100, addr _ProcTimer

        mov     @hTimer, eax

;****************************************************************

; 消息循環(huán)

;****************************************************************

        .while TRUE

                invoke GetMessage, addr @stMsg, NULL, 0, 0

                .break .if eax == 0

                invoke TranslateMessage, addr @stMsg

                invoke DispatchMessage, addr @stMsg

        .endw

;****************************************************************

; 清除定時器

;****************************************************************

        invoke KillTimer, NULL, @hTimer

        ret

_WinMain endp

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

start:

        call    _WinMain

        invoke ExitProcess, NULL

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

        end start

這個程序的代碼用到的大部分知識都是前面已經(jīng)講到的,在_WinMain中,用一個同樣的窗口類建立了兩個窗口,兩個窗口屬于同一個窗口類,所以它們的窗口過程都是_ProcWinMain,為了關閉任何一個窗口都可以結束程序,WM_CLOSE消息中用DestroyWindow函數(shù)摧毀了兩個窗口。程序設置了一個周期為100ms的定時器,Windows會每隔100ms調(diào)用_ProcTimer子程序。在_ProcTimer中,將其中一個窗口的客戶區(qū)拷貝到另一個窗口的客戶區(qū)中,方法是通過GetDC獲取窗口的DC句柄,并用BitBlt函數(shù)完成拷貝工作,所以在右邊的窗口顯示了一句“Win32 Assembly, Simple and powerful!”,左邊的窗口中也會出現(xiàn)這句話。

 

程序每100ms將右邊窗口的客戶區(qū)拷貝到左邊的窗口客戶區(qū)中,通過左邊窗口的客戶區(qū)就可以了解右邊客戶區(qū)的DC對應的究竟是什么內(nèi)容。

 

通過左邊窗口的變化可以驚奇地發(fā)現(xiàn):右邊窗口客戶區(qū)的內(nèi)容并不是程序自己輸出到客戶區(qū)的那句文本,而是以客戶區(qū)為矩形區(qū)域的屏幕上我們真正看到的東西,它竟然包括其他窗口覆蓋在上面的東西。這就意味著,掃雷游戲和紙牌游戲通過自己客戶區(qū)對應的設備環(huán)境畫圖形,圖形數(shù)據(jù)竟然畫到了DcCopy窗口客戶區(qū)對應的設備環(huán)境中。

 

這個例子驗證了“設備環(huán)境”只是“環(huán)境”而不是“設備”,它并不存儲發(fā)給它的圖形數(shù)據(jù),圖形數(shù)據(jù)透過它寫到了它所描述的“設備”上,每個窗口客戶區(qū)的“設備環(huán)境”對應的設備都是屏幕,但它們在位置上可能重疊,所以向一個窗口的客戶區(qū)寫數(shù)據(jù)相當于同時寫了下層窗口的客戶區(qū)。

 

為了讓當前激活的窗口在視覺上保持在最上面,下層窗口向自己客戶區(qū)寫的內(nèi)容首先要經(jīng)過Windows的“過濾”,只有沒有被其他窗口覆蓋掉的部分才真正被寫到了屏幕上。

 

讀者應該時刻提醒自己——“設備環(huán)境”只是一個環(huán)境,是設備屬性的一組定義,程序輸出的圖形數(shù)據(jù)透過“設備環(huán)境”被定向到了具體的設備上,“設備環(huán)境”本身并不存儲這些數(shù)據(jù)。

 

Device ContextContext的含義:設備環(huán)境的上面是應用程序,下面是具體設備,而它是用來“聯(lián)系上下關系”用的。

 

有人可能認為:屏幕上的窗口就像放在桌面上的一張張紙,雖然一張紙可能暫時被另一張遮住,但紙上寫的東西還是存在的,移開另一張紙就可以再次露出來。但實際情況是:桌面更像一個用粉筆寫的公告黑板,一個窗口相當于劃了一塊空間寫告示,寫另一個告示的時候要把老告示的內(nèi)容擦去一部分以便寫新的內(nèi)容,擦去的東西也就不存在了,如果要恢復老告示,那么必須把擦去的部分重新寫上去。

 

2、獲取設備環(huán)境句柄

要想對任何設備繪圖,首先必須獲取設備的“設備環(huán)境句柄”(hDC),幾乎所有的GDI函數(shù)的操作目標都是hDC,在程序中得到一個hDC有幾種方法。

 

最常用的方法是在WM_PAINT消息中用BeginPaint函數(shù)得到hDCWM_PAINT消息的代碼結構一般是:

.if    eax == WM_PAINT      ;eaxuMsg

       invoke BeginPaint, hWnd, addr stPS

       ;刷新客戶區(qū)的代碼

       invoke EndPaint, hWnd, addr stPS

       xor eax, eax

       ret

 

BeginPaint函數(shù)的返回值就是需要刷新區(qū)域的hDC。要注意的是:BeginPaint返回的hDC對應的尺寸僅是無效區(qū)域,無法用它繪畫到這個區(qū)域以外的地方去。由于窗口過程每次接收WM_PAINT消息時的無效區(qū)域可能是不同的,所以這個hDC的值僅在WM_PAINT消息中有效,程序不應該保存它并把它用在WM_PAINT消息以外的代碼中。基于同樣的道理,BeginPaintEndPaint函數(shù)只能用在WM_PAINT消息中,因為只有這時候才存在無效區(qū)域。

 

程序中常常有這種需求,就是在非WM_PAINT消息中主動繪畫客戶區(qū),由于BeginPaintEndPaint函數(shù)必須在WM_PAINT消息中使用,所以這時必須用另外的方法獲取hDC,可以使用以下的方法:

invoke GetDC, hWnd            ;獲取hDC

;返回值是hDC

;繪圖代碼

invoke ReleaseDC, hWnd      ;釋放hDC

 

GetDC函數(shù)返回的hDC對應窗口的整個客戶區(qū),當使用完畢的時候,hDC必須用ReleaseDC函數(shù)釋放。對于用GetDC獲取的hDCWindows建議使用的范圍限于單條消息內(nèi),當程序在處理某條消息的時候需要繪畫客戶區(qū)時,可以用GetDC獲取hDC,但在消息返回前,必須用ReleaseDC將它釋放掉,如果在下一條消息中需要繼續(xù)使用到hDC,那么必須重新用GetDC函數(shù)獲取。

 

上面的兩種方法獲取的hDC都是窗口的hDC,如果要操作的是其他的東西,如打印機、位圖等,就不能使用BeginPaintGetDC函數(shù)了。當繪圖的對象是一個設備的時候,可以用CreateDC函數(shù)來建立一個DC

invoke CreateDC, lpszDriver, lpszDevice, lpszOutput, lpInitData

lpszDriver指向設備名稱,如顯示設備的設備名是DISPLAY,打印機的設備名一般為WINSPOOL,下面這幾句代碼建立的DC對應整個屏幕:

szDriver         db    “DISPLAY”,0

                    

                     invoke CreateDC, addr szDriver, NULL, NULL, NULL

                     mov hDC, eax

 

當繪圖對象是位圖的時候,同樣需要一個和位圖句柄相聯(lián)系的DC,這時可以用函數(shù)CreateCompatibleDC來創(chuàng)建一個顯示表面僅存在于內(nèi)存中的DC

invoke CreateCompatibleDC, hDC

參數(shù)中的hDC是用來參考的DC句柄,如果指定的參數(shù)是NULL,那么建立的DC將和當前屏幕的設置兼容,為了用CreateCompatibleDC建立的DC繪畫一個位圖,還需要用SelectObject函數(shù)將hDC和位圖句柄聯(lián)系起來。

 

CreateDCCreateCompatibleDC函數(shù)建立的hDC在使用結束以后,必須用DeleteDC函數(shù)刪除,注意這里不能用ReleaseDC,這個函數(shù)是和GetDC配合用的。

 

BeginPaint/EndPaint以及GetDC獲取的hDC的使用時間不能超出本條消息,與此相比,用CreateDC以及CreateCompatibleDC建立的hDC就沒有這個限制,可以在任何時刻建立它并且一直使用到不再需要為止。

 

 

三、色彩和坐標

1Windows中的色彩

可以表示的顏色總數(shù)由顏色深度決定,也就是存儲每個像素所用的位數(shù),各種顯示設備可以顯示的顏色總數(shù)可能大不相同,如果設備支持的顏色深度太淺,就會影響到圖像的質量,會讓人看起來覺得很粗糙和不自然。

 

一種顏色可以分解成紅、綠、藍三原色,所以可以用紅、綠、藍3個分量的組合來表示各種顏色。

 

當設備支持的顏色深度少于等于8位時(如8位(256色)、4位(16色)、2位(4色)或1位(2色)),總體位數(shù)太少,不足以用來表達3個顏色分量,這時系統(tǒng)建立一個色彩表,像素數(shù)據(jù)用來做索引在色彩表中獲取顏色值,所以低于8位的顏色稱為索引色。

 

只有當顏色深度大于8位的時候,像素數(shù)據(jù)中才直接包含紅、綠、藍3個分量。當顏色深度為16位的時候,紅、綠、藍各用5位表示,剩下的1位用做屬性位,實際可以表示的顏色數(shù)目為2^15=32 768種,16位深度的彩色又稱為16位色、高彩色或增強色。當顏色深度為24位的時候,3個分量各用8位表示,實際可以表示的顏色數(shù)目為2^24=16777216種,24位深度的彩色又稱為24位色、16M色或真彩色。對于人的眼睛來說,超過16位的顏色就已經(jīng)很難分辨了。

 

Win32的編程中,統(tǒng)一使用32位的整數(shù)表示一個深度為24位的顏色,在這32位中只使用低24位,每一種原色分量占用8位,其中0~7位為紅色,8~15位為綠色,16~23位為藍色。在程序中用到一種顏色常數(shù)的時候,可以如下使用:

move ax, 紅色 + 綠色*100h + 藍色*10000h ;將顏色放入eax

 

當顯示設備無法表示24位色的時候,Windows會自動用設備可以顯示的最接近的顏色來代替它,當顯示設備的顏色深度比較低的時候,可以通過函數(shù)GetNearestColor來得知一種顏色(顏色)會被系統(tǒng)替換成哪種顏色:

invoke GetNearestColor, hDC, dwColor       ;返回真正使用的顏色值

 

但是當顯示設備顏色深度太低的時候,經(jīng)過Windows自動轉換的圖像可能會讓人覺得很不自然,所以在有些時候,程序員可能希望預先得知設備的顏色深度,然后根據(jù)具體情況顯示不同的圖形。

 

顯示設備的顏色深度可以用以下函數(shù)獲取:

invoke GetDeviceCaps, hDC, PLANES

mov ebx, dwPlanes

invoke GetDeviceCaps, hDC, BITSPIXEL

mul ebx

mov dwColorDepth, eax

 

第一個函數(shù)調(diào)用返回DC的色彩平面數(shù),第二個函數(shù)調(diào)用返回每個像素的色彩位數(shù),顏色深度最后可以通過dwPlanes乘以dwBitsPixel得到。

 

 

2Windows中的坐標系

要用GDI函數(shù)繪圖,就必須首先了解這些函數(shù)使用的坐標系,在默認的狀態(tài)下,Windows坐標系以左上角做坐標原點,以右方當做X坐標的正方向,以下方當做Y坐標的正方向。坐標的數(shù)值要用一個有符號的16位數(shù)來表示,范圍從-32 768~32767,坐標的單位為像素。這種坐標系定義方法的好處是:窗口中每一點的坐標不會因為窗口的大小改變而改變,試想一下,如以數(shù)學中通常的表示方法,以左下角做坐標原點,那么當窗口高度被用戶調(diào)整的時候,客戶區(qū)中每一點的Y坐標都會變化,在具體使用中就會有諸多不便。

 

但是Windows也提供了其他的一些坐標映射方法供程序員使用,可以用SetMapMode函數(shù)來為一個DC設置新的坐標映射方法:

invoke SetMapMode, hDC, iMapMode

可以設置的參數(shù)包括坐標原點、坐標和邏輯單位和坐標的正方向等,參數(shù)中的iMapMode為新的映射方式,其可以選擇的取值如下表所示,Windows默認使用的映射方式為MM_TEXT

Windows中可用的坐標映射方式

映射方式

原點

邏輯單位

X正方向

Y正方向

MM_TEXT(默認方式)

左上

像素

MM_HIENGLISH

左上

0.001英寸

MM_LOENGLISH

左上

0.01英寸

MM_HIMETRIC

左上

0.01毫米

MM_LOMETRIC

左上

0.1毫米

MM_TWIPS

左上

1/1440英寸

MM_ISOTROPIC

可變

可變(x=y

可變

可變

MM_ANISOTROPIC

可變

可變(x!=y

可變

可變

 

可以看到,除了默認的MM_TEXT方式外,下面5種映射方式:MM_HIENGLISHMM_LOENGLISHMM_HIMETRICMM_LOMETRICMM_TWIPS采用的都是原點位于左上角、X正方向向上的映射方式,另外,它們的坐標邏輯單位是不同的。

 

最后的兩種映射方式MM_ISOTROPICMM_ANISOTROPIC提供了更靈活的選擇,設置為這兩種映射方式后,程序可以繼續(xù)調(diào)用SetViewportOrgExSetViewportExtExSetWindowExtEx函數(shù)來自由設置坐標系的原點、邏輯單位和坐標的正方向等所有參數(shù)。在其他映射方式下的時候,不能使用這3個設置函數(shù),這時任何對它們的調(diào)用都會被忽略。

 

posted on 2010-09-24 13:51 luqingfei 閱讀(4219) 評論(2)  編輯 收藏 引用 所屬分類: Win32匯編程語言序設計

評論

# re: Win32匯編--圖形操作--GDI原理 2011-11-21 14:55 Jet

好,十分有用  回復  更多評論   

# re: Win32匯編--圖形操作--GDI原理 2015-05-02 15:07 11

感謝  回復  更多評論   

導航

<2025年10月>
2829301234
567891011
12131415161718
19202122232425
2627282930311
2345678

統(tǒng)計

留言簿(6)

隨筆分類(109)

隨筆檔案(105)

Blogers

Game

Life

NodeJs

Python

Useful Webs

大牛

搜索

積分與排名

最新評論

閱讀排行榜

評論排行榜

青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            亚洲欧美视频| 玖玖精品视频| 欧美一区二粉嫩精品国产一线天| 黄色成人在线网站| 国产欧美日韩一区二区三区在线| 欧美三级在线视频| 欧美日韩精品一本二本三本| 欧美一区2区三区4区公司二百| 亚洲欧美国产另类| 久久精品成人一区二区三区| 久久精品一区二区三区四区| 久久久噜噜噜久久中文字幕色伊伊| 欧美一区二区精品在线| 久久久91精品| 欧美精品久久99| 国产精品大全| 国内精品久久国产| 亚洲靠逼com| 性感少妇一区| 亚洲第一成人在线| 一区二区欧美精品| 久久国产主播| 欧美手机在线| 亚洲第一中文字幕| 午夜国产不卡在线观看视频| 久久婷婷久久| 亚洲精品综合精品自拍| 午夜在线播放视频欧美| 欧美精品久久久久久久| 日韩一区二区免费高清| 午夜精品久久久| 免费日韩成人| 国产日韩视频| 亚洲最新视频在线| 久久人体大胆视频| 一区二区三区精品视频| 免费欧美电影| 国产在线视频不卡二| 亚洲一区二区免费| 亚洲第一区色| 久久激情网站| 国产欧美亚洲精品| 亚洲免费伊人电影在线观看av| 欧美日韩国产探花| 久久午夜视频| 国产麻豆日韩欧美久久| 亚洲乱码久久| 另类人畜视频在线| 亚洲图片在区色| 欧美电影专区| 亚洲国产成人精品久久| 亚洲欧美春色| 99精品视频网| 欧美日韩1区2区3区| 91久久亚洲| 欧美激情偷拍| 嫩模写真一区二区三区三州| 精品av久久707| 久久香蕉国产线看观看av| 亚洲一区免费| 国产精品一区二区三区成人| 亚洲一区二区欧美日韩| 亚洲免费观看| 欧美日韩精品免费 | 老司机亚洲精品| 国产一区清纯| 久久精品欧洲| 久久美女性网| 亚洲精品一区二区三区四区高清| 欧美国产日韩一区| 免费高清在线一区| 最新成人av网站| 亚洲精品欧美| 欧美久久电影| 亚洲视频在线播放| 亚洲永久精品国产| 国模精品娜娜一二三区| 农村妇女精品| 欧美国产日韩一区二区| 9l国产精品久久久久麻豆| 亚洲视频视频在线| 亚洲精品偷拍| 国产精品一区免费在线观看| 久久gogo国模啪啪人体图| 久久成人在线| 99re66热这里只有精品4| 日韩网站在线看片你懂的| 国产欧美欧美| 欧美二区在线看| 国产精品va在线播放| 久久九九全国免费精品观看| 久久亚裔精品欧美| 亚洲深夜福利在线| 久久精品女人的天堂av| 一区二区冒白浆视频| 午夜精品视频在线观看| 亚洲黄色在线视频| 亚洲字幕在线观看| 亚洲精品乱码| 午夜亚洲福利| 一本色道久久综合亚洲二区三区| 亚洲综合成人婷婷小说| 亚洲精品国产精品国自产在线 | 亚洲一区二区精品| 在线欧美日韩国产| 中文有码久久| 亚洲精品一区二区三区四区高清| 亚洲视频你懂的| 亚洲精品色婷婷福利天堂| 亚洲在线不卡| 亚洲乱码精品一二三四区日韩在线 | 亚洲免费一区二区| 亚洲欧洲综合另类| 久久精品夜色噜噜亚洲a∨| 一区二区免费看| 美女诱惑黄网站一区| 久久蜜桃香蕉精品一区二区三区| 欧美日韩亚洲国产一区| 欧美高清不卡在线| 激情五月综合色婷婷一区二区| 在线视频一区观看| 日韩亚洲欧美一区二区三区| 久久视频精品在线| 久久综合一区| 国产午夜精品全部视频在线播放| 亚洲视频精选| 欧美激情无毛| 欧美高清你懂得| 在线电影一区| 久久精品亚洲精品国产欧美kt∨| 欧美一区网站| 国产精品久久夜| 一本色道综合亚洲| 亚洲视频在线一区| 欧美四级电影网站| 一区二区三区欧美视频| 亚洲视频一区| 国产精品久久久久久影院8一贰佰| 亚洲精品日日夜夜| 中文日韩在线| 国产精品久久久久久福利一牛影视| 亚洲伦理一区| 午夜激情综合网| 国产亚洲一区二区在线观看| 欧美一区二区三区免费视频| 久久久噜噜噜久噜久久| 韩国av一区二区三区四区| 久久精品在这里| 欧美h视频在线| 亚洲精品麻豆| 欧美三级在线播放| 亚洲自拍16p| 鲁大师影院一区二区三区| 亚洲第一二三四五区| 欧美肥婆bbw| 亚洲视频第一页| 久久久久久久综合色一本| 亚洲电影免费观看高清完整版在线| 六月婷婷久久| 亚洲无亚洲人成网站77777 | 日韩一级大片| 亚洲网站在线播放| 黄色国产精品| 欧美**字幕| 99国产一区二区三精品乱码| 亚洲欧美一区二区三区极速播放| 国产人成精品一区二区三| 久久精品在线观看| 91久久国产自产拍夜夜嗨| 亚洲欧美日韩精品在线| 国内精品久久久久国产盗摄免费观看完整版 | 国产精品一区二区三区免费观看 | 欧美日韩国产综合在线| 一本色道综合亚洲| 国产精品一区二区在线观看| 久久久久久久999精品视频| 亚洲每日在线| 免费成人高清在线视频| 一本久久综合亚洲鲁鲁| 国产综合久久久久久鬼色| 欧美日韩大陆在线| 久久国产精品亚洲va麻豆| 99re在线精品| 欧美成人r级一区二区三区| 亚洲主播在线播放| 亚洲激情电影在线| 国产一区二区三区在线观看视频| 欧美女人交a| 久久偷窥视频| 欧美亚洲尤物久久| 9国产精品视频| 亚洲国产精品久久91精品| 久久精品国产精品亚洲精品| aa日韩免费精品视频一| 一区二区在线视频| 国产精一区二区三区| 欧美三级乱人伦电影| 欧美精品乱人伦久久久久久| 乱码第一页成人| 久久九九全国免费精品观看|