簡體   English   中英

使用Windows API獲取黑色屏幕截圖

[英]Getting Black Screenshots with Windows API

我正在轉儲客戶端窗口的屏幕並使用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上的另一個用戶始終會獲得黑屏圖像。 有什么我沒有正確解決? 如何修復這個bug? 我嘗試過使用PNGBMP編碼器但結果相同(很可能是因為此時HBITMAP已經是黑色)。

正如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;
}

它基於這個答案。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM