第五章 绘图基础 (GDI、设备环境、点线绘制、填充)

来源:互联网 发布:不是淘宝网禁售商品 编辑:程序博客网 时间:2024/06/04 18:00

光栅设备 raster  device   矢量设备 vector device    映射模式 mapping 转换 transform 图元文件 metafile  区域region  路径 path

剪裁 clipping  调色板 palettes 打印 printing

计算机图形、计算机图像 Computer Graphics

stock  pen 备用画笔

 

GDI 的结构:

windows图形设备接口(GDI)支持与设备无关的图形。(这里有个概念设备无关性

设备无关性:就是操作系统屏蔽了硬件设备的差异,使用户编程时无需考虑特殊的硬件设置。因为如果你脱离WINDOWS API的话,绘图势必要与显卡驱动打交道,

而API帮助你脱离了直接对驱动的操作,实现与驱动无关的操作。这里就用到了GDI 函数。

图形输出设备分为两大类:光栅设备 和 矢量设备

大多数PC输出设备是光栅设备,这也就意味着它们将图像表示成以点的形式构成的矩阵。该类输出设备有视频显示适配器、点阵打印机和激光打印机。

矢量设备则使用线条来绘制图像,通常指绘图机。

GDI包含几百个函数,可以分为下面几大类。

1> 获取(或建立) 和 释放(或销毁) 设备环境的函数。

如绘制时,需要处理使用一个设备环境句柄。 在处理WM_PAINT消息时使用 BeginPaint 和EndPaint函数。在处理其他消息时,通过调用GetDC 和 ReleaseDC函数来达到相同的目的。

2> 获取设备环境信息的函数

如使用GetTextMetrics函数来获取当前被选入设备环境的字体的大小信息。

3> 绘图函数

如使用TextOut 在窗口的客户区输出文本。 以及使用GDI函数绘制线条和填充区域。在第14、15章中,如何绘制位图图像。

4> 设置和获取设备环境属性的函数

设备环境的属性确定绘图函数绘图时的各种细节。例如,可以使用SetTextColor函数来指定TextOut(或者其他文本输出函数)绘制的文本的颜色。

前面使用的SetTextAlign函数来通知GDI,TextOut函数中文本字符的起始位置应当在字符的右边,而不是默认的左边。所有的设备环境的属性都有一个默认值,这个默认值在获取设备环境时就已经被设置好了。对所有的以Set开头的函数,都有相应的一个以Get开头的函数用于获取当前设备环境的属性。

5> 使用GDI“对象”的函数

例如 使用“逻辑画笔”选入设备环境,当前被选入设备环境的画笔被视为设备环境的一个属性。这样,便可以通过这只画笔绘制任何线条。

随后,从设备环境中取消选入的画笔对象,并销毁这个对象。因为画笔定义占用了一定的内存空间,所以销毁画笔是非常重要的。

除了画笔,还可以在字体、创建用于填充封闭区域的画刷、位图以及GDI的其他一些方面使用GDI对象。

 设备环境

如果希望在图形输出设备上绘制图形,必须首先获取设备环境(即DC)的句柄。当Windows把这个句柄交给你的程序,Windows同时也就给予你使用这个设备的权限。接着,在GDI函数中将这个句柄作为一个参数,告诉Windows在哪个设备上进行绘图。

设备环境包含许多决定GDI函数如何工作的属性。这些属性使得GDI函数只需要提供少量的参数(如起始坐标),而不需要提供Windows在设备上显示对象时需要的所有信息。

例如,当你调用TextOut函数时,仅需要在函数中指定设备环境句柄、起始坐标、文本以及文本的长度,不需要指定字体、文本的颜色、文本的背景的颜色或者字符间距。所有这些属性都是设备环境的一部分。当你想改变这些属性时,可以调用函数来执行。之后调用的TextOut函数就会使用新的设备环境的属性。

获取设备环境句柄

1> 获取和释放设备环境句柄最常用的方法是在处理WM_PAINT消息时使用BeginPaint函数和EndPaint函数:

hdc = BeginPaint(hwnd,&ps);          //other program linesEndPaint(hwnd,&ps);

其中,变量ps是一个类型为PAINTSTRUCT的结构。这个结构中的字段hdc 和 BeginPaint函数返回的设备环境句柄的值相同。

PAINTSTRUCT结构还包含一个名为rcPaint的矩形结构,该结构定义了一个包围窗口客户区无效范围的矩形。

使用从BeginPaint函数获取的设备环境句柄,就只能在这个矩形区域内绘图。调用BeginPaint函数将使这个区域有效。
2> 设备环境句柄还可以在处理非WM_PAINT消息时由Windows程序获取:

hdc = GetDC(hwnd);          //other program linesReleaseDC(hwnd,hdc);

其中,设备环境指的是窗口句柄为hwnd的窗口客户区。从GetDC函数返回的句柄可以在整个客户区内绘制。并且,GetDC和ReleaseDC函数并不使任何客户区的无效变为有效。

3> 获取整个窗口:

hdc = GetWindowDC(hwnd);          //other program linesReleaseDC(hwnd,hdc);

这里的设备环境除了包括客户区,还包含窗口标题栏、菜单、滚动条和客户区的外框。应用程序很少使用。如果使用,还应当捕获WM_NCPAINT消息(非客户区绘制)。Windows使用这个消息在窗口的非客户区绘图。

4> 使用CreateDC

hdc = CreateDC(pszDriver, pszDevice, pszOutput, pDate);   //other program linesDeleteDC(hdc);

 例如,通过调用下面的函数获取当前整个屏幕的设备环境句柄

hdc = CreateDC(TEXT("DISPLAY"), NULL, NULL, NULL);

5> CreateIC函数获取一个“信息上下文”(information context)句柄。参数与CreateDC相同。这个函数仅用从设备上获取一些关于设备环境的信息,不能在上面绘制任何东西。

6> 内存设备环境 :用于处理位图。把一个位图选入设备环境,并且调用GDI函数绘制这个位图。

hdc = CreateCompatibleDC(hdc);         //other program linesDeleteDC(hdcMem);

获取设备环境的信息

设备环境通常指的是物理的显示设备,如视频显示器或者打印机。经常需要获取这些设备的某些信息,包括显示器的大小(以像素或者物理尺寸的方式)和它的色彩能力。这些信息可以通过调用GetDeviceCaps函数来获取:

iValue = GetDeviceCaps(hdc, iIndex);

其中,参数iIndex是定义在WINGDI.H头文件中的29个标识符之一。

例如,当iIndex的值为HORZRES 、VERTRES时,GetDeviceCaps函数分别以像素为单位返回设备的宽度和高度。(这里指的是像素尺寸,而不是每度量单位里的像素数)

HORZSIZE 和 VERTSIZE 官方文档中称为“以毫米计的物理屏幕宽度 和 高度”。
设备尺寸

“分辨率”定义为每度量单位(通常是英寸)中含有的像素数。

分辨率 = 像素尺寸 / 度量尺寸

“像素尺寸”(pixel size) 或者 “像素规模”(pixel dimension)表示设备在水平和垂直方向上显示的总的像素数。

“度量尺寸”(metrical size) 或者 “度量规模”(metrical dimension)是指以英寸或者毫米为单位的设备的客户区域的大小。

保存设备环境

每次调用GetDC 和 BeginPaint函数时,会返回一个设备环境句柄,它的所有属性都被设定为默认值。当设备环境调用ReleaseDC 和 EndPaint时,对属性所做的任何改变都会丢失。这样每次使用都需要获取一个新的设备环境句柄时初始化这个设备环境。

我们可以在释放设备环境时保存对属性做的改变,以便下次调用GetDC 和 BeginPaint函数时,这些属性仍然有效。

为此,在注册窗口类时将CS_OWNDC标志作为窗口类样式的一部分即可:仅适用于GetDC 和 BeginPaint函数。

wndclass.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;

有一些情况下,可能想改变某些设备环境属性,然后使用变更后的属性进行绘制,接着再恢复原来的设备环境。

调用 idSaved = SaveDC(hdc);  来保存设备环境的状态,现在可以改变一些属性。

而调用 RestoreDC(hdc, idSaved); 可以返回调用 SaveDC 函数之前存在的设备环境。

PS: 也可以写成类似于汇编语言中的PUSH 和 POP指令。调用SaveDC函数时,返回值可以不必保存:

SaveDC(hdc);

然后改变一些属性,并再次调用SaveDC函数。而为了将设备环境恢复到已保存的状态,则调用函数:

RestoreDC(hdc,-1);

这会使设备环境恢复到最近一次由SaveDC函数保存的状态。

 点和线的绘制

SetPixel 函数 将坐标X 和 坐标Y 的像素点设定为某个特定的颜色:

SetPixel(hdc, x, y, crcolor);

GetPixel 函数返回指定坐标位置的像素点的颜色:

crColor = GetPixel(hdc, x, y);

实例:

LRESULT CALLBACK WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam){int i,j;static intcxClient,cyClient;HDChdc;PAINTSTRUCTps;switch (uMsg){case WM_CREATE:return 0;case WM_SIZE:cxClient = LOWORD(lParam);cyClient = HIWORD(lParam);return 0;case WM_PAINT:hdc = BeginPaint(hwnd,&ps);for (j = cyClient/2,i = 0; i < cxClient; i++){SetPixel(hdc,i,j,RGB(255,0,0));}for (i = 1; i < cxClient;)//为什么在这里会卡掉{for (j = 0; j < cyClient; j ++){SetPixel(hdc,i,j,RGB(0, 255, 0));}i += 30;//这里不懂为什么,我把 i += 30; 换在for循环的第三个参数中,程序就卡掉了}EndPaint(hwnd,&ps);return 0;case WM_DESTROY:PostQuitMessage(0);return 0;}return DefWindowProc(hwnd, uMsg, wParam, lParam);}

运行结果:

画一条直线,必须调用两个函数。第一个函数用来指定直线的起点,第二个函数用来指定直线的终点。

MoveToEx(hdc, xBeg, yBeg, NULL);LineTo(hdc, xEnd, yEnd);

实例:

LRESULT CALLBACK WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam){int i;HDChdc;PAINTSTRUCTps;POINTpt[] = {100,100, 150,50, 200,100,200,200, 100,200, 100,100};switch (uMsg){case WM_CREATE:return 0;case WM_PAINT:hdc = BeginPaint(hwnd,&ps);MoveToEx(hdc,pt[0].x,pt[0].y,NULL); //每一个终点都是一个新的起始点for(i = 1; i < 6; i++)LineTo(hdc,pt[i].x,pt[i].y);EndPaint(hwnd,&ps);return 0;case WM_DESTROY:PostQuitMessage(0);return 0;}return DefWindowProc(hwnd, uMsg, wParam, lParam);}

运行结果:


PolylineTo 函数:最后一个参数表示点的个数。

PolylineTo (hdc, pt, 5);

它使用当前位置作为起始点,画完线后,在返回前会将当前位置设置为最后一根线的终点。

实例-正弦波曲线:

#include <math.h>  sin函数头文件LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam){static int cxClient ,cyClient;int i;POINTpt[1000];   HDC         hdc ;    PAINTSTRUCT ps ;     switch (message)     { case WM_SIZE: cxClient = LOWORD(lParam); //获取客户区宽度 cyClient = HIWORD(lParam); //获取客户区高度 return 0;     case WM_PAINT:          hdc = BeginPaint (hwnd, &ps) ;  MoveToEx(hdc,0,cyClient / 2,NULL);  //画中间的直线  LineTo(hdc,cxClient,cyClient / 2);  for (i = 0; i < 1000; i++)  {  pt[i].x = i*cxClient / 1000;  pt[i].y = (int) (cyClient / 2*(1-sin(2 * 3.1415 * i / 1000)));  }  Polyline(hdc,pt,1000);          EndPaint (hwnd, &ps) ;          return 0 ;     case WM_DESTROY:          PostQuitMessage (0) ;           return 0 ;     }     return DefWindowProc (hwnd, message, wParam, lParam) ;}运行结果:


边框绘制函数: Rectangle 、Ellipse、RoundRect、Chord、Pie

这些函数是在画线,但是同时它们还使用当前填充区域的画刷来填充一个封闭的区域。因为这个画刷默认是纯白色,开始使用不易被发现<>。

绘制-矩形:

Rectangle(hdc, xLeft, yTop, xRight, yBottom); //(xLeft, yTop)是矩形左上角的坐标,(xRight, yBottom)是矩形右下角坐标。

绘制-椭圆:

Ellipse(hdc, xLeft, yTop, xRight, yBottom);

绘制圆角矩形:

RoundRect(hdc, xLeft, yTop, xRight, yBottom,xCornerEllipse,yCornerEllipse);

最后两个参数分别是圆角矩形四个角 小椭圆的宽度 和 高度。

实例:

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) ;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) ;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) ;}

