Implicit sharing between QImage and QPixmap objects
-
I knew that QImage and QPixmap both implement implicit data sharing, but I thought that this only applied between
multiple copies of objects of the same class. I had a big surprise this week when I managed to fix a long-standing
bug in my code with the realisation that, apparently, a QImage and a QPixmap can implicitly share the same buffer.
In an application performing live video streaming, I had the following function being called from the camera
thread each time a frame was ready:
void ImageDisplayArea::updatePixmap(int nx, int ny, const uchar &imagedata) { // Check that the incoming image has the right dimensions; if not then we ignore it, but // remind the camera what dimension we actually want. As resizeEvent() takes care of this // in any case, this is only likely to happen when streaming is started for the first time: if (width() != nx || height() != ny) { emit displayAreaResized(width(), height()); return; } // Create a temporary QImage using the form of constructor which takes the data and leaves // it in-place without copying or modifying it: QImage theTempImage(imagedata, width(), height(), QImage::Format_RGB32); // Now update the pixmap, deep-copying the data into the pixmap object: we wrap this step // in a mutex to prevent it from happening at the same time as paintEvent() is drawing the // pixmap on the screen QMutexLocker ml(&itsMutex); itsPixmap = QPixmap::fromImage(theTempImage); }
After calling this routine to update the pixmap, the camera thread would then use QMetaObject::invokeMethod to
fire a 'repaint' event in the GUI thread, causing the image to be updated with the new pixmap:
void ImageDisplayArea::paintEvent(QPaintEvent *event) { if (!itsPixmap.isNull()) { // Set up the QPainter object QPainter painter(this); // Draw in the pixmap: we wrap this in a mutex in case the camera driver thread tries to // write a new image into the pixmap at the same time: QMutexLocker ml(&itsMutex); painter.drawPixmap(0, 0, itsPixmap); } }
I kept on getting access violations which I couldn't understand. Even deep-copying the 'imagedata' into a local
QImage object within updatePixmap (using a different form of the QImage constructor from the one shown above), and
initialising the QPixmap from this local QImage object, failed to solve them. To cut a long story short,
eventually it dawned on me that perhaps my call to "QPixmap::fromImage(theTempImage)" hadn't caused a deep copy
after all. I tried adding "itsPixmap.detach()" at the end of updatePixmap:
void ImageDisplayArea::updatePixmap(int nx, int ny, const uchar &imagedata) { // Check that the incoming image has the right dimensions; if not then we ignore it, but // remind the camera what dimension we actually want. As resizeEvent() takes care of this // in any case, this is only likely to happen when streaming is started for the first time: if (width() != nx || height() != ny) { emit displayAreaResized(width(), height()); return; } // Create a temporary QImage using the form of constructor which takes the data and leaves // it in-place without copying or modifying it: QImage theTempImage(imagedata, width(), height(), QImage::Format_RGB32); // Now update the pixmap, deep-copying the data into the pixmap object: we wrap this step // in a mutex to prevent it from happening at the same time as paintEvent() is drawing the // pixmap on the screen QMutexLocker ml(&itsMutex); itsPixmap = QPixmap::fromImage(theTempImage); itsPixmap.detach(); // Necessary to decouple the QPixmap from the QImage }
With this, my access violations are a thing of the past.
I don't really have a question, therefore, as my problem is solved, but I record this here as a 'cautionary tale'.
If I were to ask a question, it would be this: are there any other examples in Qt of data buffers being implicitly
shared between two obects of different classes?