32、Windows API GDI(4)
区域(Regions)、路径(Paths)与修剪(Clip)操作
一、区域[2]
区域是一种对象,具有形状、位置、大小,用于进行填充、绘制、反转、边沿勾勒等操作,或用于限制DC的输出范围(修剪)。
区域的形状可以是任意的,可以是标准的矩形、椭圆、扇形等,也可以是多边形,还可以是这些形状组合(与、或、异或等)。[2]
在创建区域后,可以对区域进行填充和反转等操作。
填充区域使用API函数FillRgn或PaintRgn。FillRgn可以指定画刷,而PaintRgn使用当前DC画刷。使用DC中被选入的当前画刷。
无论什么形状的区域,都有一个边沿,这个边沿是一个矩形( RECT),是能进入这个区域框的最小的矩形。GetRgnBox函数用于获取这个矩形。
FrameRgn勾勒(Frame)是使用指定的画刷在区域外为区域绘制边框。
1、区域边沿、区域填充、反转与勾勒操作
创建及填充示例
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
/* 头文件 */
#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)
{
return (FALSE);
}
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:
break;
case WM_LBUTTONDOWN:
{
// 获得点击的位置,传递给ElliRgns
POINT point;
point.x = LOWORD(lParam);
point.y = HIWORD(lParam);
ElliRgns(hWnd, point);
}
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return (DefWindowProc(hWnd, message, uParam, lParam));
}
return (0);
}
2、组合、比较、移动等操作
在创建区域后,还可以将若干个区域组合在一起,成为一个新的区域。组合的方式可以是两个区域的交集、合集、差集、合集与交集的差集(两集合异或)。
CombineRgn
EqualRgn
OffsetRgn
PtInRegion Determines whether the specified point is inside the specified region.
组合示例
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
/* 头文件 */
#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);
DeleteDC( 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
hdc = GetDC(hwnd);
// 将区域选择入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);
// 测试是否在小方格中
if (PtInRegion(hrgnCell, ptlHit.x, ptlHit.y))
{
// 如果是的话则填充,第一行使用的填充画刷是一样的。
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
hdc = GetDC(hwnd);
// 根据点击的不同位置创建不同的位图
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);
DeleteDC( hdc );
}
/*************************************
* MainWndProc
* 功能 窗口消息处理函数
**************************************/
LRESULT CALLBACK MainWndProc(
HWND hwnd,
UINT uMsg,
WPARAM wParam,
LPARAM lParam)
{
switch (uMsg)
{
// 释放资源,退出
case WM_DESTROY:
for(i=0; i<8; i++)
DeleteObject(hBrushGrid[i]);
ExitProcess(0);
// 初始化画刷、方格
case WM_CREATE:
InitGridBurshAndRect();
break;
// 绘制
case WM_PAINT:
DrawGridLine(hwnd);
break;
// 鼠标左键
case WM_LBUTTONDOWN:
ptlHit = MAKEPOINTS((LPPOINTS)lParam);
// 填充一个小方格
PaintGrid(hwnd, ptlHit);
// 填充右侧的格式画刷方格
PaintPattern(hwnd, ptlHit);
break;
default:
break;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
// WinMain
int WINAPI WinMain(HINSTANCE hinstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
WNDCLASSEX wcx;
HWND hwnd;
MSG msg;
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,
CW_USEDEFAULT,
800, 600,
(HWND) NULL, (HMENU) NULL, hinstance, (LPVOID) NULL);
if (!hwnd)
return 1;
// 显示
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
while ((fGotMessage = GetMessage(&msg, (HWND) NULL, 0, 0)) != 0 && fGotMessage != -1)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
UNREFERENCED_PARAMETER(lpCmdLine);
}
二、路径[3]
路径是很多被填充或被勾勒了的形状和图形。
路径的创建方法比较特殊。整个创建过程称作“路径托架(bracket)”。
创建路径时,首先调用BeginPath函数,然后可以调用一系统的绘制函数,最后再调用EedPath函数。在这个过程中,所有的绘制函数的输出内容都不会被真正输出到屏幕上,而是输出到“路径”中,路径就是整个BeginPath和EedPath之间所有调用的输出的图形的组合。在EndPath调用之后,路径会被选择到DC中。
可以将路径转换为一个区域,然后就可以进行区域的所有操作。将路径转换为区域使用函数PathToRegion,所转换的是DC的当前路径,返回转换后得到的区域。
三、修剪[4]
修剪是区域和路径比较高级的操作。修剪是使用DC的区域和路径对象来限制绘制输出的范围。修剪区域可以将所有输出都限制在区域的范围内,只有在区域范围内的输出才会显示在界面上,如果输出的范围在区域之外,则不会反映到界面上。
在进行修剪操作前,首先需设置DC的修剪区域和修剪路径。设置使用区域使用API函数SelectClipRgn。还可以设置DC的修剪路径,修剪路径可以和DC原有的修剪区域进行合并,合并的方法也包括集合的交、并、差、复制、异或。设置修剪路径使用API函数SelectClipPath。
修剪示例
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
/* 头文件 */
#include <Windows.h>
#include <math.h>
#include "resource.h"
/* 全局变量 */
CHOOSEFONT cf; // 选择字体
LOGFONT lf; // 逻辑字体
HFONT hfont; // 字体对象
HFONT hfontOld; // DC的原有字体
HDC hdc; // DC句柄
int nXStart, nYStart; // 用于画直线
RECT rc; // 窗口客户区RECT
double aflSin[90]; // 用于绘制放射直线簇
double aflCos[90]; // 用于绘制放射状直线簇
double flRadius,a; // 半径
int iMode; // 修剪路径组合模式
HRGN hrgn; // 修剪区域
int i;
COLORREF crOld;
// 窗口消息处理函数
LRESULT APIENTRY MainWndProc(
HWND hwnd,
UINT message,
WPARAM wParam,
LPARAM lParam)
{
PAINTSTRUCT ps;
switch (message)
{
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
EndPaint(hwnd, &ps);
break;
case WM_COMMAND: // 菜单命令
switch (wParam)
{
case IDM_VANISH: // 消除客户区(在设置了修剪全其输出范围也会被限制)
hdc = GetDC(hwnd);
GetClientRect(hwnd, &rc);
FillRect(hdc, &rc, (HBRUSH)GetStockObject(WHITE_BRUSH));
DeleteDC( hdc);
break;
// 根据用户的菜单输入,设置修剪路径的组合模式
case IDM_AND:
iMode = RGN_AND;
break;
case IDM_COPY:
iMode = RGN_COPY;
break;
case IDM_DIFF:
iMode = RGN_DIFF;
break;
case IDM_OR:
iMode = RGN_OR;
break;
case IDM_XOR:
iMode = RGN_XOR;
break;
case IDM_CLIP:
{
// 获取窗口客户区的DC
hdc = GetDC(hwnd);
// 创建并为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);
}
for (i = 0; i < 90; i++)
{
aflCos[i] = cos( (((double)i) / 180.0)
* 3.14159);
}
flRadius = 1000;// 线的长度(窗口大小为600*650)
// 画线,第一度画一条线
for (i = 0; i < 90; i++)
{
MoveToEx(hdc, nXStart, nYStart,
(LPPOINT) NULL);
LineTo(hdc, nXStart + ((int) (flRadius
* aflCos[i])),
nYStart + ((int) (flRadius
* aflSin[i])));
}
// 选择回字体,释放
SelectObject(hdc, hfontOld);
DeleteObject(hfont);
DeleteDC( hdc);
// 刷新窗口
UpdateWindow(hwnd);
break;
}
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);
break;
default:
return DefWindowProc(hwnd, message, wParam,
lParam);
}
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd, message, wParam, lParam);
}
return 0;
}
// WinMain
int WINAPI WinMain(
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
MSG msg;
HWND hWnd;
WNDCLASS wc;
wc.style = CS_OWNDC;
wc.lpfnWndProc = (WNDPROC)MainWndProc;
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 = MAKEINTRESOURCE(IDR_MENU_CLIP);//菜单
wc.lpszClassName = "clip";
if (!RegisterClass(&wc))
{
return (FALSE);
}
hWnd = CreateWindow(
"clip",
"clip",
WS_OVERLAPPEDWINDOW,
100, 100, 600, 650,
NULL,
NULL,
hInstance,
NULL
);
if (!hWnd)
{
return (FALSE);
}
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int)(msg.wParam);
UNREFERENCED_PARAMETER(lpCmdLine);
}
进一步可以参见参考文献[1,2~4]。
四、坐标变换
图形输出是在平面上操作,具有坐标。GDI支持对输出的函数进行坐标变换。API函数SetWorldTransform实现了坐标变换的功能。
此外,[1]中还介绍了调色板的使用。
参考
[1] 精通Windows API 函数、接口、编程实例
[2] http://msdn.microsoft.com/en-us/library/dd162913%28VS.85%29.aspx
[3] http://msdn.microsoft.com/en-us/library/dd162779%28VS.85%29.aspx
[4] http://msdn.microsoft.com/en-us/library/dd183435%28VS.85%29.aspx