五、GDI图形基础

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<NUM;++i)
		{
			p[i].x=cxClient*i/NUM;
			p[i].y=cyClient/2*(1-sin(TWOPI*i/NUM));
		}
		Polyline(hdc,p,NUM);
		EndPaint(hwnd,&ps);
		break;
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)
<pre name="code" class="cpp">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<TWOPI;Angle+=TWOPI/360)
		{
			MoveToEx(hdc,0,0,NULL);
			LineTo(hdc,(int)(radius*cos(Angle)+0.5),(int)(-radius*sin(Angle)+0.5));
	
		}

		EndPaint(hwnd,&ps);
		return 0;

	case WM_DESTROY:
		DeleteObject(AllhRgn);
		PostQuitMessage(0);
		return 0;
	default:
		break;
	}
	return DefWindowProc(hwnd,uMsg,wParam,lParam);
}



 

                
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Visual C++6.0使用GDI+的一般方法 1. 载解压GDI+开发包; 2. 正确设置include & lib 目录; 3. stdafx.h 添加: #ifndef ULONG_PTR #define ULONG_PTR unsigned long* #endif #include 4. 程序中添加GDI+的包含文件gdiplus.h以及附加的类库gdiplus.lib。 通常gdiplus.h包含文件添加在应用程序的stdafx.h文件中,而gdiplus.lib可用两种进行添加: 第一种是直接在stdafx.h文件中添加下列语句: #pragma comment( lib, "gdiplus.lib" ) 另一种方法是: 在VC.net中添加库文件在:项目菜单->属性->链接器->输入 举个例子: (1)在应用程序项目的应用类中,添加一个成员变量,如下列代码: ULONG_PTR m_gdiplusToken; 其中,ULONG_PTR是一个DWORD数据类型,该成员变量用来保存GDI+被初始化后在应用程序中的GDI+标识,以便能在应用程序退出后,引用该标识来调用Gdiplus:: GdiplusShutdown来关闭GDI+。 (2)在应用类中添加ExitInstance的重载,并添加下列代码用来关闭GDI+: int CGDITestApp::ExitInstance() { Gdiplus::GdiplusShutdown(m_gdiplusToken); return CWinApp::ExitInstance(); } (3)在应用类的InitInstance函数中添加GDI+的初始化代码: 注意:下面这些GDI+的初始化代码必须放在m_pMainWnd->UpdateWindow();之前。 CWinApp::InitInstance(); Gdiplus::GdiplusStartupInput gdiplusStartupInput; Gdiplus::GdiplusStartup(&m_gdiplusToken, &gdiplusStartupInput, NULL); (4)在需要绘图的窗口或视图类中添加GDI+的绘制代码。 下面分别就单文档和基于对话框应用程序为例,说明使用GDI+的一般过程和方法。 1. 在单文档应用程序中使用GDI+ 在上面的过程中,我们就是以一个单文档应用程序Ex_GDIPlus作为示例的。下面列出第4步所涉及的代码: void CGDITestView::OnDraw(CDC* pDC) { CGDITestDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); // TODO: add draw code for native data here usingnamespace Gdiplus; Graphics graphics(pDC->m_hDC); Pen newPen(Color(255,0,0),3); HatchBrush newBrush(HatchStyleCross,Color(255,0,255,0),Color(255,0,0,255));//创建一个填充画刷,前景色为绿色,背景色为蓝色 graphics.DrawRectangle(&newPen,50,50,100,60);// 在(50,50)处绘制一个长为100,高为60的矩形 graphics.FillRectangle(&newBrush,50,50,100,60); // 在(50,50)处填充一个长为100,高为60的矩形区域 } 编译并运行,结果如图:

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值