【Windows程式設計】系列第五篇:GDI圖形繪製
上兩篇我們學習了文字字元輸出以及Unicode編寫程式,知道如何用常見Win32輸出文字字串,這一篇我們來學習Windows程式設計中另一個非常重要的部分GDI圖形繪圖。Windows的GDI函式包含數百個API可供我們使用,本篇把最常用的GDI繪圖做一個講解。GDI可以繪製點、直線曲線、填充封閉區域、點陣圖以及文字,其中文字部分已經在上一篇中將了,請參考【Windows程式設計】系列第三篇:文字字元輸出。
跟前面的GDI物件一樣,本篇的這些繪圖函式也必須要裝置上下文控制代碼(HDC)作為函式引數,從前文我們知道,HDC可以在處理WM_PAINT的時候用BeginPaint函式獲取,也可以從GetDC、GetWindowDC拿到。
既然是畫圖,就少不了顏色的描述,Windows中的顏色有幾種表示,其中COLORREF在GDI繪製中用的最多,它實際上是一個無符號32為整型。其中紅、綠、藍各佔一個位元組,最高位元組不使用,如下圖所示:
該值可以用Windows提供的RGB巨集來生成,Windows中RGB的定義為:
#define RGB(r,g,b) ((COLORREF)(((BYTE)(r)|((WORD)((BYTE)(g))<<8))|(((DWORD)(BYTE)(b))<<16)))
除此之外,Windows還有結構體RGBQUAD也表示顏色,這種一般用於點陣圖結構資訊中。
- 畫畫素點
Windows提供了SetPixel和GetPixel函式來設定和獲取畫素點的顏色。函式原型為:
COLORREF SetPixel(HDC hdc, int X, int Y, COLORREF crColor);
COLORREF GetPixel(HDC hdc, int nXPos, int nYPos);
該函式並不常使用。
- 畫筆畫刷
在圖形繪製之前,可以建立畫筆給後續的畫圖使用,建立畫筆的API函式為:
HPEN CreatePen(int fnPenStyle, int nWidth, COLORREF crColor); HBRUSH CreateSolidBrush(COLORREF crColor); HBRUSH CreatePatternBrush(HBITMAP hbmp); HBRUSH CreateHatchBrush(int fnStyle, COLORREF clrref);
它可以指定畫筆風格,寬度和顏色。風格可以是實線、虛線、點虛線等,具體參考MSDN說明的各種型別。
- 畫線條
Windows提供的畫線條函式有十幾個,常用的直線繪製為LineTo,多條線段一般用Polyline、PolylineTo、PolyPolyine等,曲線可以畫橢圓、橢圓弧、貝塞爾樣條曲線。這些函式的原型請參考MSDN,後面我們將用例項來演示這些函式的用法。
- 封閉區域填充
Windows的繪圖如果是一個封閉區,則內部是可以填充的,當然如果你不顯示填充,系統會用預設顏色來填,比如視窗背景色。我們也可以在繪製封閉圖形之前建立畫刷,如果把建立的畫刷選入裝置環境中,系統將用畫刷填充內部區。常見的會封閉的繪圖API函式有畫直角矩形Rectangle、圓角矩形RoundRect、橢圓Ellipse、扇形圖Pie以及弦割圖Chord。
- 點陣圖輸出
Windows關於點陣圖的輸出內容很多,包括裝置相關和裝置無關點陣圖、以及位塊轉移、透明、縮放等等,本文僅針對點陣圖畫刷進行例項演示,其他內容將來可單獨寫一篇介紹。用點陣圖做畫刷時先要使用LoadImage函式載入點陣圖檔案,然後用CreatePatternBrush建立一個模式畫刷即可。
- 文字輸出
這個在前面已經討論過了,請參考【Windows程式設計】系列第三篇:文字字元輸出一文。
- 繪圖屬性
在繪製圖形時,環境裝置有5個屬性會影響大多數繪圖:
畫筆位置:在畫線條時,會從畫筆所在的位置開始畫,畫筆位置可以用MoveToEx函式來設定。
畫筆:繪圖時會採用當前環境中的畫筆進行繪製,如果顯示不建立,將會用系統預設的畫筆。
背景:某些GDI會有透明和不透明的設定。
背景顏色:比如文字輸出的間隙顏色。
繪製模式:比如劃線是可以設定實線、虛線等,填充時可能有不同的填充繪製模式。
下面我們通過一個完整的例項,來演示上面這些常見函式的具體運用以及實際使用效果。
#include <windows.h>
static TCHAR szAppName[] = TEXT("GDI Test");
static LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{
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, // window class name
szAppName, // window caption
WS_OVERLAPPEDWINDOW, // window style
CW_USEDEFAULT, // initial x position
CW_USEDEFAULT, // initial y position
400, // initial x size
300, // initial y size
NULL, // parent window handle
NULL, // window menu handle
hInstance, // program instance handle
NULL); // creation parameters
ShowWindow(hWnd, iCmdShow);
UpdateWindow(hWnd);
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
//繪製指定屬性的直線
static void DrawLine(HDC hDC, int x0, int y0, int x1, int y1, int style, int width, COLORREF color)
{
HPEN hPen = CreatePen(style, width, color);
HPEN hOldPen = (HPEN)SelectObject(hDC, hPen);
MoveToEx(hDC, x0, y0, NULL);
LineTo(hDC, x1, y1);
SelectObject(hDC, hOldPen);
DeleteObject(hPen);
}
//繪製實心圓
static void DrawCircle(HDC hDC, int x, int y, int len, COLORREF color)
{
HBRUSH hBrush = CreateSolidBrush(color);
HBRUSH hOldBrush = (HBRUSH)SelectObject(hDC, hBrush);
HPEN hPen = CreatePen(PS_SOLID, 1, color);
HPEN hOldPen = (HPEN)SelectObject(hDC, hPen);
Ellipse(hDC, x-len/2, y-len/2, x+len/2, y+len/2);
SelectObject(hDC, hOldBrush);
DeleteObject(hPen);
SelectObject(hDC, hOldPen);
DeleteObject(hOldBrush);
}
//繪製填充矩形
static void DrawRect(HDC hDC, int left, int top, int width, int height, int style, COLORREF color)
{
HBRUSH hBrush = CreateHatchBrush(style, color);
HBRUSH hOldBrush = (HBRUSH)SelectObject(hDC, hBrush);
Rectangle(hDC, left, top, left+width, top+height);
SelectObject(hDC, hOldBrush);
DeleteObject(hOldBrush);
}
//繪製點陣圖填充矩形
static void DrawBmpRect(HDC hDC, int left, int top, int width, int height, LPCTSTR file)
{
HBITMAP hBitmap = (HBITMAP)LoadImage(NULL, file, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE|LR_CREATEDIBSECTION);
HBRUSH hBrush = CreatePatternBrush(hBitmap);
HBRUSH hOldBrush = (HBRUSH)SelectObject(hDC, hBrush);
Rectangle(hDC, left, top, left+width, top+height);
SelectObject(hDC, hOldBrush);
DeleteObject(hOldBrush);
DeleteObject(hBitmap);
}
static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
HDC hDC;
PAINTSTRUCT ps;
switch (message)
{
case WM_CREATE:
return 0;
case WM_PAINT:
{
hDC = BeginPaint(hWnd, &ps);
for (int i=10; i<50; i+=4)
{
SetPixel(hDC, i, 10, RGB(0, 0, 0)); //繪製畫素點
}
//繪製不同模式的直線
DrawLine(hDC, 120, 30, 200, 30, PS_SOLID, 2, RGB(0,0,0));
DrawLine(hDC, 120, 50, 200, 50, PS_DASH, 1, RGB(100,0,200));
DrawLine(hDC, 120, 70, 200, 70, PS_DASHDOT, 1, RGB(100,250,100));
//繪製弧線、弦割線、餅圖
Arc(hDC, 10, 30, 40, 50, 40, 30, 10, 40);
Chord(hDC, 10, 60, 40, 80, 40, 60, 10, 70);
Pie(hDC, 10, 90, 40, 110, 40, 90, 10, 100);
POINT pt[4] = {{90,130},{60,40},{140,150},{160,80}};
//繪製橢圓、矩形
Ellipse(hDC,pt[0].x, pt[0].y, pt[1].x, pt[1].y);
Rectangle(hDC, pt[2].x, pt[2].y, pt[3].x, pt[3].y);
//繪製貝塞爾曲線
PolyBezier(hDC, pt, 4);
//標出貝塞爾曲線的四個錨點
DrawCircle(hDC, pt[0].x, pt[0].y, 8, RGB(0,255,0));
DrawCircle(hDC, pt[1].x, pt[1].y, 8, RGB(0,0,255));
DrawCircle(hDC, pt[2].x, pt[2].y, 8, RGB(0,0,0));
DrawCircle(hDC, pt[3].x, pt[3].y, 8, RGB(255,0,0));
//繪製圓
DrawCircle(hDC, 100, 180, 60, RGB(0, 250, 250));
//繪製不同填充模式的矩形
DrawRect(hDC, 220, 20, 60, 40, HS_BDIAGONAL, RGB(255,0,0));
DrawRect(hDC, 220, 80, 60, 40, HS_CROSS, RGB(0,255,0));
DrawRect(hDC, 290, 20, 60, 40, HS_DIAGCROSS, RGB(0,0,255));
DrawRect(hDC, 290, 80, 60, 40, HS_VERTICAL, RGB(0,0,0));
//繪製點陣圖
DrawBmpRect(hDC, 180, 140, 180, 100, TEXT("chenggong.bmp"));
//繪製文字
TextOut(hDC, 20, 220, TEXT("GDI畫圖輸出測試程式"), 11);
}
EndPaint(hWnd, &ps);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0 ;
}
return DefWindowProc (hWnd, message, wParam, lParam);
}
本例項執行結果如下圖所示,圖中可以看到線條不平滑,這是因為Win32的畫圖函式是沒有抗鋸齒功能的,圖越小,鋸齒越明顯。可以使用微軟提供的GDI+繪圖函式,具有抗鋸齒效果。
Windows的GDI基本繪製其實並不難掌握,只要仔細閱讀MSDN上API的詳細使用說明就一定能正確使用,但是在建立GDI物件並使用後,一定要記得釋放。