由於用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所佔的資源。
封裝的類:
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 函數輸出到文件。
而具體承擔繪圖任務的函數是自定義可以添加的:
//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)); |
上述代碼繪圖效果:
Demo源碼等更多信息見原博客:
http://www.straka.cn/blog/encapsulate-gdi-draw-method/