windows GDI+ 绘图的简单封装
Brief: A simple package of GDI+ drawing methods makes the drawing in mfc much easier.
由于用mfc经常需要在界面上进行一些绘制输出,所以用windows GDI比较多,但是用windowsGDI 绘图比较麻烦,刚开始学的时候还经常弄不清一堆DC, Object, HANDLE到底是干啥的。后来就琢磨清楚了,但为了用起来更方便,就弄个类,这样画什么就直接调对应的函数和常用的控制参数,比如大小位置颜色,而不需要自己去操控上下文、绘制画笔画刷等等麻烦事,而且一个函数完成一个简单图形的绘制,后面又学了gdi+,就重写了部分函数实现,用了更简单的方式完成。
为了避免闪烁,这里用了双缓存的原理,其实就是相当于在内存里开辟空间画完后在显示在屏幕上。至于闪烁的缘由,在我的另一篇博文里面有浅浅的探究:http://www.straka.cn/blog/flickering-in-mfc/
这里也不得不指明这么做是有损效率的,因为期间会重复创建和销毁画笔画刷等对象。但对于多数应用场景,这个损失是可以接受的。
要使用GDI+(Graphics device interface),要做些铺垫工作,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
ULONG_PTR m_gdiplusToken; BOOL CGDIDemoApp::InitInstance() { ... CWinApp::InitInstance(); Gdiplus::GdiplusStartupInput gdiplusStartupInput; Gdiplus::GdiplusStartup(&m_gdiplusToken, &gdiplusStartupInput, NULL); ... } int CGDIDemoApp::ExitInstance() { // TODO: 在此添加专用代码和/或调用基类 Gdiplus::GdiplusShutdown(m_gdiplusToken); return CWinApp::ExitInstance(); } |
一般为了方便在应用程序的实例初始化阶段就可以把gdiplus一起初始化了,然后实现ExitInstance虚函数,在其中释放使用gdiplus所占的资源。
封装的类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
class CDrawMethod { HWND m_dlgHWND; //the handle of dialog window CDC *m_pDC; //the device context of dialog CDC m_dcMem; //the memory dc, to contain all the drawing than flush onto screen CBitmap m_bmpMem; //bitmap bind to memory dc--m_dcMem Gdiplus::Graphics* m_pGraph; //point to the object of Gdiplus CDrawMethod(HWND hwnd, CDC *pDC); ~CDrawMethod(); //call this func to init the member before any draw method int BeginDraw(); //after all operate has been executed(drawn on memory DC), call this to draw on screen void DrawOnScreen(); //call this func after all operate has done, free or release the resources, in case of memory leak int EndDraw(); //save picture in memory DC to path as jpg file int SaveMemDCAsJPG(char* strPath); int SaveMemDCAsBMP(char* strPath); //return the memory bitmap member CBitmap *GetMemBitmap(); //return the memory DC member CDC *GetMemDC(); public: CRect m_rtClient; }; |
主要的成员和函数都在上述代码中列出,使用的话每次新建类实例,传入对话框句柄HANDLE和设备上下文DC,然后调用BeginDraw()初始化,等全部函数执行完毕,再调用DrawOnScreen()画到屏幕上,最后EndDraw()释放资源。如果需要更进一步的操作可以用GetMemBitmap() 和 GetMemDC()方法取得内存位图和画布进一步操作。但是注意这里的指针是指向成员的,用完不可释放,类内部管理。如果画完不需要显示在显示器上也可以用SaveMemDCAsJPG 和 SaveMemDCAsBMP 函数输出到文件。
而具体承担绘图任务的函数是自定义可以添加的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
//putout the text follow the params onto the dc TEXTMETRIC PutOutTextA(char* lpsz, int X, int Y, unsigned long lColor, int nFontHeight, int nFontWidth = FW_NORMAL, EShadowType eShadow = NONE, unsigned long lShadowColor = 0, LPCWSTR lpFont = TEXT("微软雅黑")); TEXTMETRIC PutOutTextW(char* lpsz, int X, int Y, unsigned long lColor, int nFontHeight, int nFontWidth = FW_NORMAL, EShadowType eShadow = NONE, unsigned long lShadowColor = 0, LPCWSTR lpFont = TEXT("微软雅黑")); //draw rectangle frame void DrawRectFrame(CRect rect,int width, unsigned long lColor,byte alpha=255); //draw shadow of rectangle //@param coeff: control the color gradiant rate void DrawRectShadow(CRect rect, unsigned long lColor, byte alpha, int width, float coeff=0.8); //fill inside of rectangle void FillRect(CRect rect, unsigned long lColor, byte alpha); void DrawEllipseFrame(CRect rect, int width, unsigned long lColor, byte alpha=255); void DrawEllipseShadow(CRect rect, unsigned long lColor, byte alpha, int width, float coeff=0.8); void FillEllipse(CRect rect, unsigned long lColor, byte alpha); void DrawPolygonFrame(Gdiplus::Point* arrPoints, int ctn, int width, unsigned long lColor, byte alpha=255); void FillPolygon(Gdiplus::Point* arrPoints, int ctn, unsigned long lColor, byte alpha); //draw round corner rectangle void DrawRoundRect(CRect rect, float arcSize, float lineWidth, unsigned long lColor, byte alpha, bool fillPath = false, unsigned long lColorFill = 0, byte alphaFill = 0); void DrawRoundRect(float x, float y, float Width, float Height, float arcSize, float lineWidth, unsigned long lColor, byte alpha, bool fillPath = false, unsigned long lColorFill = 0, byte alphaFill = 0); //method to draw cubic of area located by points array void FillCubicSurface(Gdiplus::Point* arrPoints, int ctn, unsigned long lColor, byte alpha = 155,unsigned long frameColor = 0); //draw image on path by method 1 to location by rect bool DrawImage(char* pStr, CRect rect); //draw png picture at path input to location by rect void DrawPNG(WCHAR* strPath, CRect rect); |
我就把常用的一些添加了进来,画矩形、椭圆形、多边形框、及填充矩形、椭圆形、多边形内部、画矩形、椭圆形阴影,画圆角矩形、画立方体,绘制图片,以及输出文字。
其中画矩形阴影实现就是画了尺寸稍大的多个矩形,这种比较简单,当然也有别的处理方法,根据大家需要另外添加函数即可。
使用示例:
在对话框OnPaint函数中添加代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
if (IsIconic()) { ... } else { //CDialogEx::OnPaint(); CPaintDC dc(this); // 用于绘制的设备上下文 CDrawMethod drawMethod(m_hWnd,&dc); drawMethod.BeginDraw(); ...//draw objects here drawMethod.DrawOnScreen(); drawMethod.EndDraw(); } |
在第11行处添加画图代码,比如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
drawMethod.DrawRectFrame(rect1,1,RGB(0,255,0),150); drawMethod.DrawRectShadow(rect1,RGB(200,255,255),200,5); drawMethod.FillRect(rect1,RGB(255,0,0),150); drawMethod.DrawEllipseFrame(rect2,1,RGB(100,20,155),200); drawMethod.DrawEllipseShadow(rect2,RGB(0,255,255),200,5,0.7); drawMethod.FillEllipse(rect2,RGB(255,255,0),190); Gdiplus::Point pts[8]; pts[0].X = 170; pts[0].Y = 250; pts[1].X = 280; pts[1].Y = pts[0].Y; pts[2].X = 400; pts[2].Y = 100; pts[3].X = 330; pts[3].Y = pts[2].Y; pts[4].X = pts[0].X; pts[4].Y = 210; pts[5].X = pts[1].X; pts[5].Y = pts[4].Y; pts[6].X = pts[2].X; pts[6].Y = 75; pts[7].X = pts[3].X; pts[7].Y = pts[6].Y; drawMethod.FillCubicSurface(pts,8,RGB(0,200,50),200,RGB(0,255,0)); |
上述代码绘图效果:
代码下载:
https://github.com/atp798/BlogStraka/