【问题标题】:Qt: How to make QImage aware of updated memory buffer?Qt:如何让 QImage 知道更新的内存缓冲区?
【发布时间】:2019-09-16 02:30:30
【问题描述】:

我需要将库中保存的像素数据绘制为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 实例” - 我看不出有什么问题,因为这基本上是 QImage 本身必须做的如果被告知其内存缓冲区已更改-它将不得不丢弃所有内容并重新创建自己。那么,有什么问题呢?
  • 基于QImage构造函数文档中的“缓冲区必须在QImage的生命周期内保持有效”警告,看起来QImage保留了提供给构造函数的uchar指针,而不是制作复制到内部缓冲区中。
  • 如果缓冲区内只有几个字节值发生了变化,为什么它需要重新创建自己?我会理解重新创建图像格式或缓冲区尺寸的变化,但这都是静态的。
  • @akoch 根据更改的字节和图像格式,它可能需要重新解释许多其他图像字节。简单的方法是在任何更改时从头开始重新解析图像数据。

标签: c++ qt vnc-viewer


【解决方案1】:

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

不幸的是,似乎没有直接的方法来更新此cacheKey 或以其他方式“无效”QImage。你有两个选择:

  1. 在需要时重新创建QImage。无需new 它,这样可以节省堆分配。创建一个后缓冲的QImage 似乎是一个“便宜”的操作,所以我怀疑这是一个瓶颈。
  2. QImage 做一个简单的操作(即setPixel 将单个像素变为黑色,然后变为旧值)。这有点“骇人听闻”,但可能是解决此 API 缺陷的最有效方法(据我所知,它将触发对 cacheKey 的更新)。

【讨论】:

  • 谢谢,我可以确认这两个选项都适用于我的场景。我现在选择 2)。
【解决方案2】:

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

作为QImage 确实始终包含来自帧缓冲区的数据的证据,这里有一个程序,每次单击窗口时都会将新颜色写入其帧缓冲区,然后调用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();
}

【讨论】:

  • 感谢您的示例。我也可以使用QQuickPaintedItem 子类而不是QWidget 来重现它。不过,不知道为什么它在我的实际使用场景中不起作用。
  • 您可以尝试计算 myImage.bits() 返回的数据数组的哈希码,并在每次尝试重新绘制时将其打印出来,以验证 @987654332 中的数据@ 实际上正在更改。
【解决方案3】:

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

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-08
    • 2011-07-17
    • 1970-01-01
    相关资源
    最近更新 更多