绘图编程
1. 绘图基础
绘图设备 DC (Device Context),绘图上下文 / 绘图描述表。os提供的绘画工具,由它代用户进行绘画。
HDC:DC句柄,表示绘图设备
GDI:Windows graphics device interface(Win32 提供的绘图API)
颜色:RGB 三原生
每个颜色用3个字节保存24位保存 0~2^24-1。
曾经使用16位:最低5位红色,中间5位绿色,后6位蓝色。
还有使用32位:前3个8位代表RGB,最后8位代表透明度,用于三维图形开发。
- 颜色使用:COLORREF类 – 实际为 DWORD。例如:
COLORREF nColor = 0
; - 赋值使用RGB宏:nColor = RGB(0, 0, 255);
- 获取RGB:GetRValue / GetGValue / GetBValue 。 BYTE nRed = GetRValue(nColor);
获得绘图设备
WINUSERAPI
HDC
WINAPI
BeginPaint(
_In_ HWND hWnd,
_Out_ LPPAINTSTRUCT lpPaint);
绘图操作
释放绘图设备
WINUSERAPI
BOOL
WINAPI
EndPaint(
_In_ HWND hWnd,
_In_ CONST PAINTSTRUCT *lpPaint);
2. 基本图形绘制
画点
// 设置定点的颜色
WINGDIAPI COLORREF WINAPI SetPixel(
_In_ HDC hdc, // dc句柄
_In_ int x, // X 坐标
_In_ int y, // Y 坐标
_In_ COLORREF color // 设置的颜色
); // 返回原来的颜色
在 WM_PAINT
消息中调用函数绘制图形:
void OnPaint(HWND hwnd)
{
PAINTSTRUCT ps = { 0 };
HDC hdc = BeginPaint(hwnd, &ps);
for (int i = 50; i < 100; i++)
{
for (int j = 50; j < 100; j++)
{
SetPixel(hdc, i, j, RGB(i, 0, j));
}
}
EndPaint(hwnd, &ps);
}
画线
需要配合使用
- MoveToEx:指明窗口当前点
- LineTo:从窗口当前点到指定点绘制一条直线,同时也会调用 MoveToEx
- 当前点:上一次绘图时最后一点,初始值为 ( 0, 0 ) 点。每个窗口都有。
在处理 WM_PAINT 消息内调用:
void DrawLine(HWND hwnd)
{
PAINTSTRUCT ps = { 0 };
HDC hdc = BeginPaint(hwnd, &ps);
MoveToEx(hdc, 50, 50, NULL);
LineTo(hdc, 300, 300);
EndPaint(hwnd, &ps);
}
封闭图形
能够用画刷填充的图形 Rectangle / Ellipse
void DrawRect(HWND hwnd)
{
PAINTSTRUCT ps = { 0 };
HDC hdc = BeginPaint(hwnd, &ps);
Rectangle(hdc, 100, 100, 300, 300);
Ellipse(hdc, 100, 100, 300, 300);
EndPaint(hwnd, &ps);
}
3. GDI 绘图对象
使用Paint画图时只能画出黑色
画笔
线的颜色、线型(实线、虚线、点线)、线粗
HPEN 画笔句柄
1. 创建画笔
// 创建成功返回句柄
WINGDIAPI
HPEN
WINAPI CreatePen(
_In_ int iStyle, // 画笔样式
_In_ int cWidth, // 画笔的粗细
_In_ COLORREF color // 画笔颜色
);
// 画笔样式
PS_SOILD 实心线,第二个参数可支持多个像素宽,其他线型只能时一个像素宽
2. 将画笔应用到DC中
// 将画笔送给绘图设备。返回旧GDI绘图对象句柄,注意:保存原DC当中的画笔
// 可以形象的理解为用一个新的GDI绘图对象从DC那里交换旧的GDI绘图对象。
WINGDIAPI
HGDIOBJ
WINAPI
SelectObject(
_In_ HDC hdc, // 绘图设备句柄
_In_ HGDIOBJ h // GDI 绘图对象句柄,画笔句柄。HPEN 是 HGDIOBJ 的一种
);
3. 绘图
4. 取出DC中的画笔
将原来的画笔,使用SelectObject函数,放入DC中,交换出我们创建的画笔
5. 释放画笔
WINGDIAPI
BOOL
WINAPI
DeleteObject(
_In_ HGDIOBJ ho // GDI 绘图句柄,画笔句柄
);
只能删除不被DC使用的画笔,在释放前,必须将画笔从DC中取出。
例子,在 WM_PAINT 消息中处理:
void Draw(HWND hwnd)
{
PAINTSTRUCT ps = { 0 };
HDC hdc = BeginPaint(hwnd, &ps);
HGDIOBJ hPen = CreatePen(PS_SOLID, 2, RGB(255, 0, 0));
HGDIOBJ oldGDI = SelectObject(hdc, hPen);
Rectangle(hdc, 100, 100, 300, 300);
hPen = SelectObject(hdc, oldGDI); // 因为这个画笔是在这个函数中创建的,所以亦可以不接收
DeleteObject(hPen);
EndPaint(hwnd, &ps);
}
使用 CreatePen(PS_DASH, 1, RGB(255, 0, 0))
时第二个参数必须为1,如果为其他值,这个画笔失效。
画刷
给封闭图形填充颜色、图案。HBRUSH
画刷句柄。
使用:
- 创建画刷
CreateSoliBrush CreateHatchBrush - 将画刷应用到DC中
SelectObject - 绘图
- 将画刷从DC中取出
SelectObject - 删除画刷
DeleteObje
示例:
void Draw(HWND hwnd)
{
PAINTSTRUCT ps = { 0 };
HDC hdc = BeginPaint(hwnd, &ps);
HBRUSH hBrush = CreateSolidBrush(RGB(0, 255, 0));
HGDIOBJ hOldBrush = SelectObject(hdc, hBrush); // 默认有一把白色画刷
Rectangle(hdc, 100, 100, 300, 300);
SelectObject(hdc, hOldBrush);
DeleteObject(hBrush);
EndPaint(hwnd, &ps);
}
void Draw(HWND hwnd)
{
PAINTSTRUCT ps = { 0 };
HDC hdc = BeginPaint(hwnd, &ps);
//HBRUSH hBrush = CreateSolidBrush(RGB(0, 255, 0));
HBRUSH hBrush = CreateHatchBrush(HS_CROSS, RGB(0, 0, 255));
HGDIOBJ hOldBrush = SelectObject(hdc, hBrush); // 默认有一把白色画刷
Rectangle(hdc, 100, 100, 300, 300);
SelectObject(hdc, hOldBrush);
DeleteObject(hBrush);
EndPaint(hwnd, &ps);
}
透明画刷
通常情况下希望图形填充颜色与背景相同。
通过 GetStockObject
获取系统维护的画刷、画笔等。如无需画刷,可通过 NULL_BRUSH
NULL_PEN
获取不填充的画刷、画笔。这个函数返回的画刷无需 DeleteObject
。
void Draw(HWND hwnd)
{
PAINTSTRUCT ps = { 0 };
HDC hdc = BeginPaint(hwnd, &ps);
HGDIOBJ hBrush = GetStockObject(NULL_BRUSH);
HGDIOBJ hOldObj = SelectObject(hdc, hBrush);
HGDIOBJ hPen = GetStockObject(NULL_PEN);
HGDIOBJ hOldPen = SelectObject(hdc, hPen);
Rectangle(hdc, 100, 100, 300, 300);
SelectObject(hdc, hOldObj);
SelectObject(hdc, hOldPen);
EndPaint(hwnd, &ps);
}
位图
1. 位图绘制
位图相关:
- 光栅图形:记录图像中每一点的颜色等信息(常见)。bitmap 就是严格记录了每一个点的信息。
- 矢量图形:记录图像算法、绘图指令等(用于图谱分析、科学计算等)
HBITMAP:位图句柄
位图使用:
- 在资源中添加位图资源——兼具
GDIOBJ
和资源
的特点
- 从资源中加载位图
LoadBitmap
HBITMAP LoadBitmapA( _In_opt_ HINSTANCE hInstance, _In_ LPCSTR lpBitmapName );
- 创建一个与当前DC相匹配的DC(内存DC)
当前DC在屏幕上绘画
内存DC在内存中绘画HDC CreateCompatibleDC( _In_opt_ HDC hdc); // 传入当前DC,为NULL时代表屏幕DC;在内存构建一个虚拟地区,并返回内存DC用于在虚拟地区绘画
- 将bitmap放入匹配的DC中,
SelectObject
将bitmap放入内存中后,内存里会立即画出图片。 - 成像(1:1 比例 )
将内存中的图像成像到屏幕上WINGDIAPI BOOL WINAPI BitBlt( _In_ HDC hdc, // 目标DC,一般为“当前DC” _In_ int x, // 目的左上X坐标 _In_ int y, // 目的左上Y坐标,在窗口的什么位置成像 _In_ int cx, // 目标宽度 _In_ int cy, // 目标高度,在窗口中开辟多大空间用于成像 _In_opt_ HDC hdcSrc, // 源DC,“内存DC” _In_ int x1, // 源左上X坐标 _In_ int y1, // 源左上Y坐标,从内存中图像的哪个位置开始成像 _In_ DWORD rop // 成像方法 SRCCOPY:原样成像 ); // 当目标区域小于原图时,只能显示部分图像。
缩放成像,缩小或放大:由“目标DC中高宽”和“源DC高宽”的比例决定
BOOL StretchBlt( _In_ HDC hdcDest, // 目的DC _In_ int xDest, // X _In_ int yDest, // Y _In_ int wDest, // 目标宽 _In_ int hDest, // 目标高 _In_opt_ HDC hdcSrc, // 源DC _In_ int xSrc, // X _In_ int ySrc, // Y _In_ int wSrc, // 源DC宽 _In_ int hSrc, // 源DC高 这两个参数指定成像多大区域 _In_ DWORD rop );
- 取出位图
SelectObject - 释放位图
DeleteObject - 释放匹配的DC
DeleteDC
前2步与资源操作类似,第345步与绘图类似。
void Draw(HWND hwnd)
{
PAINTSTRUCT ps = { 0 };
HDC hdc = BeginPaint(hwnd, &ps);
//1. 添加资源
//2. 加载 bitmap
HBITMAP hBitmap = LoadBitmap(g_hInstance, (LPCWSTR)IDB_BITMAP1);
//3. 创建内存DC,并构建虚拟区域,在内存DC中画图
HDC hMemDC = CreateCompatibleDC(hdc);
//4. 将位图送给内存DC,内存DC在虚拟区域画出
HGDIOBJ hOldGdiObj = SelectObject(hMemDC, hBitmap);
//5. 将虚拟区域的图像成像到窗口中
BitBlt(hdc, 50, 50, 48, 48, hMemDC, 0, 0, SRCCOPY);
StretchBlt(hdc, 0, 0, 24, 24, hMemDC, 0, 0, 48, 48, SRCCOPY);
//6.
SelectObject(hMemDC, hOldGdiObj);
//7.
DeleteObject(hBitmap);
//8.
DeleteDC(hMemDC);
EndPaint(hwnd, &ps);
}
1:1 成像 , 缩放到原来一半
文本绘制
TextOut // 功能最弱,不能换行,不能对齐
int DrawTextA(
_In_ HDC hdc, // DC句柄
LPCSTR lpchText, // 字符串
_In_ int cchText, // 字符数
_Inout_ LPRECT lprc, // 绘制文字的矩形框
_In_ UINT format // 绘制方式
);
示例:
LRESULT CALLBACK WnProc(
HWND hwnd,
UINT msg,
WPARAM wparam,
LPARAM lparam)
{
switch (msg)
{
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
TCHAR szText[] = TEXT("12345678901234567890 qwert asdf");
TextOut(hdc, 100, 100, szText, lstrlen(szText));
Rectangle(hdc, 100, 150, 200, 200);
RECT rc;
rc.left = 100;
rc.top = 150;
rc.right = 200;
rc.bottom = 200;
// DT_CENTER 不能与DT_WORDBREAK 一起使用。前者只适用于DT_SINGLELINE,一定靠顶端
DrawText(hdc, szText, lstrlen(szText), &rc, DT_WORDBREAK);
EndPaint(hwnd, &ps);
break;
}
default:
break;
}
return DefWindowProc(hwnd, msg, wparam, lparam);
}
美化
颜色
- SetTextColor
- SetBkColor 背景色默认白色,只适用于 OPAQUE 模式
- SetBkMode (OPAQUE / TRANSPARENT) 文字背景模式,只有这两种
- OPAQUE 默认的不透明模式
- TRANSPARENT 透明模式
SetTextColor(hdc, RGB(255, 0, 0));
SetColor(hdc, RGB(0, 255, 0));
SetBkMode(hdc, TRANSPARENT);
字体
windows常用TrueType字体。字体名:标识字体类型。HFONT:字体句柄。
- 创建字体
HFONT CreateFontA( _In_ int cHeight, // 字体高 _In_ int cWidth, // 字体宽 _In_ int cEscapement, // 字符串倾斜角度, _In_ int cOrientation,// 字符旋转角度,以x轴为中心向里或向外旋转 _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_opt_ LPCSTR pszFaceName // 字体名称 );
- 应用字体到DC
SelectObject - 绘制文字
DrawText / TextOut - 取出字体
SelectObject - 删除字体
DeleteObject
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
HFONT hFont = CreateFont(30, 0, 45, 0, 900, 1, 1, 1, GB2312_CHARSET, 0, 0, 0, 0, L"黑体");
HGDIOBJ hOldObj = SelectObject(hdc, hFont);
TCHAR szText[] = TEXT("12345678901234567890 qwert asdf");
TextOut(hdc, 100, 100, szText, lstrlen(szText));
SelectObject(hdc, hOldObj);
DeleteObject(hFont); // 字体占用内存大,一定记得释放
EndPaint(hwnd, &ps);
break;
}
原创文章,作者:wdmbts,如若转载,请注明出处:https://blog.ytso.com/278402.html