RUST使用winapi获取窗口截图

469 阅读2分钟

我们可能需要三个函数

根据title获取窗口句柄

首先可使用以下Powershell命令获取win窗口信息

Get-Process |where {$_.mainWindowTItle} |format-table id,name,mainwindowtitle –AutoSize
pub fn find_window_by_title(title: &str) -> Option<winapi::shared::windef::HWND> {
    let title_wide: Vec<u16> = OsStr::new(title)
        .encode_wide()
        .chain(Some(0).into_iter())
        .collect();

    let hwnd = unsafe { FindWindowW(null_mut(), title_wide.as_ptr()) };
    if hwnd.is_null() {
        None
    } else {
        Some(hwnd)
    }
}

根据句柄获取窗口大小位置

注意此处要考虑到屏幕缩放,win默认100%缩放下dpi是96,缩放比例可通过此基准计算到。

pub fn get_window_rect(hwnd: HWND) -> Option<RECT> {
    let mut rect: RECT = RECT { left: (0), top: (0), right: (0), bottom: (0) };
    let success: i32 = unsafe { GetWindowRect(hwnd, &mut rect) };
    if success != 0 {
        let dpi = unsafe { GetDpiForWindow(hwnd) };
        let scale = dpi as i32 / 96;
        println!("屏幕的缩放系数为:{}%", scale * 100);
        rect.left *= scale; rect.right *= scale; rect.top *= scale; rect.bottom *= scale;
        Some(rect)
    } else {
        None
    }
}

根据句柄和位置获取窗口截图image

#[measure_time]
fn capture_window(hwnd: HWND, rect: RECT) -> Result<ImageBuffer<Rgba<u8>, Vec<u8>>, String> {
    unsafe {
        let hdc_window: HDC = GetDC(hwnd);
        if hdc_window.is_null() {
            return Err(format!("Failed to get window DC, Error:"));
        }
        let hdc_mem: HDC = CreateCompatibleDC(hdc_window);
        if hdc_mem.is_null() {
            ReleaseDC(hwnd, hdc_window);
            return Err(format!("Failed to create memory DC, Error:"));
        }
        let width = rect.right - rect.left;
        let height = rect.bottom - rect.top;

        let hbitmap: HBITMAP = CreateCompatibleBitmap(hdc_window, width, height);
        if hbitmap.is_null() {
            DeleteDC(hdc_mem);
            ReleaseDC(hwnd, hdc_window);
            return Err(format!("Failed to create compatible bitmap, Error:"));
        }
        if SelectObject(hdc_mem, hbitmap as *mut c_void).is_null() {
            DeleteObject(hbitmap as *mut c_void);
            DeleteDC(hdc_mem);
            ReleaseDC(hwnd, hdc_window);
            return Err(format!("Failed to select object into memory DC, Error:"));
        }

        if BitBlt(hdc_mem, 0, 0, width, height, hdc_window, 0 /* rect.left */, 0 /* rect.top */, SRCCOPY) == 0 {
            DeleteObject(hbitmap as *mut c_void);
            DeleteDC(hdc_mem);
            ReleaseDC(hwnd, hdc_window);
            return Err(format!("Failed to perform BitBlt, Error:"));
        }

        let mut bitmap_info = BITMAPINFO {
            bmiHeader: winapi::um::wingdi::BITMAPINFOHEADER {
                biSize: std::mem::size_of::<winapi::um::wingdi::BITMAPINFOHEADER>() as u32,
                biWidth: width,
                biHeight: height,
                biPlanes: 1,
                biBitCount: 32,
                biCompression: BI_RGB,
                biSizeImage: 0,
                biXPelsPerMeter: 0,
                biYPelsPerMeter: 0,
                biClrUsed: 0,
                biClrImportant: 0,
            },
            bmiColors: [winapi::um::wingdi::RGBQUAD { rgbBlue: 0, rgbGreen: 0, rgbRed: 0, rgbReserved: 0 }; 1],
        };

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

        if GetDIBits(hdc_mem, hbitmap, 0, height as u32, pixel_data.as_mut_ptr() as *mut c_void, &mut bitmap_info, DIB_RGB_COLORS) == 0 {
            DeleteObject(hbitmap as *mut c_void);
            DeleteDC(hdc_mem);
            ReleaseDC(hwnd, hdc_window);
            return Err(format!("Failed to get bitmap data, Error:"));
        }

        DeleteObject(hbitmap as *mut c_void);
        DeleteDC(hdc_mem);
        ReleaseDC(hwnd, hdc_window);

        for i in (0..pixel_data.len()).step_by(4) {
            pixel_data.swap(i, i + 2); // 交换 B 和 R
        }

        let image = ImageBuffer::<Rgba<u8>, _>::from_raw(width as u32, height as u32, pixel_data)
            .ok_or("Failed to create ImageBuffer")?;

        Ok(image)
    }
}

测试代码效果

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let window_title: &str = "title";
    let hwnd_option = find_window_by_title(window_title);
    let hwnd = match hwnd_option {
        None => {
            println!("窗口未找到");
            return Ok(());
        }
        Some(value) => {
            println!("窗口句柄: {:?}", value);
            value
        }
    };
    let rect: winapi::shared::windef::RECT = {
        let rect_: Option<winapi::shared::windef::RECT> = get_window_rect(hwnd);
        match rect_ {
            None => {
                println!("无法获取窗口的RECT");
                return Ok(());
            }
            Some(r) => {
                println!("窗口的RECT:{},{},{},{}[{}x{}]", r.left, r.top, r.right, r.bottom, r.right - r.left,  r.bottom - r.top);
                r
            }
        }
    };
    let image = capture_window(hwnd, rect)?;
    image::imageops::flip_vertical(&image).save("screenshot.png")?;
    Ok(())
}