opengl-day01 绘制窗口

 

 

  • CS_OWNDC: 为窗口分配独立的设备上下文(重要:每个窗口有自己的DC)

  • CS_HREDRAW | CS_VREDRAW: 窗口大小改变时重绘

 

 

  • HDC: 设备上下文句柄,Windows的绘图接口

  • 使用内存DC进行离屏绘制,避免闪烁

HDC hDc = GetDC(hWnd);           // 获取窗口DC
HDC hMem = ::CreateCompatibleDC(hDc);  // 创建内存DC

  

 

  • 创建32位ARGB格式的DIB(设备无关位图)

  • buffer 指针指向像素数据,可以直接操作
BITMAPINFO bmpInfo;
// ... 设置位图信息
HBITMAP hBmp = CreateDIBSection(hDc, &bmpInfo, DIB_RGB_COLORS, (void**)&buffer, 0, 0);

  

while (true) {
    // 消息处理
    if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
        // 处理消息
    }
    
    // 绘制逻辑
    memset(buffer, 0, width* height * 4);  // 清屏为黑色
    
    // 绘制白色横线
    unsigned char* rgba = (unsigned char*)buffer;
    int pitch = width * 4;
    memset(rgba + pitch * 10, 255, pitch);  // 第11行设为白色
    
    // 显示到窗口
    BitBlt(hDc, 0, 0, width, height, hMem, 0, 0, SRCCOPY);
}

  

 

绘制机制详解

  1. 双缓冲技术:

    • 先在内存DC中绘制

    • 然后一次性复制到屏幕DC

    • 避免闪烁

  2. 像素格式:

    • biBitCount = 32: 32位色,RGBA格式

    • 每个像素4字节:Blue(0), Green(1), Red(2), Alpha(3)

    • 由于使用 memset 设置255,实际得到的是白色(所有分量都是255)

  3. 绘制坐标:

    • pitch = width * 4: 每行的字节数

    • rgba + pitch * 10: 指向第11行开头(索引从0开始)

 

unsigned char* rgba = (unsigned char*)buffer;

  

  • buffer 原本是 void* 类型(通用指针)

  • 转换为 unsigned char* 类型

  • 赋值给新的指针变量 rgba

 

为什么要转换?

1. 原始buffer类型

cpp
void* buffer = 0;  // 通用指针,没有具体类型

  

  • void* 是"无类型指针",可以指向任何数据类型

  • 但你不能直接通过 void* 访问内存内容:

    buffer[0] = 255;  // 错误!void*不能直接下标访问
    *buffer = 0;      // 错误!不能解引用void*
    

      

2. 转换为unsigned char*的好处

unsigned char* rgba = (unsigned char*)buffer;

  

  • unsigned char 是1字节(8位)的无符号整数

  • 转换为 unsigned char* 后:

    • 可以按字节访问内存

    • 支持指针算术运算

    • 支持下标操作符

BitBlt(hDc, 0, 0, width, height, hMem, 0, 0, SRCCOPY);

  这行代码是Windows GDI中非常重要的位块传输函数,用于将图像从源设备上下文复制到目标设备上下文。

BOOL BitBlt(
  HDC hdcDest,   // 目标DC
  int nXDest,    // 目标左上角X坐标
  int nYDest,    // 目标左上角Y坐标
  int nWidth,    // 传输宽度
  int nHeight,   // 传输高度
  HDC hdcSrc,    // 源DC
  int nXSrc,     // 源左上角X坐标
  int nYSrc,     // 源左上角Y坐标
  DWORD dwRop    // 光栅操作码
);

  

参数说明
hdcDest hDc 目标DC(窗口的屏幕DC)
nXDest 0 目标位置X坐标(窗口左上角)
nYDest 0 目标位置Y坐标(窗口左上角)
nWidth width 传输宽度(窗口客户区宽度)
nHeight height 传输高度(窗口客户区高度)
hdcSrc hMem 源DC(内存DC,包含绘制好的位图)
nXSrc 0 源位置X坐标(从位图左上角开始)
nYSrc 0 源位置Y坐标(从位图左上角开始)
dwRop SRCCOPY 光栅操作:直接复制

工作原理

1. 内存DC和屏幕DC的关系

// 之前已经创建了:
HDC hDc = GetDC(hWnd);           // 获取窗口的屏幕DC
HDC hMem = ::CreateCompatibleDC(hDc);  // 创建与屏幕兼容的内存DC
HBITMAP hBmp = CreateDIBSection(...);  // 创建位图
SelectObject(hMem, hBmp);              // 将位图选入内存DC

// 此时:
// hMem(内存DC)中有一个位图,可以通过buffer指针直接修改
// hDc(屏幕DC)连接到窗口的显示区域

2. BitBlt执行的过程

内存DC (hMem)         BitBlt复制        屏幕DC (hDc)
+--------------+      ---------->      +--------------+
| 我们绘制的    |                      |  用户看到的   |
| 位图数据     |                      |  窗口内容    |
|  (离屏缓冲)  |                      |  (屏幕上)    |
+--------------+                      +--------------+
       ^                                      |
       |                                      |
       +--------------------------------------+
           通过buffer指针修改位图数据

  

