Qt: Как сделать так, чтобы QImage узнал об обновленном буфере памяти?

Мне нужно нарисовать пиксельные данные, которые хранятся в библиотеке как uint8_t * и которые часто и частично обновляются. Я получаю обратный вызов из библиотеки каждый раз, когда выполняется обновление, это выглядит так:

void gotFrameBufferUpdate(int x, int y, int w, int h);

Я пробовал создать QImage с помощью указателя данных пикселя.

QImage bufferImage(frameBuffer, width, height, QImage::Format_RGBX8888);

и позвольте обратному вызову вызвать update() моего виджета

void gotFrameBufferUpdate(int x, int y, int w, int h)
{
    update(QRect(QPoint(x, y), QSize(w, h)));
}

который просто рисует обновленную область QImage через paint():

void MyWidget::paint(QPainter *painter)
{
    QRect rect = painter->clipBoundingRect().toRect();
    painter->drawImage(rect, bufferImage, rect);
}

Проблема с этим подходом заключается в том, что QImage не работает отражать любые обновления в пиксельном буфере. Он продолжает показывать свое исходное содержимое.

Мой текущий обходной путь - воссоздавать экземпляр QImage каждый раз, когда буфер обновлено:

void gotFrameBufferUpdate(int x, int y, int w, int h)
{
    if (bufferImage)
        delete bufferImage;
    bufferImage = new QImage(frameBuffer, width, height,
                             QImage::Format_RGBX8888);

    update(QRect(QPoint(x, y), QSize(w, h)));
}

Это работает, но мне кажется очень неэффективным. Есть ли лучший способ работы с внешне обновляемыми данными пикселей в Qt? Могу ли я сообщить своему QImage об обновлениях в его буфере памяти?

(Справочная информация: я пишу собственный тип QML с бэкэндом C ++, который должен отображать содержимое сеанса VNC. Я использую LibVNC / libvncclient для этого.)


person akoch    schedule 26.04.2019    source источник
comment
Мой текущий обходной путь - воссоздавать экземпляр QImage каждый раз при обновлении буфера - я не вижу в этом проблемы, поскольку это, по сути, то, что сам QImage должен был бы сделать, если бы ему сказали, что его буфер памяти был изменен - ​​ему пришлось бы выбросьте все и воссоздайте себя заново. Так в чем проблема?   -  person Jesper Juhl    schedule 26.04.2019
comment
Исходя из того, что буфер должен оставаться действительным в течение всего времени существования предупреждений QImage в документации конструкторов QImage, похоже, что QImage сохраняет указатель uchar, предоставленный конструктору, а не делает копию во внутренний буфер.   -  person Jeremy Friesner    schedule 26.04.2019
comment
Зачем ему нужно было воссоздавать себя, если изменились всего несколько байтовых значений внутри буфера? Я бы понял воссоздание формата изображения или изменение размеров буфера, но это все статично.   -  person akoch    schedule 26.04.2019
comment
@akoch, в зависимости от того, какие байты были изменены, и формата изображения, может потребоваться переинтерпретировать множество других байтов изображения. Простой подход - просто повторно проанализировать данные изображения с нуля при любом изменении.   -  person Jesper Juhl    schedule 26.04.2019


Ответы (3)


Я предполагаю, что какой-то механизм кеширования противоречит вашим ожиданиям. QImage имеет cacheKey, который изменяется при изменении QImage. Конечно, это может произойти только в том случае, если вы измените изображение с помощью QImage функций. Насколько я понимаю, вы напрямую меняете базовый буфер, поэтому QImage cacheKey останется прежним. Кэш растрового изображения Qt имеет этот ключ в своем кеше и использует кешированное растровое изображение по соображениям производительности.

