日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Chapter 05 绘图基础

發(fā)布時間:2023/12/10 编程问答 59 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Chapter 05 绘图基础 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

整理了大半個月,終于把Chapter 05整理好,希望能夠?qū)ψ约阂约熬W(wǎng)友有所幫助。本章我們將一起學(xué)習(xí)繪圖基礎(chǔ),本章節(jié)會學(xué)習(xí)到GDI基礎(chǔ)、繪制線條和填充區(qū)域的基礎(chǔ)知識。Windows子系統(tǒng)負(fù)責(zé)在稱為圖形設(shè)備接口(Graphics Device Interface,GDI)的視頻顯示器和打印機上顯示圖形,GDI的重要性不僅體現(xiàn)在Windows上顯示信息的應(yīng)用程序時要使用GDI,Windows本身也會使用GDI顯示用戶界面的項目,比如菜單、滾動條、圖標(biāo)和鼠標(biāo)指針。

5.1??? GDI的結(jié)構(gòu)

5.1.1 GDI原理

在Windows NT中,圖形顯示主要由GDI32.DLL中導(dǎo)出的函數(shù)處理,該動態(tài)鏈接庫會調(diào)用你安裝的視頻顯示器和打印機的設(shè)備驅(qū)動程序中的一些函數(shù)。視頻驅(qū)動程序會直接訪問視頻顯示器的硬件,而打印機驅(qū)動程序則將GDI命令轉(zhuǎn)化為各種打印機所能理解的代碼或者命令,因此不同的顯示適配器和打印機需要使用不同的設(shè)備驅(qū)動程序。GDI提供了一種特殊的機制來徹底隔離應(yīng)用程序和不同輸出設(shè)備的特性,以提供與設(shè)備無關(guān)的圖形。

5.1.2 GDI函數(shù)調(diào)用

GDI包含有幾百個函數(shù),可以分成如下幾類:

?獲取(或建立)和釋放(或銷毀)設(shè)備環(huán)境的函數(shù):繪制時,需要使用一個設(shè)備環(huán)境句柄。BeginPaint和EndPaint函數(shù)(盡管從技術(shù)上來說,它們屬于USER模塊,而不是GDI模塊)允許你在處理WM_PAINT消息時做到這一點。在處理其他消息時,可以通過GetDC和ReleaseDC函數(shù)來達到相同的目的。

?獲取設(shè)備環(huán)境信息的函數(shù):例如GetTextMetrics函數(shù)可以用來獲取當(dāng)前被選入設(shè)備環(huán)境的字體的尺寸信息。GetSystemMetrics函數(shù)可以用來獲取顯示設(shè)備和系統(tǒng)配置的尺寸信息(以像素為單位)。

?繪圖函數(shù):一旦繪圖準(zhǔn)備工作就緒,繪圖函數(shù)才會發(fā)揮它的作用。例如TextOut函數(shù)在窗口的客戶區(qū)顯示,以及即將介紹的繪制線條和填充區(qū)域的GDI函數(shù)等等。

?設(shè)置和獲取設(shè)備環(huán)境屬性的函數(shù):設(shè)備環(huán)境的屬性確定會凸函數(shù)繪圖時的各種細(xì)節(jié)。例如,可以使用SetTextColor函數(shù)來指定TextOut繪制的文本的顏色。所有的設(shè)備環(huán)境的屬性都有一個默認(rèn)值,這個默認(rèn)值在獲取設(shè)備環(huán)境時候已經(jīng)被設(shè)置好了,對所有的以Set開頭的函數(shù),都有相應(yīng)的一個以Get開頭的函數(shù)用戶獲取當(dāng)前設(shè)備環(huán)境的屬性。

使用GDI“對象”的函數(shù): GDI對象包括畫筆、畫刷、字體、區(qū)域、位圖、調(diào)色板以及其他GDI對象。包含各種針對GDI對象的操作函數(shù),例如使用CreatePen、CreatePenIndirect或者ExtCreatePen函數(shù)來創(chuàng)建邏輯畫筆,使用SelectObject函數(shù)將邏輯畫筆選入某設(shè)備環(huán)境中。

5.1.3 GDI的基本圖形

在屏幕上或者打印機顯示的圖形類型可以分為下面幾類,被稱為“基本圖形”。

線條和曲線 線條是任何矢量圖形繪制系統(tǒng)的基礎(chǔ)。GDI支持直線、矩形、橢圓和貝塞爾曲線。GDI使用當(dāng)前選入設(shè)備環(huán)境的畫筆繪制線條。

可悲填充的封閉區(qū)域 當(dāng)一系列的線條或者曲線構(gòu)成一個封閉區(qū)域時,你可以使用當(dāng)前GDI的畫刷對象來填充這個區(qū)域。

位圖 位圖是一個二維的位數(shù)組,每一個元素都對應(yīng)顯示設(shè)備上的一個像素。位圖是光柵圖形的基礎(chǔ)。位圖通常位于在視頻顯示器或者打印機上顯示復(fù)雜(通常是真實世界)的圖像。位圖也通常用于顯示必須要快速繪制的小圖像,例如圖標(biāo)、鼠標(biāo)指針以及出現(xiàn)在應(yīng)用程序工具欄里的按鈕。GDI支持兩種類型的位圖:舊式的設(shè)備相關(guān)位圖和新式的設(shè)備無關(guān)位圖(DIB,從Windows3.0起)。DIB可以存放在磁盤文件中。

文本 文本通常是任何計算機圖形系統(tǒng)中最復(fù)雜的部分,而且也是最重要的部分。在所有Windows的數(shù)據(jù)結(jié)構(gòu)中,用于定義GDI字體對象和獲取字體信息的數(shù)據(jù)結(jié)構(gòu)式最龐大的。GDI從Windows3.1開始支持TrueType字體,這種字體是以填充的輪廓線為基礎(chǔ)的,某些GDI函數(shù)可以操控這些輪廓線。

5.1.4 其他

GDI的其他方面就不太容易分類,具體如下:

映射模式(mapping mode)和轉(zhuǎn)換(transform):盡管在默認(rèn)時是以像素為單位進行繪制的,但是并不是別無選擇。GDI的映射模式允許以英寸(甚至幾分之一英寸)、毫米或者其他你所想要的任何單位進行繪制。除此之外,Windows NT支持傳統(tǒng)的世界坐標(biāo)轉(zhuǎn)換,適用于傾斜和旋轉(zhuǎn)圖形對象。

圖元文件(metafile): 一個圖元文件是以二進制形式存儲的GDI命令的集合。圖元文件主要用于通過剪貼板轉(zhuǎn)換矢量圖形繪制的表現(xiàn)形式。

區(qū)域(region):區(qū)域是一個任意形狀的封閉圖形,通常可以表示為由一系列簡單區(qū)域進行布爾運算后得到的結(jié)果。在GDI內(nèi)部,可以使用一個從已知區(qū)域出發(fā)的一系列掃描線來頂一個復(fù)雜的區(qū)域。可以使用區(qū)域進行輪廓繪制、填充或者剪裁。

路徑(path):路徑是存儲在GDI內(nèi)部的直線和曲線的集合。可以用于繪制、填充和剪裁。路徑還可以轉(zhuǎn)換為區(qū)域。

剪裁(clipping):當(dāng)繪圖被限制在客戶區(qū)的一個特定的空間位置時,就發(fā)生了剪裁。那個特定的空間位置可以是矩形或者非矩形,它通常被指定為一個區(qū)域或者一個路徑。

調(diào)色板(palettes):僅在支持256種顏色時,才能使用自定義的調(diào)色板。Windows僅保留中的20種色彩以供系統(tǒng)使用。你可以改變其他236種色彩,這樣就可以準(zhǔn)確顯示按位圖形式存儲的真實圖像。

打印(printing):盡管本章只討論視頻顯示器,但在本章學(xué)到的所有知識幾乎都可以應(yīng)用于打印機。將在Chapter 13討論打印機。

5.2 設(shè)備環(huán)境

開始繪圖之前,首先讓我們在第4章的基礎(chǔ)上更嚴(yán)謹(jǐn)?shù)赜懻撘幌略O(shè)備環(huán)境。

如果希望在圖形設(shè)輸出設(shè)備上繪制圖形,必須首先獲取設(shè)備環(huán)境(即DC)的句柄。當(dāng)Windows把這個句柄交給你的程序,Windows同時也就給予你使用這個設(shè)備的權(quán)限。接著,在GDI函數(shù)中將這個句柄作為一個參數(shù),告訴Windows在哪個設(shè)備上進行繪圖。設(shè)備環(huán)境包含許多決定GDI函數(shù)如何工作的屬性,這些屬性使得GDI函數(shù)只需要提供少量的參數(shù),而不需要提供Windows在設(shè)備上顯示對象時需要的所有消息。

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

獲取和釋放設(shè)備環(huán)境句柄最常用的方法是在處理WM_PAINT消息時使用BeginPaint函數(shù)和EndPaint函數(shù):

hdc= BeginPaint(hwnd, &ps);[Otherprogram lines] EndPaint(hwnd,&ps);

其中,變量ps是一個類型為PAINTSTRUCT的結(jié)構(gòu)。這個結(jié)構(gòu)中的字段hdc和BeginPaint函數(shù)返回的設(shè)備環(huán)境句柄的值相同。PAINTSTRUCT結(jié)構(gòu)還包含一個名為rcPaint的矩形結(jié)構(gòu),該結(jié)構(gòu)定義了一個包圍窗口客戶區(qū)無效范圍的矩形。使用從BeginPaint函數(shù)獲取的設(shè)備環(huán)境句柄,就只能在這個矩形區(qū)域內(nèi)繪圖。調(diào)用EndPaint函數(shù)將使這個區(qū)域有效。

設(shè)備環(huán)境句柄還可以在處理非WM_PAINT消息時由Windows程序獲取:

hdc= GetDC(hwnd);[Other program lines]ReleaseDC(hwnd,hdc);

其中,設(shè)備環(huán)境指的是窗口句柄為hwnd的窗口客戶區(qū)。調(diào)用這些函數(shù)和使用BeginPaint、EndPaint函數(shù)組合的主要差別是從GetDC函數(shù)返回的句柄可以在整個客戶區(qū)內(nèi)繪制。并且GetDC和ReleaseDC函數(shù)并不使任何客戶區(qū)的無效區(qū)域變?yōu)楹苡行А?/p>

Windows程序還可以獲得用于整個窗口的,而不僅僅是窗口客戶區(qū)的設(shè)備環(huán)境句柄:

hdc= GetWindowDC(hwnd);[Otherprogram lines] ReleaseDC(hwnd,hdc);

這里的設(shè)備環(huán)境除了客戶區(qū),還包括窗口標(biāo)題欄、菜單、滾動條和客戶區(qū)的外框。應(yīng)用程序很少使用GetWindowDC函數(shù)。如果你想嘗試使用它,則還應(yīng)當(dāng)捕獲WM_NCPAINT(nonclient paint, 非客戶區(qū)繪制)消息,Windows使用這個消息在窗口的非客戶區(qū)繪圖。

調(diào)用BeginPaint、GetDC和GetWindowDC函數(shù)可以獲得在視頻顯示器上與一個特定的窗口相關(guān)聯(lián)的設(shè)備環(huán)境。還有一個更通用的用于獲取設(shè)備環(huán)境句柄的函數(shù)是CreateDC:

hdc= CreateDC(pszDriver,pszDevice,pszOutput,pData);[Otherprogram lines] DeleteDC(hdc);

例如,你可以通過調(diào)用下面的函數(shù)獲取當(dāng)前整個屏幕的設(shè)備環(huán)境句柄: ? ? ?

hdc= CreateDC(TEXT(“DISPLAY”), NULL, NULL, NULL);

在窗口外輸出文字或者圖像不是很好,但是對于一些特殊的應(yīng)用還是很有用的。(雖然在官方文檔中,并沒有提到這種方法,但是你還是可以通過在調(diào)用GetDC時使用一個NULL參數(shù)來得到整個屏幕的設(shè)備環(huán)境。)

有時候,僅需要獲取一些關(guān)于設(shè)備環(huán)境的信息,而不需要在上面繪制任何東西。在這些情況下,可以調(diào)用CreateIC函數(shù)獲取一個“信息上下文”(Information Context)句柄。這個函數(shù)的參數(shù)和CreateDC函數(shù)的參數(shù)相同。例如: ? ?

hdc= CreateIC(TEXT(“DISPLAY”), NULL, NULL, NULL);

但是,往設(shè)備上寫東西時,不能使用信息上下文句柄。

處理文圖時,有時可能會用到一個“內(nèi)存設(shè)備環(huán)境”:

hdcMem =CreateCompatibleDC(hdc);[Other program lines] DeleteDC(hdcMem);

可以把一個位圖選入內(nèi)存設(shè)備環(huán)境,并且調(diào)用GDI函數(shù)繪制這個位圖。將在Chapter14章節(jié)介紹這些技術(shù)。

????? 如前所述,圖元文件是以二進制形式編碼的GDI函數(shù)調(diào)用的集合。它可以通過獲取一個圖元文件的設(shè)備環(huán)境來創(chuàng)建: ? ? ?

hdcMeta= CreateMetaFile(pszFileName);[Otherprogram lines] hmf= ClosemetaFile(hdcMeta);

在圖元文件設(shè)備環(huán)境有效時,使用hdcMeta所做的任何GDI調(diào)用都不會被顯示出來,它們都會變成圖元文件的一部分。當(dāng)你調(diào)用CloseMetaFile時,圖元文件設(shè)備環(huán)境句柄變?yōu)闊o效,該函數(shù)返回一個圖元文件句柄(hmf)。我們將在Chapter 18討論圖元文件。

5.2.2 獲取設(shè)備環(huán)境的信息

????? 設(shè)備環(huán)境通常指的是物理的顯示設(shè)備,如視頻顯示器,或者打印機。經(jīng)常需要獲取這些設(shè)備的某些信息,包括顯示器的大小(以像素或者物理尺寸的方式)和它的色彩能力。這些信息可以通過調(diào)用GetDeviceCaps(意思為獲取設(shè)備的能力)函數(shù)來獲取:

iValue= GetDeviceCaps(hdc, iIndex);

其中,參數(shù)iIndex是定義在WINGDI.H頭文件中的29個標(biāo)識符之一。例如,當(dāng)iIndex的值為HORZRES時,GetDeviceCaps函數(shù)以像素為單位返回設(shè)備的寬度;使用VERTRES參數(shù)值會以像素為單位返回設(shè)備的高度。如果hdc是一個屏幕設(shè)備環(huán)境的句柄,這里獲取的信息和從GetSystemMetrics函數(shù)獲取的信息是一樣的。如果hdc是一個打印機設(shè)備環(huán)境,那么GetDeviceCaps將以像素為單位返回打印機顯示區(qū)域的高度和寬度。

????? 還可以使用GetDeviceCaps函數(shù)來確定設(shè)備處理各種類型圖形的能力,通常這對于視頻顯示器并不重要,但是對于打印機卻非常重要。例如,大多數(shù)的繪圖儀不能繪制位圖圖像,通過調(diào)用GetDeviceCaps函數(shù)可以讓你提前知道這一情況。

5.2.3 DEVCAPS1程序

DEVCAPS1程序顯示了在使用視頻顯示器設(shè)備環(huán)境時,可從GetDeviceCaps函數(shù)得到的部分(并不是全部)信息。代碼如下所示:

*---------------------------------------------------------DEVCAPS1.C -- Device Capabilities Display Program No. 1(c) Charles Petzold, 1998---------------------------------------------------------*/#include <windows.h>#define NUMLINES ((int) (sizeof devcaps / sizeof devcaps [0]))struct {int iIndex ;TCHAR * szLabel ;TCHAR * szDesc ; } devcaps [] = {HORZSIZE, TEXT ("HORZSIZE"), TEXT ("Width in millimeters:"),VERTSIZE, TEXT ("VERTSIZE"), TEXT ("Height in millimeters:"),HORZRES, TEXT ("HORZRES"), TEXT ("Width in pixels:"),VERTRES, TEXT ("VERTRES"), TEXT ("Height in raster lines:"),BITSPIXEL, TEXT ("BITSPIXEL"), TEXT ("Color bits per pixel:"),PLANES, TEXT ("PLANES"), TEXT ("Number of color planes:"),NUMBRUSHES, TEXT ("NUMBRUSHES"), TEXT ("Number of device brushes:"),NUMPENS, TEXT ("NUMPENS"), TEXT ("Number of device pens:"),NUMMARKERS, TEXT ("NUMMARKERS"), TEXT ("Number of device markers:"),NUMFONTS, TEXT ("NUMFONTS"), TEXT ("Number of device fonts:"),NUMCOLORS, TEXT ("NUMCOLORS"), TEXT ("Number of device colors:"),PDEVICESIZE, TEXT ("PDEVICESIZE"), TEXT ("Size of device structure:"),ASPECTX, TEXT ("ASPECTX"), TEXT ("Relative width of pixel:"),ASPECTY, TEXT ("ASPECTY"), TEXT ("Relative height of pixel:"),ASPECTXY, TEXT ("ASPECTXY"), TEXT ("Relative diagonal of pixel:"),LOGPIXELSX, TEXT ("LOGPIXELSX"), TEXT ("Horizontal dots per inch:"),LOGPIXELSY, TEXT ("LOGPIXELSY"), TEXT ("Vertical dots per inch:"),SIZEPALETTE, TEXT ("SIZEPALETTE"), TEXT ("Number of palette entries:"),NUMRESERVED, TEXT ("NUMRESERVED"), TEXT ("Reserved palette entries:"),COLORRES, TEXT ("COLORRES"), TEXT ("Actual color resolution:") } ;LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,PSTR szCmdLine, int iCmdShow) {static TCHAR szAppName[] = TEXT ("DevCaps1") ;HWND hwnd ;MSG msg ;WNDCLASS wndclass ;wndclass.style = CS_HREDRAW | CS_VREDRAW ;wndclass.lpfnWndProc = WndProc ;wndclass.cbClsExtra = 0 ;wndclass.cbWndExtra = 0 ;wndclass.hInstance = hInstance ;wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;wndclass.lpszMenuName = NULL ;wndclass.lpszClassName = szAppName ;if (!RegisterClass (&wndclass)){MessageBox (NULL, TEXT ("This program requires Windows NT!"),szAppName, MB_ICONERROR) ;return 0 ;}hwnd = CreateWindow (szAppName, TEXT ("Device Capabilities"),WS_OVERLAPPEDWINDOW,CW_USEDEFAULT, CW_USEDEFAULT,CW_USEDEFAULT, CW_USEDEFAULT,NULL, NULL, hInstance, NULL) ;ShowWindow (hwnd, iCmdShow) ;UpdateWindow (hwnd) ;while (GetMessage (&msg, NULL, 0, 0)){TranslateMessage (&msg) ;DispatchMessage (&msg) ;}return msg.wParam ; }LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {static int cxChar, cxCaps, cyChar ;TCHAR szBuffer[10] ;HDC hdc ;int i ;PAINTSTRUCT ps ;TEXTMETRIC tm ;switch (message){case WM_CREATE:hdc = GetDC (hwnd) ;GetTextMetrics (hdc, &tm) ;cxChar = tm.tmAveCharWidth ;cxCaps = (tm.tmPitchAndFamily & 1 ? 3 : 2) * cxChar / 2 ;cyChar = tm.tmHeight + tm.tmExternalLeading ;ReleaseDC (hwnd, hdc) ;return 0 ;case WM_PAINT:hdc = BeginPaint (hwnd, &ps) ;for (i = 0 ; i < NUMLINES ; i++){TextOut (hdc, 0, cyChar * i,devcaps[i].szLabel,lstrlen (devcaps[i].szLabel)) ;TextOut (hdc, 14 * cxCaps, cyChar * i,devcaps[i].szDesc,lstrlen (devcaps[i].szDesc)) ;SetTextAlign (hdc, TA_RIGHT | TA_TOP) ;TextOut (hdc, 14 * cxCaps + 35 * cxChar, cyChar * i, szBuffer,wsprintf (szBuffer, TEXT ("%5d"),GetDeviceCaps (hdc, devcaps[i].iIndex))) ;SetTextAlign (hdc, TA_LEFT | TA_TOP) ;}EndPaint (hwnd, &ps) ;return 0 ;case WM_DESTROY:PostQuitMessage (0) ;return 0 ;}return DefWindowProc (hwnd, message, wParam, lParam) ; }


5.2.4 設(shè)備的尺寸

視頻顯示器和打印機是兩種非常不同的設(shè)備,但是最不明顯的區(qū)別或許是“分辨率”和設(shè)備聯(lián)系起來的方式。使用打印機時,分辨率通常用每英寸的點數(shù)表示。例如有的激光打印機分辨率是每英寸300點或者600點。但是,視頻顯示器的分辨率卻是以水平和垂直方向上顯示的總的像素數(shù)給出的,如1024768。

在《Windows程序設(shè)計》這本書中,“分辨率“被嚴(yán)格定義為每度量單位(通常是英寸)中含有的像素數(shù)。我們將使用“像素尺寸(pixel size)”或者“像素規(guī)模(pixel dimension)”來表示設(shè)備在水平方向和垂直方向上顯示的總的像素數(shù)。“度量尺寸”(metrical size)和“度量規(guī)模”(metrical dimension)是以每英寸或者毫米為單位的設(shè)備的客戶區(qū)域的大小。

Windows應(yīng)用程序可以通過在調(diào)用GetSystemMetrics函數(shù)時使用SM_CXSCREEN和SM_CYSCREEN參數(shù)來獲取顯示器的像素規(guī)模。從DEVCAPS1程序可以看出來,一個程序可以在調(diào)用GetDeviceCaps函數(shù)時使用HORZRES(“水平分辨率”)和VERTRES(“垂直分辨率”)來獲取相同的值。在HORZRES中“分辨率”指的是像素尺寸,而不是每度量單位的像素數(shù)。

前兩個設(shè)備能力HORZSIZE和VERTSIZE,官方文檔中稱為“以毫米計的物理屏幕寬度”和“以毫米計的物理屏幕高度”。

在計算機排版中,1點(point size亦稱磅值)通常假定正好是1/72英寸。用TEXTMETRIC結(jié)構(gòu)中的術(shù)語來說,字號(字體的大小)等于tmHeight減去tmInternalLeading。tmHeight字段表示文本的相鄰行在屏幕上或者打印機上間隔有多大。

在Windows 98中,Windows是通過用戶選擇的顯示器像素規(guī)模和用戶為系統(tǒng)字體大小選擇的分辨率來計算顯示器的尺寸的。

在Windows NT中,HORZRES和VERTRES的值仍表示水平方向上和垂直方向上像素的數(shù)目,并且LOGPIXELX和LOGPIXELY仍和你在控制面板中的【顯示】程序中設(shè)置視頻分辨率時選擇的字體相關(guān)。與Windows 98不同之處是:HORZSIZE和VERTSIZE的值是固定的,用來表示標(biāo)準(zhǔn)顯示器的尺寸。對一般的適配器,你獲取的HORZSIZE和VERTSIZE的值分別是320毫米和240毫米。這些值是相同的,不管你選擇什么樣的像素規(guī)模。

