当前位置: 首页 > news >正文

做网站还是做阿里/电脑培训

做网站还是做阿里,电脑培训,网站根目录在哪wordpress,音乐网站需求分析视频参考:https://www.bilibili.com/video/BV1aDmqYnEnc/ BitBlt 是 Windows GDI(图形设备接口)中的一个函数,用于在设备上下文(device context, DC)之间复制位图数据。BitBlt 的主要用途是将一个图像区域从一个地方复…

视频参考:https://www.bilibili.com/video/BV1aDmqYnEnc/

BitBlt 是 Windows GDI(图形设备接口)中的一个函数,用于在设备上下文(device context, DC)之间复制位图数据。BitBlt 的主要用途是将一个图像区域从一个地方复制到另一个地方,常用于图像绘制、屏幕刷新、动画等。
BitBlt 函数的基本签名如下:

BOOL BitBlt(HDC hdcDest, // 目标设备上下文句柄int nXDest,  // 目标区域左上角的X坐标int nYDest,  // 目标区域左上角的Y坐标int nWidth,  // 复制区域的宽度int nHeight, // 复制区域的高度HDC hdcSrc,  // 源设备上下文句柄int nXSrc,   // 源区域左上角的X坐标int nYSrc,   // 源区域左上角的Y坐标DWORD dwRop  // 光栅操作代码
);

按照下面修改对 DIBSection 的内存分配进行了优化,通过直接使用 VirtualAlloc 和 VirtualFree 管理内存,这种方法比之前使用 CreateDIBSection 更加灵活,且具备一定的性能和内存管理优势。以下是两个版本的主要区别和优化原因:

  1. 直接控制内存分配和释放
    后面的代码版本用 VirtualAlloc 和 VirtualFree 直接管理内存,而不是依赖 CreateDIBSection 自动创建的位图内存。这种方式可以带来以下好处:

    更灵活的内存管理:可以更精确地控制内存分配和释放的方式(MEM_COMMIT 和 MEM_RELEASE),避免了不必要的内存开销。
    减少资源依赖:省去了调用 DeleteObject 和 CreateDIBSection 的复杂性,减少对 GDI(图形设备接口)的依赖,简化代码逻辑。

  2. 避免频繁的设备上下文操作
    在前一个版本中,每次都需要检查和重新生成设备上下文(BitmapDeviceContext)。而后面版本取消了设备上下文的依赖,通过 VirtualAlloc 直接分配内存,从而避免了设备上下文的频繁操作,这样可以减少系统开销并提高性能。

  3. 改善代码健壮性和性能
    后面的代码版本简化了 DIBSection 的创建过程,同时提高了代码的健壮性。例如:
    在释放内存前检查 BitmapMemory 是否为 NULL,确保不会重复释放无效的内存地址。
    设置了 BytesPerPixel 和 BitmapMemorySize 来准确计算内存大小,避免了内存浪费。

  4. 增加跨平台适应性
    虽然 CreateDIBSection 在 Windows 平台上性能优越,但直接使用 VirtualAlloc 可以更轻松地适配其他平台的内存管理逻辑。因此,改用 VirtualAlloc 更有利于将代码改进为跨平台兼容的代码。

  5. 精确管理内存的页属性
    通过 VirtualAlloc 和 VirtualFree,可以更好地设置页面属性,例如 PAGE_READWRITE,从而实现更细粒度的内存访问控制。在某些特殊场景下(如多线程渲染或共享内存),可以显著提高内存访问的效率和安全性。

删掉

global_variable HBITMAP BitmapHandle;
global_variable HDC BitmapDeviceContext;

删掉

// TODO: 释放之前的 DIBSection
if (BitmapHandle) { // 如果位图句柄有效,释放之前创建的 DIBSectionDeleteObject(BitmapHandle); // 删除现有的位图对象
}// 如果没有有效的设备上下文(BitmapDeviceContext),则创建一个
if (!BitmapDeviceContext) {// TODO: 在某些特殊情况下是否需要重新创建这些对象BitmapDeviceContext =CreateCompatibleDC(0); // 创建一个与屏幕兼容的设备上下文
}

删掉

