天天看點

32、Windows API GDI(4)

區域(Regions)、路徑(Paths)與修剪(Clip)操作

一、區域[2]

    區域是一種對象,具有形狀、位置、大小,用于進行填充、繪制、反轉、邊沿勾勒等操作,或用于限制DC的輸出範圍(修剪)。

區域的形狀可以是任意的,可以是标準的矩形、橢圓、扇形等,也可以是多邊形,還可以是這些形狀組合(與、或、異或等)。[2]

32、Windows API GDI(4)

    在建立區域後,可以對區域進行填充和反轉等操作。

填充區域使用API函數FillRgn或PaintRgn。FillRgn可以指定畫刷,而PaintRgn使用目前DC畫刷。使用DC中被選入的目前畫刷。

無論什麼形狀的區域,都有一個邊沿,這個邊沿是一個矩形( RECT),是能進入這個區域框的最小的矩形。GetRgnBox函數用于擷取這個矩形。

FrameRgn勾勒(Frame)是使用指定的畫刷在區域外為區域繪制邊框。

1、區域邊沿、區域填充、反轉與勾勒操作

建立及填充示例

32、Windows API GDI(4)
32、Windows API GDI(4)

區域的填充和反轉

/* 頭檔案 */

#include <windows.h>

/* 全局變量 */

LPSTR szAppName = "RGN";

LPSTR szTitle = "RGN Sample";

/* 函數聲明 */

LRESULT CALLBACK WndProc( HWND , UINT , WPARAM , LPARAM );

/*************************************

* ElliRgns

* 功能 建立橢圓區域,并進行填充和反轉

**************************************/

HRGN ElliRgns(HWND hwnd, POINT point)

{

// RECT 區域、畫刷

RECT rect, rectClient;

HRGN hrgn;

HBRUSH hBrushOld, hBrush;

// DC

HDC hdc = GetDC(hwnd);

GetClientRect(hwnd,&rectClient);

// 點的周圍一塊區域

rect.left = point.x - 40;

rect.right = point.x + 40;

rect.top = point.y - 30;

rect.bottom = point.y + 30;

// 通過RECT 建立橢圓區域

hrgn = CreateEllipticRgnIndirect(&rect);

// 建立畫刷

hBrush = CreateSolidBrush(RGB(0,255,0));

// 為DC 選擇畫刷

hBrushOld = (HBRUSH)SelectObject(hdc,hBrush);

// 使用目前畫刷繪制區域

PaintRgn(hdc,hrgn);

// 等待一斷時間後,将剛才繪制的區域進行反轉

Sleep(3000);

InvertRgn(hdc,hrgn);

// 釋放資源

hBrush = (HBRUSH)SelectObject(hdc,hBrushOld);

DeleteObject(hBrush);

DeleteObject(hrgn);

DeleteDC( hdc );

return 0;

}

int WINAPI WinMain(

HINSTANCE hInstance,

HINSTANCE hPrevInstance,

LPSTR lpCmdLine,

int nCmdShow)

MSG msg;

HWND hWnd;

WNDCLASS wc;

wc.style = CS_OWNDC;

wc.lpfnWndProc = (WNDPROC)WndProc;

wc.cbClsExtra = 0;

wc.cbWndExtra = 0;

wc.hInstance = hInstance;

wc.hIcon = NULL;

wc.hCursor = LoadCursor(NULL, IDC_ARROW);

wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);

wc.lpszMenuName = NULL;

wc.lpszClassName = szAppName;

if (!RegisterClass(&wc))

return (FALSE);

hWnd = CreateWindow(

szAppName,

szTitle,

WS_OVERLAPPEDWINDOW,

CW_USEDEFAULT, 0, CW_USEDEFAULT, 0,

NULL, NULL, hInstance, NULL

);

if (!hWnd)

ShowWindow(hWnd, nCmdShow);

UpdateWindow(hWnd);

while (GetMessage(&msg, NULL, 0, 0))

TranslateMessage(&msg);

DispatchMessage(&msg);

