Получение черных скриншотов с Windows API - PullRequest
0 голосов
/ 22 июня 2019

Я сбрасываю экран окна клиента и записываю его на диск, используя 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 всегда получает черное изображение на экране, несмотря на то, что все функции выполняются успешно.Есть что-то, к чему я не обращаюсь правильно?Как можно исправить эту ошибку?Я пытался использовать кодеры PNG и BMP, но с тем же результатом (скорее всего, потому что HBITMAP уже был черным).

1 Ответ

0 голосов
/ 26 июня 2019

Как прокомментировал 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;
}

Он основан на этом ответе.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...