返回

Rust Win32 API图像显示全黑问题排查及解决方案

windows

Win32 API 图像显示问题排查

当使用 Win32 API 在 Rust 中尝试显示图像时,出现全黑方块而非预期图像是很常见的错误。此类问题通常由以下原因造成:

问题一:位图格式不匹配

原因分析: CreateCompatibleBitmap 创建的位图,默认颜色格式可能与图像数据不匹配,导致颜色信息丢失,呈现全黑效果。尽管此处使用的图像是 RGBA 格式,兼容位图可能并非如此设置,尤其是在屏幕色彩模式非 32 位色时,容易发生不兼容。

解决方案: 必须通过 SetDIBitsToDevice 将图像数据明确设定到设备上下文中。需要注意的是 CreateCompatibleBitmap 需要用屏幕的设备上下文来初始化,确保兼容性。
操作步骤:

  1. 获取屏幕的设备上下文 (GetDC(NULL))。
  2. 基于该设备上下文,创建兼容的设备上下文(CreateCompatibleDC).
  3. 使用设备上下文初始化 CreateCompatibleBitmap,创建指定宽度和高度的兼容位图,然后,使用 BITMAPINFOHEADER 结构指定图像格式,其中,高度值需为负值以表示自上而下的位图格式。
  4. 使用 SetDIBitsToDevice 将像素数据传递到该位图中。
  5. 清理之前步骤中获取的设备上下文(DeleteDC)以及屏幕设备上下文(ReleaseDC)。

代码示例:

fn load_image_as_bitmap(image_path: &str) -> HGDIOBJ {
    let img = image::open(image_path).expect("Failed to load image");
    let (width, height) = img.dimensions();
    let img = img.to_rgba8();
    
    let hdc_screen = unsafe { GetDC(HWND(std::ptr::null_mut())) };
    if hdc_screen.0.is_null() {
        panic!("Failed to get screen DC: {}", unsafe { GetLastError().0 });
    }
    let hdc = unsafe { CreateCompatibleDC(hdc_screen) };
    if hdc.0.is_null() {
       panic!("Failed to create compatible DC: {}", unsafe { GetLastError().0 });
    }

    let hbitmap = unsafe { CreateCompatibleBitmap(hdc, width as i32, height as i32) };
    if hbitmap.0.is_null() {
        panic!("Failed to create compatible bitmap: {}", unsafe { GetLastError().0 });
    }

    let bmp_info_header = BITMAPINFOHEADER {
        biSize: std::mem::size_of::<BITMAPINFOHEADER>() as u32,
        biWidth: width as i32,
        biHeight: -(height as i32), 
        biPlanes: 1,
        biBitCount: 32,
        biCompression: 0,
        ..Default::default()
    };

    let mut bmp_info = BITMAPINFO {
        bmiHeader: bmp_info_header,
        bmiColors: [RGBQUAD::default(); 1],
    };

    unsafe {
         let res = SetDIBitsToDevice(
            hdc,
            0,
            0,
            width as u32,
            height as u32,
            0,
            0,
            0,
            height as u32,
            img.as_raw().as_ptr() as *const _,
            &mut bmp_info,
            DIB_RGB_COLORS,
         );

        if res == 0 {
            eprintln!("SetDIBitsToDevice failed: {}", GetLastError().0);
        }

    }

     unsafe {
        DeleteDC(hdc);
         ReleaseDC(HWND(std::ptr::null_mut()), hdc_screen);
    }
     
    hbitmap.into()
}

问题二:设备上下文(DC)使用不当

原因分析: 尝试直接用CreateWindowExW 返回的窗口句柄HWND 作为设备上下文是错误的,正确的做法是获取一个与窗口相关的 DC,用此 DC 做绘图操作。此外, BitBlt 中的源和目标设备上下文不正确,会导致位图数据无法正确传递。

解决方案:
需要在窗口绘制消息(WM_PAINT)处理过程中,使用BeginPaint 函数获取绘画设备上下文,并将图像通过BitBlt从兼容内存 DC 中拷贝到该设备上下文中。完成绘制后使用 EndPaint 释放资源。

操作步骤:

  1. WM_PAINT消息处理中调用BeginPaint,获得用于绘制的 HDC
  2. 创建内存设备上下文,然后选入位图。
  3. 使用 BitBlt 将位图内容绘制到窗口 DC 上。
  4. 清除内存 DC,然后通过调用EndPaint完成绘图操作,并释放 DC。

代码示例:

unsafe extern "system" fn window_proc(
    hwnd: HWND,
    msg: u32,
    wparam: WPARAM,
    lparam: LPARAM,
) -> LRESULT {
    match msg {
        WM_PAINT => {
            let mut ps: PAINTSTRUCT = std::mem::zeroed();
            let hdc = BeginPaint(hwnd, &mut ps);
            
            if let Some(bitmap) = BITMAP_HANDLE {
                let hdc_mem = CreateCompatibleDC(hdc);
                if hdc_mem.0.is_null() {
                   eprintln!("Failed to create compatible DC: {}", GetLastError().0);
                }

               if SelectObject(hdc_mem, bitmap).0.is_null() {
                  eprintln!("Failed to select bitmap into DC: {}", GetLastError().0);
               }


               if BitBlt(hdc, 0, 0, 300, 300, hdc_mem, 0, 0, SRCCOPY).is_err() {
                    eprintln!("BitBlt failed: {}", GetLastError().0);
                }


                 DeleteDC(hdc_mem);
            }

            EndPaint(hwnd, &ps);
            return LRESULT(0);
        }
        _ => DefWindowProcW(hwnd, msg, wparam, lparam),
    }
}

附加安全建议:

  • 在处理 Win32 API 资源(例如设备上下文,位图等)后,及时释放这些资源,防止资源泄漏。
  • 使用错误处理机制(GetLastError),记录 API 调用中的错误,方便调试。

使用这些改进后的方法,就可以确保在Win32 API 的窗口中,能正确加载并显示图像,避免显示全黑方块的情况发生。正确处理 HDC,并进行颜色格式和数据复制的设置是图像显示成功的关键。