3. 具体步骤可视化

// 步骤1:在内存中绘制
unsigned char* rgba = (unsigned char*)buffer;
memset(buffer, 0, width * height * 4);        // 清空为黑色
memset(rgba + pitch * 10, 255, pitch);        // 绘制白色横线

// 步骤2:BitBlt复制到屏幕
BitBlt(hDc, 0, 0, width, height, hMem, 0, 0, SRCCOPY);
// 等价于:
// 将内存DC中(0,0)开始、大小为(width,height)的区域
// 复制到屏幕DC的(0,0)位置

  

光栅操作码(Raster Operation Codes)

SRCCOPY 是最常用的操作码,表示直接复制源到目标。

其他常用操作码:

操作码说明
SRCCOPY 0x00CC0020 直接复制源到目标
SRCPAINT 0x00EE0086 源OR目标
SRCAND 0x008800C6 源AND目标
SRCINVERT 0x00660046 源XOR目标
MERGECOPY 0x00C000CA 源AND模式
MERGEPAINT 0x00BB0226 (NOT 源) OR 目标
BLACKNESS 0x00000042 输出黑色
WHITENESS 0x00FF0062 输出白色

 

// 不同的复制效果
BitBlt(hDc, 0, 0, width, height, hMem, 0, 0, SRCCOPY);   // 正常复制
BitBlt(hDc, 0, 0, width, height, hMem, 0, 0, SRCINVERT); // 异或效果(反色)
BitBlt(hDc, 0, 0, width, height, 0, 0, 0, BLACKNESS);    // 填充黑色

  BitBlt是实现双缓冲的关键:

while (true) {
    // 1. 在内存中绘制(用户看不到)
    DrawToMemoryBuffer(buffer);  // 绘制到buffer
    
    // 2. 一次性复制到屏幕(用户看到完整一帧)
    BitBlt(hDc, 0, 0, width, height, hMem, 0, 0, SRCCOPY);
    
    // 优点:避免闪烁,提高绘制性能
}

  

性能考虑

  1. 尽量一次性复制:减少BitBlt调用次数

  2. 只复制需要更新的区域:

    // 只更新变化的部分
    BitBlt(hDc, dirtyX, dirtyY, dirtyWidth, dirtyHeight, 
           hMem, dirtyX, dirtyY, SRCCOPY);
    

      

  3. 避免频繁的DC获取和释放:

    // 错误:每次循环都获取释放DC
    // 正确:在循环外获取,最后释放
    

      

    相关函数

    • StretchBlt():支持缩放的位块传输

    • TransparentBlt():支持透明色的位块传输

    • AlphaBlend():支持Alpha混合的位块传输

    BitBlt 是Windows图形编程的基础,理解它对于掌握Windows GDI编程至关重要。

 

#include <Windows.h>

 //添加了 CALLBACK 调用约定
LRESULT CALLBACK  windowProc(
	HWND hWnd,
	UINT uMsg,
	WPARAM wParam,
	LPARAM lParam
)
{
	switch (uMsg) {
	case WM_CREATE: {
		break;
	}
	case WM_SIZE: {
		break;
	}
	case WM_CLOSE:
	{
		// 询问用户是否真的要关闭
		if (MessageBox(hWnd, "确定要关闭窗口吗?", "确认", MB_YESNO | MB_ICONQUESTION) == IDYES) {
			DestroyWindow(hWnd);
		}
		break;
	}
	case WM_DESTROY:
		PostQuitMessage(0);
	default:
		break;
	}


	return DefWindowProc(hWnd, uMsg, wParam, lParam);  // 修正函数名

 }

