Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
hengkx opened this issue May 1, 2024 · 10 comments

Comments

@hengkx
Copy link

hengkx commented May 1, 2024

No description provided.

@nashaofu
Copy link
Owner

nashaofu commented May 2, 2024

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

@hengkx
Copy link
Author

hengkx commented May 2, 2024

好像是缩放导致的

@nashaofu
Copy link
Owner

nashaofu commented May 2, 2024

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

@hengkx
Copy link
Author

hengkx commented May 2, 2024

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

@nashaofu
Copy link
Owner

nashaofu commented May 2, 2024

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

let width = window.width() * window.current_monitor().scale() 

@hengkx
Copy link
Author

hengkx commented May 2, 2024

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

@ImmortalD
Copy link

我也遇到了类似的问题。实际图片比窗口大很多。图片的快和高比实际窗口的大。填充了很多空白。这是多乘了 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
Copy link

ImmortalD commented Jul 9, 2024

发现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
Copy link
Owner

发现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
Copy link
Owner

发现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;
        }
    }

#141

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants