Qt:如何让 QImage 知道更新的内存缓冲区?

Qt: How to make QImage aware of updated memory buffer?

我需要绘制由库保存的像素数据 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)));
}

它只是通过 paint():

绘制 QImage 的更新区域
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 知道其内存缓冲区的更新吗?

(背景:我正在用 C++ 后端编写自定义 QML 类型,它将显示 VNC 会话的内容。为此我使用 LibVNC/libvncclient。)

我猜想是某种缓存机制干扰了您的预期。 QImage 有一个 cacheKey,它会随着 QImage 的改变而改变。这当然只有在您通过 QImage 函数更改图像时才会发生。据我所知,您正在直接更改底层缓冲区,因此 QImagecacheKey 将保持不变。 Qt 的像素图缓存然后在其缓存中具有该键,并出于性能原因使用缓存的像素图。

不幸的是,似乎没有直接的方法来更新此 cacheKey 或 "invalidate" 和 QImage。您有两个选择:

  1. 在需要时重新创建 QImage。不需要 new 它,所以你可以节省堆分配。创建一个后台缓冲 QImage 似乎是一个 "cheap" 操作,所以我怀疑这是一个瓶颈。
  2. QImage 做一个简单的操作(即 setPixel 在单个像素上变黑,然后再变旧值)。这有点 "hackish" 但可能是解决此 API 缺陷的最有效方法(据我所知,它将触发对 cacheKey 的更新)。

AFAICT QImage class 已经按照您认为应该的方式工作——特别是,简单地写入外部帧缓冲区实际上会更新 QImage。我的猜测是,在您的程序中,其他一些代码正在将 QImage 数据复制到内部某处的 QPixmap 中(因为 QPixmap 将始终以硬件的本机格式存储其内部缓冲区因此重复在屏幕上绘制会更有效率)并且 QPixmap 在 frameBuffer 更新时不会被修改。

作为 QImage 实际上总是包含来自 frameBuffer 的数据的证据,这里有一个程序,它会在您每次单击 window 时将新颜色写入其帧缓冲区,然后调用 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();
}

如果调用 QImage::bits(),QImage 会更新。

它不分配新的缓冲区,你可以丢弃结果,但它神奇地触发了图像的刷新。 每次刷新时都需要它。

我不知道这是否是有保证的行为,也不知道它是否比重新创建它节省了任何东西。