如果程序需要視頻顯示器的實際物理尺寸,最好的解決辦法是提供一個對話框來實際要求用戶輸入它們。

最后,另外三個從GetDeviceCaps函數(shù)獲得的值是與視頻尺寸相關(guān)的。這三個值分別是ASPECTX、ASPECTY和ASPECTXY,它們分別表示每個像素點相對的寬度、高度和對角線長度,并且四舍五入到整數(shù)。

5.2.5 色彩ABC

只能顯示黑色像素和白色像素的視頻顯示器要顯示每個像素只需要一位的內(nèi)存。彩色顯示器的每個像素卻需要多個位的內(nèi)存。位數(shù)越多,可表示的色彩越多;更精確一點,2的位數(shù)次方就是它可以表示的不同色彩數(shù)目。

真彩(true color)視頻顯示器有每像素24位的分辨率(8位表示紅色、8位表示綠色和8位表示藍色)。紅、綠和藍為“三原色”。許多其他的顏色可以由這三種顏色混合。

高彩(high color)顯示器有每像素16位的分辨率,通常5位表示紅色、6位表示綠色和5位表示藍色。

一個顯示256色的視頻適配器每像素8位的內(nèi)存,然而,這8位的值通常是一個索引值,它指向一個調(diào)色板中定義的某種實際顏色。我們將在Chapter16章節(jié)中詳細(xì)地介紹。

最后,16色的視頻板卡每個像素需要4位的內(nèi)存。這16種顏色通常固定為暗或者亮的紅、綠、藍、青、紫、黃、兩種灰色、黑和白。

雖然只有在一些奇怪的程序中才有必要知道視頻適配器板卡上的內(nèi)存組織形式,但是調(diào)用GetDeviceCaps函數(shù)總可以幫助你確定這些信息。各個像素的多個色彩位可以在顯卡內(nèi)存中以順序方式存儲,也可以不同的色彩位被放在內(nèi)存的不同平面(plane)上。色彩平面的數(shù)目可以由下面的函數(shù)調(diào)用獲得: ? ??

iPlanes= GetDeviceCaps(hdc, PLANES);

每個像素的顏色位數(shù)可以由下面的調(diào)用獲得:

iBitsPixel= GetDeviceCaps(hdc, BITSPIXEL);

前面兩個函數(shù)的返回值肯定有一個為1。視頻適配器支持的色彩數(shù)可以由下面的公式來計算:

iColors= 1 << (iPlanes * iBitsPixel);

這個值和通過使用NUMCOLORS參數(shù)獲取的色彩數(shù)值可能一樣,也可能不一樣:

iColors= GetDeviceCaps(hdc,NUMCOLORS);

256色的視頻適配器會使用顏色調(diào)色板。在這種情況下,使用NUMCOLORS參數(shù)的GetDeviceCaps函數(shù)會返回Windows保留的色彩數(shù),其值為20.Windows程序使用調(diào)色板管理器設(shè)定剩余的236種色彩。對高彩和真彩視頻適配器,使用NUMCOLORS參數(shù)的GetDeviceCaps函數(shù)通常返回-1,所以它對于確定色彩數(shù)來說不是一個可靠的函數(shù)。因此,應(yīng)該使用PLANES和BITSPIXEL值按前面給出的iColors的公式來計算色彩數(shù)。

在調(diào)用大多數(shù)GDI函數(shù)時,使用COLORREF值(是一個32位的無符號長整型)來表示一個特定的顏色。COLORREF值按照紅、綠、藍的順序指定一種顏色,通常稱為“RGB色彩”。32位COLORREF值由如下四部分組成(從最高字節(jié)到最小字節(jié)順序):最高字節(jié)的8位由0組成,其后字節(jié)的8位表示藍色,藍色字節(jié)之后的8位表示綠色,最低字節(jié)的8位表示紅色。

Windows頭文件WINGDI.H中有幾個用于RGB色彩值的宏,RGB宏帶有三個參數(shù),分別表示紅、綠和藍,并且把它們組合成一個無符號長整型:

#defineRGB(r, g, b) ((COLORREF)(((BYTE)(r) | \ ((WORD)((BYTE)(g))<<8)) | \ (((DWORD)(BYTE)(b))<< 16)))

注意,這三個參數(shù)的書序是紅、綠、藍。當(dāng)r和g都為255,且b為0時,表示黃色。

當(dāng)r、g、b都為0時,表示黑色。當(dāng)r、g、b都為255時,表示白色。GetRValue、GetGValue、GetBValue宏從COLORREF值中提取RGB的原色值。

在16色或者256色的視頻適配器上,Windows能夠使用“抖動”來仿真,使設(shè)備能夠顯示更多的色彩。抖動就是使用不同色彩的相鄰像素形成一個小圖案。可以通過GetNearestColor函數(shù)確定于某種特殊的顏色值最接近的非合成顏色:?

crPureColor= GetNearestColor(hdc, crColor);

5.2.6 設(shè)備環(huán)境屬性

由前所述,Windows在設(shè)備環(huán)境中存儲著一些“屬性”,這些屬性控制GDI函數(shù)在顯示器上的操作方式。當(dāng)一個程序獲取一個設(shè)備環(huán)境句柄時,Windows設(shè)置所有的屬性為默認(rèn)值。下面將介紹部分設(shè)備環(huán)境屬性、其默認(rèn)值以及改變或者獲取其值的函數(shù)。

設(shè)備環(huán)境屬性

默認(rèn)值

修改其值的函數(shù)

獲取其值的函數(shù)

Mapping Mode

MM_TEXT

SetMapMode

GetMapMode

Window Origin

(0,0)

SetWindowOrgEx

OffsetWindowOrgEx

GetWindowOrgEx

Viewport Origin

(0,0)

SetViewportOrgEx

OffsetViewportOrgEx

GetViewportOrgEx

Window Extents

(1,1)

SetWindowExtEx

SetMapMode

ScaleWindowExtEx

GetWindowExtEx

Viewport Extents

(1,1)

SetViewportExtEx

SetMapMode

ScaleViewportExtEx

GetViewportExtEx

Pen

BLACK_PEN

SelectObject

SelectObject

Brush

WHITE_BRUSH

SelectObject

SelectObject

Font

SYSTEM_FONT

SelectObject

SelectObject

Bitmap

None

SelectObject

SelectObject

Current Position

(0,0)

MoveToEx

LineTo

PolyLineTo

PolyBezierTo

GetCurrentPositionEx

Background Mode

OPAQUE

SetBkMode

GetBkMode

Background Color

White

SetBkColor

GetBkColor

Text Color

Black

SetTextColor

GetTextColor

Drawing Mode

R2_COPYPEN

SetROP2

GetROP2

Stretching Mode

BLACKONWHITE

SetStretchBltMode

GetStretchBltMode

Polygon Fill Mode

ALTERNATE

SetPolyFillMode

GetPolyFillMode

Intercharacter Spacing

0

SetTextCharacterExtra

GetTextCharacterExtra

Brush Origin

(0,0)

SetBrushOrgEx

GetBrushOrgEx

Clipping Region

None

SelectObject

SelectClipRgn

IntersectClipRgn

OffsetClipRgn

ExcludeClipRect

SelectClipPath

GetClipBox

5.2.7 保存設(shè)備環(huán)境

通常,當(dāng)調(diào)用GetDC或者BeginPaint函數(shù)時,Windows會返回一個設(shè)備環(huán)境,它的所有屬性都被設(shè)定為默認(rèn)值。當(dāng)設(shè)備環(huán)境調(diào)用ReleaseDC或者EndPaint函數(shù)時,對屬性所做的任何改變都會丟失。如果程序需要使用非默認(rèn)的設(shè)備環(huán)境屬性,則必須在每次獲取一個新的設(shè)備環(huán)境句柄時初始化這個設(shè)備環(huán)境: ? ? ?

caseWM_PAINT:hdc= BeginPaint(hwnd,&ps);[initializedevice context attributes][pointclient area of window]EndPaint(hwnd,&ps);return0;

? ? ? 盡管這種方法通常令人滿意,但是你可能喜歡在釋放設(shè)備環(huán)境屬性時保存對屬性做的修改,以便在下次調(diào)用GetDC或者BeginPaint函數(shù)時,這些屬性仍然有效。為此,在注冊窗口類時將CS_OWNDC標(biāo)志作為窗口類樣式的一部分即可:

wndclass.style= CS_HREDRAW | CS_VREDRAW | CS_OWNDC;

現(xiàn)在,每個基于這個窗口類創(chuàng)建的窗口都有它的私有的設(shè)備環(huán)境,當(dāng)窗口被銷毀時,這個設(shè)備環(huán)境繼續(xù)存在。使用CS_OWNDC樣式時,只需要初始化設(shè)備環(huán)境屬性一次,例如,在處理WM_CREATE消息期間:?

case WM_CREATE:hdc= GetDC(hwnd);[initializedevice context attributes]ReleaseDC(hwnd,hdc);

在再次改變這些屬性值之前,它們會一直有效。

????? CS_OWNDC樣式僅影響通過GetDC和BeginPaint函數(shù)獲得的設(shè)備屬性,通過其他函數(shù)(如GetWindowDC函數(shù))獲取的設(shè)備環(huán)境并不受影響。以前,CS_OWNDC樣式不提倡使用,因為它需要一定的內(nèi)存開銷。現(xiàn)在,在處理大型圖形的Windows NT程序中,它可以明確改善性能。但是即使使用了CS_OWNDC樣式,設(shè)備環(huán)境句柄在退出窗口過程前也應(yīng)該被釋放。

????? 在一些情況下,可能想改變某些設(shè)備環(huán)境屬性,然后使用變更后的屬性進行繪制,接著再恢復(fù)原來的設(shè)備屬性。為了簡化這個過程,可以調(diào)用下面的函數(shù)保存設(shè)備環(huán)境的狀態(tài): ? ??

idSaved= SaveDC(hdc);

現(xiàn)在,可以改變一些屬性。而調(diào)用下面的函數(shù)則可以返回調(diào)用SaveDC函數(shù)之前存在的設(shè)備環(huán)境:

ReleaseDC(hdc,idSaved);

可以在調(diào)用RestoreDC之前,多次調(diào)用SaveDC。

????? 不過,大部分程序員會以一種不同的方式使用SaveDC和RestoreDC函數(shù),這種方式非常類似于匯編語言中的PUSH和POP指令。調(diào)用SaveDC函數(shù)時,返回值可以不必保存: ? ??

SaveDC(hdc);

然后改變一些屬性,并再次調(diào)用SaveDC函數(shù),而為了將設(shè)備環(huán)境恢復(fù)到已保存的狀態(tài),則調(diào)用下面的函數(shù):

RestoreDC(hdc,-1);

這會使設(shè)備環(huán)境恢復(fù)到最近一次由SaveDC函數(shù)保存的狀態(tài)。

5.3 點和線的繪制

5.3.1 設(shè)定像素

SetPixel函數(shù)將坐標(biāo)為x和y的像素點設(shè)定為某個特定的顏色:?

