基础概念
- GDI:Graphic Device Interface 图形设备接口。
- GUI:Graphic User Interface 图形用户接口。
- HDC:Handle of Device Context: 图形设备上下文句柄。
字符界面的基本单位是字符。
图形界面的基本单位是像素。
像素:px,表示一个点。
绘图与贴图的消息处理: WM_PAINT
消息。
当然你也可以指定 Timer
计时器来每隔多长时间刷新一次。
绘图
基础绘图
首先要获取HDC:
HDC GetDC(
[in] HWND hWnd
);
检索DC的窗口句柄,并且返回在指定窗口工作的DC句柄。
ReleaseDC
HDC的释放:
ReleaseDC(hwnd, hdc);
SetPixel
来绘制像素点。
COLORREF SetPixel(
[in] HDC hdc,
[in] int x,
[in] int y,
[in] COLORREF color
);
- HDC:设备上下文句柄
- x,y:绘制的坐标
- color:颜色
绘制一条线:
//绘制像素点
for (int i = 1; i <= 100; i++) {
SetPixel(hdc, 100 + i, 100, RGB(255, 0, 0));
}
从 x = 100 − 200 , y = 100 x=100-200, y=100 x=100−200,y=100 的位置绘制了100个像素点,即绘制了一条线。
MoveToEx
来更新当前的焦点坐标:
BOOL MoveToEx(
[in] HDC hdc,
[in] int x,
[in] int y,
[out] LPPOINT lppt
);
- HDC:设备上下文句柄。
- x,y:需要移动的位置。
- lppt:可用于保存上一个的位置
LineTo
来绘制一条线的路径:
BOOL LineTo(
[in] HDC hdc,
[in] int x,
[in] int y
);
参数同样需要指定HDC与到达的坐标。
示例:来绘制一个简单的三角形:
//绘制像素点
for (int i = 1; i <= 100; i++) {
SetPixel(hdc, 100 + i, 100, RGB(255, 0, 0));
}
POINT oldPos{};
MoveToEx(hdc, 200, 100, NULL);
LineTo(hdc, 300, 300);
//保存(300,300)的位置,并且移动到(100,100)的位置
MoveToEx(hdc, 100, 100, &oldPos);
//画线到(300,300)的位置
LineTo(hdc, oldPos.x, oldPos.y);
//形成一个三角形
解释:
- 首先绘制一条线,从 ( 100 , 100 ) (100,100) (100,100)到 ( 200 , 100 ) (200,100) (200,100)。
- 首先移动到 ( 200 , 100 ) (200,100) (200,100)的位置,然后调用LineTo移动到(300,300)的位置,表示我们绘制了一条从 ( 200 , 100 ) (200,100) (200,100)到 ( 300 , 300 ) (300,300) (300,300)的一条线路径。
- 然后我们使用MoveToEx的最后一个参数来保存当前点的坐标,并且移动到 ( 100 , 100 ) (100,100) (100,100)的位置,然后再次绘制到当前位置。
最后效果如下:
🍎当然,我们这样做只是为了测试这几个函数的功能,一定还有其他的更加简便的方法。
使用画笔
我们首先需要创建画笔,然后需要将画笔交给画家,然后画家再画画,这是一个绘图的编程模型:
- 创建画笔。
- 传递画笔给画家。
- 绘制。
CreatePen
创建画笔:
HPEN CreatePen(
[in] int iStyle,
[in] int cWidth,
[in] COLORREF color
);
- iStyle:画笔的样式,比较常用的有PS_SOLID,PS_DASH等等。
- cWidth:画笔的宽度,以像素点为单位。
- color:画笔的颜色。
SelectObject
传递画笔给画家:
所谓画家其实就是HDC,即绘图设备上下文句柄。
HGDIOBJ SelectObject(
[in] HDC hdc,
[in] HGDIOBJ h
);
- hdc:绘图的上下文句柄。
- h:是一个函数指针,表示的是对象句柄,这个对象可以是画笔,画刷,Bitmap,区域 等等。
- 返回值为HGDIOBJ对象。
接下来就是绘图的过程了,我们只需要传递绘制指定图形的函数即可,这个例子中我绘制了一个矩形:
//1.自制画笔
HPEN pSolidPen1 = CreatePen(PS_SOLID, 3, RGB(255, 0, 0));
HPEN pDashPen1 = CreatePen(PS_DASH, 5, RGB(0, 255, 0));
//2.画笔交给画家
HGDIOBJ hOldObj = SelectObject(hdc, pSolidPen1);
MoveToEx(hdc,100, 100, NULL);
LineTo(hdc, 200, 200); //使用pSolidPen1来绘制
hOldObj = SelectObject(hdc, pDashPen1); //pOldObj保存pSolidPen1
LineTo(hdc, 300, 300); //使用pDashPen1来绘制
SelectObject(hdc, hOldObj); //使用pSolidPen1来绘制一个矩形
Rectangle(hdc, 100,200,150,300);
注意:我创建了两个画笔,然后分别使用这两种样式的画笔绘制了图形,我们只需要 SelectObject
选择相应的画笔即可,然后便可以直接绘制图形。
我们使用这个函数的返回值来保存当前的绘图画笔,然后再下面可以复原此次画笔。
使用画刷
使用画刷与画笔的过程是一样的,我们只需要:
- 创建画刷。
- 传递画刷给画家。
- 绘图。
CreateSolidBrush
HBRUSH CreateSolidBrush(
[in] COLORREF color
);
- color:画刷的颜色。
- 返回值是一个HBRUSH,即一个画刷对象。
然后 SelectObject
可以传递画刷给画家(使得HDC为此画刷类型)。
接着绘图即可:
//1.自制画刷
HBRUSH hBrush1 = CreateSolidBrush(RGB(30, 30, 30));
HBRUSH hBrush2 = CreateSolidBrush(RGB(20, 210, 20));
//2.画刷给画家
SelectObject(hdc, hBrush1);
Rectangle(hdc, 10, 10, 100, 100);
SelectObject(hdc, hBrush2);
Ellipse(hdc, 200, 200, 400, 400);
贴图
贴图的过程比较复杂,大致可以分为如下的四个步骤:
- 加载图片。
- 创建兼容DC。
- 图片选择到兼容DC中。
- 兼容DC绘制到HDC上。
我们需要获得HDC与HINSTANCE等信息,因此首先需要获得他们的信息:
HDC hdc = GetDC(hwnd); //获取HDC
HINSTANCE hInstanc = GetModuleHandle(NULL); //获取当前窗口的实例句柄
LoadImage
加载图片:
注意我们必须加载图片是 bmp 类型
HANDLE LoadImageA(
[in, optional] HINSTANCE hInst,
[in] LPCSTR name,
[in] UINT type,
[in] int cx,
[in] int cy,
[in] UINT fuLoad
);
- hInst:贴图位置的实例句柄。
- name:图片的路劲
- type:要加载的图片的类型,位图,游标,或者是图标。
- cx,cy:加载的图片的宽度与高度。
- fuLoad:图片加载方式,可以选择从指定文件中打开:
**LR_LOADFROMFILE
**
CreateCompatibleDC
创建兼容DC:
HDC CreateCompatibleDC(
[in] HDC hdc
);
- hdc:需要为NULL,则会创建与当前窗口兼容的DC。
- 返回值是我们创建的兼容DC。
SelectObject
图片选择到兼容DC,没错,又是这个函数。
BitBlt
兼容DC绘制到HDC:
这个函数完成的是从指定源设备上下文到目标设备上下文中的像素矩形对应的颜色数据的位块传输。
BOOL BitBlt(
[in] HDC hdc,
[in] int x,
[in] int y,
[in] int cx,
[in] int cy,
[in] HDC hdcSrc,
[in] int x1,
[in] int y1,
[in] DWORD rop
);
- hdc:绘制的目标设备上下文句柄
- x,y,cx,cy:绘制的左上角坐标,与绘制宽度高度。
- hdcSrc:原设备的上下文句柄。
- x1,y1:原设备的左上角坐标
- rop:对于颜色的操作,常见的有:SRCCOPY SRCAND SRCPAINT
简单的操作示例:
//2. 贴图
void putImg(HWND hwnd) {
HDC hdc = GetDC(hwnd);
HINSTANCE hInstanc = GetModuleHandle(NULL);
//1. 加载图片
HANDLE p1 = LoadImage(hInstanc, L"img1.bmp", IMAGE_BITMAP, 207, 293, LR_LOADFROMFILE);
HANDLE p2 = LoadImage(hInstanc, L"img2.bmp", IMAGE_BITMAP, 299, 286, LR_LOADFROMFILE);
HANDLE p3 = LoadImage(hInstanc, L"img3.bmp", IMAGE_BITMAP, 265, 330, LR_LOADFROMFILE);
//2. 创建兼容DC
HDC hDc1 = CreateCompatibleDC(NULL);//与指定设备兼容的DC
HDC hDc2 = CreateCompatibleDC(NULL);
HDC hDc3 = CreateCompatibleDC(NULL);
//3. 把图片选择到兼容DC
SelectObject(hDc1, p1); //将对象选择到按指定的设备上下文中
SelectObject(hDc2, p2);
SelectObject(hDc3, p3);
//4. 从兼容DC绘制到HDC
BitBlt(hdc, 10, 10, 207, 293, hDc1, 0, 0, SRCCOPY);
BitBlt(hdc, 300, 10, 299, 286, hDc2, 0, 0, SRCCOPY);
BitBlt(hdc, 600, 10, 265, 330, hDc3, 0, 0, SRCCOPY);
}
效果如下所示:
多级缓冲绘图
我们使用上面的绘图方式是可以的,但是有可能会出现闪烁的情况,因此我们必须使用多级缓冲绘图,来解决这种问题,使得图片可以稳定加载。
步骤如下:
- 加载图片。
- 创建兼容位图。
- 创建兼容DC。
- 兼容位图选择到兼容DC。
- 图片选择到兼容DC。
- 兼容DC绘制到最终兼容DC。
- 最终兼容DC绘制到HDC。
CreateCompatibleBitmap
:创建兼容位图:
HBITMAP CreateCompatibleBitmap(
[in] HDC hdc,
[in] int cx,
[in] int cy
);
- HDC:设备上下文句柄
- cx,cy:宽度与高度
其他的函数与上面基本一致。
TransparentBlt
是另一种绘图方式,与 BitBlt
是一样的:
BOOL TransparentBlt(
[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] UINT crTransparent
);
- hdcDest:目标HDC
- 目标HDC的坐标与宽高。
- 源HDC的信息。
- crTransparent:与
BitBlt
的最后一个参数相同
代码示例:
//2. 贴图
void putImg(HWND hwnd) {
HDC hdc = GetDC(hwnd);
HINSTANCE hInstanc = GetModuleHandle(NULL);
//1. 加载图片
HANDLE bk = LoadImage(hInstanc, L"bk.bmp", IMAGE_BITMAP, 1400, 770, LR_LOADFROMFILE);
HANDLE p1 = LoadImage(hInstanc, L"img1.bmp", IMAGE_BITMAP, 207, 293, LR_LOADFROMFILE);
HANDLE p2 = LoadImage(hInstanc, L"img2.bmp", IMAGE_BITMAP, 299, 286, LR_LOADFROMFILE);
HANDLE p3 = LoadImage(hInstanc, L"img3.bmp", IMAGE_BITMAP, 265, 330, LR_LOADFROMFILE);
//2. 创建兼容位图
HBITMAP hBit1 = CreateCompatibleBitmap(hdc, 1500, 770);
//3. 创建兼容DC
HDC hBk = CreateCompatibleDC(NULL);
HDC hDc1 = CreateCompatibleDC(NULL);
HDC hDc2 = CreateCompatibleDC(NULL);
HDC hDc3 = CreateCompatibleDC(NULL);
HDC hDst = CreateCompatibleDC(NULL); //最终的画布
//4. 兼容位图设置到兼容DC中
SelectObject(hDst, hBit1);
//5. 图片选择到兼容DC中
SelectObject(hBk, bk);
SelectObject(hDc1, p1);
SelectObject(hDc2, p2);
SelectObject(hDc3, p3);
//6. 每个兼容DC绘制到总的兼容DC中(总画布)
BitBlt(hDst, 0, 0, 1500, 770, hBk, 10, 10, SRCCOPY);
BitBlt(hDst, 0, 0, 207, 293, hDc1, 0, 0, SRCCOPY);
BitBlt(hDst, 250,10, 299, 286, hDc2, 0, 0, SRCCOPY);
BitBlt(hDst, 600, 10, 265, 330, hDc3, 0, 0, SRCCOPY);
//7. 最终兼容DC绘制到HDC
TransparentBlt(hdc, 0, 0,
GetSystemMetrics(SM_CXFULLSCREEN),
GetSystemMetrics(SM_CYFULLSCREEN),
hDst, 0, 0, 1500, 770, SRCCOPY);
}
文字
CreateFont
来创建字体的样式:
参考链接:
CreateFontA 函数 (wingdi.h) - Win32 apps
HFONT CreateFontA(
[in] int cHeight,
[in] int cWidth,
[in] int cEscapement,
[in] int cOrientation,
[in] int cWeight,
[in] DWORD bItalic,
[in] DWORD bUnderline,
[in] DWORD bStrikeOut,
[in] DWORD iCharSet,
[in] DWORD iOutPrecision,
[in] DWORD iClipPrecision,
[in] DWORD iQuality,
[in] DWORD iPitchAndFamily,
[in] LPCSTR pszFaceName
);
然后便可以使用 TestOut
来创建文字了:
void text(HWND hwnd) {
HDC hdc = GetDC(hwnd);
HFONT hFont1 = CreateFont(48, 0, 0, 0, FW_DONTCARE, FALSE, TRUE, FALSE, DEFAULT_CHARSET, OUT_OUTLINE_PRECIS,
CLIP_DEFAULT_PRECIS, CLEARTYPE_QUALITY, VARIABLE_PITCH, TEXT("Impact"));
SelectObject(hdc, hFont1);
wchar_t buff[256] = L"这是一段文字";
TextOut(hdc, 100, 100, buff, wcslen(buff));
ReleaseDC(hwnd,hdc);
}