BitmapHandle =CreateDIBSection(BitmapDeviceContext, // 设备上下文,NULL 表示不绑定设备&BitmapInfo, // 位图信息,包括位图的大小、颜色深度等DIB_RGB_COLORS, // 使用 RGB 颜色类型&BitmapMemory, // 位图的内存指针,返回图像数据的指针NULL, // 内存映射文件句柄,NULL 表示不使用0);   // 偏移量,通常设置为 0// 释放设备上下文(BitmapDeviceContext)
ReleaseDC(0, BitmapDeviceContext); // 释放设备上下文(不再需要继续使用)

在创建 DIBSection(设备独立位图)时,需要指定内存来存储位图的数据,这个数据包括像素的 RGB 值,可能还要考虑字节对齐和填充。为确保位图可以正确显示,我们需要理解每行像素的对齐方式。

在这里插入图片描述

1. 位图内存计算方法

在位图中,每行的宽度(以字节为单位)需要是 4 字节的倍数,这是因为 Windows 位图格式要求每行都对齐到 4 字节。若行宽不足 4 字节,系统会自动填充,使得每行的内存占用达到 4 字节的倍数。这个填充过程称为“位图行填充”或“字节对齐”。

每行的实际字节数计算公式为:
行字节数 = ( ( 位图宽度 × 位深度 + 31 ) / 32 ) × 4 \text{行字节数} = ((\text{位图宽度} \times \text{位深度} + 31) / 32) \times 4 行字节数=((位图宽度×位深度+31)/32)×4

以 32 位位图为例:

  • 每像素 4 字节(32 位 = RGBA,每通道 8 位)。
  • 所以,每行的字节数应该是 width * 4,若不满 4 字节倍数,则按上述公式补齐。

2. 分配内存大小

总的内存大小就是每行实际字节数乘以位图的高度:

总内存大小 = 行字节数 × 位图高度 \text{总内存大小} = \text{行字节数} \times \text{位图高度} 总内存大小=行字节数×位图高度

示例代码

假设我们有一个 widthheight 定义的 32 位位图。以下代码展示如何创建 DIBSection 并分配内存:

int width = 640;  // 位图宽度
int height = 480; // 位图高度BITMAPINFO BitmapInfo;
ZeroMemory(&BitmapInfo, sizeof(BitmapInfo));// 设置位图信息头
BitmapInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
BitmapInfo.bmiHeader.biWidth = width;
BitmapInfo.bmiHeader.biHeight = -height; // 负值表示自上而下的位图
BitmapInfo.bmiHeader.biPlanes = 1;
BitmapInfo.bmiHeader.biBitCount = 32; // 32 位(每像素 4 字节)
BitmapInfo.bmiHeader.biCompression = BI_RGB;// 计算每行所需字节数并分配内存
int rowBytes = ((width * 32 + 31) / 32) * 4;  // 计算每行对齐字节数
int totalBytes = rowBytes * height;            // 计算总内存大小void* BitmapMemory;
HBITMAP hBitmap = CreateDIBSection(hdc, &BitmapInfo, DIB_RGB_COLORS, &BitmapMemory, NULL, 0);if (hBitmap && BitmapMemory) {// BitmapMemory 现在可以用于存储位图的像素数据memset(BitmapMemory, 0, totalBytes); // 初始化内存
} else {// 错误处理
}

注意事项

  1. 内存对齐:确保行对齐后再进行像素数据写入,否则图像可能会出现错位。
  2. 高度符号biHeight 为负时,位图自上而下排列;正值则自下而上排列。
  3. 颜色格式:32 位位图中,每像素占 4 字节,顺序为 BGRA。

VirtualAlloc 函数参数详解

参数:
  • lpAddress:指定内存块的起始地址。

    • 通常设为 NULL,由系统自动选择一个合适的地址。
    • 如果需要特定的地址(例如共享内存),可以指定一个具体地址(但需要确保该地址未被占用)。
  • dwSize:要分配的内存大小,单位是字节。

    • 内存大小会自动按页面大小(通常是 4KB)向上取整,系统为每一块分配的内存区域至少是一个页面。
  • flAllocationType:内存分配类型,可以是以下几种:

    • MEM_RESERVE:保留一块虚拟内存地址空间,但不实际分配物理内存。这通常用于分配较大内存,但暂不使用。
    • MEM_COMMIT:分配物理内存并映射到虚拟地址。已提交的内存可以被进程实际访问和操作。
    • MEM_RESET:将已提交的内存重置,并告知系统当前内存内容可以丢弃。
    • MEM_RELEASE:释放已分配的内存(需配合 VirtualFree 使用),可以将之前保留的内存归还给系统。
  • flProtect:内存保护属性,用于指定分配内存的访问权限:

    • PAGE_READONLY:内存只读。
    • PAGE_READWRITE:内存可读写。
    • PAGE_EXECUTE_READWRITE:内存可执行、可读写。
    • PAGE_NOACCESS:内存不可访问,用于调试或保护内存不被意外修改。
返回值:
  • 返回分配的内存起始地址(LPVOID),如果失败则返回 NULL。可以通过 GetLastError 获取失败的原因。
其他信息:
  • 内存对齐VirtualAlloc 返回的内存块通常是页面对齐的(4KB 或 64KB 边界),非常适合高效地管理大块数据。
  • 内存保护PAGE_READONLYPAGE_READWRITE 等保护属性可以用来提高内存安全性,限制不必要的读写和执行权限。
  • 性能影响VirtualAlloc 属于低级内存管理函数,其效率较低,频繁调用会影响程序性能,通常用于分配较大内存。
  • VirtualFree 配合使用:用 VirtualAlloc 分配的内存必须使用 VirtualFree 来释放。

HeapAlloc 函数参数详解

HeapAlloc 是 Windows API 中用于动态分配内存的函数之一。它在堆中分配内存块,适合需要分配小块内存的场景,相比 VirtualAlloc 更灵活,管理更细粒度。它可以与 HeapFree 配合使用来手动管理进程内存,从而控制内存分配和释放的效率。

biHeight 的作用和含义

BITMAPINFO 结构中的 biHeight 字段用于指定位图的高度,并且具有重要的方向性含义。它的取值直接影响图像在内存中的存储顺序和显示方向。

  • 定义高度biHeight 表示位图的高度,单位为像素。它指定了图像的行数。
  • 控制图像存储方向
    • 如果 biHeight正数,则图像数据会自 底部向上 存储,称为“自底向上位图”(bottom-up bitmap)。这种情况是 Windows 默认的存储方式。
    • 如果 biHeight负数,则图像数据会自 顶部向下 存储,称为“自顶向下位图”(top-down bitmap)。这种模式可以让图像按从上至下的方式存储在内存中,更符合大多数图像处理逻辑的顺序。

在这里插入图片描述
char *Row = (char *)BitmapMemory 替换为 uint8 *Row = (uint8 *)BitmapMemory 的好处在于更加清晰地表达了数据的含义和意图,以及可能带来编译器优化和跨平台一致性方面的优势。以下是主要的好处:

1. 更清晰的数据语义

  • uint8 表示一个无符号的 8 位整数,即范围是 0 到 255。对于位图或图像处理,通常每个像素通道(例如 R、G、B、A 通道)的值在 0 到 255 之间,因此使用 uint8 更能表达数据是图像像素的含义。
  • char 类型在 C++ 标准中是一个 8 位有符号或无符号类型,取决于平台或编译器设置。这可能引发一些与负值相关的问题,例如在处理像素数据时,不希望看到负数值,但 char 在一些平台上默认是有符号类型。

2. 避免符号位带来的错误或歧义

  • 使用 uint8 明确指定为无符号类型,可以避免因符号位导致的计算或比较错误。尤其在处理颜色、图像或二进制数据时,符号位没有实际意义,使用无符号类型可以避免误解。
  • 如果将 char 作为 8 位像素数据类型,则在一些平台上 char 被解释为有符号类型,这会导致数据值范围从 -128 到 127,而不是图像通常期望的 0 到 255 的范围。使用 uint8 解决了此类平台差异。

3. 跨平台一致性和编译器优化

  • 使用 uint8 明确指定类型大小,确保无论在哪个平台上,uint8 都是 8 位且无符号的。这在跨平台开发中非常有帮助,避免了 char 可能是有符号或无符号的二义性问题。
  • 在一些编译器上,uint8 可能更符合编译器的优化路径,尤其是一些用于图像处理的 SIMD(单指令多数据)指令集通常对无符号数据类型优化更好。

4. 可读性提升

  • 使用 uint8 明确表示每个像素通道为 8 位的无符号整数,方便后续代码阅读和维护,减少了潜在的类型转换和符号解释错误,使代码逻辑更加清晰、易读。

总结

char *Row 替换为 uint8 *Row 提高了数据表达的精确性,减少了符号位可能带来的歧义,并增加了跨平台的一致性和代码的可读性,尤其适用于图像处理、颜色计算等直接涉及 8 位数据的场景。

然而这个char 可能是16 位的

typedef uint8_t uint8;typedef unsigned int uint8; 在本质上有很大的区别,具体来说,二者主要在类型大小、范围、平台依赖性等方面存在差异。下面是详细的分析:
在这里插入图片描述

特性typedef uint8_t uint8;typedef unsigned int uint8;
类型大小8 位(固定)4 字节(通常,依平台而异)
取值范围0 到 2550 到 4294967295(通常)
平台一致性跨平台一致(始终 8 位)依平台可能不同(可能是 32 位或 64 位)
标准化C99 标准定义C 标准中不对大小有明确规定
使用场景字节数据(如图像、网络协议、文件)整数运算,可能不适用于精确字节处理
  uint8 *Row = (uint8 *)BitmapMemory;

1. 指针增量的大小不同

  • uint8 *Row = (uint8 *)BitmapMemory;
    Row 是一个指向 uint8 类型的指针,uint8 是一个 1 字节(8 位)的类型。因此,指针加法时,每次递增的地址值是 1 字节。也就是说:

    Row++; // 增加 1 字节,指向下一个 uint8
    
  • uint16 *Row = (uint16 *)BitmapMemory;
    Row 是一个指向 uint16 类型的指针,uint16 是一个 2 字节(16 位)的类型。因此,指针加法时,每次递增的地址值是 2 字节。也就是说:

    Row++; // 增加 2 字节,指向下一个 uint16
    

选择使用哪个类型的指针取决于数据的存储方式和你需要操作的数据类型大小。如果你正在处理图像数据并且每个像素是 8 位的(如 RGBA 图像的单通道),则使用 uint8 *Row 更合适。如果你的数据是 16 位或 2 字节宽的数据(例如某些音频数据或高分辨率图像),则使用 uint16 *Row 可以更高效地访问数据。

在图像处理和计算机图形学中,pitchstride 常被用来描述图像中行与行之间的字节偏移量,但两者通常有细微的区别,具体如下:

1. Pitch

  • 定义:Pitch 通常是图像缓冲区中一行像素实际占用的字节数,包括任何用于对齐的填充字节。
  • 用途:通常用于计算在图像行之间的内存偏移量,以确保图像在内存中按行存储时对齐。例如,可能图像的实际宽度不满足某些内存对齐要求,Pitch 就是调整后实际的每行字节数。
  • 示例:如果一行像素需要 300 字节,但出于对齐原因需要 320 字节,则 Pitch 是 320 字节。

2. Stride

  • 定义:Stride 也指行与行之间的字节偏移量,通常等于图像实际宽度(以像素表示)乘以每个像素的字节数。但在某些环境中,Stride 也可能包括了对齐字节,因此它与 Pitch 含义相同。
  • 用途:Stride 是计算行的字节偏移量的关键参数,尤其在不同图像处理库中,Stride 和 Pitch 的使用取决于实现方式,有时可互换。
  • 示例:如果图像宽度是 100 像素,每个像素 4 字节,则 Stride 是 400 字节。如果此值包含对齐(比如 512 字节),则和 Pitch 相同。

主要区别

  • Pitch 更强调图像缓冲区的实际存储宽度(含对齐字节数)。
  • Stride 在某些情况下只代表“图像宽度 * 每像素字节数”的值,也可包含对齐字节。

在具体实现时,许多库将 Pitch 和 Stride 视作同义,但要注意文档细节,因为它们的定义在不同上下文中会有所不同。

通过你的注释可以更清晰地理解 PitchStride 的实际效果。以下是详细解释:

内存布局说明

                       WIDTH ->0                                        Width * BytesPerPixel
BitmapMemory        -> 0  BB GG RR XX  BB GG RR XX  BB GG RR XX ...
BitmapMemory + Pitch   1  BB GG RR XX  BB GG RR XX  BB GG RR XX ...
  • WIDTH:表示图像的宽度(以像素为单位)。
  • BytesPerPixel:每个像素占用的字节数。此处假设每个像素 4 字节 (BB GG RR XX)。
  • BitmapMemory:指向图像在内存中的起始地址。
  • Pitch:代表从一行像素数据的开始位置到下一行数据开始位置之间的偏移字节数。

Pitch 和 Stride 区别

在上面布局的情况下,Pitch 是一行在内存中所占的总字节数,Stride 表示图像中一行实际像素宽度乘以每像素的字节数。它们的区别主要是:

  • Pitch 通常大于或等于 Stride,原因是可能包含额外的填充字节,使得图像每行数据按特定对齐方式存储(如 4 字节或 8 字节对齐)。

  • Stride 是图像宽度 (Width) 和每像素字节数 (BytesPerPixel) 的乘积,即 Width * BytesPerPixel

    • 例如,如果图像宽度为 100 像素,每像素 4 字节,那么 Stride = 100 * 4 = 400 字节。

解释示例中的 Pitch

如果 PitchStride 相同,说明没有额外的对齐填充字节。

但在某些情况下,为了满足内存对齐要求, Pitch 可能比 Stride 大,比如 Pitch 设置为 512 字节,而实际一行只需要 400 字节(Stride),则每行多出 112 字节的填充数据,用于保证内存对齐。

在这里插入图片描述

内存布局

在这种格式下,每个像素占用 4 个字节,通常按行存储。例如,假设图像为 2x2 像素,内存中的数据可能会如下所示:

像素编号红色 ®绿色 (G)蓝色 (B)填充字节 (XX)
Pixel 1255000
Pixel 2025500
Pixel 3002550
Pixel 425525500

假设图像是 2x2 像素,每个像素占 4 字节的内存空间:

  • Pixel 1: [255, 0, 0, 0]
  • Pixel 2: [0, 255, 0, 0]
  • Pixel 3: [0, 0, 255, 0]
  • Pixel 4: [255, 255, 0, 0]

内存布局(按行存储):

Row 1: [255, 0, 0, 0, 0, 255, 0, 0]  // 第一行,两个像素
Row 2: [0, 0, 255, 0, 255, 255, 0, 0] // 第二行,两个像素

在这里插入图片描述

小端模式(Little Endian)是指数据的低字节存在低地址处,数据的高字节存在高地址处。在小端模式下,如果我们将一个 4 字节的整数 0x000000FF 存储在内存中,那么它会被存储为:

内存地址:     0x00 0x01 0x02 0x03
存储数据:     0xFF 0x00 0x00 0x00

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

如何避免阻塞主窗口

GetMessage 会导致调用它的线程进入阻塞状态,等待消息队列中的新消息。因此,在常见的 Windows GUI 应用程序中,如果主窗口和消息循环都在主线程中运行,那么 GetMessage 的阻塞确实会阻塞主窗口。换句话说,GetMessage 等待消息时,主线程会暂停执行其他操作,直到收到新消息,此时才会继续处理。

如果你希望在阻塞的情况下主窗口保持响应,通常可以使用以下方法:

  1. 多线程

    • 将耗时的操作放到后台线程中运行,这样主线程只处理消息循环而不会卡住 UI。
    • 比如,使用 CreateThread 创建一个新线程,或使用标准库中的多线程工具(如 std::thread)。
  2. PeekMessage

    • 使用 PeekMessage 而不是 GetMessage,因为 PeekMessage 可以非阻塞地检查消息队列是否有消息,允许主线程在没有新消息时继续处理其他事情。
    • 不过 PeekMessage 需要搭配一定的循环逻辑来避免占用过多的 CPU 资源。
  3. 消息钩子或定时器

    • 通过 SetTimer 设置定时器来定期触发消息循环,从而让主线程定期处理 UI 更新,而不会因为 GetMessage 的阻塞而停顿。
    • 还可以用 SetWindowsHookEx 注册消息钩子,以捕获和处理特定类型的消息。

使用这些方法,可以让 GetMessage 等待消息的同时,保持主窗口的响应性。

在这里插入图片描述

PeekMessageA 函数用于检查应用程序的消息队列中是否有特定消息,并根据需要将该消息从队列中移除。与 GetMessage 不同,PeekMessage 是非阻塞的——即便消息队列中没有符合条件的消息,它也会立即返回。下面是该函数的详细参数说明:

BOOL WINAPI PeekMessageA(_Out_ LPMSG lpMsg,           // 指向接收消息结构的指针_In_opt_ HWND hWnd,          // 指定要检查消息的窗口句柄(可以为 NULL)_In_ UINT wMsgFilterMin,     // 要检查的消息类型的最小值_In_ UINT wMsgFilterMax,     // 要检查的消息类型的最大值_In_ UINT wRemoveMsg         // 指定是否从队列中移除消息
);

参数解释

  1. lpMsg
    指向一个 MSG 结构的指针。PeekMessage 将在 lpMsg 中填入符合条件的消息内容。MSG 结构包括消息的类型、窗口句柄、消息时间、消息来源坐标等。

  2. hWnd
    指定要检查消息的窗口句柄。如果 hWndNULL,则检查当前线程中所有窗口的消息;如果设置为特定的窗口句柄,则只检查该窗口的消息。

  3. wMsgFilterMinwMsgFilterMax
    用于设定消息类型的范围。wMsgFilterMin 是要过滤的最小消息值,而 wMsgFilterMax 是最大消息值。此范围可以用于指定只处理某类消息。例如,设置 wMsgFilterMinWM_KEYFIRSTwMsgFilterMaxWM_KEYLAST 可以过滤所有键盘消息。如果希望不过滤消息,通常会将这两个值设置为 00

  4. wRemoveMsg
    指定是否从消息队列中移除消息,可以取以下值:

    • PM_NOREMOVE:不从队列中移除消息,只查看消息内容。
    • PM_REMOVE:将消息从消息队列中移除,类似于 GetMessage 的行为。
    • PM_NOYIELD:只对 16 位应用程序有效,现代 32 位和 64 位应用程序中不会用到。

返回值

  • 返回值为非零 表示消息队列中存在符合条件的消息。
  • 返回值为零 表示没有符合条件的消息。

使用场景

PeekMessageA 通常在需要频繁检查消息队列但不希望阻塞的场景下使用,例如游戏主循环或其他实时更新的应用程序中,搭配非阻塞逻辑可以实现更高的响应性:

MSG msg;
while (true) {if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {if (msg.message == WM_QUIT) break;TranslateMessage(&msg);DispatchMessage(&msg);}// 其他循环逻辑,例如渲染或更新
}

在此代码中,PeekMessage 检查并移除消息,如果没有消息,程序将继续执行其他操作,不会像 GetMessage 那样因阻塞而停止。

// game.cpp : Defines the entry point for the application.
//#include <cstdint>
#include <stdint.h>
#include <windows.h>
#include <winuser.h>#define internal static        // 用于定义内翻译单元内部函数
#define local_persist static   // 局部静态变量
#define global_variable static // 全局变量typedef uint8_t uint8;
typedef uint16_t uint16;
typedef uint32_t uint32;
typedef uint64_t uint64;typedef int8_t int8;
typedef int16_t int16;
typedef int32_t int32;
typedef int64_t int64;// TODO: 全局变量
global_variable bool Running;global_variable BITMAPINFO BitmapInfo;
global_variable void *BitmapMemory;
// 后备缓冲区的宽度和高度
global_variable int BitmapWidth;
global_variable int BitmapHeight;
global_variable int BytesPerPixel = 4;internal void RenderWeirdGradient(int xOffset, int yOffset) {int width = BitmapWidth;int height = BitmapHeight;int Pitch = width * BytesPerPixel;uint8 *Row = (uint8 *)BitmapMemory;for (int Y = 0; Y < BitmapHeight; ++Y) {uint32 *Pixel = (uint32 *)Row;for (int X = 0; X < BitmapWidth; ++X) {uint8 Blue = (X + xOffset);uint8 Green = (Y + yOffset);*Pixel++ = ((Green << 8) | Blue);}Row += Pitch;}
}// 这个函数用于重新调整 DIB(设备独立位图)大小
internal void Win32ResizeDIBSection(int width, int height) {// device independent bitmap(设备独立位图)// TODO: 进一步优化代码的健壮性// 可能的改进:先不释放,先尝试其他方法,再如果失败再释放。if (BitmapMemory) {VirtualFree(BitmapMemory, // 指定要释放的内存块起始地址0, // 要释放的大小(字节),对部分释放有效,整体释放则设为 0MEM_RELEASE); // MEM_RELEASE:释放整个内存块,将内存和地址空间都归还给操作系统}// 赋值后备缓冲的宽度和高度BitmapWidth = width;BitmapHeight = height;// 设置位图信息头(BITMAPINFOHEADER)BitmapInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); // 位图头大小BitmapInfo.bmiHeader.biWidth = BitmapWidth;    // 设置位图的宽度BitmapInfo.bmiHeader.biHeight = -BitmapHeight; // 设置位图的高度BitmapInfo.bmiHeader.biPlanes = 1; // 设置颜色平面数,通常为 1BitmapInfo.bmiHeader.biBitCount = 32; // 每像素的位数,这里为 32 位(即 RGBA)BitmapInfo.bmiHeader.biCompression = BI_RGB; // 无压缩,直接使用 RGB 颜色模式// 创建 DIBSection(设备独立位图)并返回句柄// TODO:我们可以自己分配?int BitmapMemorySize = (BitmapWidth * BitmapHeight) * BytesPerPixel;BitmapMemory = VirtualAlloc(0, // lpAddress:指定内存块的起始地址。// 通常设为 NULL,由系统自动选择一个合适的地址。BitmapMemorySize, // 要分配的内存大小,单位是字节。MEM_COMMIT, // 分配物理内存并映射到虚拟地址。已提交的内存可以被进程实际访问和操作。PAGE_READWRITE // 内存可读写);// TODO:可能会把它清除成黑色
}// 这个函数用于将 DIBSection 绘制到窗口设备上下文
internal void Win32UpdateWindow(HDC DeviceContext, RECT *windowRect, int X,int Y, int Width, int Height) {int WindowWidth = windowRect->right - windowRect->left;int WindowHeight = windowRect->bottom - windowRect->top;// 使用 StretchDIBits 将 DIBSection 绘制到设备上下文中StretchDIBits(DeviceContext, // 目标设备上下文(窗口或屏幕的设备上下文)/*X, Y, Width, Height, // 目标区域的 x, y 坐标及宽高X, Y, Width, Height,*/0, 0, BitmapWidth, BitmapHeight, //0, 0, WindowWidth, WindowHeight,// 源区域的 x, y 坐标及宽高(此处源区域与目标区域相同)BitmapMemory,   // 位图内存指针,指向 DIBSection 数据&BitmapInfo,    // 位图信息,包含位图的大小、颜色等信息DIB_RGB_COLORS, // 颜色类型,使用 RGB 颜色SRCCOPY); // 使用 SRCCOPY 操作符进行拷贝(即源图像直接拷贝到目标区域)
}LRESULT CALLBACK
Win32MainWindowCallback(HWND hwnd, // 窗口句柄,表示消息来源的窗口UINT Message, // 消息标识符,表示当前接收到的消息类型WPARAM wParam, // 与消息相关的附加信息,取决于消息类型LPARAM LParam) { // 与消息相关的附加信息,取决于消息类型LRESULT Result = 0; // 定义一个变量来存储消息处理的结果switch (Message) { // 根据消息类型进行不同的处理case WM_CREATE: {OutputDebugStringA("WM_CREATE\n");};case WM_SIZE: { // 窗口大小发生变化时的消息RECT clientRect;GetClientRect(hwnd, &clientRect);// 计算绘制区域的宽度和高度int Height = clientRect.bottom - clientRect.top;int Width = clientRect.right - clientRect.left;Win32ResizeDIBSection(Width, Height);OutputDebugStringA("WM_SIZE\n"); // 输出调试信息,表示窗口大小已改变} break;case WM_DESTROY: { // 窗口销毁时的消息// TODO: 处理错误,用重建窗口Running = false;} break;case WM_CLOSE: { // 窗口关闭时的消息// TODO: 像用户发送消息进行处理Running = false;} break;case WM_ACTIVATEAPP: { // 应用程序激活或失去焦点时的消息OutputDebugStringA("WM_ACTIVATEAPP\n"); // 输出调试信息,表示应用程序激活或失去焦点} break;case WM_PAINT: { // 处理 WM_PAINT 消息,通常在窗口需要重新绘制时触发PAINTSTRUCT Paint; // 定义一个 PAINTSTRUCT 结构体,保存绘制的信息// 调用 BeginPaint 开始绘制,并获取设备上下文 (HDC),同时填充 Paint 结构体HDC DeviceContext = BeginPaint(hwnd, &Paint);// 获取当前绘制区域的左上角坐标int X = Paint.rcPaint.left;int Y = Paint.rcPaint.top;// 计算绘制区域的宽度和高度int Height = Paint.rcPaint.bottom - Paint.rcPaint.top;int Width = Paint.rcPaint.right - Paint.rcPaint.left;RECT clientRect;GetClientRect(hwnd, &clientRect);Win32UpdateWindow(DeviceContext, &clientRect, X, Y, Width, Height);#if 0local_persist DWORD Operation = WHITENESS;// 使用 WHITENESS 操作符填充矩形区域为白色PatBlt(DeviceContext, X, Y, Width, Height, Operation);// 设置窗体的颜色在刷新时白色和黑色之间来回变换if (Operation == WHITENESS) {Operation = BLACKNESS;} else {Operation = WHITENESS;}
#endif// 调用 EndPaint 结束绘制,并释放设备上下文EndPaint(hwnd, &Paint);} break;default: { // 对于不处理的消息,调用默认的窗口过程Result = DefWindowProc(hwnd, Message, wParam,LParam); // 调用默认窗口过程处理消息} break;}return Result; // 返回处理结果
}int CALLBACK WinMain(HINSTANCE hInst, HINSTANCE hInstPrev, //PSTR cmdline, int cmdshow) {WNDCLASS WindowClass = {};// 使用大括号初始化,所有成员都被初始化为零(0)或 nullptr// WindowClass.style:表示窗口类的样式。通常设置为一些 Windows// 窗口样式标志(例如 CS_HREDRAW, CS_VREDRAW)。WindowClass.style = CS_OWNDC | CS_HREDRAW | CS_VREDRAW;// CS_HREDRAW 当窗口的宽度发生变化时,窗口会被重绘。// CS_VREDRAW 当窗口的高度发生变化时,窗口会被重绘//  WindowClass.lpfnWndProc:指向窗口过程函数的指针,窗口过程用于处理与窗口相关的消息。WindowClass.lpfnWndProc = Win32MainWindowCallback;// WindowClass.hInstance:指定当前应用程序的实例句柄,Windows// 应用程序必须有一个实例句柄。WindowClass.hInstance = hInst;// WindowClass.lpszClassName:指定窗口类的名称,通常用于创建窗口时注册该类。WindowClass.lpszClassName = "gameWindowClass"; // 类名if (RegisterClass(&WindowClass)) {             // 如果窗口类注册成功HWND Window = CreateWindowEx(0,                         // 创建窗口,使用扩展窗口风格WindowClass.lpszClassName, // 窗口类的名称,指向已注册的窗口类"game",                    // 窗口标题(窗口的名称)WS_OVERLAPPEDWINDOW |WS_VISIBLE, // 窗口样式:重叠窗口(带有菜单、边框等)并且可见CW_USEDEFAULT, // 窗口的初始位置:使用默认位置(X坐标)CW_USEDEFAULT, // 窗口的初始位置:使用默认位置(Y坐标)CW_USEDEFAULT, // 窗口的初始宽度:使用默认宽度CW_USEDEFAULT, // 窗口的初始高度:使用默认高度0,             // 父窗口句柄(此处无父窗口,传0)0,             // 菜单句柄(此处没有菜单,传0)hInst,         // 当前应用程序的实例句柄0 // 额外的创建参数(此处没有传递额外参数));// 如果窗口创建成功,Window 将保存窗口的句柄if (Window) { // 检查窗口句柄是否有效,若有效则进入消息循环int xOffset = 0;int yOffset = 0;Running = true;while (Running) { // 启动一个无限循环,等待和处理消息MSG Message;    // 声明一个 MSG 结构体,用于接收消息while (PeekMessage(&Message,// 指向一个 `MSG` 结构的指针。`PeekMessage`// 将在 `lpMsg` 中填入符合条件的消息内容。0,// `hWnd` 为`NULL`,则检查当前线程中所有窗口的消息;// 如果设置为特定的窗口句柄,则只检查该窗口的消息。0, //0, // 用于设定消息类型的范围PM_REMOVE // 将消息从消息队列中移除,类似于 `GetMessage` 的行为。)) {if (Message.message == WM_QUIT) {Running = false;}TranslateMessage(&Message); // 翻译消息,如果是键盘消息需要翻译DispatchMessage(&Message); // 分派消息,调用窗口过程处理消息}RenderWeirdGradient(xOffset, yOffset);// 这个地方需要渲染一下不然是黑屏{HDC DeviceContext = GetDC(Window);RECT WindowRect;GetClientRect(Window, &WindowRect);int WindowWidth = WindowRect.right - WindowRect.left;int WindowHeigh = WindowRect.bottom - WindowRect.top;Win32UpdateWindow(DeviceContext, &WindowRect, 0, 0, WindowWidth,WindowHeigh);ReleaseDC(Window, DeviceContext);}++xOffset;}} else { // 如果窗口创建失败// 这里可以处理窗口创建失败的逻辑// 比如输出错误信息,或退出程序等// TODO:}} else { // 如果窗口类注册失败// 这里可以处理注册失败的逻辑// 比如输出错误信息,或退出程序等// TODO:}return 0;
}

在这里插入图片描述

相关文章:

游戏引擎学习第四天

视频参考:https://www.bilibili.com/video/BV1aDmqYnEnc/ BitBlt 是 Windows GDI&#xff08;图形设备接口&#xff09;中的一个函数&#xff0c;用于在设备上下文&#xff08;device context, DC&#xff09;之间复制位图数据。BitBlt 的主要用途是将一个图像区域从一个地方复…...

GIT GUI和 GIT bash区别

Git GUI 和 Git Bash 都是与 Git 版本控制工具相关的用户界面&#xff0c;但它们有不同的功能和用途。下面详细说明它们的区别及各自的作用&#xff1a; Git GUI 作用&#xff1a; Git GUI 是一个图形用户界面&#xff08;GUI&#xff09;工具&#xff0c;用于执行 Git 操作。…...

丹摩征文活动|Faster-Rcnn-训练与测试详细教程

本文 丹摩智算平台官方网站的介绍Faster-Rcnn-训练与测试提前准备进行Faster-rcnn 的环境配置数据集的介绍 丹摩智算平台官方网站的介绍 丹摩智算平台&#xff08;DAMODEL&#xff09;是专为人工智能&#xff08;AI&#xff09;开发者打造的高性能计算服务平台&#xff0c;旨在…...

星期-时间范围选择器 滑动选择时间 最小粒度 vue3

星期-时间范围选择器 功能介绍属性说明事件说明实现代码使用范例 根据业务需要&#xff0c;实现了一个可选择时间范围的周视图。用户可以通过鼠标拖动来选择时间段&#xff0c;并且可以通过快速选择组件来快速选择特定的时间范围。 功能介绍 时间范围选择&#xff1a;用户可以…...

一条SQL查询语句的执行流程(MySQL)

第一步&#xff1a;连接器&#xff08;负责跟客户端建立连接、获取权限、维持和管理连接&#xff09; 第二步&#xff1a;查询缓存 之前执行过的查询&#xff0c;MySQL以"Key - Value"的形式存在内存&#xff08;key为SQL&#xff0c;value为结果集&#xff09;&…...

linux基础——详细篇

免责声明 学习视频来自B 站up主泷羽sec&#xff0c;如涉及侵权马上删除文章。 笔记的只是方便各位师傅学习知识&#xff0c;以下代码、网站只涉及学习内容&#xff0c;其他的都与本人无关&#xff0c;切莫逾越法律红线&#xff0c;否则后果自负。 linux 基础命令重现 cd(切…...

大数据学习10之Hive高级

1.Hive高级 将大的文件按照某一列属性进行GROUP BY 就是分区&#xff0c;只是默认开窗存储&#xff1b; 分区是按行&#xff0c;如一百行数据&#xff0c;按十位上的数字分区&#xff0c;则有十个分区&#xff0c;每个分区里有十行&#xff1b; 分桶是根据某个字段哈希对桶数取…...

MongoDB笔记01-概念与安装

文章目录 前言一、MongoDB相关概念1.1 业务应用场景具体的应用场景什么时候选择MongoDB 1.2 MongoDB简介1.3 体系结构1.4 数据模型1.5 MongoDB的特点 二、本地单机部署2.1 Windows系统中的安装启动第一步&#xff1a;下载安装包第二步&#xff1a;解压安装启动1.命令行参数方式…...

ollama + fastGPT + m3e 本地部署指南

[TOC](ollama fastgptm3e本地部署) 开启WSL 因为这里使用的win部署&#xff0c;所以要安装wsl,如果是linux系统就没那么麻烦 控制面板->程序->程序和功能 更新wsl wsl --set-default-version 2wsl --update --web-download安装ubuntu wsl --install -d Ubuntudoc…...

【设计模式系列】享元模式(十五)

目录 一、什么是享元模式 二、享元模式的角色 三、享元模式的典型应用场景 四、享元模式在ThreadPoolExecutor中的应用 1. 享元对象&#xff08;Flyweight&#xff09;- 工作线程&#xff08;Worker&#xff09; 2. 享元工厂&#xff08;Flyweight Factory&#xff09;- …...

2024大兴区火锅美食节即将开幕——品味多元火锅,点燃冬季消费热潮

为响应“中国国际精品消费月”活动&#xff0c;由大兴区商务局主办、大兴区餐饮行业协会承办的2024大兴区火锅美食节将于11月15日正式启动&#xff0c;为期一个半月的美食盛宴将在大兴区掀起一场冬日的火锅热潮。此次火锅节作为北京市“食在京城、沸腾火锅”火锅美食节的重要组…...

可视化建模与UML《类图实验报告》

史铁生&#xff1a; 余华和莫言扛着我上火车&#xff0c; 推着走打雪仗&#xff0c; 还带我偷西瓜&#xff0c; 被人发现后他们拔腿就跑&#xff0c; 却忘了我还在西瓜地里。 一、实验目的&#xff1a; 1、熟悉类图的构件事物。 2、熟悉类之间的泛化、依赖、聚合和组合关系…...

VS2022项目配置笔记

文章目录 $(ProjectDir&#xff09;与 $(SolutionDir) 宏附加包含目录VC目录和C/C的区别 $(ProjectDir&#xff09;与 $(SolutionDir) 宏 假设有一个解决方案 MySolution&#xff0c;其中包含两个项目 ProjectA 和 ProjectB&#xff0c;目录结构如下&#xff1a; C:\Projects\…...

springboot029基于springboot的网上购物商城系统

&#x1f345;点赞收藏关注 → 添加文档最下方联系方式领取本源代码、数据库&#x1f345; 本人在Java毕业设计领域有多年的经验&#xff0c;陆续会更新更多优质的Java实战项目希望你能有所收获&#xff0c;少走一些弯路。&#x1f345;关注我不迷路&#x1f345; 项目视频 基于…...

网站访问在TCP/IP四层模型中的流程

访问一个网站的过程可以通过 TCP/IP 网络模型来描述。TCP/IP 模型通常被分为四层&#xff1a;应用层、传输层、网络层和链路层。以下是从这些层级的角度描述你访问一个网站时所发生的过程&#xff1a; 1. 应用层 (Application Layer) 当你在浏览器中输入一个 URL&#xff08;…...

C++笔记---包装器

1. 什么是包装器 C中的包装器是一种设计模式&#xff0c;用于将一个复杂或底层的接口进行封装&#xff0c;以便提供一个更简洁、易用的接口。包装器可以包装任何类型的可调用实体&#xff0c;如函数&#xff0c;成员函数&#xff0c;函数指针&#xff0c;仿函数对象&#xff0…...

算力与能量的全分布式在线共享来降低5G网络的用电成本。基于随机对偶次梯度法的多时隙约束耦合问题解耦方法示例;随机对偶次梯度法的在线管理策略

目录 算力与能量的全分布式在线共享来降低5G网络的用电成本。 基于随机对偶次梯度法的多时隙约束耦合问题解耦方法示例 随机对偶次梯度法的在线管理策略 策略概述 具体步骤 示例说明 算力与能量的全分布式在线共享来降低5G网络的用电成本。 主要探讨了5G网络与边缘计算设…...

海鲜特写镜头视频素材去哪找 热门视频素材网站分享

作为美食自媒体创作者&#xff0c;海鲜特写镜头的视频素材无疑是提升内容吸引力和质量的重要利器。无论你想展示新鲜的海鲜原料、精美的烹饪过程&#xff0c;还是诱人的餐桌美食&#xff0c;精致的海鲜特写镜头都能极大地吸引观众的注意力。那么&#xff0c;问题来了&#xff1…...

JMM内存模型(面试回答)

1.什么是JMM JMM就是Java内存模型(java memory model)。因为在不同的硬件生产商和不同的操作系统下&#xff0c;内存的访问有一定的差异&#xff0c;所以会造成相同的代码运行在不同的系统上会出现各种问题。所以Java内存模型(JMM)屏蔽掉各种硬件和操作系统的内存访问差异&…...

Greiner 经典力学(多体系统和哈密顿力学)第十二章 学习笔记(Rotation About a Point)

第十二章 学习笔记&#xff08;Rotation About a Point&#xff09; 上一章是绕定轴转动&#xff0c;这章是绕定点转动。这一章明显上难度了。 12.1 Tensor of Inertia 在正式的公式推导之前&#xff0c;我们先复习一个矢量公式&#xff0c;下面推导时会用到这个公式&#x…...

SQL进阶技巧:如何计算复合增长率?

目录 0 场景描述 1 数据准备 2 问题分析 3 小结 0 场景描述 复合增长率是第N期的数据除以第一期的基准数据,然后开N-1次方再减去1得到的结果。假如2018年的产品销售额为10000,2019年的产品销售额为12500,2020年的产品销售额为15000(销售额单位省略,下同)​。那么这两…...

十一:java web(3)-- Spring框架 -- Spring简介

目录 1. Servlet 与 Spring 的关系 2. Spring 框架介绍 Spring 框架的起源与发展 Spring 框架的核心特性 Spring 主要模块介绍 核心模块&#xff08;Core Container&#xff09; 数据访问与集成模块&#xff08;Data Access/Integration&#xff09; Web 模块&#xff0…...

ts 如何配置引入 json 文件

ts 如何配置引入 json 文件 参考文档&#xff1a; https://maxgadget.dev/article/how-to-import-a-json-file-in-typescript-a-comprehensive-guide 项目中有一个 .json 的文件是配置文件&#xff0c;如何引入到 ts 项目中 配置 tsconfig.json 文件&#xff0c;添加这两个 {…...

LeetCode面试经典150题C++实现,更新中

用C实现下面网址的题目 https://leetcode.cn/problems/merge-sorted-array/?envTypestudy-plan-v2&envIdtop-interview-150 1、数组\字符串 88合并两个有序数组 以下是使用 C 实现合并两个有序数组的代码及测试用例 C代码实现 #include <iostream> #include &l…...

基于springboot的家装平台设计与实现

项目描述 临近学期结束&#xff0c;还是毕业设计&#xff0c;你还在做java程序网络编程&#xff0c;期末作业&#xff0c;老师的作业要求觉得大了吗?不知道毕业设计该怎么办?网页功能的数量是否太多?没有合适的类型或系统?等等。这里根据疫情当下&#xff0c;你想解决的问…...

CSS的配色

目录 1 十六进制2 CSS中的十六进制2.1 十六进制颜色的基本结构2.2 十六进制颜色的范围2.3 简写形式2.4 透明度 3 CSS的命名颜色4 配色4.1 色轮4.2 互补色4.3 类似色4.4 配色工具 日常在开发小程序中&#xff0c;客户总是希望你的配色是美的&#xff0c;但是美如何定义&#xff…...

Parallax.js:让智能设备视差效果更智能、更自然

今天给大家分享一款功能非常强大的javascript视觉差特效引擎插件&#xff1a;Parallax.js。 Parallax.js简介 Parallax.js是一个简单的&#xff0c;轻量级的视差引擎。你可以将它作为作为jQuery或Zepto插件来使用&#xff0c;也可以以纯JS的方式来使用。 最-最-最厉害的是它…...

一文熟悉新版llama.cpp使用并本地部署LLAMA

0. 简介 最近是快到双十一了再给大家上点干货。去年我们写了一个大模型的系列&#xff0c;经过一年&#xff0c;大模型的发展已经日新月异。这一次我们来看一下使用llama.cpp这个项目&#xff0c;其主要解决的是推理过程中的性能问题。主要有两点优化&#xff1a; llama.cpp …...

vue/react做多语言国际化的时候,在语言配置中不同的语言配置不同的字体,动态引入scss里面

如果想直接在vue文件的css里面使用&#xff0c;就可以使用i18n的t函数&#xff0c;注意t外层也有引号&#xff1a; font-size: v-bind("t(style.teamCurModelFontSize)"); 前提是要引入t函数&#xff1a;...

Unity——鼠标点击信息和当前位置获取

文章目录 前言一、应用场景二、实现方法1.获取鼠标在屏幕上的位置2.获取鼠标点击位置的世界坐标3.获取鼠标点击位置的UI元素总结前言 在Unity开发中,有时会需要我们获取一些鼠标的信息用于数据交互或者角色控制。 一、应用场景 交互式UI 按钮点击:检测用户是否点击了UI按钮,…...