1. 程式人生 > >第五章 繪圖基礎 (GDI、裝置環境、點線繪製、填充)

第五章 繪圖基礎 (GDI、裝置環境、點線繪製、填充)

光柵裝置 raster  device   向量裝置 vector device    對映模式 mapping 轉換 transform 圖元檔案 metafile  區域region  路徑 path

剪裁 clipping  調色盤 palettes 列印 printing

計算機圖形、計算機影象 Computer Graphics

stock  pen 備用畫筆

GDI 的結構:

windows圖形裝置介面(GDI)支援與裝置無關的圖形。(這裡有個概念裝置無關性

裝置無關性:就是作業系統遮蔽了硬體裝置的差異,使使用者程式設計時無需考慮特殊的硬體設定。因為如果你脫離WINDOWS API的話,繪圖勢必要與顯示卡驅動打交道,

而API幫助你脫離了直接對驅動的操作,實現與驅動無關的操作。這裡就用到了GDI 函式。

圖形輸出裝置分為兩大類:光柵裝置 和 向量裝置

大多數PC輸出裝置是光柵裝置,這也就意味著它們將影象表示成以點的形式構成的矩陣。該類輸出裝置有視訊顯示介面卡、點陣印表機和鐳射印表機。

向量裝置則使用線條來繪製圖像,通常指繪圖機。

GDI包含幾百個函式,可以分為下面幾大類。

1> 獲取(或建立) 和 釋放(或銷燬) 裝置環境的函式。

如繪製時,需要處理使用一個裝置環境控制代碼。 在處理WM_PAINT訊息時使用 BeginPaint 和EndPaint函式。在處理其他訊息時,通過呼叫GetDC 和 ReleaseDC函式來達到相同的目的。

2> 獲取裝置環境資訊的函式

如使用GetTextMetrics函式來獲取當前被選入裝置環境的字型的大小資訊。

3> 繪圖函式

如使用TextOut 在視窗的客戶區輸出文字。 以及使用GDI函式繪製線條和填充區域。在第14、15章中,如何繪製點陣圖影象。

4> 設定和獲取裝置環境屬性的函式

裝置環境的屬性確定繪圖函式繪圖時的各種細節。例如,可以使用SetTextColor函式來指定TextOut(或者其他文字輸出函式)繪製的文字的顏色。

前面使用的SetTextAlign函式來通知GDI,TextOut函式中文字字元的起始位置應當在字元的右邊,而不是預設的左邊。所有的裝置環境的屬性都有一個預設值,這個預設值在獲取裝置環境時就已經被設定好了。對所有的以Set開頭的函式,都有相應的一個以Get開頭的函式用於獲取當前裝置環境的屬性。

5> 使用GDI“物件”的函式

例如 使用“邏輯畫筆”選入裝置環境,當前被選入裝置環境的畫筆被視為裝置環境的一個屬性。這樣,便可以通過這隻畫筆繪製任何線條。

隨後,從裝置環境中取消選入的畫筆物件,並銷燬這個物件。因為畫筆定義佔用了一定的記憶體空間,所以銷燬畫筆是非常重要的。

除了畫筆,還可以在字型、建立用於填充封閉區域的畫刷、點陣圖以及GDI的其他一些方面使用GDI物件。

 裝置環境

如果希望在圖形輸出裝置上繪製圖形,必須首先獲取裝置環境(即DC)的控制代碼。當Windows把這個控制代碼交給你的程式,Windows同時也就給予你使用這個裝置的許可權。接著,在GDI函式中將這個控制代碼作為一個引數,告訴Windows在哪個裝置上進行繪圖。

裝置環境包含許多決定GDI函式如何工作的屬性。這些屬性使得GDI函式只需要提供少量的引數(如起始座標),而不需要提供Windows在裝置上顯示物件時需要的所有資訊。

例如,當你呼叫TextOut函式時,僅需要在函式中指定裝置環境控制代碼、起始座標、文字以及文字的長度,不需要指定字型、文字的顏色、文字的背景的顏色或者字元間距。所有這些屬性都是裝置環境的一部分。當你想改變這些屬性時,可以呼叫函式來執行。之後呼叫的TextOut函式就會使用新的裝置環境的屬性。

獲取裝置環境控制代碼

1> 獲取和釋放裝置環境控制代碼最常用的方法是在處理WM_PAINT訊息時使用BeginPaint函式和EndPaint函式:

hdc = BeginPaint(hwnd,&ps);
          //other program lines
EndPaint(hwnd,&ps);

其中,變數ps是一個型別為PAINTSTRUCT的結構。這個結構中的欄位hdc 和 BeginPaint函式返回的裝置環境控制代碼的值相同。

PAINTSTRUCT結構還包含一個名為rcPaint的矩形結構,該結構定義了一個包圍視窗客戶區無效範圍的矩形。

使用從BeginPaint函式獲取的裝置環境控制代碼,就只能在這個矩形區域內繪圖。呼叫BeginPaint函式將使這個區域有效。
2> 裝置環境控制代碼還可以在處理非WM_PAINT訊息時由Windows程式獲取:

hdc = GetDC(hwnd);
          //other program lines
ReleaseDC(hwnd,hdc);

其中,裝置環境指的是視窗控制代碼為hwnd的視窗客戶區。從GetDC函式返回的控制代碼可以在整個客戶區內繪製。並且,GetDC和ReleaseDC函式並不使任何客戶區的無效變為有效。

3> 獲取整個視窗:

hdc = GetWindowDC(hwnd);
          //other program lines
ReleaseDC(hwnd,hdc);

這裡的裝置環境除了包括客戶區,還包含視窗標題欄、選單、滾動條和客戶區的外框。應用程式很少使用。如果使用,還應當捕獲WM_NCPAINT訊息(非客戶區繪製)。Windows使用這個訊息在視窗的非客戶區繪圖。

4> 使用CreateDC

hdc = CreateDC(pszDriver, pszDevice, pszOutput, pDate);
  	 //other program lines
DeleteDC(hdc);

 例如,通過呼叫下面的函式獲取當前整個螢幕的裝置環境控制代碼

hdc = CreateDC(TEXT("DISPLAY"), NULL, NULL, NULL);

5> CreateIC函式獲取一個“資訊上下文”(information context)控制代碼。引數與CreateDC相同。這個函式僅用從裝置上獲取一些關於裝置環境的資訊,不能在上面繪製任何東西。

6> 記憶體裝置環境 :用於處理點陣圖。把一個位圖選入裝置環境,並且呼叫GDI函式繪製這個點陣圖。

hdc = CreateCompatibleDC(hdc);
         //other program lines
DeleteDC(hdcMem);

獲取裝置環境的資訊

裝置環境通常指的是物理的顯示裝置,如視訊顯示器或者印表機。經常需要獲取這些裝置的某些資訊,包括顯示器的大小(以畫素或者物理尺寸的方式)和它的色彩能力。這些資訊可以通過呼叫GetDeviceCaps函式來獲取:

iValue = GetDeviceCaps(hdc, iIndex);

其中,引數iIndex是定義在WINGDI.H標頭檔案中的29個識別符號之一。

例如,當iIndex的值為HORZRES 、VERTRES時,GetDeviceCaps函式分別以畫素為單位返回裝置的寬度和高度。(這裡指的是畫素尺寸,而不是每度量單位裡的畫素數)

HORZSIZE 和 VERTSIZE 官方文件中稱為“以毫米計的物理螢幕寬度 和 高度”。
裝置尺寸

“解析度”定義為每度量單位(通常是英寸)中含有的畫素數。

解析度 = 畫素尺寸 / 度量尺寸

“畫素尺寸”(pixel size) 或者 “畫素規模”(pixel dimension)表示裝置在水平和垂直方向上顯示的總的畫素數。

“度量尺寸”(metrical size) 或者 “度量規模”(metrical dimension)是指以英寸或者毫米為單位的裝置的客戶區域的大小。

儲存裝置環境

每次呼叫GetDC 和 BeginPaint函式時,會返回一個裝置環境控制代碼,它的所有屬性都被設定為預設值。當裝置環境呼叫ReleaseDC 和 EndPaint時,對屬性所做的任何改變都會丟失。這樣每次使用都需要獲取一個新的裝置環境控制代碼時初始化這個裝置環境。

我們可以在釋放裝置環境時儲存對屬性做的改變,以便下次呼叫GetDC 和 BeginPaint函式時,這些屬性仍然有效。

為此,在註冊視窗類時將CS_OWNDC標誌作為視窗類樣式的一部分即可:僅適用於GetDC 和 BeginPaint函式。

wndclass.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;

有一些情況下,可能想改變某些裝置環境屬性,然後使用變更後的屬性進行繪製,接著再恢復原來的裝置環境。

呼叫 idSaved = SaveDC(hdc);  來儲存裝置環境的狀態,現在可以改變一些屬性。

而呼叫 RestoreDC(hdc, idSaved); 可以返回呼叫 SaveDC 函式之前存在的裝置環境。

PS: 也可以寫成類似於組合語言中的PUSH 和 POP指令。呼叫SaveDC函式時,返回值可以不必儲存:

SaveDC(hdc);

然後改變一些屬性,並再次呼叫SaveDC函式。而為了將裝置環境恢復到已儲存的狀態,則呼叫函式:

RestoreDC(hdc,-1);

這會使裝置環境恢復到最近一次由SaveDC函式儲存的狀態。

 點和線的繪製

SetPixel 函式 將座標X 和 座標Y 的畫素點設定為某個特定的顏色:

SetPixel(hdc, x, y, crcolor);

GetPixel 函式返回指定座標位置的畫素點的顏色:

crColor = GetPixel(hdc, x, y);

例項:

LRESULT CALLBACK WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	int i,j;
	static int		cxClient,cyClient;
	HDC		hdc;
	PAINTSTRUCT		ps;

	switch (uMsg)
	{
	case WM_CREATE:
		return 0;

	case WM_SIZE:
		cxClient = LOWORD(lParam);
		cyClient = HIWORD(lParam);
		return 0;
		
	case WM_PAINT:
		hdc = BeginPaint(hwnd,&ps);
		for (j = cyClient/2,i = 0; i < cxClient; i++)
		{
			SetPixel(hdc,i,j,RGB(255,0,0));
		}
		for (i = 1; i < cxClient;)		//為什麼在這裡會卡掉
		{
			for (j = 0; j < cyClient; j ++)
			{
			SetPixel(hdc,i,j,RGB(0, 255, 0));
			}
			i += 30;		//這裡不懂為什麼,我把 i += 30; 換在for迴圈的第三個引數中,程式就卡掉了
		}
		EndPaint(hwnd,&ps);
		return 0;
	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	}
	return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

執行結果:

畫一條直線,必須呼叫兩個函式。第一個函式用來指定直線的起點,第二個函式用來指定直線的終點。

MoveToEx(hdc, xBeg, yBeg, NULL);
LineTo(hdc, xEnd, yEnd);

例項:

LRESULT CALLBACK WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	int i;
	HDC		hdc;
	PAINTSTRUCT		ps;
	POINT	pt[] = {100,100, 150,50, 200,100,200,200, 100,200, 100,100};
	
	switch (uMsg)
	{
	case WM_CREATE:
		return 0;
		
	case WM_PAINT:
		hdc = BeginPaint(hwnd,&ps);
		MoveToEx(hdc,pt[0].x,pt[0].y,NULL); //每一個終點都是一個新的起始點
		for(i = 1; i < 6; i++)
			LineTo(hdc,pt[i].x,pt[i].y);
		EndPaint(hwnd,&ps);
		return 0;
	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	}
	return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

執行結果:


PolylineTo 函式:最後一個引數表示點的個數。

PolylineTo (hdc, pt, 5);

它使用當前位置作為起始點,畫完線後,在返回前會將當前位置設定為最後一根線的終點。

例項-正弦波曲線:

#include <math.h>  sin函式標頭檔案
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	static int cxClient ,cyClient;
	int i;
	POINT	pt[1000];
   	HDC         hdc ;
    	PAINTSTRUCT ps ;

     switch (message)
     {
	 case WM_SIZE:
		 cxClient = LOWORD(lParam); //獲取客戶區寬度
		 cyClient = HIWORD(lParam); //獲取客戶區高度

		 return 0;
     	case WM_PAINT:
          hdc = BeginPaint (hwnd, &ps) ;

		  MoveToEx(hdc,0,cyClient / 2,NULL);  //畫中間的直線
		  LineTo(hdc,cxClient,cyClient / 2);

		  for (i = 0; i < 1000; i++)
		  {
			  pt[i].x = i*cxClient / 1000;
			  pt[i].y = (int) (cyClient / 2*(1-sin(2 * 3.1415 * i / 1000)));
		  }
		  Polyline(hdc,pt,1000);
          EndPaint (hwnd, &ps) ;
          return 0 ;

     	case WM_DESTROY:
         		 PostQuitMessage (0) ;
         	 	 return 0 ;
     }
     return DefWindowProc (hwnd, message, wParam, lParam) ;
}
執行結果:


