Растровое изображение PrintWindow отличается от растрового изображения PrintScreen Key

При захвате окна вручную с помощью Print Screen + Alt ключ комбо, я получаю следующее:

введите описание изображения здесь

но если я пытаюсь сделать это программно с помощью Windows API, я получаю это:

введите описание изображения здесь

Почему расхождение? Как получить первый программно?

Вот мой код:

    [DllImport("user32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool PrintWindow(IntPtr hWnd, IntPtr hdcBlt, int nFlags);

    public Bitmap PrintWindow()
    {
        Bitmap bmp = new Bitmap(windowRect.Width, windowRect.Height, PixelFormat.Format32bppArgb);
        Graphics gfxBmp = Graphics.FromImage(bmp);
        IntPtr hdcBitmap = gfxBmp.GetHdc();

        bool success = PrintWindow(windowHandle, hdcBitmap, 0);
        gfxBmp.ReleaseHdc(hdcBitmap);

        if (!success)
        {
            Console.WriteLine("Error copying image");
            Console.WriteLine(getLastError());
        }

        gfxBmp.Dispose();

        return bmp;
    }

Обновление: делать это с BitBlt делает то же самое.

Вот код из CodeProject, который по-прежнему возвращает изображение в черной маске:

public Image CaptureWindow(IntPtr handle)
{
    // get te hDC of the target window
    IntPtr hdcSrc = User32.GetWindowDC(handle);
    // get the size
    User32.RECT windowRect = new User32.RECT();
    User32.GetWindowRect(handle,ref windowRect);
    int width = windowRect.right - windowRect.left;
    int height = windowRect.bottom - windowRect.top;
    // create a device context we can copy to
    IntPtr hdcDest = GDI32.CreateCompatibleDC(hdcSrc);
    // create a bitmap we can copy it to,
    // using GetDeviceCaps to get the width/height
    IntPtr hBitmap = GDI32.CreateCompatibleBitmap(hdcSrc,width,height);
    // select the bitmap object
    IntPtr hOld = GDI32.SelectObject(hdcDest,hBitmap);
    // bitblt over
    GDI32.BitBlt(hdcDest, 0, 0, width, height, hdcSrc, 0, 0, CopyPixelOperation.SourceCopy | CopyPixelOperation.CaptureBlt);
    // restore selection
    GDI32.SelectObject(hdcDest,hOld);
    // clean up
    GDI32.DeleteDC(hdcDest);
    User32.ReleaseDC(handle,hdcSrc);
    // get a .NET image object for it
    Image img = Image.FromHbitmap(hBitmap);
    // free up the Bitmap object
    GDI32.DeleteObject(hBitmap);

    img.Save("SampleImage.png");
    return img;
}

Я перепробовал много комбинаций CopyPixelOperation (где-то около 15 000 из 131 000), но все равно не работает.

Использование Windows 8, AMD Radeon HD 6870.


Обновление 2

Кажется, что окно прозрачное, что позволяет синему цвету окна проникать сквозь него. Когда я изменяю цвет окна на черный (используя диалог персонализации Windows), я получаю примерно что-то похожее на второе окно. Граница все еще отсутствует.

Я не нашел решения, но это понимание проблемы.

4 ответа

Причина, по которой PrintWindow не работает, заключается в том, что оно зависит от правильной обработки приложением сообщения WM_PRINT. Многие приложения не работают с WM_PRINT и не реализуют его правильно или никогда не тестируют. Поэтому полагаться на него - плохая идея, если вы не используете его только в известных и проверенных приложениях.

Если вы хотите захватить окно так, как оно показано на экране, просто растушуйте его из дескриптора окна рабочего стола ( GetDesktopWindow ()) и только растяните прямоугольник, содержащий окно.

Прозрачность - проблема с захватом окон. Невозможно запечатлеть окна современной ОС Windows, потому что нет такого типа файла изображения, который поддерживает причудливость, такую ​​как размытие основного изображения. Тем не менее, можно записать простую прозрачность в файл PNG.

Будьте осторожны с предположением, что окно выглядит лучше, чем на экране. В зависимости от того, что под ним, это может быть не так. Также может быть плохой идеей копировать то, что находится под ним, так как оно может быть не разрешено появляться на изображении, например, явное фоновое изображение, появляющееся в бизнес-презентации под снимком экрана Excel:).

Если вы blt с рабочего стола, вы копируете то, что видите на экране, включая все накладывающиеся окна и нижележащие окна (если они прозрачные). Обычно считается "более чистым" захватывать только окно, как в PrintWindow, но вы можете захотеть скомпоновать это на выбранном вами фоне, например белом или синем. Если вы хотите убрать блики с экрана, есть способы временно скрыть окна, которые перекрывают вашу цель, но это большая работа с EnumWindows и тому подобным.

Правильный захват окон не является легкой задачей в Windows, и есть много приложений захвата экрана, которые конкурируют друг с другом в силу того, насколько хорошо они справляются именно с этой проблемой. Кроме того, в Windows есть несколько способов сделать области окон прозрачными, и их также можно использовать вместе.

Кроме того, в Vista+ есть API миниатюр DWM, который позволяет вам получить копию окна приложения, нарисованного на вашем окне. Для демонстрации и источника см. ShareX (ранее zScreen).

Наконец, вы можете посмотреть на источник программного обеспечения Open Broadcaster, которое использует Direct3D для захвата экрана.

Причина Alt+PrntScrn Изображение выглядит иначе, потому что на самом деле оно не делает снимок выбранного окна - оно делает снимок чего-то вроде окна рабочего стола, но вырезает соответствующую часть.

Чтобы доказать это, возьмите всегда верхнее окно, такое как диспетчер задач, и расположите его так, чтобы оно перекрывало окно, на которое вы делаете снимок. Вы увидите, что на самом деле включает в себя оба окна в снимке. В то время как призыв к PrintWindow просто вернет указанное окно в одиночку.

Итак, если вы хотите подражать Alt+PrntScrn Точно, вам нужно BitBlt с рабочего стола. Что-то вроде этого:

IntPtr hDesktop = User32.GetDesktopWindow();
IntPtr hdcSrc = User32.GetWindowDC(hDesktop);
GDI32.BitBlt(hdcDest, 0, 0, width, height, hdcSrc, windowRect.left, windowRect.top, CopyPixelOperation.SourceCopy);

Я отчасти догадываюсь о синтаксисе здесь на основе вашего примера кода. Необработанные вызовы Windows API выглядят так:

HWND hDesktop = GetDesktopWindow();
HDC hdcSrc = GetDC(hDesktop);
BitBlt(hdcDest, 0, 0, width, height, hdcSrc, windowRect.left, windowRect.top, SRCCOPY);

По крайней мере, это работает для меня. Если у вас есть какие-либо проблемы с выяснением всего остального, дайте мне знать в комментариях.

"Почему расхождение? Как получить первый программно?"

BitBlt против PrintWindow

не уверен, что это так, но принимает во внимание rop (код растровой операции) параметр, который определяет, как данные объединяются в место назначения

это по умолчанию BLACKNESS, который предварительно заполняет целевую память полностью черными пикселями перед копированием данных скриншота

вы, вероятно, захотите установить для этого параметра значение CAPTUREBLT, который, кажется, включает все на экране, а не только конкретное окно, которое вы хотите захватить

https://docs.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-bitblt

офс работает BitBlt на ручке окна рабочего стола

Другие вопросы по тегам