int WINAPI WinMain(
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nShowCmd
)
{

	//1、注册窗口类
	/**
	这里的 :: 表示
	明确使用全局命名空间中的 WNDCLASSEXA
	避免与其他命名空间中可能存在的同名类型冲突
	namespace MyLibrary {
	struct WNDCLASSEXA {  // 自定义的同名类型
		int myData;
	};
}

void MyFunction() {
	// 如果没有::,编译器会报错或选择错误的类型
	// WNDCLASSEXA myVar;  // 错误:MyLibrary::WNDCLASSEXA 还是 ::WNDCLASSEXA?

	::WNDCLASSEXA winClass = { 0 };  // 明确使用全局的Windows API类型
	MyLibrary::WNDCLASSEXA myClass;  // 使用MyLibrary的类型
}

WNDCLASSEX 结构类似于 WNDCLASS 结构。 有两个区别。 WNDCLASSEX 包括 cbSize 成员,
该成员指定结构的大小,以及 hIconSm 成员,该成员包含与窗口类关联的小图标的句柄。
	*/
	::WNDCLASSEXA winClass = { 0 };
	winClass.lpszClassName = "Raster";
	winClass.cbSize = sizeof(::WNDCLASSEX);
	winClass.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC | CS_DBLCLKS;
	winClass.lpfnWndProc = windowProc;
	winClass.hInstance = hInstance;
	winClass.hIcon = 0;
	winClass.hIconSm = 0;
	winClass.hCursor = LoadCursor(NULL, IDC_ARROW);  // 设置光标
	winClass.hbrBackground = (HBRUSH)(BLACK_BRUSH);
	winClass.lpszMenuName = NULL;
	winClass.cbClsExtra = 0;
	winClass.cbWndExtra = 0;

	// 2、注册窗口类
	  if (!RegisterClassExA(&winClass)) {
	       MessageBox(NULL, "窗口类注册失败!", "错误", MB_ICONERROR);
		       return 1;
	}

	  //3、创建窗口  通常是CreateWindow或CreateWindowEx的返回值
	  HWND hWnd = CreateWindowExA(
	  NULL,
		  "Raster",
		  "Raster",
		  WS_OVERLAPPEDWINDOW|WS_CLIPCHILDREN|WS_CLIPSIBLINGS,    // 标准窗口样式(包含标题栏、边框等)
		  0,
		  0,
		  480,
		  320,
		  0,
		  0,
		  hInstance,
		  0
		  );


	  if (!hWnd) {
		  ::MessageBoxA(NULL, "窗口创建失败!", "错误", MB_ICONERROR | MB_OK);
		  return 1;
	  }
	
	  // 4、显示窗口
	  /*UpdateWindow(hWnd);
	  ShowWindow(hWnd, SW_SHOW);*/
	  // 4、显示窗口
	  ::ShowWindow(hWnd, nShowCmd);
	  ::UpdateWindow(hWnd);
	  

	  // 窗口 客户区的大小
	  RECT rt = { 0 };
	  GetClientRect(hWnd, &rt);
	  int width = rt.right - rt.left;
	  int height = rt.bottom - rt.top;
	  void* buffer = 0; // 指向图片中的数据

	  // DC 是windows 中绘制上下文对象
	  HDC hDc = GetDC(hWnd);
	  HDC hMem = ::CreateCompatibleDC(hDc);

	  // 创建一张图片
	  BITMAPINFO bmpInfo;
	  bmpInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
	  bmpInfo.bmiHeader.biWidth = width;
	  // bmpInfo.bmiHeader.biHeight = -height;  // 负值表示从上到下的扫描顺序
	  bmpInfo.bmiHeader.biHeight = height;
	  bmpInfo.bmiHeader.biPlanes = 1;
	  bmpInfo.bmiHeader.biBitCount = 32;
	  bmpInfo.bmiHeader.biCompression = BI_RGB;
	  bmpInfo.bmiHeader.biSizeImage = 0;
	  bmpInfo.bmiHeader.biXPelsPerMeter = 0;
	  bmpInfo.bmiHeader.biYPelsPerMeter = 0;
	  bmpInfo.bmiHeader.biClrUsed = 0;
	  bmpInfo.bmiHeader.biClrImportant = 0;

	  HBITMAP hBmp = CreateDIBSection(hDc, &bmpInfo, DIB_RGB_COLORS, (void**)&buffer, 0, 0);
	  SelectObject(hMem, hBmp);
	  memset(buffer, 0, width * height * 4);


	  MSG msg = { 0 };


	  /*
	      // 5、消息循环(正确的版本)
    MSG msg = { 0 };
    BOOL bRet;
    
    while ((bRet = ::GetMessageA(&msg, NULL, 0, 0)) != 0) {
        if (bRet == -1) {
            // 错误处理
            break;
        } else {
            ::TranslateMessage(&msg);
            ::DispatchMessageA(&msg);
        }
    }

    // 或者使用 PeekMessage 版本(适合游戏等需要实时处理的程序)
    
    while (true) {
        if (::PeekMessageA(&msg, NULL, 0, 0, PM_REMOVE)) {
            if (msg.message == WM_QUIT) {
                break;
            }
            ::TranslateMessage(&msg);
            ::DispatchMessageA(&msg);
        } else {
            // 这里可以添加游戏逻辑或其他处理
            // Sleep(1);  // 避免CPU占用过高
        }
    }
    

	  return (int)msg.wParam;
}
	  
	  
	  **/


	   // 或者使用 PeekMessage 版本(适合游戏等需要实时处理的程序)
	  while (true) {

		  if (
			  msg.message == WM_DESTROY ||
				  msg.message == WM_CLOSE ||
				  msg.message == WM_QUIT
			  ) {
			  break;

		  };
		  if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
			  TranslateMessage(&msg);
			  DispatchMessage(&msg);
		  }

		  memset(buffer, 0, width* height * 4);
		  // 修改一部分
		  unsigned char* rgba = (unsigned char*)buffer;
		  int pitch = width * 4;

		  memset(rgba+pitch*10, 255, pitch);
		  BitBlt(hDc, 0, 0, width, height, hMem, 0, 0, SRCCOPY);

	  }
		


	return 0;
}

  

posted @ 2025-12-16 10:20  巨兽~墨菲特  阅读(1)  评论(0)    收藏  举报