邊框繪製函式: Rectangle 、Ellipse、RoundRect、Chord、Pie

這些函式是在畫線,但是同時它們還使用當前填充區域的畫刷來填充一個封閉的區域。因為這個畫刷預設是純白色,開始使用不易被發現<>。

繪製-矩形:

Rectangle(hdc, xLeft, yTop, xRight, yBottom); //(xLeft, yTop)是矩形左上角的座標,(xRight, yBottom)是矩形右下角座標。

繪製-橢圓:

Ellipse(hdc, xLeft, yTop, xRight, yBottom);

繪製圓角矩形:

RoundRect(hdc, xLeft, yTop, xRight, yBottom,xCornerEllipse,yCornerEllipse);

最後兩個引數分別是圓角矩形四個角 小橢圓的寬度 和 高度。

例項:

LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	static int  cxClient, cyClient ;
	HDC         hdc ;
	PAINTSTRUCT ps ;
	
	switch (message)
	{
	case WM_SIZE:
		cxClient = LOWORD (lParam) ;
		cyClient = HIWORD (lParam) ;
		return 0 ;
		
	case WM_PAINT:
		hdc = BeginPaint (hwnd, &ps) ;
		
		Rectangle (hdc, cxClient / 8, cyClient / 8,	7 * cxClient / 8, 7 * cyClient / 8) ;
		
		MoveToEx  (hdc,0,0, NULL) ;
		LineTo    (hdc, cxClient, cyClient);
		
		MoveToEx  (hdc,0, cyClient, NULL);
		LineTo    (hdc, cxClient,0) ;
		
		Ellipse   (hdc,cxClient / 8,cyClient / 8,7 * cxClient / 8, 7 * cyClient / 8) ;
		
		RoundRect (hdc, cxClient / 4, cyClient / 4,3 * cxClient / 4, 3 * cyClient / 4,cxClient / 4, cyClient / 4) ;
		
		EndPaint (hwnd, &ps) ;
		return 0 ;
		
	case WM_DESTROY:
		PostQuitMessage (0) ;
		return 0 ;
	}
	return DefWindowProc (hwnd, message, wParam, lParam) ;
}