运行结果:

 画笔 (决定了线条的颜色、宽度和样式,其中样式可以是实线、点线或者虚线)

使用现有画笔:

WINDOWS提供三种“备用画笔”:BLACK_PEN 、 WHITE_PEN 、 NULL_PEN

其中 BLACK_PEN 为画笔的默认设备环境 、NULL_PEN表示不绘制任何图形的画笔。

在WINDOWS头文件WINDEF.H中定义了画笔句柄类型为HPEN,使用此句柄来操作画笔。

HPEN  hPen;//定义一个变量hPen = GetStockObject(WHITE_PEN);//获取画笔句柄SelectObject(hdc, hPen);//将画笔选入设备环境

也可写成SelectObject(hdc,GetStockObject(WHITE_PEN);

SelectObject函数返回一个先前选入设备环境的画笔的句柄。

hPen = SelectObject(hdc,GetStockObject(WHITE_PEN);

设备环境中当前画笔为WHITE_PEN,而变量hPen将是BLACK_PEN画笔的句柄

SelectObject(hdc, hPen); //重新选入设备环境

创建、选择和删除画笔

画笔的操作分为:创建画笔,将画笔选入设备环境,删除画笔。(一次只能有一个画笔被选入设备环境)

1> 创建画笔:通过调用CreatePen 或者 CreatePenIndirect函数创建一个“逻辑画笔”。函数返回一个逻辑画笔的句柄。

2>画笔选入设备环境:调用SelectObject函数将画笔选入设备环境中。接着就可以用这个画笔画线条了...

3> 删除画笔:调用DeleteObject 函数来删除创建的逻辑画笔。

CreatePen函数的一般语法如下:

hPen = CreatePen (iPenStyle,//确定画笔的样式。实线、点线、虚线iWidth,//画笔的宽度crColor);//画笔的颜色

几种画笔样式:
   PS_DASH:                 虚线
   PS_DASHDOT:          点划线
   PS_DASHDOTDOT: 双点划线
   PS_DOT:                   点线
   PS_INSIDEFRAME:    实线
   PS_NULL:                   无
   PS_SOLID:                实线

 下面是CreatePenIndirect函数创建一个“逻辑画笔”,首先定义一个类型为LOGPEN的“逻辑画笔”结构。

LOGPEN  logpen;      //这个结构有三个字段:

lopnStyle 表示画笔样式 。 (为无符号整型,或UINT)

lopnWidth 是以逻辑单位表示的画笔宽度。(一个POINT结构)

lopnColor 表示画笔的颜色。(COLORREF)

Windows仅使用lopnWidth结构中的X字段来设置画笔的宽度,Y字段会被忽略。

hPen = CreatePenIndirect (&logpen);   // 这个函数的使用还不太明白??????

填充空隙

空隙的颜色是由设备环境的两个属性决定的(背景模式和背景颜色)。

默认的背景模式是OPAQUE(不透明),这意味着Windows使用背景颜色来填充空隙,背景颜色在默认时是白色。

通过调用下面的函数可以改变Windows填充空隙的背景颜色

SetBkColor (hdc, crColor);

调用GetBkColor 获取设备环境当前背景颜色

设置背景模式 SetBkMode(hdc, TRANSPARENT);  同样获取背景模式GetBkMode函数 。(TRANSPARENT 透明, 可阻止Windows填充空隙

画刷

和画笔类似,操作画刷也包括创建、选入设备环境和删除。
HBRUSH hBrush;   //hBrush为画刷句柄
可以调用函数GetStockObject获取Windows系统提供的7种画刷

hBr=(HBRUSH)GetStockObject(nBrushStyle)    //nBrushStyle为画刷样式

系统提供的7种画刷
   BLACK_BRUSH   黑色画刷
   DKGRAY_BRUSH   深灰色画刷
   GRAY_BRUSH   灰色画刷
   HOLLOW_BRUSH   虚画刷
   LTGRAY_BRUSH   亮灰色画刷
   NULL_BRUSH   空画刷
   WHITE_BRUSH   白色画刷

当然,也可调用函数CreateSolidBrush和CreateHatchBrush创建自定义画刷。
CreateSolidBrush用于创建具有指定颜色的单色画刷。
CreateHatchBrush创建指定阴影图案和颜色的画刷。
例如:
   hBr=CreateSolidBrush(rgbColor);
   hBr=CreateHatchBrush(int nHctchStyle,COLORREF rgbColor);

   其中nHctchStyle可选用以下值:
   HS_BDIAGONAL   45度从左上到右下
   HS_DIAGCROSS   45度叉线
   HS_FDIAGONAL   45度从左下到右上
   HS_CROSS   垂直相交的阴影线
   HS_HORIZONTAL 水平阴影线
   HS_VERTICAL   垂直阴影线
  
   将画刷选入设备环境
   创建画刷后,通过SelectObject(hdc,hBrush);将其选入设备环境。

   删除画刷
   不使用画刷时,可用DeleteObject(hBrush);删除画刷,释放内存。

0 0
原创粉丝点击