Дисплей перекрасить изображение в окне на 60 Гц - PullRequest
0 голосов
/ 11 июля 2019

Если вы откроете скайп и нажмете «поделиться экраном», он покажет вам предварительный просмотр видео того, что будет транслироваться.

Пока у меня есть этот код:

Чтобы получить экран:

HBITMAP screenshot()
{
    // get the device context of the screen
    HDC hScreenDC = CreateDC("DISPLAY", NULL, NULL, NULL);
    // and a device context to put it in
    HDC hMemoryDC = CreateCompatibleDC(hScreenDC);

    int width = GetDeviceCaps(hScreenDC, HORZRES);
    int height = GetDeviceCaps(hScreenDC, VERTRES);

    // maybe worth checking these are positive values
    HBITMAP hBitmap = CreateCompatibleBitmap(hScreenDC, width, height);

    // get a new bitmap
    HBITMAP hOldBitmap = (HBITMAP)SelectObject(hMemoryDC, hBitmap);

    BitBlt(hMemoryDC, 0, 0, width, height, hScreenDC, 0, 0, SRCCOPY);
    hBitmap = (HBITMAP)SelectObject(hMemoryDC, hOldBitmap);

    return hBitmap;

Для отображения на форме:

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    case WM_CREATE:
        //hBitmap = (HBITMAP)LoadImage(NULL, LPCSTR("c:/users/they/documents/file.bmp"), IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
    case WM_PAINT:
        hBitmap = screenshot();
        PAINTSTRUCT ps;
        HDC hdc;
        BITMAP bitmap;
        HDC hdcMem;
        HGDIOBJ oldBitmap;

        hdc = BeginPaint(hwnd, &ps);
        hdcMem = CreateCompatibleDC(hdc);
        oldBitmap = SelectObject(hdcMem, hBitmap);

        GetObject(hBitmap, sizeof(bitmap), &bitmap);
        BitBlt(hdc, 200, 50, bitmap.bmWidth,bitmap.bmHeight,
            hdcMem, 0, 0, SRCCOPY);
        SelectObject(hdcMem, oldBitmap);
        DeleteDC(hdcMem);
        EndPaint(hwnd, &ps);
        if(millis % 70) RedrawWindow(hwnd, NULL, NULL, RDW_INVALIDATE | RDW_UPDATENOW);
}

Проблема в том, что время "millis% 70" я прочитал об очереди таймера и таймере STD, но слышу, что они ненадежны на высоких скоростях, Кроме того, перекраска как лучший способ рендеринга "видео" кадр за кадром без библиотек?

1 Ответ

0 голосов
/ 11 июля 2019

Здесь есть несколько разных проблем:

  1. Вероятно, GDI не будет достаточно быстрым, чтобы делать 60 или 70 кадров в секунду. В комментариях предлагаются другие технологии (такие как @Richard Critten), предназначенные для выполнения подобных операций в тесном сотрудничестве с аппаратным обеспечением. По общему признанию, эти API могут быть сложнее для изучения и использования.

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

При этом возможно объединить программу для копирования видеокадров с использованием GDI с более низкой частотой кадров. Я сделал это еще в дни "Видео для Windows". Мне удалось получить кадры 640x480 из окон предварительного просмотра видео с веб-камеры со скоростью около 30 кадров в секунду, время от времени пропуская кадры.

Для этого вы используете «игровой цикл», а не полагаетесь на события таймера. Игровой цикл - это плотный цикл, который следит за часами, пока не пришло время обрабатывать следующий кадр. Это сожжет много ресурсов процессора и израсходует заряд батареи ноутбука, но это, в основном, то, как работает большинство видеоигр Windows во время игры. (Хорошие игры перестанут вращаться, когда игра приостановлена.)

Типичная основанная на событиях программа Windows имеет цикл сообщений, подобный следующему:

while (GetMessage(&msg, NULL, 0, 0) > 0) {
  DispatchMessage(&msg);
}

(Фактический код может быть немного более сложным, но это то, что нас волнует.)

Вызов GetMessage ожидает, пока ваша программа не ответит.

Игровой цикл не ждет. Он проверяет, готово ли сообщение без ожидания. Он использует PeekMessage для этого. Другая вещь, которую он делает, это следить за временем. Если нечего делать, он просто зацикливается, немедленно. В полупсевдокоде это выглядит примерно так:

SomeType next_frame_time = now;
for (;;) {
  if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
    if (msg.message == WM_QUIT) break;
    DispatchMessage(msg);
  }
  if (current time >= next_frame_time) {
    HandleNextFrame();
    next_frame_time += frame interval;
  }
}

Обратите внимание, что цикл работает вечно, если не приходит сообщение WM_QUIT. И он работает так быстро, как может. Вместо того, чтобы выполнять свою работу в ответ на WM_TIMER и WM_PAINT, вы выполняете их всякий раз, когда вызывается HandleNextFrame.

Оставшийся трюк работает с часами высокого разрешения. Для этого вы можете использовать Windows API QueryPeformanceCounter. Обратите внимание, что вам нужно определить единицы измерения, используемые QueryPerformanceCounter во время выполнения, используя QueryPerformanceFrequency.

...