return (int)(msg.wParam);

UNREFERENCED_PARAMETER(lpCmdLine);

LRESULT CALLBACK WndProc(

HWND hWnd,

UINT message,

WPARAM uParam,

LPARAM lParam)

switch (message)

case WM_CREATE:

break;

case WM_PAINT:

case WM_LBUTTONDOWN:

// 獲得點選的位置,傳遞給ElliRgns

POINT point;

point.x = LOWORD(lParam);

point.y = HIWORD(lParam);

ElliRgns(hWnd, point);

case WM_DESTROY:

PostQuitMessage(0);

default:

return (DefWindowProc(hWnd, message, uParam, lParam));

return (0);

2、組合、比較、移動等操作

在建立區域後,還可以将若幹個區域組合在一起,成為一個新的區域。組合的方式可以是兩個區域的交集、合集、差集、合集與交集的差集(兩集合異或)。

CombineRgn

EqualRgn

OffsetRgn

PtInRegion Determines whether the specified point is inside the specified region.

組合示例

32、Windows API GDI(4)
32、Windows API GDI(4)

HitTest

#include <Windows.h>

RECT rctGrid; // 格子

RECT rctBrush; // 示範格式畫刷的格子

RECT rctWindow;

UINT bBrushBits[8]; // 用于建立格式畫刷

RECT rect[64]; // 六十四個小格子

HBITMAP hbm; // 位圖畫刷

HBRUSH hbrush; // 目前畫刷

HBRUSH hBrushGrid[8]; // 用于填充小方格式的畫刷

HBRUSH hbrushOld; // 預設畫刷

HRGN hrgnCell; // 區域

HDC hdc; // DC句柄

POINTS ptlHit; // 滑鼠坐标

int i;

* VOID InitGridBurshAndRect()

* 功能 初始化程式需要使用到的畫刷

* 初始化若幹個RECT

VOID InitGridBurshAndRect()

int x, y, deltaX, deltaY; // 用于畫小格子

// 建立了七種不同的畫刷

hBrushGrid[0] = CreateHatchBrush(HS_BDIAGONAL,RGB(255,0,0));

hBrushGrid[1] = CreateHatchBrush(HS_CROSS,RGB(0,255,0));

hBrushGrid[2] = CreateHatchBrush(HS_DIAGCROSS,RGB(0,0,255));

hBrushGrid[3] = CreateHatchBrush(HS_FDIAGONAL,RGB(255,0,255));

hBrushGrid[4] = CreateHatchBrush(HS_HORIZONTAL,RGB(255,255,0));

hBrushGrid[5] = CreateHatchBrush(HS_BDIAGONAL,RGB(0,255,255));

hBrushGrid[6] = CreateHatchBrush(HS_VERTICAL,RGB(0,0,0));

hBrushGrid[7] = CreateSolidBrush(RGB(200,200,200));

// 大格子邊界的坐标,後面會被劃分為8*8個小方格子,用于點選測試

rctGrid.top = rctGrid.left = 10;

rctGrid.bottom = rctGrid.right = 330;// 靠左

rctBrush.top = 10; rctBrush.left = 340;

rctBrush.bottom = 330;rctBrush.right = 660;// 靠右

// 計算小格式中的坐标,儲存在數組中

deltaX = (rctGrid.right - rctGrid.left)/8;

deltaY = (rctGrid.bottom - rctGrid.top)/8;

for (y=rctGrid.top, i=0; y < rctGrid.bottom; y += deltaY)

for(x=rctGrid.left; x < (rctGrid.right - 8) && i < 64;

x += deltaX, i++)

rect[i].left = x; rect[i].top = y;

rect[i].right = x + deltaX;

rect[i].bottom = y + deltaY;

* VOID DrawGridLine(HWND hwnd)

* 功能 根據InitGridBurshAndRect所初始化的小方格的邊界

* 畫出小方格。

VOID DrawGridLine(HWND hwnd)

hdc = GetDC(hwnd);

// 經線和緯線

