区域(Regions)、路径(Paths)与修剪(Clip)操作
一、区域[2]
区域是一种对象,具有形状、位置、大小,用于进行填充、绘制、反转、边沿勾勒等操作,或用于限制DC的输出范围(修剪)。
区域的形状可以是任意的,可以是标准的矩形、椭圆、扇形等,也可以是多边形,还可以是这些形状组合(与、或、异或等)。[2]
在创建区域后,可以对区域进行填充和反转等操作。
填充区域使用API函数FillRgn或PaintRgn。FillRgn可以指定画刷,而PaintRgn使用当前DC画刷。使用DC中被选入的当前画刷。
无论什么形状的区域,都有一个边沿,这个边沿是一个矩形( RECT),是能进入这个区域框的最小的矩形。GetRgnBox函数用于获取这个矩形。
FrameRgn勾勒(Frame)是使用指定的画刷在区域外为区域绘制边框。
1、区域边沿、区域填充、反转与勾勒操作
创建及填充示例
/* 头文件 */
#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.
组合示例
/* 头文件 */
#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。
修剪示例
/* 头文件 */
#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