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

Я пытаюсь решить то, что я думал, было бы очень простой проблемой. Я хочу, чтобы 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, чтобы просто обновить поврежденную часть окон, но я не могу заставить что-либо работать - все, что я получаю, - это пустое (черное) растровое изображение. Код, который я использую:

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 () для копирования содержимого окна, если вы хотите использовать основной протокол, но поскольку расширение Composite предоставляет новые визуальные элементы (например, с альфа-каналами) , нет гарантии, что формат исходного текста будет соответствовать формату пункта назначения. При использовании основного протокола эта ситуация приведет к ошибке сопоставления, чего не произойдет с расширением Xrender.

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

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

1 Ответ

3 голосов
/ 23 октября 2009

Сначала вам нужно изменить 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 истиной, проверьте, не является ли m_dirty пустым прямоугольником. Затем вы должны передать прямоугольник из m_dirty в XRenderComposite.

Edit:

Ключевым битом является dest.detach - без этого он никогда не заработает.

...