執行結果:

 畫筆 (決定了線條的顏色、寬度和樣式,其中樣式可以是實線、點線或者虛線)

使用現有畫筆:

WINDOWS提供三種“備用畫筆”:BLACK_PEN 、 WHITE_PEN 、 NULL_PEN

其中 BLACK_PEN 為畫筆的預設裝置環境 、NULL_PEN 表示不繪製任何圖形的畫筆。

在WINDOWS標頭檔案WINDEF.H中定義了畫筆控制代碼型別為HPEN,使用此控制代碼來操作畫筆。

HPEN  hPen;	//定義一個變數
hPen = GetStockObject(WHITE_PEN);	//獲取畫筆控制代碼
SelectObject(hdc, hPen);	//將畫筆選入裝置環境

也可寫成SelectObject(hdc,GetStockObject(WHITE_PEN);

SelectObject函式返回一個先前選入裝置環境的畫筆的控制代碼。

hPen = SelectObject(hdc,GetStockObject(WHITE_PEN);

裝置環境中當前畫筆為WHITE_PEN,而變數hPen將是BLACK_PEN畫筆的控制代碼

SelectObject(hdc, hPen); 	//重新選入裝置環境

建立、選擇和刪除畫筆

畫筆的操作分為:建立畫筆,將畫筆選入裝置環境,刪除畫筆。(一次只能有一個畫筆被選入裝置環境)

1> 建立畫筆:通過呼叫CreatePen 或者 CreatePenIndirect 函式建立一個“邏輯畫筆”。函式返回一個邏輯畫筆的控制代碼。

2>畫筆選入裝置環境:呼叫SelectObject函式將畫筆選入裝置環境中。接著就可以用這個畫筆畫線條了...

3> 刪除畫筆:呼叫DeleteObject 函式來刪除建立的邏輯畫筆。

CreatePen函式的一般語法如下:

hPen = CreatePen (
		iPenStyle,	//確定畫筆的樣式。實線、點線、虛線
		iWidth,		//畫筆的寬度
		crColor);		//畫筆的顏色

幾種畫筆樣式:
   PS_DASH:                 虛線
   PS_DASHDOT:          點劃線
   PS_DASHDOTDOT: 雙點劃線
   PS_DOT:                   點線
   PS_INSIDEFRAME:    實線
   PS_NULL:                   無
   PS_SOLID:                實線

 下面是CreatePenIndirect 函式建立一個“邏輯畫筆”,首先定義一個型別為LOGPEN的“邏輯畫筆”結構。

LOGPEN  logpen;      //這個結構有三個欄位:

lopnStyle 表示畫筆樣式 。 (為無符號整型,或UINT)

lopnWidth 是以邏輯單位表示的畫筆寬度。(一個POINT結構)

lopnColor 表示畫筆的顏色。(COLORREF)

Windows僅使用lopnWidth結構中的X欄位來設定畫筆的寬度,Y欄位會被忽略。

hPen = CreatePenIndirect (&logpen);   // 這個函式的使用還不太明白??????

填充空隙

空隙的顏色是由裝置環境的兩個屬性決定的(背景模式和背景顏色)。

預設的背景模式是OPAQUE(不透明),這意味著Windows使用背景顏色來填充空隙,背景顏色在預設時是白色。

通過呼叫下面的函式可以改變Windows填充空隙的背景顏色

SetBkColor (hdc, crColor);

呼叫GetBkColor 獲取裝置環境當前背景顏色

設定背景模式 SetBkMode(hdc, TRANSPARENT);  同樣獲取背景模式GetBkMode函式 。(TRANSPARENT 透明, 可阻止Windows填充空隙

畫刷

和畫筆類似,操作畫刷也包括建立、選入裝置環境和刪除。
HBRUSH hBrush;   //hBrush為畫刷控制代碼
可以呼叫函式GetStockObject獲取Windows系統提供的7種畫刷

hBr=(HBRUSH)GetStockObject(nBrushStyle)    //nBrushStyle為畫刷樣式

系統提供的7種畫刷
   BLACK_BRUSH   黑色畫刷
   DKGRAY_BRUSH   深灰色畫刷
   GRAY_BRUSH   灰色畫刷
   HOLLOW_BRUSH   虛畫刷
   LTGRAY_BRUSH   亮灰色畫刷
   NULL_BRUSH   空畫刷
   WHITE_BRUSH   白色畫刷

當然,也可呼叫函式CreateSolidBrush和CreateHatchBrush建立自定義畫刷。
CreateSolidBrush用於建立具有指定顏色的單色畫刷。
CreateHatchBrush建立指定陰影圖案和顏色的畫刷。
例如:
   hBr=CreateSolidBrush(rgbColor);
   hBr=CreateHatchBrush(int nHctchStyle,COLORREF rgbColor);

   其中nHctchStyle可選用以下值:
   HS_BDIAGONAL   45度從左上到右下
   HS_DIAGCROSS   45度叉線
   HS_FDIAGONAL   45度從左下到右上
   HS_CROSS   垂直相交的陰影線
   HS_HORIZONTAL 水平陰影線
   HS_VERTICAL   垂直陰影線
  
   將畫刷選入裝置環境
   建立畫刷後,通過SelectObject(hdc,hBrush);將其選入裝置環境。

   刪除畫刷
   不使用畫刷時,可用DeleteObject(hBrush);刪除畫刷,釋放記憶體。