COLORREFSetPixel( HDC hdc, // handle to DCintX, // x-coordinate of pixelintY, // y-coordinate ofpixel COLORREF crColor // pixel color );

? ? ? 第一個參數(shù)hdc,表示設(shè)備環(huán)境句柄;

????? 第二個、三個參數(shù)X和Y,分別表示特定像素點的邏輯水平坐標(biāo)和垂直坐標(biāo);

? ? ? 第四個參數(shù)crColor,表示特定顏色,是COLORREF類型,可以用RGB宏來設(shè)定。

? 當(dāng)函數(shù)執(zhí)行成功時,返回一個COLORREF類型值,表示設(shè)定的顏色值。該值可能與crColor相同,也可能與crColor相同(當(dāng)要設(shè)定的crColor值不能在當(dāng)前設(shè)備環(huán)境中找到)。當(dāng)函數(shù)執(zhí)行失敗時,返回值為1。

GetPixel函數(shù)返回指定坐標(biāo)位置的像素的顏色:

COLORREF GetPixel( HDChdc, // handle to DCint nXPos, // x-coordinate of pixel int nYPos // y-coordinate ofpixel);

第一個參數(shù)hdc,表示設(shè)備環(huán)境句柄;

第二個、三個參數(shù)nXPos和nYPos,分別表示特定像素點的邏輯水平坐標(biāo)和垂直坐標(biāo);

函數(shù)執(zhí)行成功時返回值為COLORREF類型,為指定的像素點的顏色,要求該像素點必須在當(dāng)前剪輯區(qū)域范圍內(nèi)。函數(shù)執(zhí)行失敗后返回值為CLR_INVALID。

5.3.2 直線

????? Windows可以繪制直線、橢圓弧線(橢圓圓周上的曲線)和貝塞爾樣條曲線。Windows支持7中畫線函數(shù):

    • LineTo,畫直線
    • PolyLine和PolyLineTo,畫一條由多條首尾相連的直線構(gòu)成的折線
    • PolyPolyLine,畫多條折線
    • Arc,畫橢圓弧線
    • PolyBezier和PolyBezierTo,畫貝塞爾樣條曲線
    • ArcTo和AngleArc,畫橢圓弧線
    • PloyDraw,畫多條貝塞爾樣條曲線或者一條由多條首尾相連直線構(gòu)成的折線

設(shè)備環(huán)境有5個屬性會影響這些函數(shù)所繪制的線條外觀:當(dāng)前畫筆位置(僅適用于LineTo、PolyLineTo、PolyBezierTo和ArcTo函數(shù))、畫筆、背景模式、背景顏色和繪制模式。
為了畫一條直線,需要首先指定直線的起點,然后指定直線的終點:? MoveToEx(hdc, xBeg, yBeg, NULL); LineTo(hdc, xEnd, yEnd); 當(dāng)調(diào)用LineTo函數(shù),當(dāng)前位置為直線的終點。在默認(rèn)的設(shè)備環(huán)境中,當(dāng)前位置為(0,0),可以使用GetCurrentPositionEx函數(shù)獲取當(dāng)前位置。
? ? ? 為了畫一條由多條直線構(gòu)成的折線,可以使用PolyLine和PolyLineTo函數(shù),這樣可以不需要多次調(diào)用LineTo函數(shù)。這兩個函數(shù)聲明如下:
BOOLPolyline( HDC hdc, // handle to device contextCONST POINT *lppt, // array of endpointsint cPoints // number of points in array); 第一個參數(shù)hdc,表示設(shè)備環(huán)境句柄;
? ? ? 第二個參數(shù)lppt,表示點數(shù)組指針,由多條直線的起始點依次構(gòu)成;
? ? ? 第三個參數(shù)cPoints,表示點數(shù)組中點的個數(shù);
? ? ? 當(dāng)函數(shù)執(zhí)行成功后,返回值為非零值,否則返回零值。該函數(shù)不使用或者改變當(dāng)前位置。 ? ? ? BOOLPolylineTo( HDC hdc, // handle to device contextCONST POINT *lppt, // array of pointsDWORD cCount // number of points in array);

? ? ? 該函數(shù)與PolyLine函數(shù)功能相同,除了PolyLineTo函數(shù)需要使用當(dāng)前位置,并畫線結(jié)束后,將終點作為當(dāng)前位置。

5.3.3 邊框繪制函數(shù)

  • 繪制矩形函數(shù)Rectange聲明如下:?

BOOLRectangle( HDC hdc, // handle to DC intnLeftRect, // x-coord of upper-leftcorner of rectangle int nTopRect, // y-coord ofupper-left corner of rectangle int nRightRect, // x-coord oflower-right corner of rectangle int nBottomRect // y-coord oflower-right corner of rectangle); ?

第一個參數(shù)hdc,表示設(shè)備環(huán)境句柄;
第二個、三個參數(shù)nLeftRect和nTopRect,分別表示矩形左上角坐標(biāo)的水平位置和垂直位置;
第四個、五個參數(shù)nRightRect和nBottomRect,分別表示矩形的右下角坐標(biāo)的水平位置和垂直位置;
? ? ? ? ? ? ? ? ? ? ? ?Rectangle函數(shù)不僅繪制了矩形,而且還用當(dāng)前畫刷填充了一個矩形封閉區(qū)域。

  • 繪制橢圓函數(shù)Ellipse聲明如下: ? ? ? ? ??

BOOL Ellipse( HDC hdc, // handle to DCint nLeftRect, // x-coord of upper-left corner of rectangleint nTopRect, // y-coord of upper-left corner of rectangleint nRightRect, // x-coordof lower-right corner of rectangleint nBottomRect // y-coordof lower-right corner of rectangle);int nLeftRect, // x-coord of upper-left corner of rectangleint nTopRect, // y-coord of upper-left corner of rectangleint nRightRect, // x-coordof lower-right corner of rectangleint nBottomRect // y-coordof lower-right corner of rectangle);

? ? ? ? ? ? ? ? Ellipse函數(shù)參數(shù)與Rectangle函數(shù)參數(shù)一致。

  • 繪制圓角矩形函數(shù)RoundRect聲明如下: ? ? ? ? ??

BOOL RoundRect( HDC hdc, // handle to DCint nLeftRect, // x-coord of upper-left corner of rectangleint nTopRect, // y-coord of upper-left corner ofrectangleint nRightRect, // x-coord of lower-right corner of rectangleint nBottomRect, // y-coordof lower-right corner of rectangleint nWidth, // width of ellipse int nHeight // height of ellipse);

RoundRect函數(shù)前5個參數(shù)與Rectangle函數(shù)參數(shù)一致,第6個、7個參數(shù)nWidth和nHeight分別表示圓角橢圓的寬度和高度。

  • 繪制橢圓弧線函數(shù)Arc聲明如下:

BOOL Arc( HDC hdc, // handle to device contextint nLeftRect, // x-coord ofrectangle's upper-left cornerint nTopRect, // y-coord ofrectangle's upper-left cornerint nRightRect, // x-coord ofrectangle's lower-right cornerint nBottomRect, // y-coord of rectangle's lower-right cornerint nXStartArc, // x-coord offirst radial ending pointint nYStartArc, // y-coord offirst radial ending pointint nXEndArc, // x-coord ofsecond radial ending pointint nYEndArc // y-coord ofsecond radial ending point);

? ? ? ? ?Arc函數(shù)前5個參數(shù)與Rectangle函數(shù)參數(shù)相同,第6個、7個參數(shù)nXStartArc和nYStartArc構(gòu)成的坐標(biāo)(nXStartArc, nYStartArc),該坐標(biāo)與橢圓中心點的連線與橢圓的交點坐標(biāo),是橢圓弧線開始點的坐標(biāo)。第8個、9個參數(shù)nXEndArc和nYEndArc構(gòu)成的坐標(biāo)(nXEndArc, nYEndArc),該坐標(biāo)與橢圓中心點的連線與橢圓的交點坐標(biāo),是橢圓弧線結(jié)束點的坐標(biāo)。

  • 繪制橢圓弦函數(shù)Chord聲明如下:

BOOL Chord( HDC hdc, // handle to DCint nLeftRect, // x-coord of upper-left corner of rectangleint nTopRect, // y-coord of upper-left corner ofrectangleint nRightRect, // x-coord of lower-right corner of rectangleint nBottomRect, // y-coordof lower-right corner of rectangleint nXRadial1, // x-coord of first radial's endpointint nYRadial1, // y-coord of first radial's endpointint nXRadial2, // x-coord of second radial's endpointint nYRadial2 // y-coord of second radial's endpoint);

Chord函數(shù)前5個參數(shù)與Rectangle函數(shù)參數(shù)相同,第6個、7個參數(shù)nXRadial1和nYRadial1構(gòu)成的坐標(biāo)(nXRadial1, nYRadial1),該坐標(biāo)與橢圓中心連線的交點POS1。第8個、9個參數(shù)nXRadial2和nYRadial2構(gòu)成的坐標(biāo)(nXRadial2, nYRadial2),該坐標(biāo)與橢圓中心連線的交點POS2。POS1點和POS2點連線,與兩點之間的弧線(逆時針方向)一起構(gòu)成了封閉的橢圓弦。

  • 繪制橢圓餅函數(shù)Pie聲明如下:

BOOL Pie( HDC hdc, // handle to DCint nLeftRect, // x-coord of upper-left corner of rectangleint nTopRect, // y-coord of upper-left corner ofrectangleint nRightRect, // x-coord of lower-right corner of rectangleint nBottomRect, // y-coordof lower-right corner of rectangleint nXRadial1, // x-coord of first radial's endpointint nYRadial1, // y-coord of first radial's endpointint nXRadial2, // x-coord of second radial's endpointint nYRadial2 // y-coord of second radial's endpoint);

? ? ? ? ? ?Pie函數(shù)前5個參數(shù)與Rectangle函數(shù)參數(shù)相同,第6個、7個參數(shù)nXRadial1和nYRadial1構(gòu)成的坐標(biāo)(nXRadial1, nYRadial1),該坐標(biāo)與橢圓中心連線的交點POS1。第8個、9個參數(shù)nXRadial2和nYRadial2構(gòu)成的坐標(biāo)(nXRadial2, nYRadial2),該坐標(biāo)與橢圓中心連線的交點POS2。POS1點和橢圓中心的連線、POS2點和橢圓中心的連線,與兩點之間的弧線(逆時針方向)一起構(gòu)成了封閉的橢圓餅。

5.3.4 貝塞爾樣條曲線

????? 一條貝塞爾樣條曲線使用四個點定義:兩個端點和兩個控點。兩個端點表示曲線的起點和終點。控點好像“磁鐵”一樣把曲線從兩個端點間的直線處吸彎。

????? 繪制一條或者多條貝塞爾樣條曲線的函數(shù)PolyBezier和PolyBezierTo,函數(shù)聲明分別如下: ? ?

BOOLPolyBezier( HDC hdc, // handle to device contextCONST POINT* lppt, // endpoints and control pointsDWORD cPoints // count of endpoints and controlpoints);

和 ??

BOOL PolyBezierTo( HDC hdc, // handle to device contextCONST POINT *lppt, // endpoints and control pointsDWORD cCount // count of endpoints and controlpoints);

第一個參數(shù)hdc,表示設(shè)備環(huán)境句柄;

第二個參數(shù)lppt,表示終點和控點組成的POINT數(shù)組;

第三個參數(shù)cPoints/cCount,表示終點和控點的點個數(shù);

其中前4個點分別表示第一條貝塞爾樣條曲線的起點、第一個控點、第二個控點和終點,隨后的每一條貝塞爾樣條曲線則只需要給出三個點,因為前一條貝塞爾樣條曲線的終點就是后一條貝塞爾樣條曲線的起點,如此類推,cPoints/cCount等于所繪制的貝塞爾樣條曲線個數(shù)的3倍加1。

5.3.5 使用現(xiàn)有畫筆

畫筆決定了線條的顏色、寬度和樣式,樣式可以是實線、點線或者虛線。Windows提供三種“備用畫筆”:BLACK_PEN,WHITE_PEN和NULL_PEN。畫筆的默認(rèn)設(shè)備環(huán)境是BLACK_PEN。NULL_PEN表示不繪制任何圖形的畫筆。亦可以創(chuàng)建自己的畫筆。

????? 在Windows程序中,使用句柄來操作畫筆。畫筆句柄的類型為HPEN,可以用該類型聲明一個畫筆變量。可以調(diào)用GetStockObject函數(shù)獲取備用畫筆的句柄,如 ? ? ?

HPENhPen = GetStockObject(WHITE_PEN);

現(xiàn)在必須把該畫筆選入設(shè)備環(huán)境中:

SelectObject(hdc,hPen);? ??

5.3.6 創(chuàng)建、選擇和刪除畫筆

????? 盡管使用備用對象中的畫筆非常方便,但是只能使用實心的黑色畫筆、實心的白色畫筆或者沒有畫筆三種情況。如果想獲得更豐富的效果,則必須創(chuàng)建自己的畫筆。

????? 創(chuàng)建畫筆的一般過程:

  • 首先,調(diào)用CreatePen或者CreatePenIndirect函數(shù)創(chuàng)建一個“邏輯畫筆”對象,該函數(shù)會返回該畫筆對應(yīng)的句柄。

    CreatePen函數(shù)聲明如下:

HPEN CreatePen( intfnPenStyle, // pen style int nWidth, //pen width COLORREF crColor //pen color);

第一個參數(shù)fnPenStyle,表示畫筆的樣式,包括實心(PS_SOLID)、虛線(PS_DASH)、點線(PS_DOT)、虛點(PS_DASHDOT)、虛點點式(PS_DASHDOTDOT)、不可見式(PS_NULL)、內(nèi)部框架式(PS_INSIDEFRAME);

第二個參數(shù)nWidth,表示畫筆的寬度,備用畫筆的寬度總是1個像素寬。如果指定使用虛線或者點線樣式,同時把畫筆的寬度設(shè)定為大于1個像素,那么Windows使用實心的畫筆來代替;

第三個參數(shù)crColor,表示顏色,是COLORREF類型。

函數(shù)執(zhí)行成功后,返回畫筆的句柄,否則返回NULL。

HPEN CreatePenIndirect( CONST LOGPEN *lplgpn);

參數(shù)lplgpn,指向LOGPEN的指針。LOGPEN結(jié)構(gòu)聲明如下:

typedefstruct tagLOGPEN {UINT lopnStyle;POINT lopnWidth;COLORREF lopnColor; } LOGPEN, *PLOGPEN;

其中l(wèi)opnStyle、lopnWidth、lopnColor與CreatePen的三個參數(shù)一一對應(yīng),表示相同的意義。

  • 其次,調(diào)用SelectObject將創(chuàng)建的畫筆句柄選入設(shè)備環(huán)境中,接著可以使用該畫筆繪制線條。

SelectObject(hPen);

  • ?最后,在釋放環(huán)境句柄之后,調(diào)用DeleteObject函數(shù)刪除創(chuàng)建的邏輯畫筆。此后,畫筆的句柄將不再有效。

DeleteObject(hPen);

邏輯畫筆是一個“GDI對象”,一個程序可以創(chuàng)建6個GDI對象,其他5個分別是畫刷、位圖、區(qū)域、字體和調(diào)色板。除了調(diào)色板之外,所有這些對象都可以使用SelectObject函數(shù)選入設(shè)備環(huán)境。

下面三條規(guī)則控制畫筆等GDI對象的使用:

  • ?最終應(yīng)該刪除你所創(chuàng)建的所有GDI對象
  • 當(dāng)GDI對象被選入一個有效的設(shè)備環(huán)境時,不要刪除它
  • 不要刪除備用對象。

可以通過GetObject函數(shù)獲取指定畫筆句柄的特點:

GetObject(hPen, sizeof(LOGPEN),(LPVOID)&logPen);

如果需要獲取當(dāng)前被選入設(shè)備環(huán)境的畫筆句柄,則調(diào)用:?

hPen = GetCurrentObject(hdc, OBJ_PEN);

5.3.7 填充空隙

????? 使用點式畫筆和虛線畫筆會帶來這樣一個問題:點和虛線之間的空隙是什么顏色呢?空隙的顏色是由設(shè)備環(huán)境的兩個屬性(背景模式和背景顏色)決定的。默認(rèn)的背景模式是OPAQUE(不透明),這意味著Windows使用背景顏色來填充空隙,背景顏色在默認(rèn)時是白色。

????? 改變背景顏色函數(shù)是:

SetBkColor(hdc, crColor);

? ? ? 獲取背景顏色函數(shù)是:

GetBkColor(hdc);

? ? ? 改變背景模式函數(shù)是:

SetBkMode(hdc,iBkMode);

? ? ? 獲取背景模式函數(shù)是:

GetBkMode(hdc);

5.3.8 繪圖模式

??? 當(dāng)Windows使用一個畫筆繪制直線時,它實際上是將畫筆的像素顏色和目標(biāo)顯示表面的像素顏色進行布爾運算。對像素顏色執(zhí)行一個按位布爾運算稱為“光柵操作”(raster operation, ROP),簡稱“ROP”,因為繪制一條直線只涉及兩種像素顏色(即畫筆和目標(biāo)),這里的布爾運算就被稱為“二元光柵操作”,或者“ROP2”。Windows定義了16中ROP2運算碼,每一個都表示W(wǎng)indows組合畫筆像素色和目標(biāo)像素色的一種方法。在默認(rèn)的設(shè)備環(huán)境中,繪圖模式是R2_COPYPEN,表示W(wǎng)indows只是簡單地將畫筆像素的顏色復(fù)制到目標(biāo)像素色上。詳細(xì)16中繪圖模式詳見MSDN手冊。

????? 設(shè)置繪圖模式函數(shù): ? ? ?

SetROP2(hdc, iDrawMode);

? ? ? 獲取繪圖模式函數(shù):

GetROP2(hdc);

5.4 繪制填充區(qū)域

????? Windows用于繪制帶有邊框的填充區(qū)域的7個函數(shù)如下表示:??

函數(shù)名稱

圖形

Rectangle

直角矩形

Ellipse

橢圓

RoundRect

圓角矩形

Chord

一個弓形,由橢圓圓周上的弧和一根弦組成

Pie

橢圓上的一個扇形

Polygon

多邊形

PolyPolygon

多個多邊形

?

????? Windows使用當(dāng)前被選入設(shè)備環(huán)境的畫筆來繪制圖形的邊框線。邊框線使用當(dāng)前的背景模式、背景顏色和繪圖模式。這與Windows繪制線條一樣。

????? Windows使用當(dāng)前被選入設(shè)備環(huán)境的畫刷來填充圖形。在默認(rèn)情況下,使用的是備用對象WHITE_BRUSH。Windows定義了6種備用畫刷:WHITE_BRUSH、LTGRAY_BRUSH、GRAY_BRUSH、DRGRAY_BRUSH、BLACK_BRUSH和NULL_BRUSH(又稱為HOLLOW_BRUSH)。

????? Windows定義畫刷的句柄為HBRUSH類型,可用該類型定義一個畫刷句柄變量: ? ? ?

HBRUSHhBrush;

? ? ? 和選擇備用畫筆到設(shè)備環(huán)境中一樣,選擇備用畫刷到設(shè)備環(huán)境使用GetStockObjet函數(shù)來實現(xiàn)。

hBrush= GetStockObject(GRAY_BRUSH);

5.4.1 Polygon函數(shù)和多邊形填充模式

繪制多邊形函數(shù)Polygon聲明如下: ?

BOOLPolygon( HDC hdc, // handle to DCCONST POINT *lpPoints, // polygon verticesint nCount // count of polygon vertices);

第一個參數(shù)hdc,表示設(shè)備環(huán)境句柄;

第二個參數(shù)lpPoints,是一個POINT結(jié)構(gòu)的數(shù)組指針,包含多邊形的頂點;

第三個參數(shù)nCount,表示點的個數(shù)。

如果數(shù)組中最后一點和第一個點不同,則Windows會再加一條線連接最后一個點與第一個點。(在PolyLine函數(shù)中不會這么做。)。

繪制多個多邊形函數(shù)PolyPolygon聲明如下:?

BOOLPolyPolygon( HDC hdc, // handle to DCCONST POINT *lpPoints, // array of verticesCONST INT*lpPolyCounts, // array of count ofverticesint nCount // count of polygons);

第一個參數(shù)hdc,表示設(shè)備環(huán)境句柄;

第二個參數(shù)lpPoints,是一個POINTS結(jié)構(gòu)的數(shù)組指針,包含多邊形的頂點;

第三個參數(shù)lpPolyCounts,表示多邊形頂點個數(shù)的數(shù)組;

第四個參數(shù)nCount,表示多邊形的個數(shù);

? ? ? ? ? ? 對于Polygon和PolyPolygon函數(shù),Windows都使用設(shè)備環(huán)境中的當(dāng)前畫刷來填充區(qū)域。至于內(nèi)部是如何填充的,要取決于多邊形的填充模式,可以調(diào)用SetPolyFillMode函數(shù)來設(shè)置:

SetPolyFillMode(hdc,iMode);

填充模式包括ALTERNATE(交替)和WINDING(螺旋)兩種模式。對于ALTERNATE(交替)模式,我們可以想象從一個封閉區(qū)域紅的一點向無窮遠(yuǎn)處畫一條射線。只有該射線穿越奇數(shù)條邊框線時,封閉區(qū)域才會被填充。對于WINDING(螺旋)模式,我們可以設(shè)想從區(qū)域內(nèi)的一個點畫一條伸向無窮遠(yuǎn)的射線。如果射線穿越過奇數(shù)條邊框線,則區(qū)域被填充,這和ALTERNATE模式相同。如果射線穿越過偶數(shù)條邊框線,情況比較復(fù)雜,還要考慮到邊框線的繪制方向:在被穿越的偶數(shù)條邊框線中,不同的邊框線(相對于射線的方向)的數(shù)目如果相同,則區(qū)域不會被填充;不同方向的邊框線(相對于射線的方向)的數(shù)目如果不相等,則區(qū)域會被填充。

5.4.2 用畫刷填充內(nèi)部

Rectangle、RoundRect、Ellipse、Chord、Pie、Polygon和PolyPolygon函數(shù)繪制的圖形內(nèi)部會使用當(dāng)前設(shè)備環(huán)境的畫刷(有時也稱為圖案(pattern))來填充。畫刷是一個像素的很小的位圖,Windows在水平方向上和垂直方向上重復(fù)地使用它來填充一個區(qū)域。

當(dāng)Windows使用抖動技術(shù)顯示比通常顯示器可用顏色更多的色彩時,它實際上是使用畫刷來顯示顏色的。在單色系統(tǒng)中,Windows可以通過混合黑色和白色像素來建立64種不同的灰色色調(diào)。更確切地說,Windows可以建立64個不同的畫刷。對純黑色,像素的位圖的每一個像素的值都是0.如果64位中只有一位是1(代表白色),就對應(yīng)第一個灰色色調(diào)。如果2位是白色對應(yīng)第二個灰色色調(diào),如此類推,直到所有的的位圖位都是1,對應(yīng)純白色。對于16色或者256色的視頻系統(tǒng),畫刷也是由抖動實現(xiàn)的,這樣Windows可以顯示的顏色比通常顏色更多。

Windows允許使用5種函數(shù)來建立邏輯畫刷。調(diào)用SelectObject函數(shù)將畫刷選入設(shè)備環(huán)境,調(diào)用DeleteObject函數(shù)刪除已建立的畫刷。如果它被選入了設(shè)備環(huán)境中,則不要刪除它。

5種創(chuàng)建邏輯畫刷的函數(shù)如下所示:

  • CreateSolidBrush函數(shù):聲明HBRUSHCreateSolidBrush(? COLORREF crColor?? // brush color value);參數(shù)clColor表示畫刷顏色。當(dāng)函數(shù)執(zhí)行成功后,返回畫刷句柄,否則返回NULL
  • CreateHatchBrush函數(shù),用于創(chuàng)建由水平、垂直或者對角線組成的“陰影線標(biāo)記”(hatch mark)畫刷,函數(shù)聲明:

HBRUSH CreateHatchBrush( int fnStyle, // hatch style COLORREFclrref // foreground color);

第一個參數(shù)fnStyle,表示陰影線類型,包括HS_BDIAGONAL、HS_CROSS、HS_DIAGCROSS、HS_FDIAGONAL、HS_HORIZONTAL和HS_VERTICAL六種類型,如下圖所示:

第二個參數(shù)clrref,表示陰影線的顏色。函數(shù)執(zhí)行成功后,返回邏輯畫刷句柄,否則返回NULL。

  • CreatePatternBrush函數(shù)使用指定位圖作為邏輯畫刷,聲明:

HBRUSH CreatePatternBrush( HBITMAP hbmp // handle tobitmap);

參數(shù)hbmp,表示位圖句柄,該位圖可以是設(shè)備相關(guān)位圖,也可以是設(shè)備無關(guān)位圖。函數(shù)執(zhí)行成功后,返回邏輯畫刷句柄,否則返回NULL。

  • CreateDIBPatternBrushPt函數(shù)使用設(shè)備無關(guān)位圖作為邏輯畫刷,聲明:

HBRUSH CreateDIBPatternBrushPt( CONST VOID *lpPackedDIB, // bitmap bitsUINT iUsage // usage);

? ? ? ? ? ?參數(shù)詳見MSDN文檔。

  • CreateBrushIndirect函數(shù),該函數(shù)包含了其他4個函數(shù)的功能,聲明:

HBRUSHCreateBrushIndirect( CONST LOGBRUSH*lplb // brush information);

參數(shù)lplb,表示LOGBRUSH結(jié)構(gòu)的各種屬性。LOGBRUSH包含三個字段,lbStyle字段的值決定著Windows如惡化解釋其他的兩個字段。

????? 調(diào)用GetObject函數(shù)獲取畫刷的信息,如下形式: ? ? ??

GetObject(hBrush,sizeof(LOGBRUSH), (LPVOID)&logbrush);

? ? ? Logbrush變量是LOGBRUSH類型的。

5.5 GDI映射模式

????? 到目前為止,所有的范例程序都是相對于客戶區(qū)左上角坐標(biāo)并以像素為單位來繪制的。這是默認(rèn)的情況,但并非是唯一的選擇。設(shè)備環(huán)境中的“映射模式”屬性,能影響幾乎所有在客戶區(qū)繪制的圖形。和映射模式緊密相關(guān)的還有4個其他的設(shè)備屬性,分別為窗口原點(windoworigin)、視口原點(viewportorigin)、窗口范圍(windowextents)、視口范圍(viewportextents)。

????? 在幾乎所有的GDI函數(shù)中,輸入的坐標(biāo)值都是“邏輯單位”(logic unit)。Windows必須要將邏輯單位轉(zhuǎn)換為“設(shè)備單位”(device unit),也就是像素。這些轉(zhuǎn)換都是由映射模式、窗口原點、視口原點、窗口范圍和視口范圍共同控制的。映射模式同樣暗含著x軸和y軸的方向,也就是說,它決定了隨著向顯示區(qū)域的左邊移動,x值是增加還是減少;隨著向顯示區(qū)域的上邊移動,y值是增加還是減少。

????? Windows定義了8種映射模式,具體描述如下表所示:

映射模式

邏輯單位

值增加的方向

x軸

Y軸

MM_TEXT

像素

MM_LOMETRIC

0.1mm

MM_HIMETRIC

0.01mm

MM_LOENGLISH

0.01in

MM_HIENGLISH

0.001in

MM_TWIPS

1/1440in

MM_ISOTROPIC

任意(x=y)

可選

可選

MM_ANISOTROPIC

任意(x!=y)

可選

可選

?

????? 映射模式設(shè)置可以由下面的函數(shù)實現(xiàn): ? ? ?

SetMapMode(hdc,iMapMode);

? ? ? 當(dāng)函數(shù)執(zhí)行成功后,返回值為之前的映射模式,否則返回0。

????? 映射模式獲取可以由下面的函數(shù)實現(xiàn): ? ? ?

iMapMode= GetMapMode(hdc);

5.5.1 設(shè)備坐標(biāo)和邏輯坐標(biāo)

????? 我們或許會有這樣的疑問:如果使用MM_LOENGLISH映射模式,是否會得到以百分之一英寸來度量的WM_SIZE消息?絕對不是。Windows對所有消息(例如WM_SIZE、WM_MOVE和WM_MOUSEMOVE),所有非GDI函數(shù),甚至一些GDI函數(shù),都繼續(xù)使用設(shè)備坐標(biāo)。映射模式是設(shè)備環(huán)境的一種屬性,只有當(dāng)使用以設(shè)備環(huán)境句柄作為參數(shù)的GDI函數(shù),映射模式才會生效。GetSystemMetrics是一個非GDI函數(shù),因此它將繼續(xù)以設(shè)備單位的形式,也就是像素為單位返回。盡管GetDeviceCaps是一個需要設(shè)備環(huán)境句柄的GDI函數(shù),但是Windows繼續(xù)為HORZRES和VERTRES索引返回設(shè)備單位。因為這個函數(shù)的目的之一就是以像素為單位向程序返回設(shè)備的尺寸。

????? 然而,通過調(diào)用GetTextMetrics函數(shù)獲取的TEXTMETRIC結(jié)構(gòu)中的值是以邏輯單位形式給出的。如果調(diào)用這個函數(shù)時,映射模式是MM_LOENGLISH,GetTextMetrics函數(shù)就以百分之一英寸為單位返回字符的高度和寬度。為了簡化工作,當(dāng)調(diào)用GetTextMetrics函數(shù)獲取字符的高度和寬度信息時,映射模式應(yīng)當(dāng)和基于這些尺寸繪制文本時使用的映射模式相同。

5.5.2 設(shè)備坐標(biāo)系統(tǒng)

Windows會把在GDI函數(shù)中指定的邏輯坐標(biāo)轉(zhuǎn)換為設(shè)備坐標(biāo)。在我們討論用于各種映射模式的邏輯坐標(biāo)系統(tǒng)之前,首先介紹一下Windows為視頻顯示器定義的不同的設(shè)備坐標(biāo)系統(tǒng)。盡管在大多數(shù)情況下我們都是在我們窗口的客戶區(qū)內(nèi)工作,但是在有些情況下,Windows還使用另外兩種設(shè)備坐標(biāo)系統(tǒng)。在所有的設(shè)備坐標(biāo)系統(tǒng)中,單位都是以像素形式表示的。水平方向上x值從左向右增加,垂直方向上y值從上往下增加。

當(dāng)我們使用整個屏幕時,我們是以“屏幕坐標(biāo)”(screen coordinate)的形式工作的。屏幕左上角是點(0,0)。屏幕坐標(biāo)用于WM_MOVE消息(對非子窗口)和下列的Windows函數(shù)中:CreateWindow和MoveWindows(對非子窗口)、GetMeesagePos、GetCursorPos、SetCursorPos、GetWindowRect和WindowFromPoint。(這并不一個完整的清單)。這些函數(shù)一般分為兩類:一類是與窗口無關(guān)的函數(shù)(例如兩個和鼠標(biāo)指針相關(guān)的函數(shù)),還有一類是必須根據(jù)屏幕上的點移動或者尋找窗口的函數(shù)。如果使用帶有“DISPLAY”參數(shù)的CreateDC函數(shù)來獲取整個屏幕的設(shè)備環(huán)境,那么在GDI調(diào)用中,邏輯坐標(biāo)值將被默認(rèn)映射屏幕坐標(biāo)。

“全窗口”坐標(biāo)指的是一個應(yīng)用程序的整個應(yīng)用窗口,包括標(biāo)題欄、菜單、滾動條和邊框。對于一個普通的應(yīng)用窗口,,點(0,0)是邊框的左上角。全窗口坐標(biāo)在Windows中很少用,但是如果設(shè)備環(huán)境是從GetWindowDC函數(shù)獲取的,則在GDI函數(shù)調(diào)用中,邏輯坐標(biāo)會被默認(rèn)映射為全窗口坐標(biāo)。

第三種設(shè)備坐標(biāo)系統(tǒng)是“客戶區(qū)坐標(biāo)”。點(0,0)是客戶區(qū)左上角。調(diào)用GetDC或者BeginPaint函數(shù)獲取設(shè)備環(huán)境時,在GDI函數(shù)中的邏輯坐標(biāo)將被默認(rèn)轉(zhuǎn)換為客戶區(qū)坐標(biāo)。

可以使用ClientToScreen函數(shù)將客戶區(qū)坐標(biāo)轉(zhuǎn)換到屏幕坐標(biāo),反之亦然,可以調(diào)用ScreenToClient函數(shù)把屏幕坐標(biāo)轉(zhuǎn)換為客戶區(qū)坐標(biāo)。也可以調(diào)用GetWindowRect函數(shù)以屏幕坐標(biāo)的形式獲取整個窗口的位置和大小。這三個函數(shù)為把任何一種設(shè)備坐標(biāo)鉆換為另一種設(shè)備坐標(biāo)提供了足夠的信息。

5.5.3 視口和窗口

????? 映射模式定義了Windows如何將GDI函數(shù)中指定的邏輯坐標(biāo)映射到設(shè)備坐標(biāo)。這里的設(shè)備坐標(biāo)系統(tǒng)取決于獲取設(shè)備環(huán)境所用的函數(shù)。為了繼續(xù)討論映射模式,我們需要定義一些術(shù)語。映射模式被定義為從“窗口”(windows)(邏輯坐標(biāo))到“視口”(viewport)(設(shè)備坐標(biāo))的映射。

????? 使用這兩個術(shù)語是很不正確的,因為他們在其他情況下還有其他意思。在其他的一些圖形界面系統(tǒng)中,視口常常含有“剪切區(qū)域”的意思。在Windows中,“窗口”還有一個非常具體的意思,就是描述一個程序占用屏幕的區(qū)域。在本章的討論中,我們必須先把這些屬于的陳見放到一邊。

????? 視口是以設(shè)備坐標(biāo)(像素)的形式指定的。大多數(shù)情況下,視口與客戶區(qū)相同,但是如果從GetWindowDC或者CreateDC函數(shù)獲取了設(shè)備環(huán)境,視口也可以是全窗口坐標(biāo)或者是屏幕坐標(biāo)。點(0,0)是客戶區(qū)(或者全窗口,或者屏幕)的左上角。X值向右增加,y值向下增加。

????? 窗口時以邏輯坐標(biāo)的形式指定的,可能是像素、毫米、英寸或者其他任何單位。可以在GDI繪圖函數(shù)中指定想用的邏輯窗口坐標(biāo)。

????? 對于所有的映射模式,Windows使用下面公式將窗口(邏輯)坐標(biāo)轉(zhuǎn)換為視口(設(shè)備)坐標(biāo):

?????

其中(xWindow,yWindow)是一個待轉(zhuǎn)換的邏輯點坐標(biāo),(xViewport,yViewport)是一個待轉(zhuǎn)換的設(shè)備點坐標(biāo),大多數(shù)情況下是客戶區(qū)坐標(biāo)。點(xWinOrg, yWinOrg)是在邏輯坐標(biāo)系下的窗口的原點,點(xViewOrg, yViewOrg)是在設(shè)備坐標(biāo)系下視口的原點。默認(rèn)情況下窗口原點和視口原點都是(0,0),可以改變它們。點(xWinExt,yWinExt)是在邏輯坐標(biāo)系下的窗口范圍,點(xViewExt,yViewExt)是在設(shè)備坐標(biāo)系下的視口范圍。在特定映射模式下,可以改變窗口范圍和視口范圍。

????? 窗口范圍和視口范圍可以是負(fù)值,表示邏輯x軸的值不是向右增加,邏輯y軸的值不是向下增加。

????? Windows提供了兩個函數(shù)來讓你在程序中在設(shè)備點和邏輯點之間轉(zhuǎn)換,其中將設(shè)備點轉(zhuǎn)換為邏輯點的函數(shù): ? ??

DPtoLP(hdc,pPoints, iNumber);

其中變量pPoints是一個指針,它指向一個POINT結(jié)構(gòu)的數(shù)組,iNumber是待轉(zhuǎn)換的點的個數(shù)。例如,我們會發(fā)現(xiàn)這個函數(shù)對于把從GetClientRect函數(shù)(它總以設(shè)備單位的形式)獲取的客戶區(qū)大小轉(zhuǎn)換到邏輯坐標(biāo)非常有用: ? ??

GetClientRect(hwnd,&rect); DLtoLP(hdc,(PPOINT)&rect, 2);

將邏輯點轉(zhuǎn)換為設(shè)備點的函數(shù):

LPtoDP(hdc,pPoints, iNumber);

獲取窗口原點坐標(biāo)的函數(shù):

GetWindowOrgEx(hdc,&pt);

設(shè)置窗口原點坐標(biāo)的函數(shù):?

SetWindowOrgEx(hdc,x, y, lppoint);

獲取窗口范圍的函數(shù):

GetWindowExtEx(hdc,lpsize);

在部分映射模式下,可以設(shè)置窗口范圍,該函數(shù):

SetWindowExtEx(hdc,xExtent, yExtent, lpSize);

獲取視口坐標(biāo)原點的函數(shù):?

GetViewportOrgEx(hdc,&pt);

設(shè)置視口坐標(biāo)原點的函數(shù): ? ? ? ? ? ??

SetViewportOrgEx(hdc,x, y, lppoint); 獲取視口范圍的函數(shù):

GetViewportExtEx(hdc,lpsize);

在某些映射模式下,可以設(shè)置視口范圍,該函數(shù):?

SetViewportExtEx(hdc,xExtent, yExtent, lpSize);

5.5.4 使用MM_TEXT

????? 在MM_TEXT映射模式下,默認(rèn)的原點和范圍顯示如下:

????? 窗口原點:(0,0)?? ,可以改變

????? 視口原點:(0,0)?? ,可以改變

????? 窗口范圍:(1,1),不可以改變

????? 視口范圍:(1,1),不可以改變

????? 猶豫視口范圍和窗口范圍的比例都是1,因此從邏輯坐標(biāo)到視口坐標(biāo)的轉(zhuǎn)換公式可以簡化為

????? xViewport= xWindow – xWinOrg + xViewOrg

????? yViewport= yWindow – yWinOrg + yViewOrg

5.5.5 度量映射模式

????? Windows包含5種映射模式,它們分別表示將邏輯坐標(biāo)轉(zhuǎn)換為物理坐標(biāo)的不同方式。因為在x軸上y軸上的邏輯坐標(biāo)都被映射到相同的物理度量單位。5種映射模式的度量大小,具體如下表所示:

映射模式

邏輯單位

英寸(in.)

毫米(mm)

MM_LOENGLISH

0.01 in

0.01

0.254

MM_LOMETRIC

0.1mm

0.0394

0.1

MM_HIENGLISH

0.001in

0.001

0.0254

MM_TWIPS

1/1440in

0.000694

0.0176

MM_HIMETRIC

0.01mm

0.000394

0.01

在默認(rèn)情況下,度量映射模式的窗口和視口的原點及范圍如下:

窗口原點:(0,0),可以改變

視口原點:(0,0),可以改變

窗口范圍:(?, ?), 不可改變

視口范圍:(?, ?), 不可改變

這里的問號,表示窗口和視口的范圍取決于映射模式和設(shè)備分辨率。

在WindowsNT中,視口的范圍是基于屏幕上像素的尺寸的,這個信息是使用HORZRES和VERTRES參數(shù)從GetDeviceCaps函數(shù)獲取的。窗口的范圍是基于假定的顯示尺寸,該顯示顯示是在使用HORZSIZE和VERTSIZE參數(shù)時調(diào)用GetDeviceCaps函數(shù)返回的。正如前面提到的,這些值通常是320mm和240mm。如果設(shè)置的顯示器的像素是,則視口范圍和窗口范圍如下表所示:

映射模式

視口范圍(x,y)

窗口范圍(x,y)

MM_LOMETRIC

(1024, -768)

(3200, 2400)

MM_HIMETRIC

(1024, -768)

(32000, 24000)

MM_LOENGLISH

(1024, -768)

(1260, 945)

MM_HIENGLISH

(1024, -768)

(12598, 9449)

MM_TWIPS

(1024, -768)

(18142, 13606)

y前的符號表示y軸方向的改變。對于這5種映射模式,y值隨著設(shè)備上移增加。

5.5.6 自定義的映射模式

????? 剩余的兩種映射模式分別稱為MM_ISOTROPIC和MM_ANISOTROPIC。只有在這兩種映射模式下,Windows才允許你改變視口和窗口的范圍,也就意味著你能夠改變Windows用于轉(zhuǎn)換邏輯坐標(biāo)和設(shè)備坐標(biāo)的換算因子。Isotropic的意思是”各向同性;anisotropic是“各向異性”。和前面的度量映射模式類似,MM_ISOTROPIC會同比例地縮放兩個坐標(biāo)軸,x軸上的邏輯單位和y軸上的邏輯單位表示的物理尺寸時相同的。對于建立寬高比與顯示設(shè)備無關(guān)的圖像,這是有幫助的。

????? MM_ISOTROPIC和其他度量映射模式的區(qū)別是在使用MM_ISOTROPIC映射模式時,可以控制邏輯單位的物理尺寸。如果需要的話,可以依據(jù)客戶區(qū)來調(diào)整邏輯單位的大小。這樣會使你繪制的圖像總是包含在客戶區(qū)內(nèi),并相應(yīng)的放大或者縮小。

????? Windows程序能夠通過調(diào)整窗口和視口的范圍來處理圖形大小的變化,這樣一來,程序代碼能夠在繪圖函數(shù)中使用相同的邏輯單位,而不用去管窗口的大小。

????? 有時候,MM_TEXT映射模式和度量映射模式也被稱為“完全受限”的映射模式。這意味著不能改變窗口和視口的范圍。MM_ISOTROPIC映射模式是一種“半受限”映射模式。Windows允許改變窗口和視口的范圍,但是Windows會調(diào)整它們的值了,這是為了讓x和y邏輯單位表示相同的物理尺寸。MM_ANISOTROPIC映射模式是“不受限”的,你可以改變窗口和視口的范圍,并且Windows不會相應(yīng)的調(diào)整它們的值。

  • ? ? ? MM_ISOTROPIC

????? 當(dāng)?shù)谝淮卧O(shè)置映射模式為MM_ISOTROPIC時,Windows使用與MM_LOMETRIC映射模式相同的窗口和視口范圍。有一個差別是現(xiàn)在可以根據(jù)自己的喜好調(diào)用SetWindowExtEx和SetViewportExtEx函數(shù)來改變范圍。接著,Windows將調(diào)整范圍以使得兩個軸上的邏輯單位表示相同的物理距離。

????? 一般說來,調(diào)用SetWindowExtEx函數(shù)時,要把參數(shù)設(shè)定為期望得到的邏輯窗口的邏輯大小,而在調(diào)用SetViewportExtEx函數(shù)時,則把參數(shù)設(shè)定為客戶區(qū)的實際寬度和高度。當(dāng)Windows調(diào)整這些范圍時,它必須讓邏輯窗口可以容納在對應(yīng)的物理視口之內(nèi),這就可能導(dǎo)致一部分的客戶區(qū)落在邏輯窗口外邊。應(yīng)當(dāng)在調(diào)用SetViewportExtEx之前先調(diào)用SetWindowExtEx來最有效地使用客戶區(qū)的空間。

  • ? ? ? MM_ANISOTROPIC

????? 在MM_ANISOTROPIC映射模式下,Windows不對設(shè)置的視口和窗口范圍做任何的調(diào)整。這也就意味著MM_ANISOTROPIC并不需要保持正確的高寬比。

????? 使用MM_ANISOTROPIC的一種方式是在客戶區(qū)使用任意的坐標(biāo)方式,就像我們在MM_ISOTROPIC映射模式下所做的一樣。區(qū)別是MM_ANISOTROPIC模式下不會調(diào)整視口和窗口范圍。

????? 使用MM_ANISOTROPIC映射模式的另一種方式是把x和y單位設(shè)置為固定值,但是值并不相等。

5.6 矩形、區(qū)域和剪裁

????? Windows還有其他幾個使用RECT(矩形)結(jié)構(gòu)和區(qū)域的繪圖函數(shù)。一個區(qū)域指的是屏幕上的一塊空間,它由矩形、多邊形和橢圓組合而成。

5.6.1 處理矩形

  • FillRect函數(shù):使用指定的畫刷填充矩形(到達但不包括右下坐標(biāo))。方式:

FillRect(hdc, &rect, hBrush);

這個函數(shù)不需要把畫刷提前選入設(shè)備環(huán)境中。

  • FrameRect函數(shù):使用畫刷繪制一個矩形框,但是它并不填充矩形。方式:

FrameRect(hdc,&rect, hBrush);

  • InvertRect函數(shù):翻轉(zhuǎn)矩形內(nèi)所有的像素,將1變?yōu)?,將0變?yōu)?。方式:

InvertRect(hdc, &rect);

  • SetRect函數(shù):設(shè)置RECT結(jié)構(gòu)的4個字段。方式:

SetRect(&rect, xLeft, yTop, xRight, yBottom);

  • OffsetRect函數(shù):將矩形沿x軸和y軸移動幾個單位。方式:Offset(&rect, x, y);
  • ?InflateRect函數(shù):增大或者減小矩形的尺寸。方式:InflateRect(&rect, x, y);
  • ?SetRectEmpty函數(shù):設(shè)置矩形結(jié)構(gòu)的個字段為0:。方式:SetRectEmpty(&rect);
  • CopyRect函數(shù):將一個矩形結(jié)構(gòu)復(fù)制到另一個矩形結(jié)構(gòu)。方式:CopyRect(&DestRect, &SrcRect);
  • IntersectRect函數(shù):獲取兩個矩形的交集。方式:IntersetRect(&DestRect, &SrcRect1, &SrcRect2);
  • UnionRect函數(shù):獲取兩個矩形的并集。方式:UnitRect(&DestRect, &SrcRect1, &SrcRect2);
  • IsRectEmpty函數(shù):判斷矩形是否為空。方式:bEmpty = IsRectEmpty(&rect);
  • PtInRect函數(shù):判斷點是否在矩形內(nèi)部。方式:bInRect = PtInRect(&rect, point);
  • =函數(shù):復(fù)制矩形結(jié)構(gòu)字段。方式:DestRect= SrcRect;

5.6.2 隨機矩形

????? 在任何一個圖形系統(tǒng)中,總存在這樣一個有趣的程序,即簡單地使用隨機的尺寸和顏色不停地繪制一系列的圖像,例如隨機大小和顏色的矩形。在Windows中可以創(chuàng)建這樣的一個程序:但是這不是想象中的那樣容易。我們需要意識到,不能在處理WM_PAINT消息中簡單地使用while(TRUE)循環(huán)。當(dāng)然,這樣做會很有效,但是這樣做的結(jié)果是,程序?qū)⑼V箤ζ渌⒌奶幚?#xff0c;而且程序不能退出或者最小化。

????? 一種可接受的方式是設(shè)置一個向你的窗口函數(shù)發(fā)送WM_TIMER消息的Windows計時器。對每個WM_TIMER消息,可以調(diào)用GetDC函數(shù)獲取設(shè)備環(huán)境,然后繪制一個隨機矩形。接著調(diào)用ReleaseDC函數(shù)釋放設(shè)備環(huán)境。但是那樣做又使程序失去一些趣味性,因為程序不能很快地繪制隨機矩形。必須等待每個WM_TIMER消息,那樣會依賴系統(tǒng)時鐘的精度。

????? 在Windows中有很多的“空閑時間”,在這期間所有的消息隊列都是空的。Windows就在等待鍵盤或者鼠標(biāo)的輸入。那么能否在空閑期間從某種程度上獲取控制并繪制隨機矩形,而一旦有效加載到程序的消息隊列,就釋放控制呢?這正是PeekMessage函數(shù)的“用武之地”。下面是PeekMessage函數(shù)調(diào)用的一個例子:

PeekMessage(&msg,NULL, 0, 0, PM_REMOVE);

函數(shù)的前4個參數(shù)與GetMessage函數(shù)相同。最后一個參數(shù)PM_REMOVE,表示刪除消息隊列中的消息。如果不想刪除,則設(shè)置最后一個參數(shù)為PM_NOREMOVE。

????? GetMessage函數(shù)并不把控制權(quán)交給程序,除非它從程序的消息隊列中獲得了消息。但是PeekMessage函數(shù)卻總是立即返回,不管消息是否出現(xiàn)。當(dāng)一個消息在程序的消息隊列中時,PeekMessage函數(shù)的返回值是TRUE,而消息則像正常處理一樣處理,當(dāng)隊列中沒有消息時,PeekMessage返回FASLE。

????? 這允許我們替換正常的消息循環(huán),正常的消息循環(huán)如下所示:

while(GetMessage(&msg, NULL, 0, 0)) {TranslateMessage(&msg);DispatchMessage(&msg); }

????? 替換后的消息循環(huán)如下:

while (TRUE) { if ( PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {if ( msg.message == WM_QUIT){ break; } TranslateMessage(&msg); DispatchMessage(&msg); } else {[other program lines to do some work] } } return msg.wParam;

注意:GetMessage函數(shù)檢測到WM_QUIT消息時,返回值為FALSE,這與PeekMessage函數(shù)不一樣。

? ????? 如果PeekMessage函數(shù)返回TRUE,那么消息會正常執(zhí)行。如果返回FALSE,那么程序可以在返回給Windows控制之前做些事情(如顯示一個隨機矩形)。

參考如上方法,我們實現(xiàn)一個顯示隨機矩形的程序,程序代碼如下所示:

/*------------------------------------------RANDRECT.C -- Displays Random Rectangles(c) Charles Petzold, 1998------------------------------------------*/#include <windows.h> #include <stdlib.h> // for the rand functionLRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; void DrawRectangle (HWND) ;int cxClient, cyClient ;int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,PSTR szCmdLine, int iCmdShow) {static TCHAR szAppName[] = TEXT ("RandRect") ;HWND hwnd ;MSG msg ;WNDCLASS wndclass ;wndclass.style = CS_HREDRAW | CS_VREDRAW ;wndclass.lpfnWndProc = WndProc ;wndclass.cbClsExtra = 0 ;wndclass.cbWndExtra = 0 ;wndclass.hInstance = hInstance ;wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;wndclass.lpszMenuName = NULL ;wndclass.lpszClassName = szAppName ;if (!RegisterClass (&wndclass)){MessageBox (NULL, TEXT ("This program requires Windows NT!"),szAppName, MB_ICONERROR) ;return 0 ;}hwnd = CreateWindow (szAppName, TEXT ("Random Rectangles"),WS_OVERLAPPEDWINDOW,CW_USEDEFAULT, CW_USEDEFAULT,CW_USEDEFAULT, CW_USEDEFAULT,NULL, NULL, hInstance, NULL) ;ShowWindow (hwnd, iCmdShow) ;UpdateWindow (hwnd) ;//充分利用“空余時間”繪制隨機矩形while (TRUE){if (PeekMessage (&msg, NULL, 0, 0, PM_REMOVE)){if (msg.message == WM_QUIT)break ;TranslateMessage (&msg) ;DispatchMessage (&msg) ;}elseDrawRectangle (hwnd) ;}return msg.wParam ; }LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam) {switch (iMsg){case WM_SIZE:cxClient = LOWORD (lParam) ;cyClient = HIWORD (lParam) ;return 0 ;case WM_DESTROY:PostQuitMessage (0) ;return 0 ;}return DefWindowProc (hwnd, iMsg, wParam, lParam) ; }void DrawRectangle (HWND hwnd) {HBRUSH hBrush ;HDC hdc ;RECT rect ;if (cxClient == 0 || cyClient == 0)return ;SetRect (&rect, rand () % cxClient, rand () % cyClient,rand () % cxClient, rand () % cyClient) ;hBrush = CreateSolidBrush (RGB (rand () % 256, rand () % 256, rand () % 256)) ;hdc = GetDC (hwnd);FillRect (hdc, &rect, hBrush) ;ReleaseDC (hwnd, hdc) ;DeleteObject (hBrush) ; }


5.6.3 建立和繪制區(qū)域

????? 一個區(qū)域是對顯示器一塊區(qū)間的描述,這個空間可以是矩形、多邊形和橢圓的組合。可以使用區(qū)域進行繪圖或者剪裁。將區(qū)域選入設(shè)備環(huán)境,就可以使用這個區(qū)域來剪裁(也就是說,將繪制動作限制在客戶區(qū)的一個特定部分)。同畫筆和畫刷一樣,區(qū)域也是GDI對象,應(yīng)當(dāng)通過調(diào)用DeleteObject函數(shù)來刪除所有建立的區(qū)域。

????? 當(dāng)建立一個區(qū)域時,Windows會返回一個類型為HRGN的區(qū)域句柄。最簡單的區(qū)域是一個矩形區(qū)域。可以用下面兩種方法建立一個矩形區(qū)域: ? ? ?

hRgn= CreateRectRgn(xLeft, yTop, xRight, yBottom);

或者 ? ? ? ? ? ?

hRgn= CreateRectRngIndirect(&rect);

建立橢圓區(qū)域的兩種方法:

hRgn= CreateEllipticRgn(xLeft, yTop, xRight, yBottom);

或者

hRgn= CreateEllipticRgnIndirect(&rect);

創(chuàng)建圓角矩形可以通過CreateRoundRectRgn函數(shù)來實現(xiàn)。

創(chuàng)建一個多邊形區(qū)域的函數(shù):

hRgn= CreatePolygonRgn(&point, iCount, iPolyFillMode);

同理,可以通過CreatePolyPolygonRgn函數(shù)創(chuàng)建多個多邊形區(qū)域。

CombineRgn函數(shù)可以把兩個區(qū)域按照某種組合方式,生成一個新的區(qū)域,如下:?

iRgnType= CombineRgn(hDestRgn, hSrcRgn1, hSrcRgn2, iCombine);

區(qū)域組合方式如下表所示:

iCombine值

新的區(qū)域

RGN_AND

兩個源區(qū)域的公共部分

RGN_OR

兩個源區(qū)域的全部

RGN_XOR

兩個源區(qū)域的全部,但除去公共部分

RGN_DIFF

hSrcRgn1不在hSrcRgn2中的部分

RGN_COPY

hSrcRgn1的全部(忽略hSrcRgn2)

?????

?

?

?

?

?



返回值iRgnType為如下值之一:

  • NULLREGION,指的是一個空的區(qū)域;
  • SIMPLEREGION,指的是一個簡單的矩形、橢圓或者多邊形;
  • COMPLEXREGION,指的是矩形、橢圓或者多邊形的組合;
  • ERROR,指的是有錯誤發(fā)生。

當(dāng)區(qū)域建立后,可以用如下四個操作區(qū)域的函數(shù):

  • FillRgn函數(shù):用指定畫刷填充指定區(qū)域。方式:FillRgn(hdc,hRgn, hBrush);
  • FrameRgn函數(shù):用指定畫刷在區(qū)域周圍繪制邊框。方式:FrameRgn(hdc,hRgn, hBrush, xFrame, yFrame);其中參數(shù)xFrame和yFrame表示繪制邊框的邏輯寬度和高度。
  • PaintRgn函數(shù):使用當(dāng)前被選入設(shè)備環(huán)境的畫刷來填充區(qū)域。方式:PaintRgn(hdc, hRgn);

當(dāng)使用完一個區(qū)域后,使用DeleteObject函數(shù)刪除該區(qū)域。

5.6.4 矩形與區(qū)域的剪裁

????? 區(qū)域在剪裁中扮演著重要角色。InvalidateRect函數(shù)使顯示的矩形區(qū)域無效,并產(chǎn)生一個WM_PAINT消息。GetUpdateRect函數(shù)可以獲取無效矩形的坐標(biāo),并且使用ValidateRect函數(shù)使客戶區(qū)的矩形有效。當(dāng)接收到一個WM_PAINT消息時,PAINTSTRUCT結(jié)構(gòu)中的無效矩形的坐標(biāo)是可以利用的。這個結(jié)構(gòu)是通過BeginPaint函數(shù)填充的。這個無效矩形也定義了一個“剪裁區(qū)域”,不能在剪裁區(qū)域之外繪圖。

Windows有兩個類似InvalidateRect和ValidateRect的函數(shù),用于處理矩形而不是矩形:

InvalidateRgn(hdc, hRgn, bErase);

和?

ValidateRgn(hdc, hRgn);

當(dāng)接收一條由無效區(qū)域產(chǎn)生的WM_PAINT消息時,剪裁區(qū)域在形狀上不一定是矩形。

可以通過將一個區(qū)域選入設(shè)備環(huán)境來創(chuàng)建你自己的裁剪區(qū)域,將區(qū)域選入設(shè)備環(huán)境可以使用

SeletObject(hdc,hRgn);

或者?

SelectClipRgn(hdc,hRgn);

? ? ? GDI為剪裁區(qū)域做了一個副本,因此當(dāng)把區(qū)域?qū)ο筮x入到設(shè)備環(huán)境后,可以刪除它。Windows還包括幾個操作這個剪裁區(qū)域的函數(shù),例如ExcludeClipRect函數(shù)用來從剪裁區(qū)域中去除一個矩形;IntersectClipRgn函數(shù)用來建立一個新的剪裁區(qū)域,這個新的剪裁區(qū)域是先前的剪裁區(qū)域和某個矩形的交集;OffsetClipRgn函數(shù)用來把一個剪裁區(qū)域移動到客戶區(qū)的另一部分。

5.7 總結(jié)

????? 這次,我們花了大篇幅的時間和文字來介紹繪圖基礎(chǔ)內(nèi)容,雖然看是冗余,但是這將為后續(xù)的繪圖操作打下良好的基礎(chǔ)。之后我們將在Chapter 13介紹打印機、Chapter 14/15介紹位圖,Chapter 17介紹文本和字體、Chapter 18介紹圖元文件。好了,我們下次將學(xué)習(xí)鍵盤操作。

?


總結(jié)

以上是生活随笔為你收集整理的Chapter 05 绘图基础的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。