在上几篇中,我们说到了通过GdipCreateFromHDC函数将HDC(设备上下文句柄)装换成GDI+的Graphics对象,讲到了画笔和画刷的使用,并演示了何利用GDI+Flat来画图片和画文字。
其实,我们使用GdiplusFlat的根本目的并不是为了作图,而是为了做出一个漂亮的界面,这年头,用户喜欢个性的、美观的界面,而不像原来一样,只要功能实用,界面用系统的窗口主题无所谓。这也恰恰证明了计算机的普及,而不是专业人士的专用品了。
那么,我们在自绘界面的时候往往遇到过这样的问题:
1。窗口在自绘时闪烁问题严重。
2。自绘不能一次完成,用户看到的现象是,窗口先画出来背景,然后是标题,然后图片文字出现。这样的程序想必任何人都不愿意用。
解决之道:使用“双缓冲绘图”
何谓“双缓冲绘图”?
首先我们需要明白,我们直接用GdiplusFlat或者其他东西绘图时,是直接绘制到显示设备上的,如左图:
也就是说,我们在图形设备上”一笔笔作图“时,因为每一笔之间有间隔,所以用户就看到自绘不能一次完成
那么闪烁的原因呢?闪烁是因为,每一次绘图,都要先把之前的图形抹去,再绘新图,所以造成了闪烁问题。
双缓冲绘图(如右图所示),是指创建一个和窗口的DC大小一样的”内存DC“,先把需要画的东西一步步画在内存DC里,然后一次性复制到窗口DC上,由于复制是一次性的,因此不需要先抹除原先的的内容,因此就没有闪烁问题了,由于复制是一次性的,用户看起来窗口就好像一次性绘制好了。
至于说内存DC是何时创建,这个就根据需要了,我一般是先创建,不销毁,用的时候用,如果什么时候需要绘图什么时候申请会影响效率
创建内存DC一般采用CreateCompatibleDC函数
此函数的原型:
HDC CreateCompatibleDC( _In_ HDC hdc );
参数hdc:现有的 DC 的句柄。如果此句柄为 NULL,则该函数将创建与应用程序的当前屏幕兼容内存 DC。
返回值:成功返回内存DC的句柄,失败返回NULL
创建完之后需要创建设备无关位图对象,用CreateDIBSection函数
HBITMAP CreateDIBSection( _In_ HDC hdc, _In_ const BITMAPINFO *pbmi, _In_ UINT iUsage, _Out_ VOID **ppvBits, _In_ HANDLE hSection, _In_ DWORD dwOffset );
hdc:设备上下文句柄,此处为0即可
pbmi:一个BITMAPINFO结构的指针
后4个参数为0即可
BITMAPINFO结构:
typedef struct tagBITMAPINFO { BITMAPINFOHEADER bmiHeader; RGBQUAD bmiColors[1]; } BITMAPINFO, *PBITMAPINFO;
bmiColors:留空即可
bmiHeader:BITMAPINFOHEADER结构
typedef struct tagBITMAPINFOHEADER { DWORD biSize; LONG biWidth; LONG biHeight; WORD biPlanes; WORD biBitCount; DWORD biCompression; DWORD biSizeImage; LONG biXPelsPerMeter; LONG biYPelsPerMeter; DWORD biClrUsed; DWORD biClrImportant; } BITMAPINFOHEADER, *PBITMAPINFOHEADER;
biSize:BITMAPINFOHEADER结构大小
biWidth、biHeight:宽度、高度
biPlanes:必须为1
biBitCount:一个像素点的颜色位数,一般为32
biCompression--最后:留空即可
源码:
HDC mdc;
case WM_CREATE: mdc = CreateCompatibleDC(0); BITMAPINFO bitmapinfo; RtlZeroMemory(&bitmapinfo, sizeof(BITMAPINFO)); bitmapinfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); bitmapinfo.bmiHeader.biWidth = 500; bitmapinfo.bmiHeader.biHeight = 500; bitmapinfo.bmiHeader.biPlanes = 1; bitmapinfo.bmiHeader.biBitCount = 32; HBITMAP b,old; b = CreateDIBSection(0, &bitmapinfo, 0, 0, 0, 0); old = (HBITMAP)SelectObject(mdc, b); if (old != NULL){ DeleteObject(old); } DeleteObject(b); break; case WM_DESTROY://窗口已经销毁 DeleteDC(mdc); PostQuitMessage(0);//退出消息循环,结束应用程序 return 0; break;随后mdc就可以直接用了
下面说说DC复制:
DC复制是指把一个DC中的指定部分(或全部)复制到另一个DC中,上面说的DC既可以说窗口DC,也可以是内存DC
StretchBlt函数:
BOOL StretchBlt( _In_ HDC hdcDest, _In_ int nXOriginDest, _In_ int nYOriginDest, _In_ int nWidthDest, _In_ int nHeightDest, _In_ HDC hdcSrc, _In_ int nXOriginSrc, _In_ int nYOriginSrc, _In_ int nWidthSrc, _In_ int nHeightSrc, _In_ DWORD dwRop );
hdcDest:要复制到的目标DC句柄
nXOriginDest、nYOriginDest:要复制到目标DC的左上角坐标
nWidthDest、nHeightDest:要复制到目标DC上的图形的宽度,高度
hdcSrc:被复制的DC句柄
nXOriginSrc、nYOriginSrc:被复制的左上角
nWidthSrc、nHeightSrc:被复制的宽度、高度
dwRop:光栅运算操作,一般为 SRCCOPY(直接复制)
返回值:非0成功,0失败
透明复制:
AlphaBlend函数:
BOOL AlphaBlend( _In_ HDC hdcDest, _In_ int xoriginDest, _In_ int yoriginDest, _In_ int wDest, _In_ int hDest, _In_ HDC hdcSrc, _In_ int xoriginSrc, _In_ int yoriginSrc, _In_ int wSrc, _In_ int hSrc, _In_ BLENDFUNCTION ftn );
hdcDest:要复制到的目标DC句柄
xoriginDest、yoriginDest:要复制到目标DC的左上角坐标
wDest、hDest:要复制到目标DC上的图形的宽度,高度
hdcSrc:被复制的DC句柄
xoriginSrc、yoriginSrc:被复制的左上角
wSrc、hSrc:被复制的宽度、高度
ftn:一个BLENDFUNCTION结构
typedef struct _BLENDFUNCTION { BYTE BlendOp; BYTE BlendFlags; BYTE SourceConstantAlpha; BYTE AlphaFormat; } BLENDFUNCTION, *PBLENDFUNCTION, *LPBLENDFUNCTION;
BlendOp:只能是:AC_SRC_OVER
BlendFlags:必须是0
SourceConstantAlpha:透明度
AlphaFormat:只能为AC_SRC_ALPHA
完整源码:
#include "stdafx.h" #include <gdiplus.h> #include <gdiplusflat.h> #pragma comment(lib,"gdiplus.lib")//very important #include <windows.h> #include <windowsx.h> #pragma comment(lib,"user32.lib") #pragma comment(lib,"gdi32.lib") #pragma comment(lib,"Msimg32.lib") //GDI+Flat typedef struct _GdiplusStartupInput{ unsigned int GdiplusVersion; unsigned int DebugEventCallback; BOOL SuppressBackgroundThread; BOOL SuppressExternalCodecs; }GdiplusStartupInput; extern "C" int WINAPI GdiplusStartup(int* token, GdiplusStartupInput *input, int *output); extern "C" void WINAPI GdiplusShutdown(int token); extern "C" int WINAPI GdipCreateFromHDC(HDC hdc, int* graphics); extern "C" int WINAPI GdipDeleteGraphics(int graphics); //画笔 extern "C" int WINAPI GdipCreatePen1(unsigned int argb_color, float width, int unit, int** pen); extern "C" int WINAPI GdipDeletePen(int* pen); //画矩形 画直线 extern "C" int WINAPI GdipDrawRectangle(int graphics, int* pen, float x, float y, float width, float height); extern "C" int WINAPI GdipDrawLine(int graphics, int* pen, float x1, float y1, float x2, float y2); //画刷 typedef struct _PointF{ float x; float y; }PointF; extern "C" int WINAPI GdipCreateSolidFill(unsigned int argb_color, int** brush); extern "C" int WINAPI GdipCreateLineBrush(PointF* point1, PointF* point2, unsigned int argb_color1, unsigned int argb_color2, int wrapMode, int** lineGradient); extern "C" int WINAPI GdipDeleteBrush(int* brush); //画填充矩形 extern "C" int WINAPI GdipFillRectangle(int graphics, int* brush, float x, float y, float width, float height); //画图片 extern "C" int WINAPI GdipLoadImageFromFile(WCHAR* filename, int** image); extern "C" int WINAPI GdipLoadImageFromStream(LPSTREAM stream, int** image); extern "C" int WINAPI GdipGetImageDimension(int* image, float* width, float* height); extern "C" int WINAPI GdipDrawImageRect(int graphics, int* image, float x, float y, float width, float height); extern "C" int WINAPI GdipDisposeImage(int* image); //画文字 typedef struct _RectF{ float x; float y; float Width; float Height; }RectF; extern "C" int WINAPI GdipSetTextRenderingHint(int graphics, int mode); extern "C" int WINAPI GdipSetSmoothingMode(int graphics, int smoothingMode); extern "C" int WINAPI GdipCreateFontFamilyFromName(WCHAR* name, int* fontCollection, int** fontFamily); extern "C" int WINAPI GdipCreateStringFormat(int formatAttributes, short language, int** format); extern "C" int WINAPI GdipSetStringFormatAlign(int* format, int align); extern "C" int WINAPI GdipCreateFont(int* fontFamily, float emSize, int style, int unit, int** font); extern "C" int WINAPI GdipDrawString(int graphics, WCHAR* string, int length, int* font, RectF* layoutRect, int* stringFormat, int* brush); extern "C" int WINAPI GdipDeleteFont(int* font); extern "C" int WINAPI GdipDeleteStringFormat(int* format); extern "C" int WINAPI GdipDeleteFontFamily(int* fontFamily); int token; int* pen; int* brush; int* linebrush; int* image; HDC mdc; //************************************************************* LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam); WNDCLASS wc; const TCHAR* AppName = TEXT("MyWindowClass1"); HWND hwnd1; int APIENTRY _tWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPTSTR lpCmdLine, _In_ int nCmdShow) { //GDI+开启 GdiplusStartupInput StartupInput = { 0 }; StartupInput.GdiplusVersion = 1; if (GdiplusStartup(&token, &StartupInput, NULL))MessageBox(0, TEXT("GdiPlus开启失败"), TEXT("错误"), MB_ICONERROR); //这里是在构建窗口类结构 wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = WndProc;//窗口回调函数指针 wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance;//实例句柄 wc.hIcon = LoadIcon(hInstance, TEXT("ICON_1")); wc.hCursor = LoadCursor(NULL, IDC_ARROW);//默认指针 wc.hbrBackground = (HBRUSH)(COLOR_WINDOW);//默认背景颜色 wc.lpszMenuName = NULL; wc.lpszClassName = AppName;//窗口类名 //注册窗口类 if (!RegisterClass(&wc)) { MessageBox(NULL, TEXT("注册窗口类失败!"), TEXT("错误"), MB_ICONERROR); return 0; } //创建窗口 int style = WS_OVERLAPPEDWINDOW; hwnd1 = CreateWindowEx(NULL, AppName, TEXT("窗口标题"), style, 50, 50, 500, 500, 0, LoadMenu(hInstance, TEXT("MENU1")), hInstance, 0); if (hwnd1 == NULL) { MessageBox(NULL, TEXT("创建窗口失败!"), TEXT("错误"), MB_ICONERROR); return 0; } //无边框窗口 SetWindowLong(hwnd1, GWL_STYLE, WS_OVERLAPPED | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS); //显示、更新窗口 ShowWindow(hwnd1, nCmdShow); UpdateWindow(hwnd1); //消息循环 MSG msg; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } //GDI+关闭 GdiplusShutdown(token);//可以把这个写在消息循环后面,程序退出就销毁,或者在不需要GDI+时调用,比如GDI+窗口的WM_DESTROY消息里调用 return msg.wParam; } LRESULT CALLBACK WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg){ case WM_PAINT: HDC hdc; PAINTSTRUCT ps; hdc = BeginPaint(hwnd, &ps); int graphics; GdipCreateFromHDC(mdc, &graphics);//创建Graphics对象 GdipCreateSolidFill(0xFFFFFFFF, &brush); GdipFillRectangle(graphics, brush, 0, 0, 500, 500); GdipDeleteBrush(brush); GdipCreateSolidFill(0x900000FF, &brush);//创建单色画刷 PointF p1; p1 = { 0, 0 }; PointF p2; p2 = { 280, 20 }; GdipCreateLineBrush(&p1, &p2, 0xFFFF0000, 0xB00000FF, 0, &linebrush);//创建线性渐变画刷 GdipSetTextRenderingHint(graphics, 3);//设置Graphics对象的文本渲染模式 int* fontfamily; GdipSetSmoothingMode(graphics, 4);//设置Graphics对象的渲染质量 GdipCreateFontFamilyFromName(L"宋体", NULL, &fontfamily);//创建一个基于指定的字体系列的FontFamily对象 int* format; GdipCreateStringFormat(0, 0, &format);//创建一个基于字符串的格式标志和语言的 StringFormat 对象 GdipSetStringFormatAlign(format, 0);//设置文本对齐方式 int* font; GdipCreateFont(fontfamily, 14, 0, 2, &font);//创建字体 RectF rect; rect = { 20, 20, 280, 20 }; GdipDrawString(graphics, L"I love Win32 and GdiplusFlat", -1, font, &rect, 0, brush);//画文字 rect = { 20, 60, 280, 20 }; GdipDrawString(graphics, L"I love Win32 and GdiplusFlat", -1, font, &rect, 0, linebrush); GdipDeleteFont(font);//销毁指定Font对象,释放资源 GdipDeleteStringFormat(format);//销毁指定StringFormat对象,释放资源 GdipDeleteFontFamily(fontfamily);//销毁指定FontFamily对象,释放资源 GdipDeleteBrush(brush);//销毁画刷 GdipDeleteBrush(linebrush);//销毁画刷 p1 = { 0, 0 }; p2 = { 400, 120 }; GdipCreateLineBrush(&p1, &p2, RGB(164, 73, 163) | 0xFF000000, RGB(232, 162, 0) | 0xA0000000, 0, &linebrush);//创建线性渐变画刷 GdipCreateFontFamilyFromName(L"华文新魏", NULL, &fontfamily); GdipCreateStringFormat(0, 0, &format); GdipSetStringFormatAlign(format, 0); GdipCreateFont(fontfamily, 30, 0, 2, &font); rect = { 20, 100, 400, 120 }; GdipDrawString(graphics, L"醉时空欢,是一种态度。忘却烦恼,用最真诚的心写最朴实的代码", -1, font, &rect, 0, linebrush); GdipDeleteFont(font); GdipDeleteStringFormat(format); GdipDeleteFontFamily(fontfamily); GdipDeleteBrush(linebrush);//销毁画刷 StretchBlt(hdc, 0, 0, 500, 500, mdc, 0, 0, 500, 500, SRCCOPY);//DC复制 BLENDFUNCTION blend; blend = { AC_SRC_OVER, 0, 150, AC_SRC_ALPHA }; AlphaBlend(hdc, 20, 200, 200, 200, mdc, 0, 0, 200,200, blend);//透明复制 GdipDeleteGraphics(graphics);//销毁Graphics对象 EndPaint(hwnd, &ps); return 0;//告诉系统,WM_PAINT消息我已经处理了,你那儿凉快哪儿玩去吧。 case WM_CREATE: mdc = CreateCompatibleDC(0); BITMAPINFO bitmapinfo; RtlZeroMemory(&bitmapinfo, sizeof(BITMAPINFO)); bitmapinfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); bitmapinfo.bmiHeader.biWidth = 500; bitmapinfo.bmiHeader.biHeight = 500; bitmapinfo.bmiHeader.biPlanes = 1; bitmapinfo.bmiHeader.biBitCount = 32; HBITMAP b,old; b = CreateDIBSection(0, &bitmapinfo, 0, 0, 0, 0); old = (HBITMAP)SelectObject(mdc, b); if (old != NULL){ DeleteObject(old); } DeleteObject(b); break; case WM_DESTROY://窗口已经销毁 DeleteDC(mdc); PostQuitMessage(0);//退出消息循环,结束应用程序 return 0; break; case WM_LBUTTONDOWN://鼠标左键按下 //让无边框窗口能够拖动(在窗口客户区拖动) PostMessage(hwnd, WM_SYSCOMMAND, 61458, 0); break; /*case WM_MOUSEMOVE://鼠标移动 int xPos, yPos; xPos = GET_X_LPARAM(lParam);//鼠标位置X坐标 yPos = GET_Y_LPARAM(lParam);//鼠标位置Y坐标 //不要用LOWORD和HIWORD获取坐标,因为坐标有可能是负的 break;*/ default: break; } return DefWindowProc(hwnd, uMsg, wParam, lParam);//其他消息交给系统处理 }
效果图: