如前所诉,windows输出图形到显示器上采用绘制的形式,除了文字之外,windows还有能绘制其余几个基本图形,通过绘制这些基本图形最后组合出复杂界面。基本图形包括:直线和曲线、填入区域、点阵图、文字。
与文字输出一样,windows程序并不直接操作输出对象,而是通过操作句柄来操作绘图设备。除了更安全的原因之外,操作HDC还有抽象的意味,如以绘图设备指代显示器或者打印机使得不需要对两个输出设备分别进行编码。windows只是做了抽象并没有完全的封装,我们还是可以通过相应的API来取得HDC所指代的设别的信息。
int WINAPI GetDeviceCaps(HDC, int);
//第二个参数可以取如下值(还有一些设备的特殊值)
#define DRIVERVERSION 0 /* Device driver version */
#define TECHNOLOGY 2 /* Device classification */
#define HORZSIZE 4 /* Horizontal size in millimeters */
#define VERTSIZE 6 /* Vertical size in millimeters */
#define HORZRES 8 /* Horizontal width in pixels */
#define VERTRES 10 /* Vertical height in pixels */
#define BITSPIXEL 12 /* Number of bits per pixel */
#define PLANES 14 /* Number of planes */
#define NUMBRUSHES 16 /* Number of brushes the device has */
#define NUMPENS 18 /* Number of pens the device has */
#define NUMMARKERS 20 /* Number of markers the device has */
#define NUMFONTS 22 /* Number of fonts the device has */
#define NUMCOLORS 24 /* Number of colors the device supports */
#define PDEVICESIZE 26 /* Size required for device descriptor */
#define CURVECAPS 28 /* Curve capabilities */
#define LINECAPS 30 /* Line capabilities */
#define POLYGONALCAPS 32 /* Polygonal capabilities */
#define TEXTCAPS 34 /* Text capabilities */
#define CLIPCAPS 36 /* Clipping capabilities */
#define RASTERCAPS 38 /* Bitblt capabilities */
#define ASPECTX 40 /* Length of the X leg */
#define ASPECTY 42 /* Length of the Y leg */
#define ASPECTXY 44 /* Length of the hypotenuse */
这些取出来的信息单位各不相同需要做下厘清,常用到的名称定义如下:
px:像素,既屏幕上的一个小方块(并不一定是正方形的)。
dip(Density independent pixels):设备无关像素,逻辑像素。
分辨率:X或Y轴上像素点的数量(逻辑像素或者实际像素)
dpi(dots per inch):每英寸的像素点个数。
density:逻辑像素所代表的在设备上的实际像素个数。
并以上各个单位之间都可以根据情况做换算。
除了位置和大小信息HDC中还包含色彩信息,GDI使用COLORREF类型来表示颜色,由R,G,B的颜色混合成其他颜色。
#define RGB(r,g,b) ((COLORREF)(((BYTE)(r) | \
((WORD)((BYTE)(g)) << 8)) | \
(((DWORD)(BYTE)(b)) << 16)))
未完成绘制任务,还需要其他内容的支持,HDC还包含如下表格中的内容:
调用API函数可以改变HDC的设置,若windows的风格为CS_OWNDC,用户所做的设置会被保留下来,否则在DC释放后所做的设置会被抛弃,下次获取HDC的时候又会重新建立一个。获取HDC的API如下:
//WM_PAINT 中获取和释放DC
hdc = BeginPaint (hwnd, &ps) ;
EndPaint (hwnd, &ps) ;
//非WM_PAINT 中获取和释放DC
hdc = GetDC (hwnd) ;
ReleaseDC (hwnd, hdc) ;
//整个窗口的的DC
hdc = GetWindowDC (hwnd) ;
ReleaseDC (hwnd, hdc) ;
//另一个更通用的接口
hdc = CreateDC (pszDriver, pszDevice, pszOutput, pData) ;
DeleteDC (hdc) ;
在获得HDC后,要绘制图形通过调用API来实现。windows提供的API能够满足大部分的绘图需求:
LineTo:画直线。
Polyline和PolylineTo:画一系列相连的直线。
PolyPolyline:画多组相连的线。
Arc:画椭圆线。
PolyBezier和PolyBezierTo:画贝塞尔曲线。
ArcTo和AngleArc:画椭圆线。
PolyDraw:画一系列相连的线以及贝塞尔曲线。
Rectangle:画矩形。
Ellipse:画椭圆。
RoundRect:画带圆角的矩形。
Pie:画椭圆的一部分,使其看起来像一个扇形。
Chord:画椭圆的一部分,以呈弓形。
粗略的使用这些绘制函数:
/*--------------------------------------------------
LINEDEMO.C -- Line-Drawing 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 = 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 ("Program requires Windows NT!"),
szAppName, MB_ICONERROR) ;
return 0 ;
}
hwnd = CreateWindow (szAppName, TEXT ("Line Demonstration"),
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL) ;
ShowWindow (hwnd, iCmdShow) ;
UpdateWindow (hwnd) ;
while (GetMessage (&msg, NULL, 0, 0))
{
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
}
return msg.wParam ;
}
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static int cxClient, cyClient ;
HDC hdc ;
PAINTSTRUCT ps ;
switch (message)
{
case WM_SIZE:
cxClient = LOWORD (lParam) ;
cyClient = HIWORD (lParam) ;
return 0 ;
case WM_PAINT:
hdc = BeginPaint (hwnd, &ps) ;
//画一距离边为长、高的1/8的矩形
Rectangle (hdc, cxClient / 8, cyClient / 8, 7 * cxClient / 8, 7 * cyClient / 8) ;
//画两条对角线
MoveToEx (hdc, 0, 0, NULL) ;
LineTo (hdc, cxClient, cyClient) ;
MoveToEx (hdc, 0, cyClient, NULL) ;
LineTo (hdc, cxClient, 0) ;
//画一个内切于矩形的椭圆
Ellipse (hdc, cxClient / 8, cyClient / 8, 7 * cxClient / 8, 7 * cyClient / 8) ;
//画一距离边为长、高的1/4的圆角矩形
RoundRect (hdc, cxClient / 4, cyClient / 4, 3 * cxClient / 4, 3 * cyClient / 4, cxClient / 4, cyClient / 4) ;
EndPaint (hwnd, &ps) ;
return 0 ;
case WM_DESTROY:
PostQuitMessage (0) ;
return 0 ;
}
return DefWindowProc (hwnd, message, wParam, lParam) ;
}
程序以默认的方式使用黑色,实线画出线条。画线所用到的信息保存在DC的画笔中,我们也可以自己选择默认画笔或者设置画笔信息。
GetStockobject //获取预设的画笔(或其他对象)的句柄
SelectObject //将画笔设置为当前画笔
CreatePen //创建一个画笔,画笔的对象由系统管理,返回handle
CreatePenIndirect //创建逻辑画笔,对象由用户自己保存,返回handle
DeleteObject //删除设备对象,删除后句柄失效
但画笔对象被删除后句柄也同时失效,我们需要主动释放创建的画笔,但不能释放在使用中的对象。画笔的建立需要指定,风格,宽度,和颜色。系统提供如下风格:
而虚线中的空白部分与背景颜色和模式相关一般而言调用SetBkMode (hdc, TRANSPARENT) 将模式设为透明模式。另外通过函数SetROP2设置将像素输出到绘图设备中是的位运算。
除了绘制线条之外系统还提供填充图形的接口:
就是用色块填入封闭的空间,与画线的函数的差别就在于填充使用的画刷中的信息。使用如下API建立画刷:
CreateSolidBrush
CreateBrushIndirect
并设置画刷的颜色和风格:
目前位置所有关于位置的信息都基于屏幕或者客户区的左上角为原点向下向右为正方向的坐标系。windows也支持我们重新定义坐标系有如下预设的坐标系可供选择:
另外还提供了ClientToScreen和ScreenToClient将屏幕坐标和客户区的左边进行换算,如以DISPLAY的方式调用CreateDC时,可以通过ScreenToClient的到客户区的坐标。一般坐标之前的转换都是采用向量与一个3*3的矩形相乘得到最终的结果,以上模式的差别就在于在坐标映射时矩阵中的值不同。
DC还提供了一个与窗口无效区域类似的功能,只在某个区域中绘图,称之为剪裁区域,设置裁剪区域后,只有在裁剪区域中的的绘图才有效。裁剪区域与pen和brush一样是个绘图的object,通过如下函数建立不同的裁减区域:
//建立矩形裁剪区
CreateRectRgn
CreateRectRgnIndirect
//建立椭圆剪裁区
CreateEllipticRgn
CreateEllipticRgnIndirect
//建立圆角的矩形剪裁区
CreateRoundRectRgn
//建立多边形剪裁区
CreatePolygonRgn
通过函数iRgnType = CombineRgn (hDestRgn, hSrcRgn1, hSrcRgn2, iCombine) ;来将hSrcRgn1,hSrcRgn2合并为一个裁剪区hDestRgn,hDestRgn当有个初始值。
/*--------------------------------------------------
CLOVER.C -- Clover Drawing Program Using Regions
(c) Charles Petzold, 1998
--------------------------------------------------*/
#include <windows.h>
#include <math.h>
#define TWO_PI (2.0 * 3.14159)
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
static TCHAR szAppName[] = TEXT ("Clover") ;
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, TEXT ("Draw a Clover"),
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL) ;
ShowWindow (hwnd, iCmdShow) ;
UpdateWindow (hwnd) ;
while (GetMessage (&msg, NULL, 0, 0))
{
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
}
return msg.wParam ;
}
LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
{
static HRGN hRgnClip ;
static int cxClient, cyClient ;
double fAngle, fRadius ;
HCURSOR hCursor ;
HDC hdc ;
HRGN hRgnTemp[6] ;
int i ;
PAINTSTRUCT ps ;
switch (iMsg)
{
case WM_SIZE:
//重新获取窗口大小
cxClient = LOWORD (lParam) ;
cyClient = HIWORD (lParam) ;
//设置并显示鼠标为IDC_WAIT
hCursor = SetCursor (LoadCursor (NULL, IDC_WAIT)) ;
ShowCursor (TRUE) ;
//删除裁剪区,每次窗口大小变化后都重设裁剪区
if (hRgnClip)
DeleteObject (hRgnClip) ;
//创建四个椭圆形裁剪区
hRgnTemp[0] = CreateEllipticRgn (0, cyClient / 3, cxClient / 2, 2 * cyClient / 3) ;
hRgnTemp[1] = CreateEllipticRgn (cxClient / 2, cyClient / 3, cxClient, 2 * cyClient / 3) ;
hRgnTemp[2] = CreateEllipticRgn (cxClient / 3, 0, 2 * cxClient / 3, cyClient / 2) ;
hRgnTemp[3] = CreateEllipticRgn (cxClient / 3, cyClient / 2, 2 * cxClient / 3, cyClient) ;
//创建合并矩形裁剪区 设置裁剪区初值
hRgnTemp[4] = CreateRectRgn (0, 0, 1, 1) ;
hRgnTemp[5] = CreateRectRgn (0, 0, 1, 1) ;
//总裁剪区
hRgnClip = CreateRectRgn (0, 0, 1, 1) ;
//合并所有的裁剪区
CombineRgn (hRgnTemp[4], hRgnTemp[0], hRgnTemp[1], RGN_OR) ;
CombineRgn (hRgnTemp[5], hRgnTemp[2], hRgnTemp[3], RGN_OR) ;
//排除掉两个裁剪区的重叠区域
CombineRgn (hRgnClip, hRgnTemp[4], hRgnTemp[5], RGN_XOR) ;
//释放所有裁剪区
for (i = 0 ; i < 6 ; i++)
DeleteObject (hRgnTemp[i]) ;
SetCursor (hCursor) ;
ShowCursor (FALSE) ;
return 0 ;
case WM_PAINT:
hdc = BeginPaint (hwnd, &ps) ;
//设置原点坐标
SetViewportOrgEx (hdc, cxClient / 2, cyClient / 2, NULL) ;
//选中裁剪区(裁剪区是以客户区坐标为基准的,hdc坐标调整与裁剪区的坐标无关)
SelectClipRgn (hdc, hRgnClip) ;
//原点到矩形的一个端点的距离(_hypot求对角线)
fRadius = _hypot (cxClient / 2.0, cyClient / 2.0) ;
//从原点每隔5度画出一条长度为fRadius的射线
for (fAngle = 0.0 ; fAngle < TWO_PI ; fAngle += TWO_PI / 360)
{
MoveToEx (hdc, 0, 0, NULL) ;
LineTo (hdc, (int) ( fRadius * cos (fAngle) + 0.5), (int) (-fRadius * sin (fAngle) + 0.5)) ;
}
EndPaint (hwnd, &ps) ;
return 0 ;
case WM_DESTROY:
//删除裁剪区
DeleteObject (hRgnClip) ;
PostQuitMessage (0) ;
return 0 ;
}
return DefWindowProc (hwnd, iMsg, wParam, lParam) ;
}