Сохраняйте QPixmap-копию содержимого экрана, используя X11, XDamage, XRender и другие хитрости

StackOverflow https://stackoverflow.com/questions/1427711

  •  07-07-2019
  •  | 
  •  

Вопрос

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

QDesktopWidget *w = QApplication::desktop();
if (w)
{
    QRect r = w->screenGeometry();
    QPixmap p = QPixmap::grabWindow(w->winId(), 0, 0, r.width(), r.height())
    QByteArray bitmap;
}

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

Мне нужно, чтобы этот код был быстрым, поэтому я пытаюсь сделать это сам.Моей отправной точкой был демонстрация qx11mirror, Однако, по сути, это делает то же самое.Он использует расширение XDamage, чтобы определить, когда что-то изменилось, но вместо того, чтобы использовать информацию о поврежденном прямоугольнике для простого обновления этой части кэшированного пиксельного изображения, он просто устанавливает "грязный" флаг, который в любом случае запускает полное обновление.

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

void QX11Mirror::x11Event(XEvent *event)
{
    if (event->type == m_damageEvent + XDamageNotify)
    {
        XDamageNotifyEvent *e = reinterpret_cast<XDamageNotifyEvent*>(event);

        XWindowAttributes attr;
        XGetWindowAttributes(QX11Info::display(), m_window, &attr);
        XRenderPictFormat *format = XRenderFindVisualFormat(QX11Info::display(), attr.visual);
        bool hasAlpha             = ( format->type == PictTypeDirect && format->direct.alphaMask );
        int x                     = attr.x;
        int y                     = attr.y;
        int width                 = attr.width;
        int height                = attr.height;

            // debug output so I can see the window pos vs the damaged area:
        qDebug() << "repainting dirty area:" << x << y << width << height << "vs" << e->area.x << e->area.y << e->area.width << e->area.height;

        XRenderPictureAttributes pa;
        pa.subwindow_mode = IncludeInferiors; // Don't clip child widgets    
        Picture picture = XRenderCreatePicture(QX11Info::display(),
                                               m_window,
                                               format,
                                               CPSubwindowMode,
                                               &pa);

        XserverRegion region = XFixesCreateRegionFromWindow(QX11Info::display(),
                                                            m_window, WindowRegionBounding);

        XFixesTranslateRegion(QX11Info::display(), region, -x, -y);
        XFixesSetPictureClipRegion(QX11Info::display(), picture, 0, 0, region);
        XFixesDestroyRegion(QX11Info::display(), region);


        //QPixmap dest(width, height);
        XRenderComposite(QX11Info::display(),                       // display
                         hasAlpha ? PictOpOver : PictOpSrc,         // operation mode
                         picture,                                   // src drawable
                         None,                                      // src mask
                         dest.x11PictureHandle(),                   // dest drawable
                         e->area.x,                                 // src X
                         e->area.y,                                 // src Y
                         0,                                         // mask X
                         0,                                         // mask Y
                         e->area.x,                                 // dest X
                         e->area.y,                                 // dest Y
                         e->area.width,                             // width
                         e->area.height);                           // height

            m_px = dest;
        XDamageSubtract(QX11Info::display(), e->damage, None, None);
            emit windowChanged();

    }
    else if (event->type == ConfigureNotify)
    {
        XConfigureEvent *e = &event->xconfigure;
        m_position = QRect(e->x, e->y, e->width, e->height);
        emit positionChanged(m_position);
    }
}

Кто-нибудь может указать мне правильное направление?Документация для XRender, XDamage и других расширений X11 довольно плохая.

Причины использования XRender поверх XCopyArea

Следующий текст взят из здесь.

Вполне возможно создать GC для окна и использовать XCopyArea() для копирования содержимого окна, если вы хотите использовать основной протокол, но поскольку составное расширение предоставляет новые визуальные элементы (например, с альфа-каналами), нет никакой гарантии, что формат исходного изображения будет соответствовать формату целевого.С основным протоколом такая ситуация приведет к ошибке сопоставления, чего не произойдет с расширением Xrender.

Кроме того, основной протокол не имеет представления об альфа-каналах, что означает, что он не может создавать составные окна, использующие новый ARGB visual.Когда исходный и целевой файлы имеют одинаковый формат, использование основного протокола начиная с X11R6.8 также не дает преимуществ в производительности.Этот выпуск также является первым, поддерживающим новое расширение Composite.

Итак, в заключение, нет никаких недостатков, а есть только преимущества в выборе Xrender вместо основного протокола для этих операций.

Это было полезно?

Решение

Сначала вам нужно изменить DamageReportLevel в вызове DamageCreate в QX11Mirror::setWindow с DamageReportNotEmpty на XDamageReportBoundingBox.

Далее вам нужно вызвать dest.detach() перед вызовом XRenderComposite.Однако на самом деле вам не нужны оба m_px и dest в качестве переменных-членов - вы можете просто использовать m__px.

В этом примере также отсутствует вызов XRenderFreePicture, который должен выполняться после вызова XRenderComposite:

XRenderFreePicture(QX11Info::display(), picture);

Вам не нужно дублировать весь код QX11Mirror::pixmap в QX11Mirror::x11Event.Вместо этого измените тип m_dirty с bool на QRect, затем попросите x11Event обновить m_dirty прямоугольником из XDamageNotifyEvent, т.е.e->область.Затем в QX11Mirror: pixmap вместо того, чтобы проверять, имеет ли значение m_dirty значение true, проверьте, не является ли m_dirty пустым прямоугольником.Затем вы должны передать прямоугольник из m_dirty в XRenderComposite.

Редактировать:

Dest.detach - это ключевой бит - без него вы никогда не заставите его работать.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top