简体   繁体   English

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

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

I need to draw pixel data that is being held by a library as uint8_t * and that is updated frequently and partially.我需要绘制由库保存的像素数据作为uint8_t *并且经常和部分更新。 I get a call-back from the library every time an update is done, that looks like this:每次更新完成时,我都会收到来自库的回调,如下所示:

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

I've tried creating a QImage using the pixel data pointer我尝试使用像素数据指针创建QImage

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

and let the call-back trigger update() of my widget并让回调触发我的小部件的update()

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

which simply draws the updated area of the QImage via paint() :它只是通过paint() QImage的更新区域:

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

The problem with this approach is that the QImage does not seem to reflect any updates to the pixel buffer.这种方法的问题在于QImage似乎没有反映像素缓冲区的任何更新。 It keeps showing its initial contents.它不断显示其初始内容。

My current workaround is to re-create a QImage instance each time the buffer is updated:我目前的解决方法是每次更新缓冲区时重新创建一个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)));
}

This works but seems very inefficient to me.这有效,但对我来说似乎效率很低。 Is there a better way of dealing with externally updated pixel data in Qt?有没有更好的方法来处理 Qt 中外部更新的像素数据? Can I make my QImage aware of updates to its memory buffer?我可以让我的QImage知道其内存缓冲区的更新吗?

(Background: I'm writing a custom QML type with C++ backend that shall display the contents of a VNC session. I'm usingLibVNC/libvncclient for this.) (背景:我正在编写一个带有 C++ 后端的自定义 QML 类型,它将显示 VNC 会话的内容。我为此使用了LibVNC/libvncclient 。)

The QImage is updated if one calls QImage::bits().如果调用 QImage::bits(),则 QImage 会更新。

It does not allocate a new buffer, you can discard the result, but it magically triggers a refresh of the image.它不会分配新的缓冲区,您可以丢弃结果,但它会神奇地触发图像刷新。 It is required every time you want a refresh.每次您想要刷新时都需要它。

I don't know if this is guaranteed behaviour, nor if it saves anything over recreating it.我不知道这是否是有保证的行为,也不知道它是否可以通过重新创建来节省任何东西。

I would guess that some sort of caching mechanism is interfering with your expectations.我猜想某种缓存机制正在干扰您的期望。 QImage has a cacheKey , which changes if the QImage is altered. QImage 有一个cacheKey ,如果QImage被改变,它就会改变。 This can of course only happen if you change the image through QImage functions.这当然只有在您通过QImage函数更改图像时才会发生。 As far as I can see, you're changing the underlying buffer directly, so QImage 's cacheKey will remain the same.据我所知,您正在直接更改底层缓冲区,因此QImagecacheKey将保持不变。 Qt's pixmap cache then has that key in its cache, and uses the cached pixmap for performance reasons. Qt 的像素图缓存然后在其缓存中具有该键,并出于性能原因使用缓存的像素图。

Unfortunately, there doesn't seem to be a direct way to update this cacheKey or otherwise "invalidate" a QImage .不幸的是,似乎没有直接的方法来更新这个cacheKey或以其他方式“无效” QImage You have two options:您有两个选择:

  1. Recreate the QImage when you need it.需要时重新创建QImage No need to new it, so you can save a heap allocation.无需new它,因此您可以节省堆分配。 Creating a back-buffered QImage seems like a "cheap" operation, so I doubt this is a bottleneck.创建一个后缓冲QImage似乎是一个“便宜”的操作,所以我怀疑这是一个瓶颈。
  2. Do a trivial operation on the QImage (ie setPixel on a single pixel to black and then to the old value).QImage做一个简单的操作(即将单个像素上的setPixel设置为黑色,然后设置为旧值)。 This is somewhat "hackish" but probably the most efficient way to work around this API deficiency (it will trigger an update to cacheKey as far as I can tell).这有点“hackish”,但可能是解决此 API 缺陷的最有效方法(据我cacheKey ,它将触发对cacheKey的更新)。

AFAICT the QImage class already does work the way you think it should -- in particular, simply writing into the external frame-buffer does, in fact, update the contents of the QImage . AFAICT QImage类已经按照您认为的方式工作 - 特别是,简单地写入外部帧缓冲区实际上会更新QImage的内容。 My guess is that in your program, some other bit of code is copying the QImage data into a QPixmap internally somewhere (since a QPixmap will always store its internal buffer in the hardware's native format and thus it will be more efficient to paint onto the screen repeatedly) and it is that QPixmap that is not getting modified when the frameBuffer is updated.我的猜测是,在您的程序中,其他一些代码正在内部某处将QImage数据复制到QPixmap (因为QPixmap将始终以硬件的本机格式存储其内部缓冲区,因此在屏幕上绘制会更有效重复),并且在更新 frameBuffer 时不会修改QPixmap

As evidence that a QImage does in fact always contain the data from the frameBuffer, here is a program that writes a new color into its frame-buffer every time you click on the window, and then calls update() to force the widget to re-draw itself.作为QImage实际上始终包含来自 frameBuffer 的数据的证据,这里有一个程序,每次单击窗口时都会将新颜色写入其帧缓冲区,然后调用update()强制小部件重新- 绘制自己。 I see that the widget changes color on every mouse-click:我看到小部件在每次单击鼠标时都会改变颜色:

#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();
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM