(1)提供一种特殊机制彻底隔离应用程序与不同输出设备(eg.显示器或打印机),以便支持 与设备无关的图形。
光栅设备(如显示器、激光打印机):图像是由点构成的矩阵
图形输出设备
矢量设备(如绘图仪):使用 线条来绘制图形
(2)Windows GDI允许使用逻辑坐标系统来保证程序与硬件的独立,也可以统用设备坐标系统 (单位:像素)来迎合硬件的需求。
(3)GDI总体上是一个静态显示系统,对动画的支持有限。DirectX可支持动画。
5.2 设备环境
5.2.1 获取设备环境句柄
(1)在WM_PAINT中获取的是无效区的句柄
hDC = BeginPaint(hWnd,&ps);
//其他代码
EndPaint(hWnd,&ps);
(2)在非WM_PAINT中
获取整个客户区DC |
获取整个窗口DC(含非客户区) |
hDC = GetDC(hWnd); //GetDC(NULL)时为屏幕DC //其他代码 Release(hWnd,hDC); |
hDC = GetWindowDC(hWnd); //其他代码 Release(hWnd,hDC); |
(3)更通用的方法(未必一定要窗口相关联,也可以是内存或打印机的DC)
//①整个屏幕DC
hDC =CreateDC(TEXT(“DISPLAY”),NULL,NULL,NULL);
DeleteDC(hDC);
//②内存DC
hdcMem= CreateCompatibleDC(hDC);
DeleteDC(hdcMem);
//③获得图元文件的设备环境句柄
hdcMeta= CreateMetaFile(pszFileName);
hmf =CloseMetaFile(hdcMeta);
(4)只需要获取设备环境信息,而不在其上绘制:CreateIC——(Information Context)
5.2.2 获取设环境的信息
(1)设备的分辨率
①显示器:每毫米(或英寸)的像素总数。
②打印机:每毫米(或英寸)的点数,1磅≈1/72英寸。
(2)GetDeviceCaps(hdc,iIndex)
iIndex |
iValue(返回值) |
备注 |
HORZRES |
以像素为单位的设备宽度 |
1、当hdc为整个屏幕DC时,与GetSystemMetrics获得的信息一致。 2、当hdc为打印机时,获得的是打机印显示区域的宽度和高度。 |
VERTRES |
以像素为单位的设备高度 |
|
HORZSIZE |
以毫米为单位的屏幕宽度 |
1、Windows98下: HORZSIZE = 25.4×HORZRES/LOGPIXELSX. VERTSIZE = 25.4×VERTRES/LOGPIXELSY 2、Windows NT下:为“标准显示器”的大小,即320×240,但可通过上述公 式算出这两个真实的值出来。 3、对于打印机是物理尺寸,对于显示器是逻辑尺寸。 |
VERTSIZE |
以毫米为单位的屏幕高度 |
|
LOGPIXELSX |
每英寸的水平像素数 |
1、对于打印机:可以用上述公式运算出来。 2、对于显示器:与Windows设置的字体有关:正常字体96dpi,大字体为120dpi |
LOGPIXELSX |
每英寸的垂直像素数 |
5.2.3 关于GetDeviceCaps的扩展学习
(1)屏幕物理分辨率与屏幕分辨率的区别
①物理分辨率:屏幕的最高分辨率,显示器一生产出来,就固定下来了。
②屏幕像素规模(HORZRES和VERTRES)与屏幕分辨率(屏幕dpi)
A、屏幕像素规模:如1024×768
B、屏幕dpi = 25.4×HORZRES/HORZSIZE或 25.4×VERTRES/VERTSIZE
屏幕dpi是可改变。降低像素规模时(如1024×768改成800×600),dpi可能会变化,也可能不变。经测试,台式机和笔记本上dpi变化有所不同。
显示效果 |
说明 |
|
笔记本 LED显示器 |
屏幕显示区域缩窄,但像素大小不变 |
同时影响HORZRES和HORZSIZE, 但dpi不变 |
台式机 CRT显示器 |
屏幕显示区域不变,但像素增大 (LED某些分辨率下也会出现) |
只影响HORZRES,屏幕dpi变大 |
③下面以台式机CRT显示器为例,讨论屏幕dpi变化的情况下,显示的图像大小变化:
如15.6英寸(13.5×7.6)在全屏幕模式下,当设置为1366×768像素时,dpi 为100。当设置683×384下,dpi为50。明显,屏幕上每个像素大小,第2种情况应比第1种大 1倍,因为GPU会把屏幕上的4个像素当1个像素(2×2)用。这就导致了显示的图像变大、变 模糊了。当然,如果是在笔记本显示器下测试,因dpi不变,显示出来的图像大小也就不变了。因 此当改变了屏幕像素规模时,一定要看下屏幕dpi是否变化,才能判断出显示出来的图像大小是否 有变化。
(2)逻辑dpi(=LOGPIXELSX或LOGPIXELSY),Windows下默认为96dpi。
①为什么要使用逻辑dpi,而不直接使用屏幕实际的分辨率
因为早期显示器并不存储物理尺寸等信息,所以Windows无法获取显示器的真实尺寸,也 就没办法得到屏幕分辨率(dpi),这将导致windows在输出时,不知该将1英寸长度的物体,转为 多少个像素输出。所以只能采用市面上常见的显示器的分辨率,硬性规定一个默认值(如Windows 下为96dpi),如此便可以将1英寸长度转化为96个像素输出(注意,这时屏幕上看到的那段长度 不一定就是严格的1英寸长,因为96个像素被输往屏幕后,要根据屏幕上每个像素的实际大小来算 出显示的实际长度,而屏幕像素大小要通过其dpi计算得到)。现在由于EDID技术,将物理尺寸等 信息直接存进显示器,所以将物体按屏幕实际dpi(不是逻辑dpi)显示将成为一种趋势,因为这 可以让我们的显示器按照物体的真实尺寸来显示的。
②逻辑dpi的含义:将1英寸的长度转化为相应的像素,以这样的像素量输出。
如:10磅字体,在逻辑96dpi下将被转化为10/72*96,即13个像素输出;在120dpi下将被 转化为16个像素输出。至于在屏幕上这96个像素(或120)显示出来的实际大小,要根据屏幕dpi 去算出来。通常,在屏幕dpi相等的情况下(如1024×768,设dpi为100),逻辑dpi为120时 显示出来的字要更大,因为13<16。这两种字体在屏幕上的实际长度分别为13/100英寸和 16/100英寸。
③改变屏幕dpi与改变逻辑dpi的不同。
实验:将96px×96px,物理尺寸为1×1英寸蓝色方块输往屏幕
|
|
A、将屏幕dpi由200dpi→100dpi (如1600×1200→800×600,假设这两种情况都是全屏下。否则,这时windows 会调整桌面大小,而让屏幕dpi保持不变) |
改变了屏幕dpi,这时意味着屏幕上每个像素变大了,所以显示出来方块会比实际的1×1 英寸大。 |
B、将逻辑dpi由96→120。 (在相同屏幕dpi下,如1024×768下,只调整逻辑dpi的值) |
1、注意这时屏幕像素大小并没变,因为屏幕dpi没变化。 2、改变了逻辑dpi,意味着要将1英寸长度转为120个像素输出。注意到了吗,Windows偷偷地 将方块像素比增加了,从96px×96px方块调大成120px×120px方块输出。所以,显示 出来的图形也就比原来的更大了。 |
5.2.4 色彩ABC
#define RGB(r,g,b) ((COLORREF)(((BYTE)(r))| \
((WORD)((BYTE)(g))<<8))| \
(((DWORD)(BYTE)(b))<<16)))
5.2.5 保存设备环境属性
(1)GetDC或BeginPaint返回的设备环境属性值是默认的。ReleaseDC或EndPaint后,所做的 任何改变都会丢失。
(2)如何解决?
①注册窗口类时wndClass.style=CS_HREDRAW | CS_VREDRAW | CS_OWNDC,这样这样基于 这个窗口类创建的窗口都有它私有的设备环境。CS_OWNDC只影响通过GETDC和BeginPaint获得的DC ,通过其他函数(如GetWindowDC)获得的设备环境并不受影响。
②在WM_CREATE消息初始化设备环境的属性
case WM_CREATE:
hdc = GetDC(hWnd);
//初始化各个属性
ReleaseDC(hWnd,hdc);
(3)保存与恢复
idSaved= SaveDC(hdc); //可多次save,并保存在不同的id中。
RestoreDC(hdc,idSaved);//RestoreDC(hdc,-1)为恢复到最近一次保存的状态。
5.3 点和线的绘制
5.3.1 设定像素
SetPixel(hdc,x,y,crColor);
COLORREF crColor = GetPixel(hdc,x,y);
5.3.2 直线
(1)MoveTo和MoveToEx
MoveTo返回值是DWORD型,用于表示运行函数前的当前位置(x,y)——早期 windows。而现在的坐标(x,y)都为32位的,所以该函数不能现在的windows中返回正确的坐标。
MoveToEx返回值BOOL。MoveToEX(hdc,x,y,&pt),第三个参数为运行该函数前的当前 位置。不需要为NULL。即MoveToEX(hdc,x,y,NULL)——现在Windows中。
(2)获取当前位置:GetCurrentPosition(hdc,&pt)
(3)LineTo、PolyLineTo等带To的函数会把最后一次的终点设为当前位置。
(4)PolyLine和PolyLineTo的区别
POINT apt[] ={,,,,,,,,,} //首尾点相同
int iArrayLen = sizeof(apt)/sizeof(POINT); //用 MoveToEx和LineTo绘制
MoveToEx(hdc,apt[].x,apt[].y,NULL)
for(int i=;i<iArrayLen;i++)
{
LineTo(hdc,apt[i].x,apt[i].y);
} //用 PolyLine绘制,并不改变当前位置。
PolyLine(hdc,apt,iArrayLen);//最后一个参数表示要绘制的点的个数。 //用 PolyLineTo绘制,要将当前位置作为起点,绘制完后,会改变当前位置。
MoveToEx(hdc,apt[].x,apt[].y,NULL);//从当前绘制为起点
PolyLine(hdc,apt+,iArrayLen-); //画下后面的点。
【SINWAVE程序】
/*------------------------------------------------------------
SINEWAVE.C -- Sine Wave Using Polyline
(c) Charles Petzold, 1998
------------------------------------------------------------*/
#include <windows.h>
#include <math.h> #define NUM 1000
#define TWOPI (2*3.14159) LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
static TCHAR szAppName[] = TEXT("SinWave");
HWND hwnd;
MSG msg;
WNDCLASS wndclass;
wndclass.style = CS_HREDRAW | CS_VREDRAW;
wndclass.lpfnWndProc = WndProc;
wndclass.cbClsExtra = ;
wndclass.cbWndExtra = ;
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 ;
} hwnd = CreateWindow(szAppName, // window class name
TEXT("SinWave"), // window caption
WS_OVERLAPPEDWINDOW, // window style
CW_USEDEFAULT, // initial x position
CW_USEDEFAULT, // initial y position
CW_USEDEFAULT, // initial x size
CW_USEDEFAULT, // 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, , ))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
HDC hdc;
PAINTSTRUCT ps;
static int cxClient, cyClient;
POINT apt[NUM];
switch (message)
{
case WM_CREATE: return ;
case WM_SIZE:
cxClient = LOWORD(lParam);
cyClient = HIWORD(lParam);
return ;
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps); // 画x轴
MoveToEx(hdc, , cyClient / , NULL);
LineTo(hdc, cxClient, cyClient / ); // 计算坐标
for (int i = ; i < NUM; i++)
{
apt[i].x = i * cxClient / NUM;
apt[i].y = (int)(cyClient / * ( - sin(TWOPI * i / NUM)));
}
// 画正弦曲线
Polyline(hdc, apt, NUM); //调用一次且在设备驱动程序层面上,比调用1000次LineTo要快很多 EndPaint(hwnd, &ps);
return ; case WM_DESTROY:
PostQuitMessage();
return ;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}
5.3.3 边框绘制函数
(1)边界偏差(off-by-one):Windows在“边框”内绘图,即只画右坐标与底坐 标之前的点。即不包含右坐标与底坐标的点。
(2)图解边框绘图函数
【LineDraw】
/*------------------------------------------------------------
LINEDRAW.C -- Line-Draw Demonstration Program
(c) Charles Petzold, 1998
------------------------------------------------------------*/
#include <windows.h>
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
static TCHAR szAppName[] = TEXT("LineDemo");
HWND hwnd;
MSG msg;
WNDCLASS wndclass;
wndclass.style = CS_HREDRAW | CS_VREDRAW;
wndclass.lpfnWndProc = WndProc;
wndclass.cbClsExtra = ;
wndclass.cbWndExtra = ;
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 ;
} hwnd = CreateWindow(szAppName, // window class name
TEXT("LineDraw Demo"), // window caption
WS_OVERLAPPEDWINDOW, // window style
CW_USEDEFAULT, // initial x position
CW_USEDEFAULT, // initial y position
CW_USEDEFAULT, // initial x size
CW_USEDEFAULT, // 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, , ))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
HDC hdc;
PAINTSTRUCT ps;
static int cxClient, cyClient;
switch (message)
{
case WM_CREATE: return ;
case WM_SIZE:
cxClient = LOWORD(lParam);
cyClient = HIWORD(lParam);
return ;
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps); //
Rectangle(hdc, cxClient / , cyClient / , * cxClient / , * cyClient / ); // 画对角线
MoveToEx(hdc, , , NULL);
LineTo(hdc, cxClient, cyClient);
MoveToEx(hdc, cxClient, , NULL);
LineTo(hdc, , cyClient);
Ellipse(hdc, cxClient / , cyClient / , * cxClient / , * cyClient / );
RoundRect(hdc, cxClient / , cyClient / , * cxClient / , * cyClient / , cxClient / , cyClient / ); EndPaint(hwnd, &ps);
return ; case WM_DESTROY:
PostQuitMessage();
return ;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}
5.3.4 贝塞尔样条曲线
(1)PolyBezier(hdc,apt,iCount)
①apt是个POINT结构的数组,前四点分别表示曲线起点、第一个控制点、 第二个控点、第二个点。随后的每一条贝塞尔样条曲线则只需要给出三个点,因为前一条贝塞尔 样条曲线的终点就是后一条的起点
②iCount=3*曲线的条数+1;
(2)PolyBezierTo函数把当前位置作为第一个起点,所以后面每画一条曲线只需再给3个点。 当函数返回时,把曲线终点设置为当前位置。
【Bezier】
/*------------------------------------------------------------
LINEDRAW.C -- Bezier Splines Demo
(c) Charles Petzold, 1998
------------------------------------------------------------*/
#include <windows.h>
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
static TCHAR szAppName[] = TEXT("Bezier");
HWND hwnd;
MSG msg;
WNDCLASS wndclass;
wndclass.style = CS_HREDRAW | CS_VREDRAW;
wndclass.lpfnWndProc = WndProc;
wndclass.cbClsExtra = ;
wndclass.cbWndExtra = ;
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 ;
} hwnd = CreateWindow(szAppName, // window class name
TEXT("Bezier"), // window caption
WS_OVERLAPPEDWINDOW, // window style
CW_USEDEFAULT, // initial x position
CW_USEDEFAULT, // initial y position
CW_USEDEFAULT, // initial x size
CW_USEDEFAULT, // 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, , ))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
void DrawBezier(HDC hdc, POINT apt[])
{
//画贝 塞尔曲线
PolyBezier(hdc, apt, );
//画控 制线
MoveToEx(hdc, apt[].x, apt[].y, NULL);
LineTo(hdc, apt[].x, apt[].y);
MoveToEx(hdc, apt[].x, apt[].y, NULL);
LineTo(hdc, apt[].x, apt[].y);
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
HDC hdc;
PAINTSTRUCT ps;
static int cxClient, cyClient;
static POINT apt[];
switch (message)
{
case WM_CREATE: return ;
case WM_SIZE:
cxClient = LOWORD(lParam);
cyClient = HIWORD(lParam);
apt[].x = cxClient / ;
apt[].y = cyClient / ;
apt[].x = cxClient / ;
apt[].y = cyClient / ;
apt[].x = cxClient / ;
apt[].y = * cyClient / ;
apt[].x = * cxClient / ;
apt[].y = cyClient / ;
return ;
case WM_MOUSEMOVE: //鼠标移动时
case WM_LBUTTONDOWN: //左键按下时
case WM_RBUTTONDOWN: //右键按下时
if (wParam & MK_LBUTTON || wParam &MK_RBUTTON)
{
hdc = GetDC(hwnd);
SelectObject(hdc, GetStockObject(WHITE_PEN));
DrawBezier(hdc, apt); //用白色画刷重绘一遍,即擦除旧的曲线
if (wParam & MK_LBUTTON) //左键改变第1个控制点 {
apt[].x = LOWORD(lParam);
apt[].y = HIWORD(lParam);
}
if (wParam & MK_RBUTTON) //右键改变第2个控 制点
{
apt[].x = LOWORD(lParam);
apt[].y = HIWORD(lParam);
}
SelectObject(hdc, GetStockObject(BLACK_PEN));
DrawBezier(hdc, apt); ReleaseDC(hwnd, hdc);
}
return ;
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps); DrawBezier(hdc, apt);
EndPaint(hwnd, &ps);
return ; case WM_DESTROY:
PostQuitMessage();
return ;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}
5.3.5 使用现有画笔(备用画笔——画笔宽度总 是为1)
(1)获取画笔句柄——3种默认画笔(WHITE_PEN、BLACK_PEN、NULL_PEN),其中 NULL_PEN表示不绘任何图形。比如,画矩形时只要填充区域,不要边框时,可用NULL_PEN。
HPEN hPen= GetStockObject(WHITE_PEN);
(2)选入设备环境:
HPEN hOldPen = SelectObject(hdc, (HGDIOBJ)hPen);
HPEN hOldPen = SelectObject(hdc, GetStockObject(WHITE_PEN));
5.3.6 创建、选择和删除画笔
(1)使用GDI对象的规则
①当GDI对象被选入一个有效的设备环境时,不要删除它
②最终应删除你所创建的所有GDI对象
③不要删除备用库里的对象
HPEN hPen = CreatePen(PS_DASH, 1, RGB(255, 0, 0));//创建画笔 HPEN hOldPen = SelectObject(hdc, (HGDIOBJ)hPen); //选入画笔 //画图代码 SelectObject(hdc, (HGDIOBJ)hOldPen); //恢复旧画笔 DeleteObject((HGDIOBJ)hPen); //删除画笔,上一行不能省略。 |
(2)hPen = CreatePen(iPenStyle,iWidth,crColor);
iPenStyle参数 |
iWidth |
crColor |
|
①当iWidth =0时,Windows会重设为1个像素 ②对于PS_SOLID、PS_NULL、PS_INSIDEFRAME,iWidth表示画笔的宽度 ③如果是虚线或点线,当iWidth>1时,会用实心画笔代替。 |
①设备画笔颜色,是COLORREF值。 ②PS_INSIDEFRAME唯一能使用混合色的画笔样式,但要iWidth>1。 |
(3)hPen = CreatePenIndirect(&logpen);
LOGPEN结构(LOGPEN logpen) |
|
lopnStyle |
画笔样式 |
lopnWidth |
画笔宽度,是一个POINT结构,x字段画笔的宽度,y字段被忽略 |
lopnColor |
画笔颜色,COLORREF值。 |
(4)删除未被保存句柄的设备环境中的画笔——不能删除被选入设备环境句柄的
画笔!
① 方法1://选入默认画笔,返回的是设备中的画笔,再删除。
DeleteObject(SelectObject(hdc,GetStockObject(BLACK_PEN)));
②方法2:
hPen = SelectObject(hdc,CreatePen(PS_DASH),0,RGB(255,0,0));//自定义画笔
DeleteObject(SelectObject(hdc,hPen));//返回自定义画笔句柄,并删除。
(5)获取画笔
①hPen =GetCurrentObject(hdc,OBJ_PEN); //获得当前设备环境中的画笔句柄
②GetObject(hPen,sizeof(LOGPEN),(LPVOID)&logpen);//画笔属性保存logpen中。
5.3.7 填充空隙——点式画笔或虚线画笔之间空
隙的颜色
空隙颜色由设备环境的背景模式和背景颜色决定(注意不是窗口的背景颜色
)
背景模式 |
OPAQUE(不透明) TRANSPARENT(透明) |
SetBkMode(hdc,TRANSPARENT); int iMode = GetBkMode (hdc); |
背景颜色 |
COLORREF值 |
SetBkColor(hdc,crColor); COLORREF crColor = GetBkColor(hdc); |
5.3.8 绘图模式(默认R2_COPYPEN)
(1)线条最终显示的颜色由画笔颜色及目标区域表面的颜色共同决定。
(2)光栅操作(rasteroperation,ROP):画笔的像素颜色与目标表面像素按位布尔运算。因
画线只涉及两种像素颜色(画笔与目标),也被称为二元光栅操作(ROP2)。
(3)16种不同的ROP2运算:如R2_COPYPEN、R2_MASKNOTPEN、R2_BLACK等。
(4)获取当前绘制模式和设置绘图模式:
①iDrawMode =GetROP2(hdc);
②SetROP2(hdc,iDrawMode);
5.4 绘制填充
5.4.1 画刷
(1)备用库画刷:6种(WHITE_BRUSH、LTGRAY_BRUSH、GRAY_BRUSH、DKGRAY_BRUSH、
BLACK_BRUSH和NULL_BRUSH)。
(2)使用画刷
HBRUSHhBrush = GetStockObject(GRAY_BRUSH); //获取画刷
SelectObject(hdc,hBrush); //选入设备环境中
(3)应用举例——画矩形
不要边框线,只要填充 |
SelectObject(hdc,GetStockObject(NULL_PEN)); |
只绘边框,不要填充内部 |
SelectObject(hdc,GetStockObject(NULL_BRUSH)); |
5.4.2 Polygon函数和多边形填充模式
(1)Polygon和PolyPolygon函数
① Polygon(hdc,apt,iCount);
apt |
POINT结构的数组,表示各个顶点 |
若最后一个点与第一个点不同,Windows会自动加1条连接这两个点的线,以形成闭合区域 |
iCount |
顶点的个数 |
②PolyPolygon(hdc, ,aiCounts,iPolyCount);
apt |
POINT结构的数组,表示所有的顶点 |
aiCounts |
是个数组,每个元素表示1个多边形的顶点数。 |
iPolyCount |
多边形的个数 |
③PolyPolygon的功能等价代码
for(int i=0,iAccum =0;i<iPolycount;i++) { Polygon(hdc,apt+iAccum,aiCounts[i]); //第i个多边形; iAccum +=aiCounts[i]; //绘完第i个多边形后,顶点索引 后移; } |
(2)图解多边形的填充模式:ALTERNATE(交替)和WINDING(螺旋)
SetPolyFillMode(hdc,iMode);
1、特征图形与填充效果图
效果图
/*------------------------------------------------------------
ALTWIND.C -- Alternate and Winding Fill Modes
(c) Charles Petzold, 1998
------------------------------------------------------------*/
#include <windows.h>
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
static TCHAR szAppName[] = TEXT("AltWind");
HWND hwnd;
MSG msg;
WNDCLASS wndclass;
wndclass.style = CS_HREDRAW | CS_VREDRAW;
wndclass.lpfnWndProc = WndProc;
wndclass.cbClsExtra = ;
wndclass.cbWndExtra = ;
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 ;
} hwnd = CreateWindow(szAppName, // window class name
TEXT("Alternate And Winding Fill Modes"), // window caption
WS_OVERLAPPEDWINDOW, // window style
CW_USEDEFAULT, // initial x position
CW_USEDEFAULT, // initial y position
CW_USEDEFAULT, // initial x size
CW_USEDEFAULT, // 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, , ))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
HDC hdc;
PAINTSTRUCT ps;
static int cxClient, cyClient;
static POINT aptFigure[] = { , , , , , , , , , ,
, , , , , , , , , }; //特征图形
POINT apt[];
switch (message)
{
case WM_SIZE:
cxClient = LOWORD(lParam);
cyClient = HIWORD(lParam);
return ;
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
SelectObject(hdc, GetStockObject(GRAY_BRUSH));
// 左幅图ALTERNATE模式填充
for (int i = ; i < ; i++)
{
apt[i].x = cxClient * aptFigure[i].x / ;
apt[i].y = cyClient * aptFigure[i].y / ; //按比例放大,因为apt[i].y /cylient = aptFigure[i].y / 100;
}
SetPolyFillMode(hdc, ALTERNATE);
Polygon(hdc, apt, );
// 右幅图ALTERNATE模式填充
for (int i = ; i < ; i++)
{
apt[i].x += cxClient / ;
}
SetPolyFillMode(hdc, WINDING);
Polygon(hdc, apt, );
EndPaint(hwnd, &ps);
return ; case WM_DESTROY:
PostQuitMessage();
return ;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}
5.4.3 用画刷(8×8的小位图)填充内部
(1)创建逻辑画刷hBrush =CreateSolidBrush(crColor); //也可能是一个抖动位图
(2)阴影线标记的画刷hBrush = CreateHatchBrush(iHatchStyle,crColor);
iHatchStyle表示阴影线标记的外观 |
说明:1、阴影线颜色由crColor指定。 2、阴影线之间的空隙由背景模式和背影颜色决定,由画笔 类似。 |
(3)用自己的位图画刷:CreatePatternBrush和CreateDIBPatternBrushPt。
(4)包含其他4个函数功能的(更强大):hBrush = CreateBrushIndirect (&logbrush);
LOGBRUSH结构体(三个字段,lbStyle 字段的值决定Windows如何解释其他两个字段) |
||
lbStyle(UINT) |
lbColor(COLORREF) |
lbHatch(LONG) |
BS_SOLID |
画刷的颜色 |
被忽略 |
BS_HOLLOW |
被忽略 |
被忽略 |
BS_HATCHED |
阴影线的颜色 |
阴影线画刷的样式 |
BS_PATTERN |
被忽略 |
位图的句柄 |
BS_DIBPATTERNPT |
被忽略 |
指向DIB的指针 |
(5)获取画刷信息GetObject(hBrush,sizeof(LOGBRUSH), (LPVOID)&logbrush);