5.1 GDI结构
GDI是win子系统,主要负责显示图形。不只应用程序使用GDI,同时win本身也使用GDI显示 接口对象,如菜单、图标等。
从程序写作角度来说,GDI由几百个函数调用和一系列相关数据结构组成。
GDI函数虽然众多,但主要可以分为几大类:
1、取得和释放设备内容的函数。如BeginPaint…EndPaint等
2、取得有关设备内容的函数。如GetTextMetrics等。
3、绘图函数。如TextOut等
4、设定和取得设备内容参数的函数。这些参数(属性)决定绘图函数的工作细节。如SetTextColor指定TextOut输入的文字颜色
5、使用GDI对象的函数。这个概念有点混乱,举个例子,比如我们要的画笔属性在设备内容属性中没有,这时我们就要自定义,如CreatePen等,返回逻辑画笔句柄。这个与其他GDI函数不同的是,他们不需要设备内容的句柄。我们大概知道是这类函数就可以了。
GDI图形,主要分为:
1、直线和曲线。向量图形绘制的基础。
2、填入区域。我们可以用GDI进行填图。
3、位图。是位的矩形数组。
4、文字。
另外还有,映像模式和变换、Metafile、路径、剪裁绘图
获得设备内容信息
一个设备内容通常指一个实际显示设备。
iValue=GetDeviceCaps(hdc,iIndex);//获得设备信息如显示器大小,色彩显示能力等
//这个函数可以获得的信息实在太多,具体我们可以使用时查阅msdn
设备内容属性
设备内容保存了控制GDI函数在显示设备上如何操作的属性。如我们使用TextOut,不用指定颜色大小。win从这个内容中取得信息。(程序获取设备内容句柄时,Win使用默认属性值)
保存设备内容
GetDC BeginPaint建立一个新的设备内容(默认值),对属性的修改在ReleaseDC或EndPaint释放就会丢失。
那如果我们要在释放设备内容后仍然保存内容属性呢?
只要将CS_OWNDC属性加入窗口类别就好。
wnd.style=CS_HREDRAW|CS_VERDRAW|CS_OWNDC;
//该类建立的每个窗口都有自己的设备内容,一直存在直到窗口删除,就只需要初始化一次。
//可以放在WM_CREATE消息中
//CS_OWNDC只对GetDC和BeginPaint有效
case WM_CREATE:
hdc=GetDC(hwnd);
......初始化设备内容属性
ReleaseDC(hwnd,hdc);
5.3点和线的绘制
画点的两个函数
SetPixel(hdc,x,y,crColor); //设定指定点的颜色
crColor=GetPixel(hdc,x,y); //获得指定点的颜色
画线函数
LineTo //直线
Polyline PolylineTo //一系列相连直线
PolyPolyline //多组相连直线
Arc //椭圆线
PolyBezier PolyBezierTo //贝塞尔曲线
ArcTo AngleArc //椭圆线
PolyDraw // 画一系列相连的线以及贝塞尔曲线。
Rectangle //画矩形
Ellipse //画带圆角的矩形
RoundRect //画椭圆的一部分,使其看起来像一个扇形
PieChord //画椭圆的一部分,以呈弓形
画直线要用两函数
MoveToEx(hdc,xbeg,ybeg,NULL); //设定当前位置,默认(0,0),最后参数指出先前位置
LineTo(hdc,xend,yend); //MoveTo用于16位版本
GetCurrentPosition(hdc,&pt);
LineDemo代码
//LineDemo
#define NUM 1000
#define TWOPI 2**3.14
case WM_SIZE:
cxClient=LOWORD(lParam); //WM_SIZE消息lParam参数低高字节分别代表客户窗口的长和宽
cyClient=HIWORD(lParam);
break;
case WM_PAINT:
hdc=BeginPaint(hwnd,&ps);
MoveToEx(hdc,0,cyClient/2,NULL);
LineTo(hdc,cxClient,cyClient/2);
for(i=0;i
Bezier代码
//Bezier曲线
void DrawBezier (HDC hdc, POINT apt[])
{
PolyBezier (hdc, apt, 4) ;
MoveToEx (hdc, apt[0].x, apt[0].y, NULL) ;
LineTo (hdc, apt[1].x, apt[1].y) ;
MoveToEx (hdc, apt[2].x, apt[2].y, NULL) ;
LineTo (hdc, apt[3].x, apt[3].y) ;
}
case WM_SIZE:
cxClient=LOWORD(lParam);//WM_SIZE消息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,cxClient,0,NULL);
LineTo(hdc,0,cyClient);
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;
画笔(stock Pens)
Win提供三种现有画笔 WHITE_PEN,BLACK_PEN,NULL_PEN(什么都不画)
hPen=GetStockObject(WHITE_PEN);
oldPen=SelectObject(hdc,hPen); //返回先有画笔
SelectObject(hdc,oldPen); //恢复之前画笔
DeleteObject(hPen);
CreatePen()
CreatePenIndirect()//建立逻辑画笔,是一种GDI对象,是可以建立的六种GDI之一,其他五
//类是画刷,位图,区域,字体,调色盘(不能用selectObject选入设备内容)
//建立一个LOGPEN(逻辑画笔)的结构,并呼叫CreatePenIndirect来建立画笔
LOGPEN logpen; //有三个成员,MSDN
hPen=CreatePenIndirect(&logpen);
//和createPen一样,无需设备内容句柄,只有selectObject之后才现设备内容发生关系
需要注意的是:
a、最后要删除自建立的画笔 b、GDI对象在有效设备内容中使用时不要删除 c、不要删除现有对象
绘图方式
设备内容中不同的 绘图方式也影响所画线的外观。win画线时,实际上是执行的画笔象素与原来像素之间的某种布尔运算。简称ROP(位映像运算)
5.4绘制填充区域
画线->画图形。win中七个用来画带边缘的填入图形。
同样用设备内容中的当前画笔,也可以选择win定义的六种现有画刷。
HBRUSH hBrush;
hBrush=GetStockObject(GRAY_BRUSH); //获得画刷句柄
SelectObject(hdc,hBrush);
SelectObject(hdc,GetStockObject(NULL_PEN)); //画没有边界框的图形,选择NULL_PEN
SelectObject(hdc,GetStockObject(NULL_BRUSH)://图形边界框,不填充,注意和上面的区别
Polygon(hdc,apt,iCount);//与Polyline不同的是,在最后一个点会加一条与第一个点的连线
多边形填充模式:ALTERNATE与WINDING的区别
这个实在是看书没懂,在网上找了相关的解释。很简单,究竟是理解能力太差了。。。
直接上图、

用画刷填充
hBrush=CreateSolidBrush(crColor); //逻辑画刷
hBrush=CreateHatchBrush(iHatchStyle,crColor);//由水平,垂直,斜线组成的画刷
//同样可以建立位图画刷
SelectObject(hdc,hBrush);
DeleteObject(hBrush);//同样注意不能删除当前选入DC的画刷
GetObject(hBrush,sizeof(LOGBRUSH),(LPOVID)&logbrush);//取得画刷信息
ALTWINDING代码
LRESULT CALLBACK WindowProc( HWND hwnd,UINT uMsg,WPARAM wParam, LPARAM lParam )
{
static POINT p0[10]={{10,70}, {50,70}, 50,10, 90,10, 90,50,30,50, 30,90, 70,90, 70,30, 10,30 };
//注意这里初始化点时如果为了区分加花括号,一开始错加了圆括号。结果错的莫名其妙
//点的顺序即为连线顺序
static INT cxClient,cyClient;
HDC hdc;
PAINTSTRUCT ps;
POINT p1[10];
int i;
switch (uMsg)
{
case WM_SIZE:
cxClient=LOWORD(lParam);
cyClient=HIWORD(lParam);
return 0;
case WM_PAINT:
hdc=BeginPaint(hwnd,&ps);
for(i=0;i<10;++i)
{
p1[i].x=cxClient*p0[i].x/200;
p1[i].y=cyClient*p0[i].y/100;
}
SelectObject(hdc,GetStockObject(GRAY_BRUSH));
//SelectObject(hdc,GetStockObject(NULL_PEN));
SetPolyFillMode(hdc,ALTERNATE);
Polygon(hdc,p1,10);
for(i=0;i<10;++i)
{
p1[i].x=p1[i].x+cxClient/2;
}
SetPolyFillMode(hdc,WINDING);
Polygon(hdc,p1,10);
EndPaint(hwnd,&ps);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
default:
break;
}
return DefWindowProc(hwnd,uMsg,wParam,lParam);
}
5.5GDI映射模式
不重复了,见前面
5.6矩形、区域和裁剪
矩形函数
FillRect(hdc,&rect,hBrush);//逻辑坐标,指定画刷填入矩形,不需要选入brush至设备内容
FrameRect(hdc,&rect,hBrush);//使用画刷画矩形,不是填入
InvertRect(hdc,&rect); //翻转矩形图素
裁剪区域是什么?(裁剪区域总是使用设备坐标)
是对显示器上一个范围的描述,是矩形,多边形和椭圆的组合。和画笔等一样也是GDI对象。将裁剪区域选进设备内容后就可以用其进行裁剪,(简单说就是可以将绘图范围限制为显示区域的某一部分。)
hRgn = CreateRectRgn (xLeft, yTop, xRight,yBottom) ;
hRgn = CreateRectRgnIndirect (&rect) ; //矩形
hRgn = CreateEllipticRgnIndirect(&rect) ;
hRgn = CreateEllipticRgnIndirect(&rect) ; //椭圆裁剪区域,其他形状函数类似
裁剪区域的合并
iRgnType = CombineRgn (hDestRgn, hSrcRgn1,hSrcRgn2, iCombine) ;
//该函数将两个裁剪区域组合成新裁剪区域。这三个区域的句柄必须都有效
//iCombine说明两区域的组合方式
RGN_AND //两个剪裁区域的公共部分
RGN_OR // 两个剪裁区域的全部
RGN_XOR //两个剪裁区域的全部除去公共部分
RGN_DIFF hSrcRgn1 //不在hSrcRgn2中的部分
RGN_COPY //hSrcRgn1的全部(忽略hSrcRgn2)
LRESULT CALLBACK WindowProc( HWND hwnd,UINT uMsg,WPARAM wParam, LPARAM lParam) //CLOVER代码
{
static INT cxClient,cyClient;
static HRGN AllhRgn;
HRGN hRgnTmp[6];
int i;
double radius;
PAINTSTRUCT ps;
HDC hdc;
double Angle;
HCURSOR hCursor;
switch (uMsg)
{
case WM_SIZE:
cxClient=LOWORD(lParam);
cyClient=HIWORD(lParam);
hCursor=SetCursor(LoadCursor(NULL,IDC_WAIT));
ShowCursor(TRUE);
if(AllhRgn) delete AllhRgn;
hRgnTmp[0]=CreateEllipticRgn(0,cyClient/3,cxClient/2,2*cyClient/3);
hRgnTmp[1]=CreateEllipticRgn(cxClient/2,cyClient/3,cxClient,2*cyClient/3);
hRgnTmp[2]=CreateEllipticRgn(cxClient/3,0,2*cxClient/3,cyClient/2);
hRgnTmp[3]=CreateEllipticRgn(cxClient/3,cyClient/2,2*cxClient/3,cyClient);
hRgnTmp[4]=CreateRectRgn(0,0,1,1);
hRgnTmp[5]=CreateRectRgn(0,0,1,1);;
AllhRgn=CreateRectRgn(0,0,1,1);
// 用于存放合并后生成的新剪裁区域需要初始化 即指向一个小的区域 否则函数CombineRgn可能会执行失败
//这里还是没搞懂为什么合并区域的句柄没初始化会执行失败
CombineRgn(hRgnTmp[4],hRgnTmp[0],hRgnTmp[1],RGN_OR);
CombineRgn(hRgnTmp[5],hRgnTmp[2],hRgnTmp[3],RGN_OR);
CombineRgn(AllhRgn,hRgnTmp[4],hRgnTmp[5],RGN_XOR);
for(i=0;i<6;++i)
DeleteObject(hRgnTmp[i]);
SetCursor(hCursor);
ShowCursor(FALSE);
return 0;
case WM_PAINT:
hdc=BeginPaint(hwnd,&ps);
radius=hypot(cxClient/2.0,cyClient/2.0);
SelectObject(hdc,AllhRgn);
SetViewportOrgEx(hdc,cxClient/2,cyClient/2,NULL);
for(Angle=0.0;Angle