设备环境
1、CreateIC |
---|
《4、文本输出》中介绍了设备环境句柄的获取方法。但有时并不需要绘图,只是获取一些设备环境句柄相关的信息,可以使用CreateIC。CreateIC也是返回一个HDC,但是不能对其进行绘制。
2、GetDeviceCaps |
---|
GetDeviceCaps用于获取设备环境的一些显示能力,比如:
- HORZSIZE:以毫米为单位的物理屏幕宽度
- VERTSIZE:以毫米为单位的物理屏幕高度
- HORZRES:水平方向像素数
- VERTRES:垂直方向像素数
- LOGPIXELSX:水平方向每英寸像素数
- LOGPIXELSY:垂直方向每英寸像素数
HORZSIZE和VERTSIZE的值并不可靠。它们并不等于实际的物理屏幕尺寸,在win10系统中测试也不满足书本中提供的公式:
HORZSIZE = 25.4 * HORZRES / LOGPIXELSXVERTSIZE = 25.4 * VERTRES / LOGPIXELSY
书中还提到,在Windows NT中,HORZSIZE和VERTSIZE是固定的值,用来表示标准的显示器尺寸。
3、字号和字体在屏幕显示大小 |
---|
字号(字体大小)是用“点值”(也称“磅”)来表示的,1磅大约是1/72英寸。通过GetDeviceCaps获取到的LOGPIXELSY表示垂直方向每英寸像素数,那么LOGPIXELSY / 72就是1磅的字体高度,这个值和TEXTMETRIC中的tmHeight - tmInternalLeading相等。
我们可以在系统设置中改变屏幕分辨率。如果减小屏幕分辨率,LOGPIXELSY 并不会改变,只是总像素数减少了,单个像素的面积变大,所以同样的字号显示得更大。
4、色彩 |
---|
在GDI函数中,使用COLORREF来表示一个颜色值,是一个32位无符号整型。最高8位是0,其后每8位分别用于指定红、绿、蓝。所以实际只有24位被实际用于表示颜色。windows提供了几个宏用于COLORREF:
#define RGB(r,g,b) ((COLORREF)(((BYTE)(r)|((WORD)((BYTE)(g))<<8))|(((DWORD)(BYTE)(b))<<16)))
#define GetRValue(rgb) (LOBYTE(rgb))
#define GetGValue(rgb) (LOBYTE(((WORD)(rgb)) >> 8))
#define GetBValue(rgb) (LOBYTE((rgb)>>16))
5、保存设备环境 |
---|
默认情况下,设备环境句柄被释放后,对设备环境属性所做的所有改变都会丢失。如果想保存这些修改,可以:
- 第一种方法:注册窗口类时,增加CS_OWNDC样式
基于这种窗口类创建的窗口,对其设备环境句柄属性所做的修改都会被保存,下次可以获取到具有相同属性的设备环境句柄,直到窗口被销毁。但这只对GetDC和BeginPaint获得的设备环境起作用。 - 第二种方法:SaveDC和RestoreDC
绘制点和线
1、点 |
---|
虽然一般不会直接操作像素,但GDI提供了相关函数
- SetPixel
- GetPixel
2、线 |
---|
- LineTo:从当前位置绘制直线到特定位置
- Polyline、PolylineTo:根据点数组绘制折线
- PolyPolyline:绘制多条折线
LineTo和PolylineTo的绘制和画笔当前位置有关,MoveToEx可以修改设备环境的画笔当前位置。GetCurrentPositionEx可以获取画笔当前位置。
3、GDI对象–画笔 |
---|
有6种GDI对象:画笔、画刷、位图、区域、字体和调色板。
使用GDI对象时,需要调用SelectObject把GDI对象选入设备环境中,调用GetCurrentObject可以获取设备环境中当前使用的GDI对象。不再使用的GDI对象需要调用DeleteObject删除GDI。windows提供了一些预定义好的GDI对象,可以调用GetStockObject来获取预定义的GDI对象。
画笔决定了线条的颜色、宽度和样式。windows提供了3种预定义画笔:
- BLACK_PEN
- WHITE_PEN
- NULL_PEN
BLACK_PEN是设备环境中的默认画笔。需要使用其他画笔时,可以调用SelectObject把画笔选入设备环境中。
除了预定义画笔,还可以自定义画笔:
- CreatePen:根据颜色、宽度和样式创建画笔对象。
- CreatePenIndirect:根据逻辑画笔LOGPEN创建画笔对象,实际LOGPEN就是一个描述颜色、宽度和样式属性的一个结构。
使用完毕后,应该删除所有创建的画笔对象。但不能在被选入设备环境时删除,也不能删除预定义的画笔对象。
调用GetObject可以从画笔对象句柄中读取画笔属性到逻辑画笔LOGPEN中:
GetObject(hPen, sizeof(LOGPEN), (LPVOID)&logpen);
SelectObject的返回值是设备环境中上一个GDI对象,这一特性可以用于DeleteObject中:
SelectObject(hdc, CreatePen(PS_DASH, 0 RGB(255, 0, 0)));
DeleteObject(SelectObject(hdc, GetStockObject(BLACK_PEN)));
或者
hPen = SelectObject(hdc, CreatePen(PS_DASH, 0 RGB(255, 0, 0)));
DelectObject(SelectObject(hdc, hPen));
4、填充间隙 |
---|
绘制中,很多地方存在间隙,比如虚线间或者文本输出间的空白部分。
这些间隙是由设备环境的背景模式和背景颜色共同控制的
- SetBkMode、GetBkMode
- SetBkColor、GetBkColor
只有两种背景模式
- TRANSPARENT:透明,不进行间隙填充
- OPAQUE:不透明,用背景色进行间隙填充
背景模式默认是不透明,背景颜色默认是白色。
5、绘图模式 |
---|
对像素颜色执行一个按位布尔运算称为“光栅操作”(ROP),如果只涉及两种像素颜色则称为“二元光栅操作”(ROP2)。
使用画笔进行绘制时,像素最终颜色是原颜色和画笔颜色进行一个光栅操作的结果。Windows定义了16中ROP2运算,被称为绘图模式。
- SetROP2:设置绘图模式
- GetROP2:获取绘图模式
填充区域
1、边框绘制 |
---|
下列函数先用画笔绘制边框线,再用画刷填充区域。
- Rectangle:直角矩形
BOOL WINAPI Rectangle(HDC hdc, int left, int top, int right, int bottom);
Rectangle根据左上角和右下角坐标绘制矩形,实际windows绘制时不包含右下角像素,而是往上、左缩了一个像素。Rectangle(hdc, 1, 1, 5, 4)绘制结果:
- Ellipse:椭圆
BOOL WINAPI Ellipse(HDC hdc, int left, int top, int right, int bottom);
Ellipse根据左上角和右下角确定一个虚拟的矩形边框,然后在矩形边框内绘制矩形。
- RoundRect:圆角矩形
BOOL WINAPI RoundRect(HDC hdc, int left, int top, int right, int bottom, int width, int height);
RoundRect相比Rectangle多了width和height参数。圆角矩形的圆角是按照椭圆绘制的,每个圆角对应椭圆的一个象限。width和height定义了椭圆对应虚拟矩形边框的宽和长,加上左上角和右下角左边可以确定4个椭圆。
- Chord:弓形
- Pie:扇形
BOOL WINAPI Chord(HDC hdc,int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4);
BOOL WINAPI Pie(HDC hdc, int left, int top, int right, int bottom, int xr1, int yr1, int xr2, int yr2);
Chord和Pie绘制的图形都是椭圆的一部分,所以前面参数和Ellispe相同,先确定一个椭圆。后面4个参数在椭圆弧线上确定了起点、终点两个点。
- Arc:弧线
BOOL WINAPI Arc(HDC hdc, int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4);
Arc绘制的并不是一个有填充区域的封闭图形,而是椭圆上的一条弧线。
- Polygon:多边形
BOOL WINAPI Polygon(HDC hdc, CONST POINT *apt, int cpt);
Polygon根据提供的坐标点绘制多边形,如果最后一个点和第一个点不同,则连接两点。
- PolyPolygon:多个多边形
BOOL WINAPI PolyPolygon(HDC hdc, CONST POINT *apt, CONST INT *asz, int csz);
PolyPolygon根据提供的坐标点绘制多个多边形。
Polyfon和PolyPolygon绘制的多边形可能构成多个封闭区域,如何对这些区域进行填充取决于多边形的填充模,调用SetPolyFillMode可以设置设备环境的多边形填充模式:
- ALTERNATE
判断某个封闭区域是否填充时,可以从封闭区域内部向多边形外部画一条射线,如果射线经过了多边形的奇数条边框线,则区域需要填充,否则不需要填充。 - WINDING
判断某个封闭区域是否填充时,可以从封闭区域内部向多边形外部画一条射线,如果射线经过了多边形的奇数条边框线,则需要进行填充,这和ALTERNATE模式一样。
但是,当经过边框线为偶数条时,需要判断其经过的边框线的方向。这里的方向是指多边形的绘制方向,依赖于传入的坐标点顺序。如果相对于射线方向,不同方向的边框线数量相等,则区域不需要填充,否则需要填充。
,第二个点是(60,20),所以边框线AB的方向是从A到B。
从中间的五边形区域向左画一条射线,经过了边框线EA和CD。关键是相对于射线的方向怎么理解?我认为是垂直于射线的方向,比如图中的射线是水平的,则相对于射线的方向应该取垂直方向。那么,EA边框线在垂直方向是向上,CD边框线在垂直方向也是向上。即向下的边框线数目是0,向上的边框线数目是2,故需要填充。
2、画刷 |
---|
windows提供了6种预定义画刷:
- WHITE_BRUSH(设备环境默认画刷)
- TLGGRAY_BRUSH
- GRAY_BRUSH
- DKGRAY_BRUSH
- BLACK_BRUSH
- NULL_BRUSH
当然也可以创建自定义画刷。windows提供了5个函数来创建画刷:
- CreateSoildBrush:创建一个纯色画刷
HBRUSH WINAPI CreateSolidBrush(COLORREF color);
- CreateHatchBrush:创建阴影线画刷
HBRUSH WINAPI CreateHatchBrush(int iHatch, COLORREF color);
// 6种阴影线样式
#define HS_HORIZONTAL 0 /* ----- */
#define HS_VERTICAL 1 /* ||||| */
#define HS_FDIAGONAL 2 /* \\\\\ */
#define HS_BDIAGONAL 3 /* / */
#define HS_CROSS 4 /* +++++ */
#define HS_DIAGCROSS 5 /* xxxxx */
- CreatePatternBrush、CreateDIBPatternBrush:创建位图画刷
HBRUSH WINAPI CreatePatternBrush(HBITMAP hbm);
HBRUSH WINAPI CreateDIBPatternBrush(HGLOBAL h, UINT iUsage);
- CreateBrushIndirect:根据逻辑画刷LOGBRUSH创建画刷
HBRUSH WINAPI CreateBrushIndirect(CONST LOGBRUSH *plbrush);
/* Logical Brush (or Pattern) */
typedef struct tagLOGBRUSH
{
UINT lbStyle;
COLORREF lbColor;
ULONG_PTR lbHatch;
} LOGBRUSH, *PLOGBRUSH, NEAR *NPLOGBRUSH, FAR *LPLOGBRUSH;
lbStyle属性定义了另外两个属性的意义:
lbStyle | lbColor | lbHatch |
---|---|---|
BS_SOILD | 画刷颜色 | 忽略 |
BS_HOLLOW(空画刷) | 忽略 | 忽略 |
BS_HATCHED | 阴影线颜色 | 阴影线样式 |
BS_PATTERN | 忽略 | 位图句柄 |
BS_DIBPATTERNPT | 忽略 | 指向DIB的指针 |
类似画笔,可以利用GetObject从画刷句柄中读取画刷信息到逻辑画刷中:
GetObject(hBrush, sizeof(LOGBRUSH), (LPVOID)&logbrush);
映射模式
矩形、区域和裁剪
1、处理矩形 |
---|
除了Rectangle,windows还提供了一些其它处理矩形的函数:
- FillRect:使用指定画刷填充矩形
- FrameRect:使用指定画刷绘制矩形,但不填充
- InvertRect:翻转矩形内所有像素
- SetRect:矩形的矩形坐标
- OffsetRect:平移矩形
- InflateRect:缩放矩形
- SetRectEmpty:把RECT各个字段设为0
- CopyRect:复制矩形
- IntersectRect:获取两个矩形的交集
- UnionRect:获取两个矩形的并集
- IsRectEmpty:判断矩形是否为空
- PtInRect:判断点是否在矩形内
2、区域 |
---|
区域是一种GDI对象,是对一块空间的描述,可以是任意形状。
将区域选进设备环境,是对设备环境绘图空间的一种裁剪,就会把绘图限制在区域指定的空间内,在区域外的绘图动作会被忽略。
- 创建矩形区域
HRGN WINAPI CreateRectRgn(int x1, int y1, int x2, int y2);
HRGN WINAPI CreateRectRgnIndirect(CONST RECT *lprect);
- 创建圆角矩形区域
HRGN WINAPI CreateRoundRectRgn(int x1, int y1, int x2, int y2, int w, int h);
- 创建椭圆区域
HRGN WINAPI CreateEllipticRgn(int x1, int y1, int x2, int y2);
HRGN WINAPI CreateEllipticRgnIndirect(CONST RECT *lprect);
- 创建多边形区域
HRGN WINAPI CreatePolygonRgn(CONST POINT *pptl, int cPoint, int iMode);
HRGN WINAPI CreatePolyPolygonRgn(CONST POINT *pptl, CONST INT *pc, int cPoly,int iMode);
- 合并区域
int WINAPI CombineRgn(HRGN hrgnDst, HRGN hrgnSrc1, HRGN hrgnSrc2, int iMode);
// iMode区域合并方式
#define RGN_AND 1
#define RGN_OR 2
#define RGN_XOR 3
#define RGN_DIFF 4 // hrgnSrc1不在hrgnSrc2的部分
#define RGN_COPY 5 // hrgnSrc1全部
// 返回值
#define ERROR 0 // 错误
#define NULLREGION 1 // 空区域
#define SIMPLEREGION 2 // 矩形、椭圆或多边形简单区域
#define COMPLEXREGION 3 // 矩形、椭圆或多边形组合区域
类似矩形,windows也提供了一些处理区域的函数
- FillRgn
- FrameRgn
- InvertRgn
- PaintRgn:使用设备环境中画刷填充区域
3、裁剪 |
---|
除了把区域选入设备环境外,还可以调用InvalidateRgn使一个区域无效,从而在处理WM_PAINT消息时把绘制动作限制在特定区域内。相对的,ValidateRgn可以使一个区域有效。