nashaofu / xcap

XCap is a cross-platform screen capture library written in Rust. It supports Linux (X11, Wayland), MacOS, and Windows. XCap supports screenshot and video recording (to be implemented).
https://docs.rs/xcap
Apache License 2.0
491 stars 58 forks source link

请问win11下为什么截取的图片会比实际窗口小很多,有没有什么办法可以截取窗口的大小 #128

Open hengkx opened 6 months ago

nashaofu commented 6 months ago

大小应该没有计算窗口边框,只计算了绘制区域,这个是一个已知问题,解决有一点麻烦

hengkx commented 6 months ago

好像是缩放导致的

nashaofu commented 6 months ago

显示器缩放参数,分辨率都给我下把,我复现下

hengkx commented 6 months ago

缩放150% 屏幕分辨率3840*2160

nashaofu commented 6 months ago

获取窗口宽高应该按照下面这样,你可以试试

let width = window.width() * window.current_monitor().scale() 
hengkx commented 6 months ago

没办法截取实际显示的大小吗?

ImmortalD commented 4 months ago

我也遇到了类似的问题。实际图片比窗口大很多。图片的快和高比实际窗口的大。填充了很多空白。这是多乘了 scale_factor https://github.com/nashaofu/xcap/blob/v0.0.10/src/windows/capture.rs#L130-L141 导致的。 这个问题很麻烦。我发现GetObjectW大多数情况下获取的bitmap的宽和高是真实的(包含缩放)。但是在少数情况下是不包含缩放的。例如Dxf游戏。获取的就是未包含缩放的。需要乘scale_factor才和实际相同。但是乘scale_factor是没有意义的,因为实际只有GetObjectW那么多像素。乘了会导致图片会被填充了空白。例如下面图片明显填充了空白,有颜色部分是实际的图片,下边和右边的透明色(白色)是乘scale_factor导致的无效填充. image

这种无效填充还会导致BGRA到RGBA时间大变长,其实可以加一个函数返回BGRA数据,例如opencv直接处理的就是BGRA格式。 https://github.com/nashaofu/xcap/blob/v0.0.10/src/windows/capture.rs#L67-L73

基于以上情况删除这2行代码 https://github.com/nashaofu/xcap/blob/v0.0.10/src/windows/capture.rs#L140-L141 ,大多数情况下截图正常,但是少数Dxf这种游戏会缩小图片。且没有无效填充。为了解决缩小问题,可以把图片放大。如果获取的GetObjectW的宽和高和GetWindowRect是一样的。且scale_factor又不是1,那就缩放图片就好了。这样应该能解决大多数问题。没有严格测试。当然图片缩放的方式有很多,使用rust的image也可以。为了尽量使用win32 api,所以使用StretchBlt函数替代BitBlt,且综合上面这些情况。对函数capture_window进行优化

#[allow(unused)]
pub fn capture_window(hwnd: HWND, scale_factor: f32) -> XCapResult<RgbaImage> {
    unsafe {
        let box_hdc_window: BoxHDC = BoxHDC::from(hwnd);
        let rect = get_window_rect(hwnd)?;
        let mut width = rect.right - rect.left;
        let mut height = rect.bottom - rect.top;

        let hgdi_obj = GetCurrentObject(*box_hdc_window, OBJ_BITMAP);
        let mut bitmap = BITMAP::default();

        let mut horizontal_scale = 1.0;
        let mut vertical_scale = 1.0;

        if GetObjectW(
            hgdi_obj,
            mem::size_of::<BITMAP>() as i32,
            Some(&mut bitmap as *mut BITMAP as *mut c_void),
        ) != 0
        {
            width = bitmap.bmWidth;
            height = bitmap.bmHeight;
        }

        let window_width = rect.right - rect.left;

        let mut new_width = width;
        let mut new_height = height;

        // bitmap 获取失败也乘scale_factor,很少的程序有这种情况,目前测试到的乘scale_factor没问题
        if scale_factor != 1f32 && (window_width == bitmap.bmWidth || bitmap.bmWidth == 0) {
            new_width = (width as f32 * scale_factor) as i32;
            new_height = (height as f32 * scale_factor) as i32;
        }

        // 内存中的HDC,使用 DeleteDC 函数释放
        // https://learn.microsoft.com/zh-cn/windows/win32/api/wingdi/nf-wingdi-createcompatibledc
        let box_hdc_mem = BoxHDC::new(CreateCompatibleDC(*box_hdc_window), None);
        let box_h_bitmap = BoxHBITMAP::new(CreateCompatibleBitmap(*box_hdc_window, new_width, new_height));

        let previous_object = SelectObject(*box_hdc_mem, *box_h_bitmap);

        let mut is_success = false;

        // https://webrtc.googlesource.com/src.git/+/refs/heads/main/modules/desktop_capture/win/window_capturer_win_gdi.cc#301
        if get_os_major_version() >= 8 {
            is_success = PrintWindow(hwnd, *box_hdc_mem, PRINT_WINDOW_FLAGS(2)).as_bool();
        }

        if !is_success && DwmIsCompositionEnabled()?.as_bool() {
            is_success = PrintWindow(hwnd, *box_hdc_mem, PRINT_WINDOW_FLAGS(0)).as_bool();
        }

        if !is_success {
            is_success = PrintWindow(hwnd, *box_hdc_mem, PRINT_WINDOW_FLAGS(3)).as_bool();
        }

        if !is_success {
            // 并非所有设备都支持 StretchBlt 函数。 有关详细信息,请参阅 GetDeviceCaps。
            // https://learn.microsoft.com/zh-cn/windows/win32/api/wingdi/nf-wingdi-stretchblt
            SetStretchBltMode(*box_hdc_mem, COLORONCOLOR);
            is_success = StretchBlt(
                *box_hdc_mem,
                0,
                0,
                new_width,
                new_height,
                *box_hdc_window,
                0,
                0,
                width,
                height,
                SRCPAINT,
            ).as_bool();

            if is_success {
                SelectObject(*box_hdc_mem, previous_object);
                // 这里使用new_width,否则图片被截取
                return to_rgba_image(box_hdc_mem, box_h_bitmap, new_width, new_height);
            }
        }

        if !is_success {
            is_success = BitBlt(
                *box_hdc_mem,
                0,
                0,
                width,
                height,
                *box_hdc_window,
                0,
                0,
                SRCCOPY,
            )
                .is_ok();

            if is_success {
                SelectObject(*box_hdc_mem, previous_object);
                // 这里使用width,使用new_width会产生无意思的透明填充
                return to_rgba_image(box_hdc_mem, box_h_bitmap, width, width);
            }
        }

        if !is_success {
            SelectObject(*box_hdc_mem, previous_object);
            return Err(XCapError::new("Get data failed"));
        }

        // PrintWindow 的返回
        to_rgba_image(box_hdc_mem, box_h_bitmap, new_width, new_height)
    }
}
ImmortalD commented 4 months ago