К сожалению, не существует прямого способа обновить этот cacheKey или иным образом "сделать недействительным" QImage. У вас есть два варианта:

  1. Воссоздайте QImage, когда вам это нужно. Не нужно new это, поэтому вы можете сохранить выделение в куче. Создание QImage с обратной буферизацией кажется «дешевой» операцией, поэтому я сомневаюсь, что это узкое место.
  2. Выполните тривиальную операцию с QImage (например, setPixel на один пиксель в черный, а затем в старое значение). Это несколько «хакерский», но, вероятно, наиболее эффективный способ обойти этот недостаток API (насколько я могу судить, это вызовет обновление до cacheKey).
person rubenvb    schedule 26.04.2019
comment
Спасибо, я могу подтвердить, что оба варианта работают в моем сценарии. Я пока выберу 2). - person akoch; 30.04.2019

QImage обновляется, если вызывается QImage :: bits ().

Он не выделяет новый буфер, вы можете отменить результат, но он волшебным образом запускает обновление изображения. Это требуется каждый раз, когда вы хотите обновить.

Я не знаю, гарантировано ли это поведение и спасает ли оно что-нибудь от его воссоздания.

person Andrea    schedule 14.01.2021

AFAICT класс QImage уже работает так, как вы думаете, - в частности, простая запись во внешний буфер кадра фактически обновляет содержимое QImage. Я предполагаю, что в вашей программе какой-то другой бит кода копирует данные QImage в QPixmap где-то внутри (поскольку QPixmap всегда будет хранить свой внутренний буфер в собственном формате оборудования, и поэтому будет более эффективно рисовать на экране неоднократно), и именно этот QPixmap не изменяется при обновлении frameBuffer.

В качестве доказательства того, что QImage на самом деле всегда содержит данные из frameBuffer, вот программа, которая записывает новый цвет в свой кадровый буфер каждый раз, когда вы щелкаете по окну, а затем вызывает update(), чтобы заставить виджет повторно рисовать сам. Я вижу, что виджет меняет цвет при каждом щелчке мышью:

#include <stdio.h>
#include <stdint.h>
#include <QPixmap>
#include <QWidget>
#include <QApplication>
#include <QPainter>

const int width = 500;
const int height = 500;
const int frameBufferSizeBytes = width*height*sizeof(uint32_t);
unsigned char * frameBuffer = NULL;

class MyWidget : public QWidget
{
public:
   MyWidget(QImage * img) : _image(img) {/* empty */}
   virtual ~MyWidget() {delete _image;}

   virtual void paintEvent(QPaintEvent * e)
   {
      QPainter p(this);
      p.drawImage(rect(), *_image);
   }

   virtual void mousePressEvent(QMouseEvent * e)
   {
      const uint32_t someColor = rand();
      const size_t frameBufferSizeWords = frameBufferSizeBytes/sizeof(uint32_t);
      uint32_t * fb32 = (uint32_t *) frameBuffer;
      for (size_t i=0; i<frameBufferSizeWords; i++) fb32[i] = someColor;

      update();
   }

private:
   QImage * _image;
};

int main(int argc, char ** argv)
{
   QApplication app(argc, argv);

   frameBuffer = new unsigned char[frameBufferSizeBytes];
   memset(frameBuffer, 0xFF, frameBufferSizeBytes);

   QImage * img = new QImage(frameBuffer, width, height, QImage::Format_RGBX8888);
   MyWidget * w = new MyWidget(img);
   w->resize(width, height);
   w->show();

   return app.exec();
}
person Jeremy Friesner    schedule 26.04.2019
comment
Спасибо за пример. Я могу воспроизвести это с помощью подкласса QQuickPaintedItem вместо QWidget. Однако не знаю, почему это не работает в моем реальном сценарии использования. - person akoch; 30.04.2019
comment
Вы можете попробовать вычислить хэш-код массива данных, возвращаемого myImage.bits (), и распечатать его каждый раз, когда вы пытаетесь перерисовать его, просто чтобы убедиться, что данные в QImage действительно изменяются. - person Jeremy Friesner; 30.04.2019