for (i=rctGrid.left; i<=rctGrid.right;

i+=(rctGrid.right - rctGrid.left)/8)

MoveToEx(hdc, i, rctGrid.top, NULL);

LineTo(hdc, i, rctGrid.bottom);

for (i=rctGrid.top; i<=rctGrid.bottom;

i+=(rctGrid.bottom - rctGrid.top)/8)

MoveToEx(hdc, rctGrid.left, i, NULL);

LineTo(hdc, rctGrid.right, i);

ReleaseDC(hwnd, hdc);

*VOID PaintGrid(HWND hwnd,POINTS ptlHit)

* 功能 使用畫刷填充ptlHit點所在的小方格

VOID PaintGrid(HWND hwnd,POINTS ptlHit)

// 建立區域,大方格

hrgnCell = CreateRectRgn(rctGrid.left, rctGrid.top,

rctGrid.right, rctGrid.bottom);

// 獲得視窗的DC

// 将區域選擇入DC

SelectObject(hdc, hrgnCell);

// 測試點是否在區域中

if (PtInRegion(hrgnCell, ptlHit.x, ptlHit.y))

// 如果點在大方格中,循環測試點在哪個小方格中

for (i=0; i<8; i++)

bBrushBits[i] = 0xFF;

for(i=0; i<64; i++)

DeleteObject(hrgnCell);

// 每一個小方格建立一個區域

hrgnCell = CreateRectRgn(rect[i].left,

rect[i].top,

rect[i].right, rect[i].bottom);

// 測試是否在小方格中

// 如果是的話則填充,第一行使用的填充畫刷是一樣的。

FillRgn(hdc,hrgnCell,hBrushGrid[i/8]);

// 釋放

SelectObject(hdc, hbrushOld);

DeleteObject(hbrush);

DeleteObject(hbm);

DeleteDC( hdc);

*VOID PaintPattern(HWND hwnd, POINTS ptlHit)

* 功能 在示範格式畫刷的方格中使用格式畫刷進行繪制

VOID PaintPattern(HWND hwnd, POINTS ptlHit)

// 擷取DC

// 根據點選的不同位置建立不同的位圖

i = ptlHit.x % 8;

bBrushBits[i] += 0x50;

// 建立位圖

hbm = CreateBitmap(8, 8, 1, 1, (LPBYTE)bBrushBits);

// 建立格式畫刷

hbrush = CreatePatternBrush(hbm);

// 為DC選擇畫刷

hbrushOld = SelectObject(hdc, hbrush);

// 繪制矩形

Rectangle(hdc, rctBrush.left, rctBrush.top,

rctBrush.right, rctBrush.bottom);

// 釋放DC

ReleaseDC(hwnd,hdc);

* MainWndProc

* 功能 視窗消息處理函數

LRESULT CALLBACK MainWndProc(

HWND hwnd,

UINT uMsg,

WPARAM wParam,

switch (uMsg)

// 釋放資源,退出

for(i=0; i<8; i++)

DeleteObject(hBrushGrid[i]);

ExitProcess(0);

// 初始化畫刷、方格

InitGridBurshAndRect();

// 繪制

DrawGridLine(hwnd);

// 滑鼠左鍵

ptlHit = MAKEPOINTS((LPPOINTS)lParam);

// 填充一個小方格

PaintGrid(hwnd, ptlHit);

// 填充右側的格式畫刷方格

PaintPattern(hwnd, ptlHit);

return DefWindowProc(hwnd, uMsg, wParam, lParam);

// WinMain

int WINAPI WinMain(HINSTANCE hinstance,

WNDCLASSEX wcx;

HWND hwnd;

WORD wport = 80;

BOOL fGotMessage;

HWND hwndCap = NULL;

// 注冊視窗類,并建立視窗,用于顯示截取的位圖

wcx.cbSize = sizeof(wcx);

wcx.style = CS_HREDRAW | CS_VREDRAW;

wcx.lpfnWndProc = MainWndProc;

wcx.cbClsExtra = 0;

wcx.cbWndExtra = 0;

wcx.hInstance = hinstance;

wcx.hIcon = LoadIcon(NULL, MAKEINTRESOURCE(IDI_APPLICATION));

wcx.hCursor = LoadCursor(NULL, IDC_ARROW);

wcx.hbrBackground = (HBRUSH)GetStockObject( WHITE_BRUSH );

wcx.lpszMenuName = NULL;

wcx.lpszClassName = "MainWClass";

wcx.hIconSm = NULL;

if( !RegisterClassEx(&wcx))

return 1;

// 建立視窗

hwnd = CreateWindow(

"MainWClass",

"Brush_Pen",

WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS |

WS_MAXIMIZE | WS_POPUPWINDOW,

CW_USEDEFAULT,

800, 600,

(HWND) NULL, (HMENU) NULL, hinstance, (LPVOID) NULL);

if (!hwnd)

// 顯示

ShowWindow(hwnd, nCmdShow);

UpdateWindow(hwnd);

while ((fGotMessage = GetMessage(&msg, (HWND) NULL, 0, 0)) != 0 && fGotMessage != -1)

return msg.wParam;

二、路徑[3]

路徑是很多被填充或被勾勒了的形狀和圖形。

路徑的建立方法比較特殊。整個建立過程稱作“路徑托架(bracket)”。

建立路徑時,首先調用BeginPath函數,然後可以調用一系統的繪制函數,最後再調用EedPath函數。在這個過程中,所有的繪制函數的輸出内容都不會被真正輸出到螢幕上,而是輸出到“路徑”中,路徑就是整個BeginPath和EedPath之間所有調用的輸出的圖形的組合。在EndPath調用之後,路徑會被選擇到DC中。

可以将路徑轉換為一個區域,然後就可以進行區域的所有操作。将路徑轉換為區域使用函數PathToRegion,所轉換的是DC的目前路徑,傳回轉換後得到的區域。

三、修剪[4]

    修剪是區域和路徑比較進階的操作。修剪是使用DC的區域和路徑對象來限制繪制輸出的範圍。修剪區域可以将所有輸出都限制在區域的範圍内,隻有在區域範圍内的輸出才會顯示在界面上,如果輸出的範圍在區域之外,則不會反映到界面上。

在進行修剪操作前,首先需設定DC的修剪區域和修剪路徑。設定使用區域使用API函數SelectClipRgn。還可以設定DC的修剪路徑,修剪路徑可以和DC原有的修剪區域進行合并,合并的方法也包括集合的交、并、差、複制、異或。設定修剪路徑使用API函數SelectClipPath。

修剪示例

32、Windows API GDI(4)
32、Windows API GDI(4)

區域、路徑、修剪

#include <math.h>

#include "resource.h"

CHOOSEFONT cf; // 選擇字型

LOGFONT lf; // 邏輯字型

HFONT hfont; // 字型對象

HFONT hfontOld; // DC的原有字型

int nXStart, nYStart; // 用于畫直線

RECT rc; // 視窗客戶區RECT

double aflSin[90]; // 用于繪制放射直線簇

double aflCos[90]; // 用于繪制放射狀直線簇

double flRadius,a; // 半徑

int iMode; // 修剪路徑組合模式

HRGN hrgn; // 修剪區域

COLORREF crOld;

// 視窗消息處理函數

LRESULT APIENTRY MainWndProc(

PAINTSTRUCT ps;

hdc = BeginPaint(hwnd, &ps);

EndPaint(hwnd, &ps);

case WM_COMMAND: // 菜單指令

switch (wParam)

case IDM_VANISH: // 消除客戶區(在設定了修剪全其輸出範圍也會被限制)

GetClientRect(hwnd, &rc);

FillRect(hdc, &rc, (HBRUSH)GetStockObject(WHITE_BRUSH));

// 根據使用者的菜單輸入,設定修剪路徑的組合模式

case IDM_AND:

iMode = RGN_AND;

case IDM_COPY:

iMode = RGN_COPY;

case IDM_DIFF:

iMode = RGN_DIFF;

case IDM_OR:

iMode = RGN_OR;

case IDM_XOR:

iMode = RGN_XOR;

case IDM_CLIP:

// 擷取視窗客戶區的DC

// 建立并為DC選擇字型

// 消息在菜單中點選此項前需先選擇字型

hfont = CreateFontIndirect(cf.lpLogFont);

hfontOld = (HFONT)SelectObject(hdc, hfont);

// 五個點,用于建立五角星區域

POINT point[5]= {{0,200},{600,200},{100,600},{300,0},{500,600}};

// 建立多邊形區域,五角星

hrgn = CreatePolygonRgn(point, 5, WINDING);

// 将區域選擇為修剪區域

SelectClipRgn(hdc, hrgn);

// 輸出的文字

LPSTR szOut = "Lorem ipsum dolor sit amet, consectetur \n"

"adipisicing elit, sed do eiusmod tempor \n"

"incididunt ut labore et dolore magna \n"

"aliqua. Ut enim ad minim veniam, quis \n"

"nostrud exercitation ullamco laboris nisi \n"

"ut aliquip ex ea commodo consequat. Duis \n"

"aute irure dolor in reprehenderit in \n"

"voluptate velit esse cillum dolore eu \n"

"fugiat nulla pariatur. Excepteur sint \n"

"occaecat cupidatat non proident, sunt \n"

"in culpa qui officia deserunt mollit \n"

"anim id est laborum.\n";

// 視窗客戶區,用于輸出文字

RECT rect;

GetClientRect(hwnd,&rect);

// 設定文字背景為透明

SetBkMode(hdc, TRANSPARENT);

// 開始建立路徑

BeginPath(hdc);

// 路徑的形狀為輸出的文字

DrawText(hdc, szOut, lstrlen(szOut),&rect , DT_CENTER);

EndPath(hdc);// 路徑已經為DC的目前路徑

// 為DC選擇修剪路徑,使用使用者通過菜單輸入的模式

// 注意在進行此操作前需通過菜單選擇組合模式

SelectClipPath(hdc, iMode);

// 輸出放射狀直線,以左上角為原點

// 計算線的終點

for (i = 0; i < 90; i++)

aflSin[i] = sin( (((double)i) / 180.0)

* 3.14159);

aflCos[i] = cos( (((double)i) / 180.0)

flRadius = 1000;// 線的長度(視窗大小為600*650)

// 畫線,第一度畫一條線

MoveToEx(hdc, nXStart, nYStart,

(LPPOINT) NULL);

LineTo(hdc, nXStart + ((int) (flRadius

* aflCos[i])),

nYStart + ((int) (flRadius

* aflSin[i])));

// 選擇回字型,釋放

SelectObject(hdc, hfontOld);

DeleteObject(hfont);

// 重新整理視窗

case IDM_FONT:

// 使用者選擇字型

cf.lStructSize = sizeof (CHOOSEFONT);

cf.hwndOwner = hwnd;

cf.lpLogFont = &lf;

cf.Flags = CF_SCREENFONTS | CF_EFFECTS;

cf.rgbColors = RGB(0, 255, 255);

cf.nFontType = SCREEN_FONTTYPE;

ChooseFont(&cf);

return DefWindowProc(hwnd, message, wParam,

lParam);

return DefWindowProc(hwnd, message, wParam, lParam);

wc.lpfnWndProc = (WNDPROC)MainWndProc;

wc.lpszMenuName = MAKEINTRESOURCE(IDR_MENU_CLIP);//菜單

wc.lpszClassName = "clip";

"clip",

100, 100, 600, 650,

NULL,

hInstance,

NULL

進一步可以參見參考文獻[1,2~4]。

四、坐标變換

圖形輸出是在平面上操作,具有坐标。GDI支援對輸出的函數進行坐标變換。API函數SetWorldTransform實作了坐标變換的功能。

    此外,[1]中還介紹了調色闆的使用。

參考

[1] 精通Windows API 函數、接口、程式設計執行個體

繼續閱讀