• <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>

            luqingfei@C++

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

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

             

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

             

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

             

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

            include          gdi32.inc

            includelib       gdi32.lib

             

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

            歸納起來(lái),GDI操作可以從3個(gè)方面去了解——When, WhereHow

            When——指的是進(jìn)行圖形操作的時(shí)機(jī),究竟什么時(shí)刻最適合程序進(jìn)行圖形操作呢?——“GDI程序的結(jié)構(gòu)”

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

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

             

             

            一、GDI程序的結(jié)構(gòu)

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

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

             

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

             

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

             

            所以對(duì)DOS程序來(lái)說(shuō),程序想在什么時(shí)候輸出信息那就是什么時(shí)候,根本不存在When這個(gè)問(wèn)題。

             

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

             

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

             

            如果程序因?yàn)槊τ谔幚砥渌聞?wù)以至于無(wú)法及時(shí)響應(yīng)WM_PAINT消息,那么窗口客戶區(qū)原先被覆蓋的地方可能會(huì)被Windows暫時(shí)畫成一塊白色(或者背景色)的矩形,或者根本就是保留被覆蓋時(shí)的情形,直到程序有時(shí)間去響應(yīng)WM_PAINT消息為止。我們常常可以看到這種情況發(fā)生在死鎖程序的客戶區(qū)內(nèi),這就是因?yàn)樗梨i的程序無(wú)法響應(yīng)WM_PAINT消息來(lái)恢復(fù)客戶區(qū)造成的。

             

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

             

            2、GDI程序的結(jié)構(gòu)

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

             

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

             

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

             

            3、探討WM_PAINT消息

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

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

            l         當(dāng)窗口客戶區(qū)被自己的下拉式菜單覆蓋,或者被自己彈出的對(duì)話框覆蓋后,Windows會(huì)嘗試保存被覆蓋的區(qū)域并在以后恢復(fù)它,如果因?yàn)槟撤N原因無(wú)法保存并恢復(fù)的話,Windows會(huì)發(fā)送一個(gè)WM_PAINT消息通知程序。

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

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

             

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

             

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

             

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

             

            由于存在“無(wú)效區(qū)域”這樣一個(gè)東西,所以程序在WM_PAINT消息中對(duì)客戶區(qū)刷新完畢后工作并沒(méi)有結(jié)束,如果不使無(wú)效區(qū)域變得有效,Windows會(huì)在下一輪消息循環(huán)中繼續(xù)放入一個(gè)WM_PAINT消息。當(dāng)Windows檢查“繪圖信息結(jié)構(gòu)”的時(shí)候發(fā)現(xiàn)沒(méi)有了無(wú)效區(qū)域,也就不會(huì)繼續(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)中間并沒(méi)有調(diào)用ValidateRect來(lái)使無(wú)效區(qū)域變得有效,這是因?yàn)?/span>BeginPaint函數(shù)和EndPaint函數(shù)隱含有這個(gè)功能,如果不是以BeginPaint/EndPaint當(dāng)做消息處理代碼的頭尾的話,那么在WM_PAINT消息返回的時(shí)候就必須調(diào)用ValidateRect函數(shù)。

             

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

            PAINTSTRUCT STRUCT

                   hdc                DWORD        ?

                   fErase            DWORD        ?

                   rcPaint           RECT            <>

                   fRestore         DWORD        ?

                   fIncUpdate     DWORD        ?

                   rgbReserved   BYTE 32 dup(?)

            PAINTSTRUCT ENDS

             

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

             

             

            二、設(shè)備環(huán)境

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

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

             

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

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

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

             

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

             

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

            //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      '請(qǐng)嘗試用別的窗口覆蓋本窗口!',0

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

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

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

                            .code

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

            ; 定時(shí)器過(guò)程

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

            _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

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

            ; 窗口過(guò)程

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

            _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

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

            ; 設(shè)置定時(shí)器

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

                    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

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

            ; 清除定時(shí)器

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

                    invoke KillTimer, NULL, @hTimer

                    ret

            _WinMain endp

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

            start:

                    call    _WinMain

                    invoke ExitProcess, NULL

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

                    end start

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

             

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

             

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

             

            這個(gè)例子驗(yàn)證了“設(shè)備環(huán)境”只是“環(huán)境”而不是“設(shè)備”,它并不存儲(chǔ)發(fā)給它的圖形數(shù)據(jù),圖形數(shù)據(jù)透過(guò)它寫到了它所描述的“設(shè)備”上,每個(gè)窗口客戶區(qū)的“設(shè)備環(huán)境”對(duì)應(yīng)的設(shè)備都是屏幕,但它們?cè)谖恢蒙峡赡苤丿B,所以向一個(gè)窗口的客戶區(qū)寫數(shù)據(jù)相當(dāng)于同時(shí)寫了下層窗口的客戶區(qū)。

             

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

             

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

             

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

             

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

             

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

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

             

            最常用的方法是在WM_PAINT消息中用BeginPaint函數(shù)得到hDC,WM_PAINT消息的代碼結(jié)構(gòu)一般是:

            .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對(duì)應(yīng)的尺寸僅是無(wú)效區(qū)域,無(wú)法用它繪畫到這個(gè)區(qū)域以外的地方去。由于窗口過(guò)程每次接收WM_PAINT消息時(shí)的無(wú)效區(qū)域可能是不同的,所以這個(gè)hDC的值僅在WM_PAINT消息中有效,程序不應(yīng)該保存它并把它用在WM_PAINT消息以外的代碼中?;谕瑯拥牡览恚?/span>BeginPaintEndPaint函數(shù)只能用在WM_PAINT消息中,因?yàn)橹挥羞@時(shí)候才存在無(wú)效區(qū)域。

             

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

            invoke GetDC, hWnd            ;獲取hDC

            ;返回值是hDC

            ;繪圖代碼

            invoke ReleaseDC, hWnd      ;釋放hDC

             

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

             

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

            invoke CreateDC, lpszDriver, lpszDevice, lpszOutput, lpInitData

            lpszDriver指向設(shè)備名稱,如顯示設(shè)備的設(shè)備名是DISPLAY,打印機(jī)的設(shè)備名一般為WINSPOOL,下面這幾句代碼建立的DC對(duì)應(yīng)整個(gè)屏幕:

            szDriver         db    “DISPLAY”,0

                                

                                 invoke CreateDC, addr szDriver, NULL, NULL, NULL

                                 mov hDC, eax

             

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

            invoke CreateCompatibleDC, hDC

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

             

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

             

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

             

             

            三、色彩和坐標(biāo)

            1、Windows中的色彩

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

             

            一種顏色可以分解成紅、綠、藍(lán)三原色,所以可以用紅、綠、藍(lán)3個(gè)分量的組合來(lái)表示各種顏色。

             

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

             

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

             

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

            move ax, 紅色 + 綠色*100h + 藍(lán)色*10000h ;將顏色放入eax

             

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

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

             

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

             

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

            invoke GetDeviceCaps, hDC, PLANES

            mov ebx, dwPlanes

            invoke GetDeviceCaps, hDC, BITSPIXEL

            mul ebx

            mov dwColorDepth, eax

             

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

             

             

            2、Windows中的坐標(biāo)系

            要用GDI函數(shù)繪圖,就必須首先了解這些函數(shù)使用的坐標(biāo)系,在默認(rèn)的狀態(tài)下,Windows坐標(biāo)系以左上角做坐標(biāo)原點(diǎn),以右方當(dāng)做X坐標(biāo)的正方向,以下方當(dāng)做Y坐標(biāo)的正方向。坐標(biāo)的數(shù)值要用一個(gè)有符號(hào)的16位數(shù)來(lái)表示,范圍從-32 768~32767,坐標(biāo)的單位為像素。這種坐標(biāo)系定義方法的好處是:窗口中每一點(diǎn)的坐標(biāo)不會(huì)因?yàn)榇翱诘拇笮「淖兌淖?,試想一下,如以?shù)學(xué)中通常的表示方法,以左下角做坐標(biāo)原點(diǎn),那么當(dāng)窗口高度被用戶調(diào)整的時(shí)候,客戶區(qū)中每一點(diǎn)的Y坐標(biāo)都會(huì)變化,在具體使用中就會(huì)有諸多不便。

             

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

            invoke SetMapMode, hDC, iMapMode

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

            Windows中可用的坐標(biāo)映射方式

            映射方式

            原點(diǎn)

            邏輯單位

            X正方向

            Y正方向

            MM_TEXT(默認(rèn)方式)

            左上

            像素

            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

            可變

            可變

             

            可以看到,除了默認(rèn)的MM_TEXT方式外,下面5種映射方式:MM_HIENGLISHMM_LOENGLISH,MM_HIMETRIC,MM_LOMETRICMM_TWIPS采用的都是原點(diǎn)位于左上角、X正方向向上的映射方式,另外,它們的坐標(biāo)邏輯單位是不同的。

             

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

             

            posted on 2010-09-24 13:51 luqingfei 閱讀(4191) 評(píng)論(2)  編輯 收藏 引用 所屬分類: Win32匯編程語(yǔ)言序設(shè)計(jì)

            評(píng)論

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

            好,十分有用  回復(fù)  更多評(píng)論   

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

            感謝  回復(fù)  更多評(píng)論   

            導(dǎo)航

            <2010年8月>
            25262728293031
            1234567
            891011121314
            15161718192021
            22232425262728
            2930311234

            統(tǒng)計(jì)

            留言簿(6)

            隨筆分類(109)

            隨筆檔案(105)

            Blogers

            Game

            Life

            NodeJs

            Python

            Useful Webs

            大牛

            搜索

            積分與排名

            最新評(píng)論

            閱讀排行榜

            評(píng)論排行榜

            久久国产精品一国产精品金尊| 日本精品久久久中文字幕| 久久一区二区免费播放| 亚洲国产成人精品91久久久 | 午夜精品久久影院蜜桃| 色偷偷91久久综合噜噜噜噜| 精品国产日韩久久亚洲| 精品一区二区久久久久久久网站| www亚洲欲色成人久久精品| 亚洲国产成人久久精品99| 久久人人爽人人爽人人片AV不| 久久精品国产亚洲一区二区| 国内精品伊人久久久久妇| 99re久久精品国产首页2020| 久久综合鬼色88久久精品综合自在自线噜噜 | 久久综合综合久久狠狠狠97色88| 国产综合成人久久大片91| 亚洲伊人久久精品影院| 久久精品成人影院| 97久久精品国产精品青草| 久久无码AV中文出轨人妻| 九九久久精品国产| 久久精品国产亚洲欧美| 久久久久久九九99精品| 亚洲欧美久久久久9999| 亚洲综合婷婷久久| 久久精品成人免费看| 久久精品国产亚洲av麻豆图片| 精品久久久久久国产牛牛app| MM131亚洲国产美女久久| 久久水蜜桃亚洲av无码精品麻豆| 久久夜色精品国产噜噜亚洲a | 久久丫忘忧草产品| 欧美日韩精品久久久久| 久久婷婷午色综合夜啪| 久久伊人五月天论坛| 久久亚洲中文字幕精品一区四| 蜜桃麻豆www久久国产精品| 精品一久久香蕉国产线看播放 | 亚洲综合精品香蕉久久网| 亚洲AV成人无码久久精品老人|