You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Windows平台Tauri应用捕获前台窗口截图Buffer全0问题排查

问题

在Windows平台的Tauri应用中尝试捕获前台窗口截图并保存为PNG文件(仅针对Windows系统),但得到的buffer全为0,生成的图片是纯黑色。怀疑问题出在bitmap_info_header的配置上,但对图像相关操作比较陌生,无法确定具体错误点。

原代码:

use image::{ImageBuffer};
use std::mem;
use std::path::Path;
use std::ptr::null_mut;
use winapi::shared::minwindef::DWORD;
use winapi::shared::windef::RECT;
use winapi::um::wingdi::{
    BitBlt, GetDIBits, BITMAPINFO, BITMAPINFOHEADER, BI_RGB, DIB_RGB_COLORS, RGBQUAD, SRCCOPY,
};
use winapi::um::winuser::{GetDC, GetForegroundWindow, GetWindowRect, ReleaseDC};

pub fn get_screenshot_buffer() -> SCBufferDetails {
    let hwnd = unsafe { GetForegroundWindow() };

    unsafe {
        let mut rect: RECT = std::mem::zeroed();
        GetWindowRect(hwnd, &mut rect);
        let width = rect.right - rect.left;
        let height = rect.bottom - rect.top;

        // returns handle to the device context
        let hdc = GetDC(hwnd);
        let mut buf: Vec<u32> = vec![0; (width * height) as usize];

        // perform a bit-block transfer from the window's DC to itself (src -> src)
        // effectively captures the screenshot of the window
        BitBlt(hdc, 0, 0, width, height, hdc, 0, 0, SRCCOPY);

        // Prepare the bitmap info header
        let mut bitmap_info_header: BITMAPINFOHEADER = mem::zeroed();
        bitmap_info_header.biSize = mem::size_of::<BITMAPINFOHEADER>() as DWORD;
        bitmap_info_header.biWidth = width;
        bitmap_info_header.biHeight = -height; // Negative height to ensure top-down image
        bitmap_info_header.biPlanes = 1;
        bitmap_info_header.biBitCount = 32;
        bitmap_info_header.biCompression = BI_RGB;

        // Prepare the bitmap info structure
        let mut bitmap_info: BITMAPINFO = mem::zeroed();
        bitmap_info.bmiHeader = bitmap_info_header;

        // retrieve the bitmap information from the window's DC and stores it in the buf buffer.
        GetDIBits(
            hdc,
            null_mut(),
            0,
            height as u32,
            buf.as_mut_ptr() as *mut _,
            &mut bitmap_info,
            DIB_RGB_COLORS,
        );
        // release the device context
        ReleaseDC(hwnd, hdc);

        return SCBufferDetails {
            buffer: buf,
            height: height as u32,
            width: width as u32,
        };
    }
}

问题分析与修复方案

你的代码存在几个核心错误,导致无法正确捕获图像:

  1. 无效的BitBlt操作:直接将窗口DC自身作为源和目标,没有实际捕获图像数据
  2. GetDIBits传入空位图句柄:无法从空句柄读取有效像素数据
  3. 缺少图像缓冲区:没有创建用于存储截图的内存位图和兼容DC

修复后的完整代码:

use std::mem;
use std::ptr::null_mut;
use winapi::shared::minwindef::{DWORD, HBITMAP, HDC};
use winapi::shared::windef::RECT;
use winapi::um::wingdi::{
    BitBlt, CreateCompatibleBitmap, CreateCompatibleDC, DeleteDC, DeleteObject, GetDIBits,
    SelectObject, BITMAPINFO, BITMAPINFOHEADER, BI_RGB, DIB_RGB_COLORS, SRCCOPY,
};
use winapi::um::winuser::{GetDC, GetForegroundWindow, GetWindowRect, ReleaseDC};

// 补充定义SCBufferDetails结构
#[derive(Debug)]
pub struct SCBufferDetails {
    pub buffer: Vec<u32>,
    pub width: u32,
    pub height: u32,
}

pub fn get_screenshot_buffer() -> SCBufferDetails {
    let hwnd = unsafe { GetForegroundWindow() };

    unsafe {
        let mut rect: RECT = mem::zeroed();
        GetWindowRect(hwnd, &mut rect);
        let width = rect.right - rect.left;
        let height = rect.bottom - rect.top;

        let window_dc: HDC = GetDC(hwnd);
        // 创建兼容内存DC作为图像缓冲区
        let mem_dc: HDC = CreateCompatibleDC(window_dc);
        // 创建与窗口DC兼容的位图
        let hbitmap: HBITMAP = CreateCompatibleBitmap(window_dc, width, height);
        // 将位图选入内存DC,保存原DC中的位图用于后续恢复
        let old_bitmap = SelectObject(mem_dc, hbitmap);

        // 将窗口DC的内容复制到内存DC的位图中
        BitBlt(mem_dc, 0, 0, width, height, window_dc, 0, 0, SRCCOPY);

        // 配置位图信息头
        let mut bitmap_info_header: BITMAPINFOHEADER = mem::zeroed();
        bitmap_info_header.biSize = mem::size_of::<BITMAPINFOHEADER>() as DWORD;
        bitmap_info_header.biWidth = width;
        bitmap_info_header.biHeight = -height; // 负高度表示图像从上到下排列
        bitmap_info_header.biPlanes = 1;
        bitmap_info_header.biBitCount = 32;
        bitmap_info_header.biCompression = BI_RGB;
        bitmap_info_header.biSizeImage = (width as u32 * height as u32 * 4) as DWORD; // 32位色每个像素占4字节

        let mut bitmap_info: BITMAPINFO = mem::zeroed();
        bitmap_info.bmiHeader = bitmap_info_header;

        let mut buf: Vec<u32> = vec![0; (width * height) as usize];

        // 从内存DC的位图中读取像素数据到缓冲区
        GetDIBits(
            mem_dc,
            hbitmap,
            0,
            height as u32,
            buf.as_mut_ptr() as *mut _,
            &mut bitmap_info,
            DIB_RGB_COLORS,
        );

        // 清理GDI资源,避免内存泄漏
        SelectObject(mem_dc, old_bitmap);
        DeleteObject(hbitmap as _);
        DeleteDC(mem_dc);
        ReleaseDC(hwnd, window_dc);

        SCBufferDetails {
            buffer: buf,
            width: width as u32,
            height: height as u32,
        }
    }
}

关键修复说明

  • 新增内存DC与位图:通过CreateCompatibleDC和CreateCompatibleBitmap创建专门的图像缓冲区,这是Windows平台捕获屏幕内容的标准流程
  • 修正BitBlt目标:将窗口内容复制到内存DC的位图中,而非原代码的无效自复制
  • 传入有效位图句柄:使用创建的兼容位图句柄给GetDIBits,确保能读取到真实像素数据
  • 补充biSizeImage字段:明确指定图像数据的大小,避免系统自动计算出错
  • 完善资源清理:所有创建的GDI对象(DC、位图)都进行了正确释放,防止内存泄漏

内容的提问来源于stack exchange,提问作者WICKED

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 13:02:11