发现BRGA转RGBA在一些特定情况下会执行几十秒。大量循环的调用win api且每次返回结果都是一样,导致大量耗时 https://github.com/nashaofu/xcap/blob/v0.0.10/src/windows/capture.rs#L70

例如测试一张1920*1080的图片,最坏的情况响应调用win32 api 1980*1080次。这是很耗时的:

#[test]
fn test_api() {
    let start = Instant::now();
    let cn = 1920 * 1080;
    for _ in 0..cn {
        get_os_major_version();
    }
    println!("use time:{:?}", start.elapsed());
}
warning: `xcap` (lib test) generated 2 warnings (run `cargo fix --lib -p xcap --tests` to apply 2 suggestions)
    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.19s
     Running unittests src\lib.rs (target\debug\deps\xcap-ca775331104c7238.exe)
use time:86.3485221s

发现耗时80s, 改成如下会提升速度:

    let os_major_version = get_os_major_version();
    for src in buffer.chunks_exact_mut(4) {
        src.swap(0, 2);
        // fix https://github.com/nashaofu/xcap/issues/92#issuecomment-1910014951
        if src[3] == 0 && os_major_version < 8 {
            src[3] = 255;
        }
    }
nashaofu commented 4 months ago

发现BRGA转RGBA在一些特定情况下会执行几十秒。大量循环的调用win api且每次返回结果都是一样,导致大量耗时 https://github.com/nashaofu/xcap/blob/v0.0.10/src/windows/capture.rs#L70

例如测试一张1920*1080的图片,最坏的情况响应调用win32 api 1980*1080次。这是很耗时的:

#[test]
fn test_api() {
    let start = Instant::now();
    let cn = 1920 * 1080;
    for _ in 0..cn {
        get_os_major_version();
    }
    println!("use time:{:?}", start.elapsed());
}
warning: `xcap` (lib test) generated 2 warnings (run `cargo fix --lib -p xcap --tests` to apply 2 suggestions)
    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.19s
     Running unittests src\lib.rs (target\debug\deps\xcap-ca775331104c7238.exe)
use time:86.3485221s

发现耗时80s, 改成如下会提升速度:

    let os_major_version = get_os_major_version();
    for src in buffer.chunks_exact_mut(4) {
        src.swap(0, 2);
        // fix https://github.com/nashaofu/xcap/issues/92#issuecomment-1910014951
        if src[3] == 0 && os_major_version < 8 {
            src[3] = 255;
        }
    }

有时间的话,你提交一个pr吧

nashaofu commented 2 months ago

发现BRGA转RGBA在一些特定情况下会执行几十秒。大量循环的调用win api且每次返回结果都是一样,导致大量耗时 https://github.com/nashaofu/xcap/blob/v0.0.10/src/windows/capture.rs#L70

例如测试一张1920*1080的图片,最坏的情况响应调用win32 api 1980*1080次。这是很耗时的:

#[test]
fn test_api() {
    let start = Instant::now();
    let cn = 1920 * 1080;
    for _ in 0..cn {
        get_os_major_version();
    }
    println!("use time:{:?}", start.elapsed());
}
warning: `xcap` (lib test) generated 2 warnings (run `cargo fix --lib -p xcap --tests` to apply 2 suggestions)
    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.19s
     Running unittests src\lib.rs (target\debug\deps\xcap-ca775331104c7238.exe)
use time:86.3485221s

发现耗时80s, 改成如下会提升速度:

    let os_major_version = get_os_major_version();
    for src in buffer.chunks_exact_mut(4) {
        src.swap(0, 2);
        // fix https://github.com/nashaofu/xcap/issues/92#issuecomment-1910014951
        if src[3] == 0 && os_major_version < 8 {
            src[3] = 255;
        }
    }

https://github.com/nashaofu/xcap/pull/141