一,GDI 简介
1. GDI 函数
GDI 包含几百个函数,可分为以下几大类:
- 获取(或建立)和释放(或销毁)设备环境的函数
- 获取设备环境信息的函数
- 绘图函数
- 设置和获取设备环境属性的函数
- 使用GDI “对象” 的函数
2. GDI 基本图形
- 线条和曲线
- 可被填充的封闭区域
- 位图
- 文本
3.其他概念
- 映射模式(mapping mode)和转换(transform)
- 图元文件(metafile)
图元文件是以二进制形式编码的GDI函数调用的集合 - 区域(region)
- 路径(path)
- 剪裁(clipping)
- 调色板(patettes)
- 打印(printing)
二,设备环境
设备环境句柄(HDC)(32位无符号整数)
- 是程序使用 GDI 函数的“通行证”
- 设备环境的属性决定绘制的一些特性,如颜色
- 当程序完成对客户区的绘制后,必须释放设备环境句柄(释放之后就无效了)
- 必须在处理同一条消息时,获取HDC并释放HDC,不能在两条消息中传递一个HDC(唯一例外是CreateDC 创建的设备环境)。
1. 获取设备环境
1.1 :方法一:BeginPaint() 和 EndPaint()
处理 WM_PAINT 时使用。
case WM_PAINT:
hdc=BeginPaint(hwnd,&ps);
(using GDI functions)
EndPaint(hwnd,&ps);
return 0;
BeginPaint() 函数:擦去无效区域,填充ps结构,返回设备环境句柄。
EndPaint() 函数:释放设备环境句柄。
注意:
处理 WM_PAINT 消息时,使用无效矩形以避免不必要的GDI 函数调用(否则绘制时,需要GDI函数自己恢复无效区域,影响程序运行效率)
1.2 方法二: GetDC() 和 ReleaseDC()
hdc = GetDC(hwnd);
(使用 GDI 函数)
ReleaseDC(hwnd,hdc);
GetDC 返回设备环境句柄中的“裁剪”区是整个客户区,与无效矩形没有任何关系。也不会使无效区域有效化。
GetDC(NULL);
获取整个屏幕设备环境
注意:
即使程序再处理非 WM_PAINT 消息时进行了绘制,他仍然必须收集足够的信息以便在收到 WM_PAINT 时能更新显示。
1.3 方法三:GetWindowDC() 和 ReleaseDC()
获取整个窗口的设备环境句柄
意味着可以在窗口的标题栏,菜单,滚动条和客户区外框输出,同样程序必须要处理 WM_NCPAINT(非客户区绘制)消息
hdc = GetWindowDC(hwnd);
...
ReleaseDC(hwnd,hdc);
1.4 方法四:CreateDC() 和 DeleteDC()
hdc = CreateDC(pszDriver,pszDevice,pszOutput,pData);
...
DeleteDC(hdc);
例如,获取整个屏幕的设备环境句柄:
hdc = CreateDC(TEXT("DISPLAY"),NULL,NULL,NULL);
1.5 只用于获取信息,不绘制——CreateIC()
获取一个“信息上下文”句柄,其参数与CreateDC相同
!!!不能用于绘制!!!
hdc = CreateIC(TEXT("DISPLAY"),NULL,NULL,NULL);
1.6 内存设备环境(处理位图时)
hdcMem = CreateConpatibleDC(hdc);
...
DeleteDC(hdcMem);
将一个位图选入内存设备环境,并且调用GDI函数绘制这个位图
图元文件是以二进制形式编码的GDI函数调用的集合,可通过获取一个图元文件的设备环境来创建
hdcMeta = CreateMetaFile(pszFilename);
...
hmf = CloseMetaFile(hdcMeta);
在图元文件设备环境有效时,使用hdcMeta 所做的任何GDI调用都不会被直接显示出来,它们都会变成图元文件的一部分,当调用 CloseMetaFile 时,图元文件设备环境句柄变为无效,,该函数返回一个图元文件的句柄(hmf),18章将详细讨论图元文件。
2. 获取设备环境的信息
iValue = GetDeviceCaps(hdc,iIndex);
2.1 设备的尺寸
本书中,“分辨率”被严格定义为 每度量单位(通常是英寸)中含有的像素数。
“像素规模”或“像素尺寸”表示设备在水平和垂直方向上显示的总的像素数。
“度量规模”或“度量尺寸”表示以英寸或毫米为单位的客户区域的大小。
像素尺寸/度量尺寸=分辨率
2.1.1 获取像素规模
可以调用 GetSystemMetrics()(使用SM_CXSCREEN和SM_CYSCREEN)参数来获取显示器的像素规模
还可以调用 GetDeviceCaps()(使用HORZRES 和 VEERTRES)获取同样的值。
2.1.2 获取逻辑像素
可以用 GetDeviceCaps()(使用 LOGPIXELSX 和 LOGPIXELSY)
获取“逻辑像素”,即“以每英寸的像素数计算的非实际分辨率”
2.1.3 获取逻辑尺寸
可以用 GetDeviceCaps()(使用 HORZSIZE 和 VERTSIZE)
获取“逻辑宽度”,“逻辑高度”,即“以毫米为单位的物理屏幕宽/高度”,其实是用HORZRES,VERTRES,LOGPIXELSX,LOGPIXELSY的值计算出来的,公式:
其实是标准显示器的尺寸,并不能反映当前显示器的尺寸
(像素规模是可以改的)
GetDeviceCaps 函数获得值与视频尺寸相关,
ASPECTX(每个像素点的相对宽度)
ASPECTY(每个像素点的相对高度)
ASPECTXY(每个像素点的相对对角线长度)(前两个平方和开根)
程序需要视频显示器的实际物理尺寸,最好的解决办法是让用户输入。
2.1.4 字符尺寸
字体字符大小又“点值”(也叫磅值point size),一点为1/72英寸。
10点值的字读起来舒服,行间距为13磅或12磅。
3.色彩ABC
3.1 获取视频适配器板卡上的内存组织形式
各个像素的多个色彩位可以在显卡内存中以顺序方式存储,也可以不同的色彩位被存放在内存的不同平面(plane)上
每个像素颜色需要的内存:
iBitsPixel = GetDeviceCaps(hdc,BITSPIXEL);
获取色彩平面的数目:
iPlanes = GetDeviceCaps(hdc,PLANES);
上面两个函数肯定有一个的返回值为1.
视频适配器支持的色彩数:iColor = 1<<(iPlanes*iBitPixel);
对于使用 NUMCOLORS 参数的 GetDeviceCaps 获取的颜色,只是Windows保留的色彩数,或对高彩和真彩返回-1。总之,其结果不准确,建议用上面的公式算
3.2 表示颜色
COLORREF(32位无符号长整型)来表示特定颜色,按 红,蓝,绿(各8位,最高8位为0)的顺序指定。
可以用 RGB 宏组合该值:
#define RGB(r,g,b) ((COLORREF)(((BYTE)(r)|((WORD)((BYTE)(g))<<8))|(((DWORD)(BYTE)(b))<<16)))
例:
RGB(255,255,0);
可利用“抖动”仿真更多颜色。
抖动就是用不同的色彩的相邻像素形成一个小的图案,可调用 GetNearestColor 确定与某种特殊颜色值最接近的非合成颜色:
crPureColor = GetNearestColor(hdc,crColor);
4,设备环境属性
设备环境属性 | 默认值 | 修改该值的函数 | 取得该值的函数 |
---|---|---|---|
Mapping Mode | MM_TEXT | SetMapMode | GetMapMode |
Window Origin | (0, 0) | SetWindowOrgEx OffsetWindowOrgEx | GetWindowOrgEx |
Viewport Origin | (0, 0) | SetViewportOrgEx OffsetViewportOrgEx | GetViewportOrgEx |
Window Extents | (1, 1) | SetWindowExtEx, SetMapMode, ScaleWindowExtEx | GetWindowExtEx |
Viewport Extents | (1, 1) | SetViewportExtEx,SetMapMode,ScaleViewportExtEx | GetViewportExtEx |
Pen | BLACK_PEN | SelectObject | SelectObject |
Brush | WHITE_BRUSH | SelectObject\SelectObject | |
Font | SYSTEM_FONT | SelectObject | SelectObject |
Bitmap | None | SelectObject | SelectObject |
Current Position | (0, 0) | MoveToEx,LineTo,PolylineTo,PolyBezierTo | GetCurrentPositionEx |
Background Mode | OPAQUE | SetBkMode | GetBkMode |
Background Color | White | SetBkColor | GetBkColor |
Text Color | Black | SetTextColor | GetTextColor |
Drawing Mode | R2_COPYPEN | SetROP2 | GetROP2 |
Stretching Mode | BLACKONWHITE | SetStretchBltMode | GetStretchBltMode |
Polygon Fill Mode | ALTERNATE | SetPolyFillMode | GetPolyFillMode |
Intercharacter Spacing | 0 | SetTextCharacterExtra | GetTextCharacterExtra |
Brush Origin | (0, 0) | SetBrushOrgEx | GetBrushOrgEx |
Clipping Region | None | SelectObject,SelectClipRgn,IntersectClipRgn,OffsetClipRgn,ExcludeClipRect,SelectClipPath | GetClipBox |
5.保存设备环境
情况一:释放设备环境时保存对属性的改变(默认释放HDC改变不被保存)
注册窗口类时 CS_OWNDC ,基于这个窗口类创建的窗口都有它的私有的设备环境,即使窗口被销毁,设备环境仍然存在。使用该样式只需初始化设备环境属性一次。(在处理WM_CREATE时),之后再再次改变这些值时,值保持不变。
CS_OWNDC 样式仅影响 GetDC 和 BeginPaint 获得的设备环境。
情况二:临时改变设备环境属性,绘制后再恢复原来的属性
idSaved = SaveDC(hdc);
...(改变属性,并绘制)
RestoreDC(hdc,idSaved);
还可以不保存返回值(类似于 栈):
SaveDC(hdc);
...(改变属性,并绘制)
RestoreDC(hdc,-1);//恢复最近一次SaveDC保存的状态
三,点和线的绘制
1. 设定像素
SetPixel :将某像素点设定为某个特定的颜色
SetPixel(hdc,x,y,crColor);
GetPixel:返回指定坐标像素点的颜色:
crColor = GetPixel(hdc,x,y);
2. 画线
Windows 可以绘制直线,椭圆弧线 和 贝塞尔样条曲线
Window98 支持的7中画线函数:
- LineTo —— 画直线
- Polyline 和 PolylineTo —— 画一条由多条首尾相连的直线构成的折线。
- PolyPolyline —— 画多条折线
- Arc —— 画椭圆弧线
- PolyBezier 和 PolyBezierTo —— 画贝塞尔样条曲线
Windows NT 还支持:
- ArcTo 和 AngleArc —— 画椭圆弧线
- PolyDraw —— 画多条贝塞尔样条曲线 或 一条由多条首尾相连直线构成的折线
既画线又填充 的函数:
- Rectangle —— 画矩形
- Ellipse —— 画椭圆
- RoundRect —— 画圆角矩形
- Pie —— 画椭圆的一部分,使其看起来像一个扇形
- Chord —— 画出由弦分割出的部分椭圆,形状呈弓形
设备环境有 5 个属性会影响这些函数绘制的线条外观:
- ① 当前画笔位置(仅适于 LineTo,PolylineTo,PolyBezierTo,ArcTo )
- ② 画笔
- ③ 背景模式
- ④ 背景颜色
- ⑤ 绘制模式
画一条直线:
MoveToEx(hdc,xBeg,yBeg,NULL);//指定起点
LineTo(hdc,xEnd,yEnd);//指定终点
MoveToEx 实际上啥都没画,只是设置设备环境“当前位置”属性。默认(0,0)是最初的当期位置
MoveToEx 的最后一个参数是一个指向 POINT 结构的指针,表示运行该函数之前的当前位置,不需要就 NULL
获取当期位置:
GetCurrentPositionEx(hdc,&pt);//pt -- a pointer to POINT
多点连线
将数组的点连接成线:
POINT apt[5] ={100,100,100,200,200,200,200,100,100,100};
Polyline(hdc,apt,5);
Polyline 不使用和改变当前位置(从数组的第一个点开始)
PolyTo 使用当前位置作起点,最后终点为最后的终点
3. 边框绘制
(其实会填充该边框,但是默认填充白色)
绘制矩形:
Rectangle(hdc,xLeft,yTop,xRight,yBottom);
绘制椭圆:
Ellipse(hdc,xLeft,yTop,xRight,yBottom);
绘制圆角矩形:
RoundRect(hdc,xLeft,yTop,xRight,yBottom,xCornerEllipse,yCornerEllipse);
//椭圆弧线
Arc(hdc,xLeft,yTop,xRight,yBottom,xStart,yStart,xEnd,yEnd);
//弓形
Chord(hdc,xLeft,yTop,xRight,yBottom,xStart,yStart,xEnd,yEnd);
//扇形
Pie(hdc,xLeft,yTop,xRight,yBottom,xStart,yStart,xEnd,yEnd);
4. 贝塞尔样条曲线
…待学习…
5. 使用现有画笔
画笔决定:线条的颜色,宽度,样式(实线,点线,虚线)
Windows 提供的三种“备用画笔”(stock pen):
- 画笔的默认设备环境是 BLACK_BEN (实线,宽度为1,黑色)
- WHITE_PEN
- NULL_PEN(不绘制任何图形)
也可创建自己的画笔
5.0. 使用句柄来操作画笔 —— HPEN
5.1. 调用 GetStockObject 获取备用画笔的句柄
hPen = GetStockObject(WHITE_PEN);
5.2. 将画笔选入设备环境:
SelectObject(hdc,hPen);
返回先前环境句柄中的画笔
直到选入另一画笔或释放该句柄
可组合使用:
SelectObject(hdc,GetStockObject(WHITE_PEN));
5.3. 创建画笔
CreatePen 或 CreatePenIndirect 创建一个 “逻辑画笔”(六个GDI对象之一)。
(GDI对象:画刷,画笔,位图,区域,字体,调色板,除调色板外其余都是由SelectObject 选入设备环境)
需用 DeleteObject 删除创建的逻辑画笔。
对画笔等GDI对象的注意事项:
- 最终应当删除创建的所有GDI对象。
- 当 GDI 对象被选入一个有效的设备环境时,不要删除它
- 不要删除备用对象
(一)CreatePen
hPen = CreatePen(iPenStyle,iWidth,crColor);
iPenStyle 为画笔样式:
对于 PS_SOLID,PS_NULL,PS_INSIDEFRAME 样式,iWidth表示画笔的宽度,iWidth = 0 时,Windows 把画笔宽度设为1像素。如果虚线或点线样式的画笔宽度大于1,Windows就会用实线来代替
PS_INSIDEFRAME 在画笔宽度大于1时,可以使用抖动色(是唯一能使用抖动色的画笔样式)
(二)CreatePenIndirect
LOGPEN(“逻辑画笔”)结构
字段:
- lopnStyle
- lopnWidth
- lopnColor
hPen = CreatePenIndirect(&logpen);
获取画笔信息:
GetObject(hPen,sizeof(LOGPEN),(LPVOID)&logpen);
获取当前设备环境中的画笔句柄:
hPen = GetCurrentObject(hdc,OBJ_PEN);
5.4. 填充空隙
点和虚线的空隙填充 由设备环境的两个属性(背景模式和背景颜色决定)。
默认的背景模式是 OPAQUE(不透明),即使用背景颜色填充空隙,背景颜色默认为白色。
改变 Windows 填充空隙的背景颜色:
SetBkColor(hdc,crColor);
获取 Windows 填充空隙的背景颜色:
GetBkColor(hdc,crColor);
改变背景模式:
TRANSPARENT (透明),不填充空隙
OPAQUE(不透明),使用背景颜色填充空隙
SetBkMode(hdc,TRANSPARENT);
获取背景模式:
SetBkMode(hdc);
5.5. 绘图模式
当 Windows 绘制时,实际上是对像素颜色按位进行布尔运算,“光栅操作”(ROP)。只涉及两种像素颜色时称为“二元光栅操作(ROP2)”,Windows 定义了 16 种 ROP2 运算码,默认是 R2_COPYPEN,将画笔像素的颜色复制到目标像素上。
画笔§:目标(D): | 1 1 | 1 0 | 0 1 | 0 0 | 布尔操作 | 绘图模式 |
---|---|---|---|---|---|---|
结果: | 0 | 0 | 0 | 0 | 0 | R2_BLACK |
0 | 0 | 0 | 1 | ~(P | D) | R2_NOTMERGEPEN | |
0 | 0 | 1 | 0 | ~P & D | R2_MASKNOTPEN | |
0 | 0 | 1 | 1 | ~P | R2_NOTCOPYPEN | |
0 | 1 | 0 | 0 | P & ~D | R2_MASKPENNOT | |
0 | 1 | 0 | 1 | ~D | R2_NOT | |
0 | 1 | 1 | 0 | P ^ D | R2_XORPEN | |
0 | 1 | 1 | 1 | ~(P & D) | R2_NOTMASKPEN | |
1 | 0 | 0 | 0 | P & D | R2_MASKPEN | |
1 | 0 | 0 | 1 | ~(P ^ D) | R2_NOTXORPEN | |
1 | 0 | 1 | 0 | D | R2_NOP | |
1 | 0 | 1 | 1 | ~P |D | R2_MERGENOTPEN | |
1 | 1 | 0 | 0 | P | R2_COPYPEN(内定) | |
1 | 1 | 0 | 1 | P | ~D | R2_MERGEPENNOT | |
1 | 1 | 1 | 0 | P | D | R2_MERGEPEN | |
1 | 1 | 1 | 1 | 1 | R2_WHITE |
设置绘图模式:
SetROP2(hdc,iDrawMode);
获取绘图模式:
iDrawMode = GetROP2(hdc);