leexgone / uiautomation-rs

The uiatomation-rs crate is a wrapper for windows uiautomation. This crate can help you make windows uiautomation API calls conveniently.
Apache License 2.0
88 stars 15 forks source link

How to get screenshot of an uielement #17

Closed constlhq closed 2 years ago

leexgone commented 2 years ago

The windows uiautomation API doesn't support to get screenshot of an uielement. You can do this with windows GDI APIs now.

This is a goog idea. I will try to support this feature on the next version. But it will take some time.

constlhq commented 2 years ago

Thanks for the advice, I've found this official example https://docs.microsoft.com/en-us/windows/win32/gdi/capturing-an-image and translated it into rust, not elegant but works.

unsafe fn CaptureAnImage(hWnd: HWND) {
    let hdcScreen: HDC;
    let hdcWindow: HDC;
    let mut hdcMemDC: CreatedHDC;
    let hbmScreen: HBITMAP;
    let mut bmpScreen: BITMAP = BITMAP::default();
    let mut dwBytesWritten: u32 = 0;
    let mut dwSizeofDIB: u32 = 0;
    let hFile: HANDLE;
    let lpbitmap: *mut c_void;
    let hDIB: isize;
    let mut dwBmpSize: u32 = 0;

// Retrieve the handle to a display device context for the client
// area of the window.
    hdcScreen = GetDC(None);
    hdcWindow = GetDC(hWnd);

// Create a compatible DC, which is used in a BitBlt from the window DC.
    hdcMemDC = CreateCompatibleDC(hdcWindow);

    if (hdcMemDC.is_invalid()) {
        println!("CreateCompatibleDC has failed");
    }

// Get the client area for size calculation.
    let mut rcClient = RECT::default();
    GetClientRect(hWnd, &mut rcClient);

// This is the best stretch mode.
    SetStretchBltMode(hdcWindow, HALFTONE);

// The source DC is the entire screen, and the destination DC is the current window (HWND).
    if (!StretchBlt(hdcWindow,
                    0, 0,
                    rcClient.right, rcClient.bottom,
                    hdcScreen,
                    0, 0,
                    GetSystemMetrics(SM_CXSCREEN),
                    GetSystemMetrics(SM_CYSCREEN),
                    SRCCOPY).as_bool()) {
        println!("StretchBlt has failed");
    }

// Create a compatible bitmap from the Window DC.
    hbmScreen = CreateCompatibleBitmap(hdcWindow, rcClient.right - rcClient.left, rcClient.bottom - rcClient.top);

    if (hbmScreen.is_invalid()) {
        println!("CreateCompatibleBitmap Failed");
    }

// Select the compatible bitmap into the compatible memory DC.
    SelectObject(hdcMemDC, hbmScreen);

// Bit block transfer into our compatible memory DC.
    if (!BitBlt(hdcMemDC,
                0, 0,
                rcClient.right - rcClient.left, rcClient.bottom - rcClient.top,
                hdcWindow,
                0, 0,
                SRCCOPY).as_bool()) {
        println!("BitBlt has failed");
    }

// Get the BITMAP from the HBITMAP.
    GetObjectA(hbmScreen, size_of::<BITMAP>() as i32, &mut bmpScreen as *mut BITMAP as *mut c_void);

    let mut bmfHeader: BITMAPFILEHEADER = BITMAPFILEHEADER::default();

    let mut bi: BITMAPINFOHEADER = BITMAPINFOHEADER {
        biSize: size_of::<BITMAPINFOHEADER>() as u32,
        biWidth: bmpScreen.bmWidth,
        biHeight: bmpScreen.bmHeight,
        biPlanes: 1,
        biBitCount: 32,
        biCompression: BI_RGB as u32,
        biSizeImage: 0,
        biXPelsPerMeter: 0,
        biYPelsPerMeter: 0,
        biClrUsed: 0,
        biClrImportant: 0,
    };

    dwBmpSize = (((bmpScreen.bmWidth * bi.biBitCount as i32 + 31) / 32) * 4 * bmpScreen.bmHeight) as u32;

// Starting with 32-bit Windows, GlobalAlloc and LocalAlloc are implemented as wrapper functions that
// call HeapAlloc using a handle to the process's default heap. Therefore, GlobalAlloc and LocalAlloc
// have greater overhead than HeapAlloc.
    hDIB = GlobalAlloc(GHND, dwBmpSize as usize);
    lpbitmap = GlobalLock(hDIB);

// Gets the "bits" from the bitmap, and copies them into a buffer
// that's pointed to by lpbitmap.
    GetDIBits(hdcWindow, hbmScreen, 0,
              bmpScreen.bmHeight as u32,
              lpbitmap,
              &mut bi as *mut BITMAPINFOHEADER as *mut c_void as *mut BITMAPINFO, DIB_RGB_COLORS);

// A file is created, this is where we will save the screen capture.
    hFile = CreateFileA("captureqwsx.bmp",
                        FILE_ACCESS_FLAGS(GENERIC_WRITE),
                        FILE_SHARE_MODE(0),
                        std::ptr::null(),
                        CREATE_ALWAYS,
                        FILE_ATTRIBUTE_NORMAL, None).unwrap();

// Add the size of the headers to the size of the bitmap to get the total file size.
    dwSizeofDIB = dwBmpSize + size_of::<BITMAPFILEHEADER>() as u32 + size_of::<BITMAPINFOHEADER>() as u32;

// Offset to where the actual bitmap bits start.
    bmfHeader.bfOffBits = (size_of::<BITMAPFILEHEADER>() + size_of::<BITMAPINFOHEADER>()) as u32;

// Size of the file.
    bmfHeader.bfSize = dwSizeofDIB;

// bfType must always be BM for Bitmaps.
    bmfHeader.bfType = 0x4D42; // BM.
    WriteFile(hFile, &bmfHeader as *const BITMAPFILEHEADER as *const c_void, size_of::<BITMAPFILEHEADER>() as u32, &mut dwBytesWritten, std::ptr::null_mut());
    WriteFile(hFile, &bi as *const BITMAPINFOHEADER as *const c_void, size_of::<BITMAPINFOHEADER>() as u32, &mut dwBytesWritten, std::ptr::null_mut());
    WriteFile(hFile, lpbitmap, dwBmpSize, &mut dwBytesWritten, std::ptr::null_mut());

// Unlock and Free the DIB from the heap.
    GlobalUnlock(hDIB);
    GlobalFree(hDIB);

// Close the handle for the file that was created.
    CloseHandle(hFile);

// Clean up.
    DeleteObject(hbmScreen);
    DeleteDC(hdcMemDC);
    ReleaseDC(None, hdcScreen);
    ReleaseDC(hWnd, hdcWindow);
}