【问题标题】:Getting Black Screenshots with Windows API使用 Windows API 获取黑色屏幕截图
【发布时间】:2019-11-04 17:42:30
【问题描述】:

我正在转储客户端窗口的屏幕并使用C++Windows API 将其写入磁盘,如下所示:

const auto window_handle = FindWindow(nullptr, ...);
const auto output_file_path = L"output.png";

// Get the window screenshot
std::cout << "Performing screenshot... ";
HBITMAP bitmap;
const auto screen_shot_successfully_performed = perform_screen_shot(window_handle, bitmap);

if (screen_shot_successfully_performed)
{
    std::cout << "OK!" << std::endl;

    // Save it
    std::cout << "Saving image... ";
    const auto saving_image_succeeded = save_image(bitmap, output_file_path);
    if (saving_image_succeeded)
    {
        std::cout << "OK!" << std::endl;
    }
}

ImageEncoding.h:

#pragma once

#include <windows.h>

#include <gdiplus.h>
#include <gdiplustypes.h>
#include <iostream>
#include "ErrorHandling.h"
#include "Conversions.h"
using namespace Gdiplus;
#pragma comment (lib, "Gdiplus.lib")

inline int get_encoder(const WCHAR* format, CLSID* p_clsid)
{
    UINT image_encoders_count = 0;
    UINT image_encoder_array_size = 0;

    GetImageEncodersSize(&image_encoders_count, &image_encoder_array_size);
    if (image_encoder_array_size == 0)
    {
        return -1; // Failure
    }

    const auto p_image_codec_info = static_cast<ImageCodecInfo*>(malloc(image_encoder_array_size));
    if (p_image_codec_info == nullptr)
    {
        return -1; // Failure
    }

    GetImageEncoders(image_encoders_count, image_encoder_array_size, p_image_codec_info);

    for (UINT image_encoder_index = 0; image_encoder_index < image_encoders_count; image_encoder_index++)
    {
        const auto image_codec_info = p_image_codec_info[image_encoder_index];
        const auto mime_type = image_codec_info.MimeType;
        const auto comparison_result = wcscmp(mime_type, format);
        if (comparison_result == 0)
        {
            *p_clsid = image_codec_info.Clsid;
            free(p_image_codec_info);
            return image_encoder_index; // Success
        }
    }

    free(p_image_codec_info);
    return -1; // Failure
}

inline WCHAR* get_image_format_from_filename(const WCHAR* filename)
{
    std::wstring wide_string_filename(filename);
    const std::string filename_string(wide_string_filename.begin(),
                                      wide_string_filename.end());
    const auto file_extension = get_file_extension(filename_string);
    std::stringstream image_format_buffer;
    image_format_buffer << "image/" << file_extension;
    const auto encoder_format = image_format_buffer.str();
    return to_wide_char(encoder_format.c_str());
}

inline bool save_image(const HBITMAP bitmap, const WCHAR* filename)
{
    GdiplusStartupInput gdiplus_startup_input;
    ULONG_PTR gdiplus_token;
    const auto startup_status = GdiplusStartup(&gdiplus_token, &gdiplus_startup_input, nullptr);
    if (startup_status != Gdiplus::Ok)
    {
        std::cout << "[ERROR] GdiplusStartup() failed: " << startup_status << std::endl;
        return false;
    }

    auto image = new Bitmap(bitmap, nullptr);

    CLSID my_cls_id;
    const auto format = get_image_format_from_filename(filename);
    const auto encoder_return_value = get_encoder(format, &my_cls_id);

    if (encoder_return_value == -1)
    {
        std::cout << "[ERROR] Encoder not available: ";
        std::wcout << format << std::endl;
        delete image;
        return false;
    }

    const auto image_saving_status = image->Save(filename, &my_cls_id, nullptr);
    if (image_saving_status != Gdiplus::Ok)
    {
        std::cout << "[ERROR] Saving image failed: " << startup_status << std::endl;
        delete image;
        return false;
    }

    delete image;
    GdiplusShutdown(gdiplus_token);

    return true;
}

inline bool perform_screen_shot(const HWND window_handle, HBITMAP& bitmap)
{
    RECT rectangle;
    const auto successful = GetClientRect(window_handle, &rectangle);
    if (!successful)
    {
        const auto last_error_message = get_last_error_as_string();
        std::cout << "[ERROR] Cannot get client rectangle: " << last_error_message << std::endl;
        exit(EXIT_FAILURE);
    }

    if (IsRectEmpty(&rectangle))
    {
        std::cout << "[ERROR] The client rectangle is empty: Maybe the window is minimized?" << std::endl;
        return false;
    }

    const auto hdc_screen = GetDC(nullptr);
    const auto hdc = CreateCompatibleDC(hdc_screen);
    bitmap = CreateCompatibleBitmap(hdc_screen,
                                    rectangle.right - rectangle.left,
                                    rectangle.bottom - rectangle.top);
    SelectObject(hdc, bitmap);
    const auto window_successfully_printed = PrintWindow(window_handle, hdc, PW_CLIENTONLY);
    if (!window_successfully_printed)
    {
        const auto last_error_message = get_last_error_as_string();
        std::cout << "[ERROR] Window not printed: " << last_error_message << std::endl;
        exit(EXIT_FAILURE);
    }

    return true;
}

