Windows编程:图标资源、光标资源、字符串资源、加速键资源、WM_PAINT消息、绘图
承接前文:
- win32窗口编程
- windows 开发基础
- win32-注册窗口类、创建窗口
- win32-显示窗口、消息循环、消息队列
- win32-鼠标消息、键盘消息、计时器消息、菜单资源
本文目录
- 图标资源
- 光标资源
- WM_SETCURSOR 消息
- 字符串资源
- 加速键资源
- WM_PAINT 消息
- 绘图
- 绘图编程
- 绘图基础
- 基本图形绘制
- GDI 绘图对象
- 画笔
- 画刷
- 位图
- 文本绘制
- 字体
图标资源
以.ico为后缀的图片,icon 图标资源。一个图标文件中可以有多个不同分辨率大小的图标,以适应不同场景下的使用。
想要使用图标资源,第一步添加图标资源,第二步加载图标资源,第三步设置窗口类。
HICON LoadIcon(HINSTANCE hInstance, //handle to application instanceLPCTSTR lpIconName //name string or resource identifier
); //成功返回HICON句柄
添加图标资源:

添加图标资源后可以自己绘制图标:从图中可以看到有不同大小的图标(256x256像素、48x48像素等),有支持彩色的图标,也有黑白图标。

代码中添加资源头文件,并将图标赋值给窗口类的成员变量。
#include "resource.h" //把资源头文件包含进来
... ...
wc.hIcon = LoadIcon(hIns, (char*)IDI_ICON1);
编译运行改变窗口图标:

在Visual Studio中编辑图标并不怎么方便,在这里可以将提前制作好的图标放在项目目录中,修改.rc 文件中对于图标资源的描述,修改为自己提前制作好的图标,然后在Visual Studio中重新加载被修改的 .rc 文件,再次编译运行。



光标资源
光标资源就是鼠标光标,这个光标在不同情况下是不同的:有的时候是箭头光标,有的时候是一个小手,有的时候是一闪一闪的竖杠,等等;在游戏中光标的形状也各不相同。一个光标就是一个图片,也是需要自己提前绘制,然后用代码进行应用。
光标资源使用步骤:
- 第一步添加光标资源,光标的默认大小是32x32像素,每个光标有一个HotSpot热点,也就是点击时生效的点(因为一个光标图片有很多像素,但是生效的一个点只有一个点,这个点的位置可以自己设置,例如箭头光标的热点就在箭头的尖上)。
- 第二步加载资源:
HCURSOR LoadCursor(HINSTANCE hInstance, //handle to application instanceLPCTSTR lpCursorName //name or resource identifier ); // hInstance 可以为 NULL, 获取系统默认的Cursor - 第三步设置光标:
- 注册窗口类时设置。
- 或 使用 SetCursor() 设置光标。
添加光标资源:

绘制光标资源:

使用图中的 Set Hot Spot Tool 来设置光标热点。
设置光标:
#include "resource.h"
... ...
//wc.hCursor = LoadCursor(NULL, IDC_ARROW); //默认光标
wc.hCursor = LoadCursor(hIns, (char*)IDC_CURSOR1);
无论是 LoadCursor() 函数还是 LoadIcon() 函数,他们的作用都是通过窗口实例句柄和资源的ID来在内存中找到这个资源。窗口实例句柄 hInstance 的作用就是在内存中找到一块保存窗口各种信息的内存,这些资源被编译后会编译到 .exe 里面,当程序运行的时候,程序就会被加载到内存,其中就包括了各种资源。
自己创建的光标只在客户区起作用,在标题栏就会自动变成默认光标。
使用 SetCursor() 函数可以在程序运行的过程中修改光标:
HCURSOR SetCursor(HCURSOR hCursor // handle to cursor
);
SetCursor()函数必须放在 WM_SETCURSOR 消息下进行调用。
WM_SETCURSOR 消息
只有光标有移动的情况,这个消息就会不断地出现。专职用法:改光标。
附带参数:
- wParam :当前使用的光标句柄。
- lParam :
- LOWORD :当前区域的代码(Hit-Test code)HTCLIENT (客户区)/ HTCAPTION(标题栏、边框)。
- HIWORD :没太大用处。
再绘制一个光标 IDC_CURSOR2,在程序运行时进行切换:
//添加全局变量 hInstance
HINSTANCE g_hInstance = 0;//WinMain()函数中给hInstance赋值
g_hInstance = hIns;//消息处理函数中添加代码:
case WM_SETCURSOR:
{HCURSOR hCursor = LoadCursor(g_hInstance, (char*)IDC_CURSOR2);SetCursor(hCursor);
}
break;
//运行后,鼠标移动时,光标闪烁变化,这是因为该消息产生后立马修改了光标,但是return DefWindowProc()又将光标修改回去
//SetCursor()后面加上 return 0;即可解决光标随着鼠标的移动不断闪烁修改的问题。
//修改如下
//在客户区光标时 IDC_CURSOR2,在标题栏 IDC_CURSOR1,在其他地方(顶层菜单栏、图标等位置处)为默认光标
case WM_SETCURSOR:
{HCURSOR hCursor1 = LoadCursor(g_hInstance, (char*)IDC_CURSOR1);HCURSOR hCursor2 = LoadCursor(g_hInstance, (char*)IDC_CURSOR2);if (LOWORD(lParam) == HTCLIENT) {SetCursor(hCursor2);return 0;}else if (LOWORD(lParam) == HTCAPTION) {SetCursor(hCursor1);return 0;}
}
break;
字符串资源
字符串资源存在的目的就是方便代码中各种字符串的修改,例如语言的切换(中英文切换等)。
具体来说,字符串资源的作用包括:
中心化管理文本:将应用程序中使用的所有文本信息集中管理,方便统一修改和维护。
多语言支持:通过为不同语言单独创建字符串资源文件,可以实现应用程序的多语言支持,使应用能够在不同语言环境下运行并显示相应的文本。
提高可维护性:由于文本信息集中管理,修改或更新某个文本只需在字符串资源中进行修改,而不需要在代码中逐个替换,提高了代码的可维护性。
便于国际化和本地化:通过使用字符串资源,可以轻松地将应用程序本地化为不同的语言和区域设置,满足不同用户群体的需求。
节省内存空间:采用字符串资源可以在编译时对字符串进行优化,并且可以节省内存空间。
- 添加字符串资源:添加字符串表,在表中增加字符串。
- 加载字符串资源:
int LoadString(HINSTANCE hInstance, // handle to resource moduleUINT uID, //字符串IDLPTSTR lpBuffer, //存放字符串BUFFint nBufferMax //字符串BUFF长度 ); //成功返回字符串长度,失败返回0
添加字符串资源:

添加字符串资源:

使用字符串资源:
char wTitle[20] = { 0 };
LoadString(hIns, STR_Chinese_WinTitle, wTitle, 20);HWND hWnd = CreateWindowEx(0, pClassName, wTitle,WS_CAPTION | WS_MINIMIZEBOX | WS_SYSMENU | WS_OVERLAPPEDWINDOW,200, 200, 640, 480, //窗口位置(200,200),大小长宽(640,480)。nullptr, nullptr, hIns, nullptr
);
在程序中尽量多使用字符串资源(string table),这样可以方便文本管理,如果需要大面积的修改文本,也会方便很多。
加速键资源
加速键,就当快捷键理解。例如记事本的快捷键:

添加资源:资源添加加速键表,增加命令ID对应的加速键。
使用:
//加载加速键表
HACCEL LoadAccelerators(HINSTANCE hInstance, //handle to moduleLPCTSTR lpTableName // accelerator table name
); //返回加速键表句柄//翻译加速键
int TranslateAccelerator(HWND hWnd, //处理消息的窗口句柄HACCEL hAccTable, //加速键表句柄LPMSG lpMsg //消息
); //如果是加速键,返回非零。该函数内部首先判断是不是加速键。
添加加速键资源: Accelerator(加速)

编辑加速键:当加速键的ID和菜单中某一项ID相同时,就实现了加速键和某一个菜单项绑定。加速键和菜单项没有什么对应关系,当它们的ID相同时,就可以说是绑定;当然加速键的ID也可以单独设置,不一定非要和菜单项的ID相同。关于菜单资源参见上一篇文章菜单资源(点击跳转)。

在这里 ID_40001 、ID_40002 、ID_40003 分别对应着菜单项的ID 新建 、打开 、退出。ID_other 不对应任何菜单项。

当菜单项被点击时,或者加速键被按下时,都可以产生 WM_COMMAND 消息。
在 WM_COMMAND 消息中:
- wParam:
- HIWORD 为 1 表示消息产生自加速键,为 0 表示消息产生自菜单项。
- LOWORD 为命令 ID。
- lParam :0.
TranslateAccelerator()函数的内部执行过程:
//伪代码
TranslateAccelerator(hWnd, hAccel, &nMsg){if(nMsg.message != WM_KEYDOWN)return 0; //不是按键被按下,那就绝对不是加速键消息,直接返回0根据nMsg.wParam(键码值),获知哪些按键被按下(假如Ctrl + N)拿着(Ctrl + N)到 hAccel 加速键表中匹配具体加速键if(没找到)return 0;if(找到){SendMessage(hWnd, WM_COMMAND, HIWORD(1)|||LOWORD(ID_40001), ... ); //产生COMMAND消息return 1;}
}
加速键的使用:
//加速键要放在消息循环中使用。//使用前首先加载加速键表HACCEL hAccel = LoadAccelerators(hIns, (char*)IDR_ACCELERATOR1);//TranslateAccelerator()函数应该放在TranslateMessage()函数之前调用,因为TranslateMessage()会区分大小写。消息循环加上翻译加速键函数的调用后,完整代码如下:while (1) //先进入死循环,循环体中进行判断是否退出循环{if (PeekMessage(&nMsg, NULL, 0, 0, PM_NOREMOVE)) //PeekMessage侦察兵侦察是否有消息,如果有消息{if ((gResult = GetMessage(&nMsg, NULL, 0, 0)) > 0) //gResult值大于零意味着GetMessage没有抓到 WM_QUIT(返回值为0) 也没有出错(出错返回值为-1){if (!TranslateAccelerator(hWnd, hAccel, &nMsg)) //返回值为0,不是加速键,则翻译、派发消息。是加速键消息,则产生一个 WM_COMMAND 消息,然后进入下次消息循环{TranslateMessage(&nMsg);DispatchMessage(&nMsg);}}else //如果抓到 WM_QUIT 或者 出错 退出while循环{break;}}else //如果PeekMessage()没有侦察到消息,空闲处理{//WriteConsole(g_dos_output, "No Message", strlen("No Message"), NULL, NULL);}}if (gResult == -1) //GetMessage()出错返回值 -1{//错误处理或直接退出程序 return -1;}else //抓到 WM_QUIT 消息,退出程序,返回 PostQuitMessage()的参数值{return nMsg.wParam; //此时 wParam 是 PostQuitMessage()的参数值}
根据 WM_COMMAND 消息的 HIWORD(wParam) 可以区分出消息来自 加速键 被按下 还是 菜单项 被点击:
//消息处理函数中对于 WM_COMMAND 消息的处理:
case WM_COMMAND:OnCommand(hWnd, wParam);
break;//OnCommand()函数的具体内容如下:
void OnCommand(HWND hWnd, WPARAM wParam) {switch (LOWORD(wParam)) {case ID_40001: {if (!HIWORD(wParam)) {MessageBox(hWnd, "菜单项新建被点击", "Information", MB_OK);}else {MessageBox(hWnd, "新建快捷键Ctrl+N被按下", "Information", MB_OK);}}break;case ID_40002:{if (!HIWORD(wParam)) {MessageBox(hWnd, "菜单项打开被点击", "Information", MB_OK);}else {MessageBox(hWnd, "打开快捷键Ctrl+O被按下", "Information", MB_OK);}}break;case ID_40003:{if (!HIWORD(wParam)) {MessageBox(hWnd, "菜单项退出被点击", "Information", MB_OK);}else {MessageBox(hWnd, "退出快捷键Ctrl+Q被按下", "Information", MB_OK);}}break;case ID_other:MessageBox(hWnd, "快捷键Ctrl+R被按下", "Information", MB_OK);break;}
}
运行:



WM_PAINT 消息
WM_PAINT :
- 产生时间:当窗口需要绘制的时候。(窗口第一次显示,窗口被拖拽、大小发生变化等等)
- 附带信息:
- wParam 、lParam :0。
- 专职用法:用于绘图。
ShowWindow() 函数在调用显示窗口的时候会发出一个 WM_PAINT 消息。
GetMessage() 函数在执行过程中也会检查窗口是否需要重新绘制,并发出 WM_PAINT 消息。
-
InvalidateRect:需要重新绘制的区域。BOOL InvalidateRect(HWND hWnd, //窗口句柄CONST RECT* lpRect, //区域的矩形坐标,参数为空的话表明整个窗口都需要重新绘制BOOL bErase //重绘前是否需要擦除 );其中 RECT 是一个封装了矩形 上下左右的结构体:
typedef struct tagRECT {LONG left;LONG top;LONG right;LONG bottom; } RECT, *PRECT, NEAR *NPRECT, FAR *LPRECT;InvalidateRect该函数一旦调用,GetMessage() 也会发出WM_PAINT消息。用于通知Windows系统某个窗口或者指定的矩形区域需要重绘。当调用InvalidateRect函数时,系统会发送一个WM_PAINT消息给窗口,提示它需要重新绘制。这在开发Windows应用程序时非常有用,可以在需要更新界面时强制触发重绘操作。
一般情况下,开发者会在需要更新窗口内容的时候调用InvalidateRect函数,然后系统会在适当的时间批量处理所有需要重绘的区域,提高绘制效率。通过使用InvalidateRect函数,开发者可以实现界面的实时更新和响应用户操作,提升用户体验。
绘图
绘图步骤:
- 开始绘图:
HDC BeginPaint(HWND hWnd, //绘图窗口LPPAINTSTRUCT lpPaint //绘图参数的BUFF ); //返回绘图设备句柄 HDC - 正式绘图
- 结束绘图
BOOL EndPaint(HWND hWnd, //绘图窗口CONST PAINTSTRUCT *lpPaint //绘图参数的指针,BeginPaint返回 );
按下鼠标左键进行字符串绘制:使用TextOut()函数,在客户区绘制字符串。

绘图编程
绘图基础
GDI - Windows graphics device interface Windows图形设备接口(Win32提供的绘图API), 是 Windows 操作系统提供的一组函数和数据结构,用于在屏幕上绘制图形、文本和图像。GDI 提供了访问和控制显示设备的功能,包括绘制形状、填充颜色、显示文本、裁剪图像等。开发人员可以使用 GDI 来创建图形用户界面(GUI)和进行图形处理,例如创建窗口、按钮、菜单等界面元素。GDI 在 Windows 中扮演着重要的角色,为应用程序提供了图形处理能力,帮助用户和开发者实现丰富的图形显示效果。
绘图设备 DC(Device Context 设备上下文):
Device Context (设备上下文) 是Windows开发中的重要概念,它指代一种用于绘制图形、文本和图像的抽象对象。
Device Context 封装了与设备相关的绘图操作,允许开发者在绘制到屏幕、打印机或其他输出设备上时使用统一的接口。通过与设备上下文进行交互,开发者可以绘制图形、操纵字体、绘制文本、处理图像和执行其他与绘图相关的操作。
DC可以看成 Windows 的画家,想要绘图就要先找到这个画家,通过 DC 句柄来找到画家。
HDC hdc = BeginPaint(hWnd, &pc); BeginPaint() 函数用来找到画家,并将返回值赋值给 HDC。BeginPaint() 的第一个参数 hWnd 窗口句柄,告诉画家在哪个窗口画画。
HDC - DC句柄,表示绘图设备。
抓到 HDC 以后,就可以使用各种绘图函数来进行图形绘制。
绘制图形结束后 EndPaint() 释放DC。
颜色:
- 计算机使用 红、绿、蓝 (Red、Green、Blue:RGB) 作为三原色,计算机使用的三原色是物理中的三原色,通过这三种颜色的不同程度的配比可以得到各种各样的颜色,拿放大镜观察屏幕可以看到这三原色构成了显示器的各种颜色。(物理中的三原色要区别于美术中的三原色,美术中的三原色是指黄红蓝,这三种颜料可以配出绝大多数颜色的颜料。)
- R值(红色值):0 ~ 255,(28 = 256,一个字节)。
- G:0 ~ 255。
- B:0 ~ 255。
- 每个点的颜色是3个字节24位保存 0 ~ 224 - 1,总共有 16,777,216 种颜色。
- 在过去的计算机中,使用16位来保存颜色值,低5位Red,第二个5位Green,最高6位Blue。
- 32位表示颜色值:前面的24位用来表示红色(R)、绿色(G)和蓝色(B)的色彩强度,而后面的8位则用来表示 alpha 通道。Alpha 通道通常用来表示颜色的透明度,数值范围从 0 到 255,0 表示完全透明,255 表示完全不透明。通过调整alpha通道的数值,可以控制颜色的透明度,使得它可以适应各种混合和叠加效果。这种32位的颜色表示方式常用于计算机图形和游戏开发中。
颜色的使用:
使用 COLORREF 类型来声明一个颜色值。
typedef DWORD COLORREF; //本质是 DWORD。typedef unsigned long DWORD; // DWORD 本质就是 unsigned long
unsigned long 类型占 4 个字节,正好 32 位,正好可以用来存储颜色值:

声明一个颜色值 COLORREF nColor = 0;
赋值使用 RGB宏:nColor = RGB(0,0,255); //蓝色
获取RGB值:GetRValue/GetGValue/GetBValue。BYTE nRed = GetRValue(nColor); 除了使用这几个函数,也自己自己通过 除法取余运算 来获得各个字节的值。
画点:
//SetPixel 设置指定点的颜色
COLORREF SetPixel(HDC hdc, // DC 句柄int X, // X 坐标int Y, // Y 坐标COLORREF crColor //设置颜色
); //返回点原来的颜色
在消息处理函数的 switch 语句中添加对 WM_PAINT 消息的处理:在像素 100,100 的位置处绘制了一个非常小的黑点。

使用 for 循环绘制渐变色: 使用 for 循环绘制点,点成线,线成面,R和G的值在绘制的过程中不断变化,形成渐变色方块,第一个方块中,Blue的分量为255,第二个方块中 Blue的分量为128:

使用 for 循环一个一个点地去绘制,并不是一种高效的绘制方法。
基本图形绘制
线的使用(直线、弧线):
- MoveToEx :指定窗口当前点(窗口当前点默认在(0,0)处,窗口客户区左上角)。
- LineTo :从窗口当前点到指定点绘制一条直线。
MoveToEx(hdc, 0, 260, NULL); //起点
LineTo(hdc,256, 390); //终点(直线),LineTo 绘制完直线后,会将窗口当前点设置到256,390。
封闭图形:能够用画刷填充的图形(Rectangle,Ellipse)。
Rectangle(hdc, 100, 100, 300, 300); //绘制直角矩形 坐标:左上->右下Ellipse(hdc, 250, 100, 300,200); //坐标:左上->右下,矩形中的内切圆或椭圆
更多的图形的绘制函数可以在网上查找,不赘述。
GDI 绘图对象
画笔
画笔的作用:线的颜色、线型、粗细。
HPEN 画笔句柄。
画笔的使用:
- 创建画笔
-
HPEN CreatePen(int fnPenStyle, //画笔样式,PS_SOLID,实心线,可以支持多个像素宽,其他线型只能是一个像素宽。int nWidth, //画笔粗细COLORREF crColor //画笔颜色); //创建成功返回句柄
-
- 将画笔应用到DC中
HGDIOBJ SelectObject(HDC hdc, //绘图设备句柄HGDIOBJ hgdiobj //GDI绘图对象句柄,画笔句柄 ); //返回原来的GDI绘图对象句柄,注意保存原来DC当中的画笔 - 绘图完成后,取出DC画笔,使用SelectObject()函数。
- 释放画笔:
BOOL DeleteObject(HGDIOBJ hObject //GDI绘图对象句柄,画笔句柄 ); //只能删除不被DC使用的画笔,所以在释放前,必须将画笔从DC中取出
代码如图所示:

画笔样式也可以是虚线 PS_DASH ,非实线画笔像素宽度必须为 1 像素。
HPEN red_pen = CreatePen(PS_DASH, 1, RGB(255, 0, 0));
画刷
画刷的作用给封闭图形填充颜色或图案,封闭图形有 Rectangle 、Ellipse。
HBRUSH :画刷句柄,保存画刷。
画刷的使用步骤跟画笔的使用步骤基本一样:
- 创建画刷
- CreateSolidBrush :创建实心画刷。
- CreateHatchBrush :创建纹理画刷。
- 将画刷应用到 DC 中:SelectObject()。
- 绘图
- 将画刷从 DC 中取出。
- 删除画刷:DeleteObject()。
void OnPaint(HWND hWnd) {PAINTSTRUCT ps = { 0 };HDC hdc = BeginPaint(hWnd, &ps); // 获得 DC 句柄HPEN red_pen = CreatePen(PS_DASH, 1, RGB(255, 0, 0)); //1个像素宽,虚线的红色画笔HGDIOBJ old_pen = SelectObject(hdc, red_pen);HBRUSH blue_brush = CreateSolidBrush(RGB(0, 0, 255));HGDIOBJ old_brush = SelectObject(hdc, blue_brush); //将DC原来的画刷置换出来,DC默认画刷是白色的。MoveToEx(hdc, 0, 260, NULL);LineTo(hdc,256, 390);LineTo(hdc, 1, 1);LineTo(hdc, 23, 199);Rectangle(hdc, 100, 100, 300, 300);HBRUSH green_brush = CreateHatchBrush(HS_CROSS, RGB(0, 255, 0));SelectObject(hdc, green_brush);Ellipse(hdc, 250, 100, 300, 200);SelectObject(hdc, old_brush);DeleteObject(blue_brush);DeleteObject(green_brush);SelectObject(hdc, old_pen); //置换出红色画笔DeleteObject(red_pen);EndPaint(hWnd, &ps); //结束绘画
}

为了让绘制的封闭图形看上去与背景相契合,可以使用透明画刷。透明画刷已经存在于操作系统中,想要使用透明画刷,只需要向操作系统借用即可。
使用 GetStockObject() 函数获取由操作系统维护的画笔、画刷、字体等等。向操作系统借用的画笔、画刷等使用完后,不需要 DeleteObject() 。
向操作系统借用透明画刷:
HBRUSH transparent_brush = GetStockObject(NULL_BRUSH);
HGDIOBJ old_brush = SelectObject(hdc, transparent_brush);
位图
- 光栅图形:记录图像中每一点的颜色等信息,光栅图形是由像素(图像的最小单元)组成的图形,它们在网格状的二维数组中描述。在光栅图形中,每个像素都有自己的颜色值,图像的清晰度和细节取决于像素的密度和分辨率。常见的光栅图形格式包括 JPEG、PNG、BMP 等。复杂的图像和真实世界的场景,但在缩放和变换时可能会失真。
- 矢量图形:记录图像算法,绘图指令等,矢量图形使用数学公式描述图形,它由线条、曲线和填充区域等基本几何形状组成。与光栅图形不同,矢量图形以对象的形式存储,因此可以在任意比例下缩放而不失真。常见的矢量图形格式包括 SVG、EPS、AI 等。矢量形适合用于图标、标志、表和大型设计元素,能够保持清晰度并支持无损缩放。
位图句柄:HBITMAP,位图句柄能在内存中找到一块内存,里面保存着位图图像每个点的颜色值等信息。
位图的使用:
-
在资源中添加位图资源。
-
从资源中加载位图
LoadBitmap()。 -
创建一个与当前 DC 相匹配的 DC (内存 DC)。
-
HDC CeateCompatibleDC(HDC hdc //当前 DC 句柄,可以为 NULL (使用 屏幕 DC)); //返回创建好的的 DC 句柄/*这里的 内存 DC 是一个在内存中的 DC,可以理解为一个虚拟的 DC*/
-
-
使用 SelectObject 将位图的数据放入虚拟的内存DC中。
-
成像(1:1比例):将内存 DC 中的图像显示在窗口上。
-
BOOL BitBlt(HDC hdcDest, //目的DCint nXDest, //目的DC左上角坐标int nYDest, //目的右上角坐标int nWidth, //目的宽度int nHeight, //目的高度,这四个参数确定成像位置和成像区域HDC hdcStr, //源DCint nXSrc, //源左上角坐标int nYSrc, //源右上角坐标,从虚拟DC的什么位置开始成像DWORD dwRop //成像方法 SRCCOPY);
-
-
缩放成像:
BOOL StretchBlt(HDC hdcDest, //handle to destination DCint nXOriginDest, //x-coord of destination upper-left cornerint nYOriginDest, //y-coord of destination upper-right cornerint nwidthDest, //width of destination rectangleint nHeightDest, //height of destination rectangleHDC hdcSrc, //handle to source DCint nXOriginSrc, //x-coord of source upper-left cornerint nYOriginSrc, //y-coord of source upper-right cornerint nWidthSrc, //源DC宽int nHeightSrc, //源DC高DWORD dwRop //raster operation code ); -
使用 SelectObject 函数取出位图。
-
释放位图 DeleteObject。
-
释放匹配的 DC:DeleteDC。
添加 bitmap :添加 bitmap 成功之后,会显示 bitmap 的绘制界面,也可以修改 Resource.rc 文件中对于 bitmap 资源的描述,修改为从外部导入的 bitmap 文件。


1:1 成像代码:
PAINTSTRUCT ps = { 0 };
HDC hdc = BeginPaint(hWnd, &ps); // 获得 DC 句柄//添加位图资源
HBITMAP hBmp1 = LoadBitmap(g_hInstance, (char*)IDB_BITMAP1);
HDC hMemdc = CreateCompatibleDC(hdc); //创建一个DC,构建一个虚拟区域,并且内存DC在虚拟区域中绘图HGDIOBJ oldbmp = SelectObject(hMemdc, hBmp1);
BitBlt(hdc, 10, 10, 1536, 1024, hMemdc, 0, 0, SRCCOPY);SelectObject(hMemdc, oldbmp);
DeleteObject(hBmp1);
DeleteDC(hMemdc);EndPaint(hWnd, &ps); //结束绘画
在窗口中 1:1 显示位图:(该图高1536像素,宽1024像素,所以 1:1 显示无法看到全部图像。)

缩放成像:
StretchBlt(hdc, 5, 5, //窗口位置512, 768, //窗口区域hMemdc, 0, 0, //从图像的的哪里开始绘制1024, 1536, //显示图像的宽高SRCCOPY);
运行:

从上图的运行结果可以看到,图片缩小后显示,出现了很多的纹理。出现这种情况可能是因为在使用StretchBlt函数时,将图像进行缩放时导致了图像质量下降。StretchBlt函数可以对图像进行拉伸和压缩,但它是基于像素级的操作,因此在缩放时可能会导致图像的锯齿状边缘和失真。
想要在窗口中显示缩小的BMP图像并保持较好的质量,可以考虑使用双线性插值进行图像缩放。双线性插值是一种图像处理算法,它可以在进行图像缩放时减少锯齿状边缘和纹理。
使用 GDI+ 库来实现双线性插值
首先配置项目属性:在 Solution Explorer 栏下,右键项目名,项目属性 properties——>Linker 链接器——>Input——>Additional Dependencies 附加属性后面:添加一个值 gdiplus.lib; (注意每个值之间用英文版分号 ; 隔开)。

或者在代码第一行添加如下代码:
#pragma comment(lib,"Gdiplus.lib") //这将使编译器在编译时自动链接所需的 Gdiplus 库,放在代码第一行
#include <gdiplus.h> //包含所需要的头文件using namespace Gdiplus; //使用命名空间
... ...void OnPaint(HWND hWnd) {//添加位图资源HBITMAP hBmp1 = LoadBitmap(g_hInstance, (char*)(IDB_BITMAP1));HBITMAP hBmp2 = LoadBitmap(GetModuleHandle(NULL), MAKEINTRESOURCE(IDB_BITMAP2));HBITMAP hBmp3 = LoadBitmap(GetModuleHandle(NULL), MAKEINTRESOURCE(IDB_BITMAP4));if ((hBmp1 == NULL) && (hBmp2 == NULL) && (hBmp3 == NULL)){MessageBox(hWnd, "Failed to load bitmap resource", "Error", MB_OK | MB_ICONERROR);exit(-1);}Bitmap bitmap1(hBmp1, NULL);Bitmap bitmap2(hBmp2, NULL);Bitmap bitmap3(hBmp3, NULL);// 缩小图片并保证质量HDC hdc = GetDC(hWnd);Graphics graphics(hdc);graphics.SetInterpolationMode(InterpolationModeHighQualityBicubic);graphics.DrawImage(&bitmap1, 0, 0, 512, 768); // 以左上角为起点,缩放到 512x768 大小graphics.DrawImage(&bitmap2, 512, 0, 512, 768);graphics.DrawImage(&bitmap3, 1024, 0, 512, 768);// 释放资源DeleteObject(hBmp1);DeleteObject(hBmp2);DeleteObject(hBmp3);ReleaseDC(hWnd, hdc);
}//消息处理
LRESULT CALLBACK WndProc(HWND hWnd, UINT msgID, WPARAM wParam, LPARAM lParam)
{switch (msgID){case WM_PAINT:OnPaint(hWnd);break;... ...
}... ...
//WinMain函数添加下面代码:
int CALLBACK WinMain(HINSTANCE hIns, HINSTANCE hPreIns, LPSTR lpCmdLine, int nCmdShow)
{//在程序开始时初始化GDI+库GdiplusStartupInput gdiplusStartupInput; //定义了一个名为gdiplusStartupInput的变量,其类型是GdiplusStartupInput。GdiplusStartupInput是一个结构体,用于指定GDI+库的初始化参数。通常情况下,你可以保持其默认值。ULONG_PTR gdiplusToken; //定义了一个名为gdiplusToken的变量,其类型是ULONG_PTR。在初始化GDI+库时,GdiplusStartup函数将为其分配一个令牌,并将此令牌存储在gdiplusToken中。这个令牌是用来标识GDI+库的实例化过程。GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL); //调用了GdiplusStartup函数来初始化GDI+库。它接受三个参数:分别是一个指向令牌的指针(&gdiplusToken),一个指向GdiplusStartupInput结构的指针(&gdiplusStartupInput),以及一个GdiplusStartupOutput结构的指针,通常为NULL... ...while(1) {... ... //消息循环}//消息循环后面添加GdiplusShutdown(gdiplusToken); //调用了GdiplusShutdown函数来清理并关闭GDI+库。它接受一个参数,即在初始化时获得的令牌gdiplusToken。该函数用于释放GDI+库所使用的资源,确保在程序结束时正确清理。... ...return ...
}
运行:消除图片缩小时产生的纹理。

修改图片绘制的透明度为 0.5 :
// 设置透明度
ColorMatrix colorMatrix = {
1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f, 0.0f, 0.0f,
0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
0.0f, 0.0f, 0.0f, 0.5f, 0.0f, // 设置透明度,范围从0.0(完全透明)到1.0(完全不透明)
0.0f, 0.0f, 0.0f, 0.0f, 1.0f
};ImageAttributes imageAttr;
imageAttr.SetColorMatrix(&colorMatrix, ColorMatrixFlagsDefault, ColorAdjustTypeBitmap);// 绘制图像
graphics.DrawImage(&bitmap1, RectF(0, 0, 512, 768), 0, 0, bitmap1.GetWidth(), bitmap1.GetHeight(), UnitPixel, &imageAttr);
graphics.DrawImage(&bitmap2, RectF(512, 0, 512, 768), 0, 0, bitmap2.GetWidth(), bitmap2.GetHeight(), UnitPixel, &imageAttr);
graphics.DrawImage(&bitmap3, RectF(1024, 0, 512, 768), 0, 0, bitmap3.GetWidth(), bitmap3.GetHeight(), UnitPixel, &imageAttr);
运行:

ColorMatrix colorMatrix = {
1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f, 0.0f, 0.0f,
0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
0.0f, 0.0f, 0.0f, 0.5f, 0.0f,
0.0f, 0.0f, 0.0f, 0.0f, 1.0f
};
这段代码创建了一个 ColorMatrix 对象,其中第四行第四列的值被设置为 0.5,即设置了图像的透明度为 50%。
ColorMatrix 是一个 5x5 的矩阵,用于在 GDI+ 中进行颜色转换。当我们将一个图像绘制到目标上时,可以使用 ColorMatrix 对图像进行颜色调整。它的基本原理是将每个像素的颜色值与 ColorMatrix 进行矩阵乘法运算,得到新的颜色值。
在这个 ColorMatrix 中,每行代表着输出颜色的一个分量(红、绿、蓝、透明度和偏移量),而每列代表输入颜色的一个分量。在这个矩阵中,第四行第四列的值表示了输出的 alpha(透明度)通道,因此将其设置为 0.5 就使得输出的透明度减半,从而实现了图像的半透明效果。
通过调整 ColorMatrix 中的各个元素,我们可以实现对图像的不同颜色分量的调整,包括亮度、对比度、饱和度以及透明度等。这种颜色转换的方式提供了一种非常灵活的方法,可以在绘制图像时对其进行各种颜色效果的处理。
文本绘制
在前文中提到了一个文本的绘制函数 TextOut (将文字绘制在指定坐标位置),这个函数对于文本的绘制功能比较基础。下面介绍 DrawText() 函数:
int DrawText(HDC hdc, //DC句柄LPCTSTR lpString, //字符串int nCount, //字符数量LPRECT lpRect, //绘制文字的矩形框UINT uFormat //绘制的方式
);
RECT rc; //矩形结构体
rc.left = 100; //矩形距离窗口左边距离
rc.top = 150; //矩形距离窗口顶端的距离
rc.right = 200;
rc.bottom = 200;
Rectangle(hdc, 100, 150, 200, 200); //绘制矩形以查看字符串绘制的位置
DrawText(hdc, "你好 hello hello Long LONG LONG", strlen("你好 hello hello Long LONG LONG"), &rc, DT_LEFT | DT_TOP | DT_WORDBREAK | DT_NOCLIP);
- DT_LEFT :水平靠左开始绘制字符串。
- DT_TOP :垂直靠上绘制字符串。
- DT_WORDBREAK :单行超出矩形右侧的文本另起一行绘制。
- DT_NOCLIP :文本太长超出矩形范围时,不剪切字符串(完整显示所有文本)。
- DT_CENTER :水平居中。
- DT_VCENTER :垂直居中。(只能单行居中,与WORDBREAK冲突)
- DT_SINGLELINE :单行绘制。
… …
文字颜色和背景:
-
文字颜色:SetTextColor。
-
SetTextColor(hdc, RGB(0, 0, 255)); //蓝色字体DrawText(hdc, "你好\n hello hello\n Long\n LONG LONG Long\n LONG", strlen("你好\n hello hello\n Long\n LONG LONG Long\n LONG"), &rc, DT_CENTER | DT_VCENTER | DT_NOCLIP);
-
-
文字背景:SetBkColor。(默认背景色白色)
-
SetBkColor(hdc, RGB(0, 128, 200)); //浅蓝色背景
-
-
文字背景模式:SetBkMode (OPAQUE :默认的不透明模式 / TRANSPARENT :透明模式)。
-
SetBkMode(hdc, TRANSPARENT);
-
字体
Windows 常用字体为 TrueType 格式的字体文件。
字体名:标识字体类型。
HFONT :字体句柄。
在 Windows 系统盘 C:/Windows/Fonts 目录下有着各种各样的字体:这些字体文件都是 TrueType 格式的,也就是每个字体文件中保存着每个字的真实点阵字型(位图字体,每个字的真实外观),即使双击打开,也不能查看到每个字,只是一个预览而已。


字体的使用:
- 创建字体:想要创建一个字体,那么该电脑中就要有该字体的字体文件。
-
HFONT CreateFont(int nHeight, //字体高度int nWidth, //字体宽度,字体高度给出后,宽度填0即可自动匹配合适的宽度int nEscapement, //字符串倾斜角度,字符串与水平方向的夹角,以0.1度为单位。(字体站得竖直,但是字符串中所有的字符不在一水平线上)int nOrientation, //字符旋转角度,以x轴为轴心,向里或向外旋转角度。int fnWeight, //字体的粗细DWORD fdwItalic, //斜体,1或0。DWORD fdwUnderline, //字符下划线DWORD fdwStrikeOut, //删除线DWORD fdwCharSet, //字符集DWORD fdwOutputPrecision, //输出精度,没什么用DWORD fdwClipPrecision, //剪切精度,没什么用DWORD fdwQuality, //输出质量,没什么用DWORD fdwPitchAndFamily, //匹配字体,没什么用LPCTSTR lpszFace //字体名称);
-
- 应用到字体 DC,(SelectObject)。
- 绘制文本,(DrawText / TextOut)。
- 取出字体,(SelectObject)。
- 删除字体,(DeleteObject)。
SetTextColor(hdc, RGB(0, 128, 200)); //字体颜色浅蓝色
SetBkColor(hdc, RGB(0, 128, 200)); //字体背景色
SetBkMode(hdc, TRANSPARENT); //设置字体背景为透明色,此时背景色会被覆盖HFONT hFont = CreateFont(30, 0, 45, 0, 900, 1, 1, 1, GB2312_CHARSET, 0, 0, 0, 0, "楷体"); //创建字体
HGDIOBJ oldFont = SelectObject(hdc, hFont);HGDIOBJ hBrush = GetStockObject(NULL_BRUSH); //创建透明画刷
HGDIOBJ oldBrush = SelectObject(hdc, hBrush);RECT rc;
rc.left = 100;
rc.top = 150;
rc.right = 200;
rc.bottom = 200;
Rectangle(hdc, 100, 150, 200, 200); //绘制矩形背景为透明色
DrawText(hdc, "你好\n hello hello\n Long\n LONG LONG Long\n LONG", strlen("你好\n hello hello\n Long\n LONG LONG Long\n LONG"), &rc, DT_CENTER | DT_VCENTER | DT_NOCLIP);// 释放资源
SelectObject(hdc, oldFont);
DeleteObject(hFont);
SelectObject(hdc, oldBrush);
DeleteObject(hBrush);
字体绘制如下:

本期文章到此结束,觉得博主文章有用的朋友可以给个一键三连鼓励一下,你们的关注与支持是我持续更新下去的动力,感谢。
相关文章:
Windows编程:图标资源、光标资源、字符串资源、加速键资源、WM_PAINT消息、绘图
承接前文: win32窗口编程windows 开发基础win32-注册窗口类、创建窗口win32-显示窗口、消息循环、消息队列win32-鼠标消息、键盘消息、计时器消息、菜单资源 本文目录 图标资源光标资源WM_SETCURSOR 消息 字符串资源加速键资源WM_PAINT 消息绘图绘图编程绘图基础基…...
【2024 短剧0元轻资产创业风口】做自己的老板,做新媒体的领路人
好省短剧邀请码2Urux1ZoQm(长按复制粘贴即可)大多数好省短剧推广活动都会通过官方渠道发布邀请码。您可以通过关注官方社交媒体账号、订阅电子邮件通知或参与官方网站上的活动,获得邀请码的机会。官方渠道通常会提前公布邀请码的获取方式和条件,您只需按照要求执行即可。好省…...
Docker安装Bitbucket
centos7版本 [rootlocalhost ~]# cat /etc/os-release NAME"CentOS Linux" VERSION"7 (Core)" ID"centos" ID_LIKE"rhel fedora" VERSION_ID"7" PRETTY_NAME"CentOS Linux 7 (Core)" ANSI_COLOR"0;31"…...
FlyMcu串口下载STLINK Utility
一、FlyMcu程序烧录软件 1、可以通过串口给STM32下载程序,如果没有STLINK,就可以用这个软件通过串口下载程序,和STC的51单片机的烧录软件STC-ISP一样,通过串口给单片机下载程序 2、创建串口下载所需要的HEX文件 3、选择串口和波…...
CSS(盒子模型,定位,浮动,扩展)
CSS 盒子模型:外边距:内边距:水平居中: 定位:相对定位:绝对定位:固定定位: 浮动:扩展: 盒子模型: 盒子模型(Box Model) 规定了元素框处理元素内容…...
AIGC如何改变人类生活20240529
AIGC如何改变人类生活 随着人工智能技术的不断发展,人类生活正经历着前所未有的变革。在这个过程中,AIGC(人工智能生成内容)的概念应运而生,它已经在很多领域产生了深远的影响。本文将探讨AIGC如何改变人类生活&#…...
【python】成功解决“TypeError: ‘method’ object is not subscriptable”错误的全面指南
成功解决“TypeError: ‘method’ object is not subscriptable”错误的全面指南 一、引言 在Python编程中,TypeError: method object is not subscriptable错误是一个常见的陷阱,特别是对于初学者来说。这个错误通常意味着你尝试像访问列表、元组、字典…...
若依 Spring Security 短信,扫码登录
1. 修改 LoginBody,添加登录类型字段 Data public class LoginBody {/*** 用户名*/private String username;/*** 用户密码*/private String password;/*** 验证码*/private String code;/*** 唯一标识*/private String uuid;/*** 登录类型*/private String logi…...
Web 网页性能优化
Web 网页性能及性能优化 一、Web 性能 Web 性能是 Web 开发的一个重要方面,侧重于网页加载速度以及对用户输入的响应速度 通过优化网站来改善性能,可以在为用户提供更好的体验 网页性能既广泛又非常深入 1. 为什么性能这么重要? 1. 性能…...
JDBC-MySQL
JDBC-MySQL 1.JDBC 操作步骤1.1 DriverManager1.2.Connection对象1.3 Statement1.4 PreparedStatement 1.JDBC 操作步骤 public void quickStart() throws ClassNotFoundException, SQLException {//1、注册驱动 (确认要使用哪个数据库)Class.forName(&…...
MySQL经典练习50题(上)(解析版)
所有笔记、生活分享首发于个人博客 想要获得最佳的阅读体验(无广告且清爽),请访问本篇笔记 MySQL经典练习50题(上) 创建数据库和表 -- 建 表 -- 学 生 表 CREATE TABLE Student( s_id VARCHAR(20), s_name VARCHAR(2…...
每日一题33:数据统计之广告效果
一、每日一题 返回结果示例如下: 示例 1: 输入: Ads 表: ------------------------- | ad_id | user_id | action | ------------------------- | 1 | 1 | Clicked | | 2 | 2 | Clicked | | 3 | 3 | Viewed…...
52、有边数限制的最短路
有边数限制的最短路 题目描述 给定一个n个点m条边的有向图,图中可能存在重边和自环, 边权可能为负数。 请你求出从1号点到n号点的最多经过k条边的最短距离,如果无法从1号点走到n号点,输出impossible。 注意:图中可…...
Spring boot实现基于注解的aop面向切面编程
Spring boot实现基于注解的aop面向切面编程 背景 从最开始使用Spring,AOP和IOC的理念就深入我心。正好,我需要写一个基于注解的AOP,被这个注解修饰的参数和属性,就会被拿到参数并校验参数。 一,引入依赖 当前sprin…...
MySQL之查询性能优化(四)
查询性能优化 MySQL客户端/服务器通信协议 一般来说,不需要去理解MySQL通信协议的内部实现细节,只需要大致理解通信协议是如何工作的。MySQL客户端和服务器之间的通信协议是"半双工"的,这意味着,在任何一个时刻&#…...
定时任务详解
文章目录 定时任务详解JDK自带第三方任务调度框架java有哪些定时任务的框架为什么需要定时任务定时任务扫表的方案有什么缺点Quartzxxl-jobxxl-job详解 elastic-job 定时任务详解 在定时任务中,操作系统或应用程序会利用计时器或定时器来定期检查当前时间是否达到了…...
OnlyOffice DocumentServer 8.0.1编译破解版本(¥100)
OnlyOffice DocumentServer 8.0.1编译破解版本(¥100) 破解20人数限制 更换中文字体 修改源码,根据业务自定义服务 根据源码在本机启动项目,便于开发 将编译好的服务打包docker镜像运行 提供各种docker镜像包&…...
Android 应用权限
文章目录 权限声明uses-permissionpermissionpermission-grouppermission-tree其他uses-feature 权限配置 权限声明 Android权限在AndroidManifest.xml中声明,<permission>、 <permission-group> 、<permission-tree> 和<uses-permission>…...
MATLAB 匿名函数
定义匿名函数定义匿名函数的基本语法如下:示例示例 1:简单数学运算示例 2:字符串操作示例 3:作为参数传递 匿名函数的高级用法使用函数句柄定义多输出函数使用局部变量使用嵌套匿名函数 注意事项 匿名函数( Anonymous…...
Java 新手入门:基础知识点一览
Java 新手入门:基础知识点一览 想要踏入 Java 的编程世界?别担心,这篇文章将用简单易懂的表格形式,带你快速了解 Java 的基础知识点。 一、Java 是什么? 概念解释Java一种面向对象的编程语言,拥有跨平台、…...
AI-调查研究-01-正念冥想有用吗?对健康的影响及科学指南
点一下关注吧!!!非常感谢!!持续更新!!! 🚀 AI篇持续更新中!(长期更新) 目前2025年06月05日更新到: AI炼丹日志-28 - Aud…...
linux之kylin系统nginx的安装
一、nginx的作用 1.可做高性能的web服务器 直接处理静态资源(HTML/CSS/图片等),响应速度远超传统服务器类似apache支持高并发连接 2.反向代理服务器 隐藏后端服务器IP地址,提高安全性 3.负载均衡服务器 支持多种策略分发流量…...
关于iview组件中使用 table , 绑定序号分页后序号从1开始的解决方案
问题描述:iview使用table 中type: "index",分页之后 ,索引还是从1开始,试过绑定后台返回数据的id, 这种方法可行,就是后台返回数据的每个页面id都不完全是按照从1开始的升序,因此百度了下,找到了…...
Objective-C常用命名规范总结
【OC】常用命名规范总结 文章目录 【OC】常用命名规范总结1.类名(Class Name)2.协议名(Protocol Name)3.方法名(Method Name)4.属性名(Property Name)5.局部变量/实例变量(Local / Instance Variables&…...
ESP32 I2S音频总线学习笔记(四): INMP441采集音频并实时播放
简介 前面两期文章我们介绍了I2S的读取和写入,一个是通过INMP441麦克风模块采集音频,一个是通过PCM5102A模块播放音频,那如果我们将两者结合起来,将麦克风采集到的音频通过PCM5102A播放,是不是就可以做一个扩音器了呢…...
css的定位(position)详解:相对定位 绝对定位 固定定位
在 CSS 中,元素的定位通过 position 属性控制,共有 5 种定位模式:static(静态定位)、relative(相对定位)、absolute(绝对定位)、fixed(固定定位)和…...
【Java_EE】Spring MVC
目录 Spring Web MVC 编辑注解 RestController RequestMapping RequestParam RequestParam RequestBody PathVariable RequestPart 参数传递 注意事项 编辑参数重命名 RequestParam 编辑编辑传递集合 RequestParam 传递JSON数据 编辑RequestBody …...
Java多线程实现之Thread类深度解析
Java多线程实现之Thread类深度解析 一、多线程基础概念1.1 什么是线程1.2 多线程的优势1.3 Java多线程模型 二、Thread类的基本结构与构造函数2.1 Thread类的继承关系2.2 构造函数 三、创建和启动线程3.1 继承Thread类创建线程3.2 实现Runnable接口创建线程 四、Thread类的核心…...
初学 pytest 记录
安装 pip install pytest用例可以是函数也可以是类中的方法 def test_func():print()class TestAdd: # def __init__(self): 在 pytest 中不可以使用__init__方法 # self.cc 12345 pytest.mark.api def test_str(self):res add(1, 2)assert res 12def test_int(self):r…...
嵌入式学习笔记DAY33(网络编程——TCP)
一、网络架构 C/S (client/server 客户端/服务器):由客户端和服务器端两个部分组成。客户端通常是用户使用的应用程序,负责提供用户界面和交互逻辑 ,接收用户输入,向服务器发送请求,并展示服务…...