在我的机器 (Windows 10 Pro 64-bit) 上,这段代码总是能完美运行。如您所见,返回值经过验证,因此应捕获错误。然而,Windows 10 64-bit 上的另一个用户总是得到一个黑屏图像,尽管所有功能都成功了。有什么我没有正确解决的吗?如何修复此错误?我试过使用PNGBMP 编码器,但结果相同(很可能是因为HBITMAP 那时已经是黑色的了)。

【问题讨论】:

  • PrintWindow() 不太好用。尝试从该 DC 到您的位图中的 GetWindowDC()BitBlt()。或者 GetClientRect()+ClientToScreen()BitBlt() 从屏幕 DC 到您的位图的那个矩形。或者查看Win10中添加的Screen Capture API。或者在 Win8 中添加的Desktop Duplication API。或 Direct3D。见Ways to capture the screen
  • 为避免 UWP 应用 (DWM) 出现黑屏,您必须添加标志 PW_RENDERFULLCONTENT(它在我的 Windows 10 版本(1803、17134.820)上运行良好)
  • @Castorix:使用此标志不会生成客户区屏幕截图。屏幕截图包括标题栏,并在我的机器上向左移动了一点。此外,未记录此标志:docs.microsoft.com/en-us/windows/desktop/api/winuser/…

标签: c++ windows winapi


【解决方案1】:

正如Remy Lebeau 所评论的那样,PrintWindow() 似乎并不可靠,因为它直接捕获窗口的客户端部分,即使它在屏幕上不可见(例如它位于另一个窗口后面)。似乎效果更好的一件事是执行常规屏幕截图并将区域限制为目标窗口的客户区域。这具有窗口必须可见才能被捕获的缺点。用于此的代码是例如以下:

// Get the horizontal and vertical screen sizes in pixel
inline POINT get_window_resolution(const HWND window_handle)
{
    RECT rectangle;
    GetClientRect(window_handle, &rectangle);
    const POINT coordinates{rectangle.right, rectangle.bottom};
    return coordinates;
}

#include <iostream>
#include <ole2.h>
#include <olectl.h>

inline bool save_bitmap(const LPCSTR file_path,
                        const HBITMAP bitmap, const HPALETTE palette)
{
    PICTDESC pict_description;

    pict_description.cbSizeofstruct = sizeof(PICTDESC);
    pict_description.picType = PICTYPE_BITMAP;
    pict_description.bmp.hbitmap = bitmap;
    pict_description.bmp.hpal = palette;

    LPPICTURE picture;
    auto initial_result = OleCreatePictureIndirect(&pict_description, IID_IPicture, false,
                                                   reinterpret_cast<void**>(&picture));

    if (!SUCCEEDED(initial_result))
    {
        return false;
    }

    LPSTREAM stream;
    initial_result = CreateStreamOnHGlobal(nullptr, true, &stream);

    if (!SUCCEEDED(initial_result))
    {
        picture->Release();
        return false;
    }

    LONG bytes_streamed;
    initial_result = picture->SaveAsFile(stream, true, &bytes_streamed);

    const auto file = CreateFile(file_path, GENERIC_WRITE, FILE_SHARE_READ, nullptr,
                                 CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);

    if (!SUCCEEDED(initial_result) || !file)
    {
        stream->Release();
        picture->Release();
        return false;
    }

    HGLOBAL mem = nullptr;
    GetHGlobalFromStream(stream, &mem);
    const auto data = GlobalLock(mem);

    DWORD bytes_written;
    auto result = WriteFile(file, data, bytes_streamed, &bytes_written, nullptr);
    result &= bytes_written == static_cast<DWORD>(bytes_streamed);

    GlobalUnlock(mem);
    CloseHandle(file);

    stream->Release();
    picture->Release();

    return result;
}

inline POINT get_client_window_position(const HWND window_handle)
{
    RECT rectangle;

    GetClientRect(window_handle, static_cast<LPRECT>(&rectangle));
    MapWindowPoints(window_handle, nullptr, reinterpret_cast<LPPOINT>(& rectangle), 2);

    const POINT coordinates = {rectangle.left, rectangle.top};

    return coordinates;
}

// https://stackoverflow.com/a/9525788/3764804
inline bool capture_screen_client_window(const HWND window_handle, const LPCSTR file_path)
{
    SetActiveWindow(window_handle);

    const auto hdc_source = GetDC(nullptr);
    const auto hdc_memory = CreateCompatibleDC(hdc_source);

    const auto window_resolution = get_window_resolution(window_handle);

    const auto width = window_resolution.x;
    const auto height = window_resolution.y;

    const auto client_window_position = get_client_window_position(window_handle);

    auto h_bitmap = CreateCompatibleBitmap(hdc_source, width, height);
    const auto h_bitmap_old = static_cast<HBITMAP>(SelectObject(hdc_memory, h_bitmap));

    BitBlt(hdc_memory, 0, 0, width, height, hdc_source, client_window_position.x, client_window_position.y, SRCCOPY);
    h_bitmap = static_cast<HBITMAP>(SelectObject(hdc_memory, h_bitmap_old));

    DeleteDC(hdc_source);
    DeleteDC(hdc_memory);

    const HPALETTE h_palette = nullptr;
    if (save_bitmap(file_path, h_bitmap, h_palette))
    {
        return true;
    }

    return false;
}

它基于this的答案。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-09-21
    • 1970-01-01
    相关资源
    最近更新 更多