How to make a QLabel widget display a QImage object, without first converting it to a QPixmap.

I have been studying the Qt v5.7.1 GUI Library on a private, informal basis, and making progress. As I was doing this, I observed a fact which is generally thought to be true, but which may not always be so.

GUI Libraries such as Qt have a set of object classes defined, which allow geometries to be drawn onto an abstract surface that exists in software, such geometries being Ellipses, Arcs, Rectangles, Polygons, Polylines, Chords etc.. Under Qt5, this is performed by way of the ‘QPainter’ class, together with a ‘QBrush’ and a ‘QPen’ class, and one out of several subclasses of the ‘QPaintDevice’ class. The crux of the operation is, that the ‘QPaintDevice’ class’s subclass needs to specify some sort of pixel buffer, so that the classes ‘QPixmap’ and ‘QImage’ certainly qualify. Multiple other classes also work. These pixel buffers are necessary physically, for the abstract drawing surface to ‘remember’ what the painting operations left behind, in the form of an image.

But what anybody who incorporates this into their Apps’ GUI will want to solve next is, to display the resulting pixel buffer. And an ugly way to do that, which is often used, is to select the ‘QPixmap’ class as the target, and then to do something like this:

 


void MainWindow::resetQ()
{
    delete sw_pixmap;
    sw_pixmap = NULL;
    sw_pixmap = new QPixmap(":/res/images/Unknown.jpg");
    int width = size().width();
    int height = size().height();
    i_label.setPixmap(sw_pixmap->scaled(width - 20, height - 50, Qt::KeepAspectRatio, Qt::SmoothTransformation));
}

 

The main problem with this is, that temporary pixmaps are being generated as arguments to function calls, by what I call ‘Naked Constructor Calls’. The compiler will generate temporary objects, and pass them in to the functions being called. And, because those objects were allocated on the stack, as soon as the function returns, they are just popped off the stack, hopefully in a way that results in no memory leaks.

When such temporary objects are simply complex numbers, I can still live with that, But in this case, they are pixel buffers!

Actually, I bent the truth slightly here. The way the function ‘setPixmap()’ is really prototyped, it accepts its argument via a reference. If this were not the case, the code above would truly become ridiculous. However, because of the way arguments are nevertheless presented and then forgotten, the ‘QLabel’ object that called this function still needs to create an internal copy of the pixel map, and must also deallocate the memory once replaced. Further, the temporary object created by ‘sw_pixmap->scaled()‘ is in fact a modified copy of ‘sw_pixmap’, and the compiler sees to it, that it gets deallocated, after the containing function call has returned. Whenever (larger) objects are returned from function-calls by value, they are stored in a special region of memory, managed by the compiler. (:4)

There has got to be a better way, in certain situations, to display whatever pixel buffer was painted to, from a QLabel object. And there is.

(This is a link to the previous exercise.)

(Updated 8/20/2020, 17h10… )

(As of 8/18/2020: )

I just wrote that the most obvious subclasses of ‘QPaintDevice’ are pixel buffers of one sort or another. But there are exceptions, which includes a very obscure exception, by which a ‘QLabel’ object is also a subclass of this same base-class! The way the inheritance works is:

QPaintDevice > QWidget > QFrame > QLabel.

What this means is that, if the constructor ‘QPainter()’ accepts a pointer to an object of class ‘QPaintDevice’, then passing in a pointer to an object of type ‘QLabel’ will not cause a compile-time error! Of course, whether the code will run properly is a separate question. It might run properly, if the physical basis exists for it to do so. This is not the case if an abstract ‘QPen’ object has been made to follow paths in thin air.

But, the ‘QPainter’ class also has the member function ‘drawImage()’ (just as much as it has the function ‘drawPixmap()’). Thus, if a ‘QImage’ was drawn to, the added step could be put into the pipeline (which renders that), of invoking the static member function:

QPixmap QPixmap::fromImage(QImage &input)

Well, there is a more direct way, which will pay off if very large images are to be rendered.

The way this is done is, that a custom sub-class of ‘QLabel’ can be defined for a project, and that something like this can be done:

For the file ‘image_placer.h‘ –

 


#ifndef IMAGE_PLACER_H
#define IMAGE_PLACER_H

#include <QLabel>
#include <QImage>
#include <QPainter>

class ImagePlacer : public QLabel
{

public:
    ImagePlacer(QImage *newImage, QWidget *parent = 0,
                bool smooth = false);
    ~ImagePlacer();

    void paintEvent(QPaintEvent *event);

private:
    QImage *image;
    QRect *orig_geo;
    bool to_smooth;

};

#endif // IMAGE_PLACER_H

 

For the file ‘image_placer.cpp‘ –

 


#include "image_placer.h"
#include <cassert>

ImagePlacer::ImagePlacer(QImage *newImage, QWidget *parent,
                         bool smooth) :
    QLabel("Start", parent)
{
    orig_geo = NULL;
    image = newImage;
    if (image != 0) {
        resize(image->size());
        orig_geo = new QRect(0, 0, width(), height());
    }
    to_smooth = smooth;
}

ImagePlacer::~ImagePlacer()
{
    delete orig_geo;
}

void ImagePlacer::paintEvent(QPaintEvent*)
{
    if (image != 0) {
        QPainter painter(this);

        painter.setRenderHint(QPainter::SmoothPixmapTransform, to_smooth);

        painter.drawImage(rect(), *image, *orig_geo);
        painter.end();
        assert(pixmap() == 0);
    }
}

 

In other words, one exact situation has been found, where a QLabel-derived object can be painted to, without requiring that the process first be translated into an intermediate QPixmap, precisely because what is being painted from is already a pixel buffer of sorts, or rather, a QImage object. (:1)

What also needs to happen is that the ‘::paintEvent()‘ handler must be overridden (as shown above), so that every repaint invokes this ‘QPainter::drawImage()‘ function once. And, this event handler was never virtual in the base-class, which means that, if the derived class was fed to a function that has been prototyped to receive a generic ‘QLabel’ object, the derived class will no longer work properly. (:2) (:3)

Now, I have constructed a complete exercise, that displays a largish image in this way, and the source-code can be found in my binaries folder:

https://dirkmittler.homeip.net/binaries/

The relevant, compressed files are named ‘Creator_Test3.tar.gz‘ and ‘Creator_Test3.zip‘.

I felt it was necessary to verify that this concept actually works, using Qt 5.7.1 .


 

 

(Update 8/19/2020, 20h40: )

1:)

I have performed the experiment as indicated above, of ‘assert’ing that the ‘ImagePlacer’ object has not generated a local ‘QPixmap’ object, to buffer the image that was being output. And, in spite of this test, the program runs, and does not trigger a failed assertion.

I had a clue that it would continue to work, because I did not invoke the base-class’s handler, in my overridden handler. Normally, it would be the responsibility of my overridden function to do so explicitly, if I wanted QLabel’s method of rendering the image to be executed.

Further, this scheme can be used in such a way, that the target and source rectangles don’t match. This can result in a kind of ‘stretchy widget’, which can be resized by the application, which will cause the output to be resized, but which will not require that a separate ‘QPainter’ instance, that was not part of my exercise, redraw the entire graphic, due to a reallocated ‘QImage’ object.

However, if such a ‘stretchy widget’ became the target of ‘QPen’ and ‘QBrush’ operations, a separate ‘QPainter’ instance would nevertheless need to apply those (see below).

I should also point out that, because my ‘ImagePlacer’ class contains a pointer to a ‘QImage’ object that was created elsewhere, before such a ‘QImage’ object is destroyed or replaced with another one, the reader’s ‘ImagePlacer’ object should be destroyed.

Also, a ‘QLabel’ object cannot hold a ‘QImage’ object.


 

 

(Update 8/20/2020, 8h00: )

2:)

I am learning that the handler which I did override, was in fact virtual in a base-class. The way I can see this is, that the code-editing panel of ‘Qt Creator’ italicizes the name of the method. Previously, I did not know what the meaning of that was.

It would not really make much sense for the handler not to be virtual. Also, I know that my handler is being called, because earlier versions of it caused error messages to be output, because I had neglected to put the:

painter.end()

Function-call after drawing to the ‘ImagePlacer’ object. In an earlier form of the code, those error messages complained, that the ‘QPainter’ object was being destroyed before ‘.end()’ was being called. Putting the ‘.end()’ function-call caused these error messages to stop.

 


 

(Update 8/20/2020, 8h55: )

3:)

A reasonable question which the reader could ask would be, ‘Why is it sometimes inadvisable, for brush- and pen-strokes to be painted directly to such a sub-class of QLabel, given the reality that the QLabel object seems to export access to the pixel-map that is the computer’s screen (to the QPainter object), and that the rectangle being drawn to, might be resized?’

And the answer to this question would be as follows:

As soon as the output rectangle is being resized, a new set of output-pixel-coordinates – i.e., screen-pixels – is being drawn to, but the actual coordinates that a single ‘QPainter’ instance would then need to paint to, would also need to be derived from this changed geometry each time. Quite plainly, even though newly painted paths will change the colour of screen-pixels being painted to, they will also fail to clear pixels which were painted to, before the resize. This will cause a kind of ‘streaking’ to take place, if the output geometry is changed, in which the previously painted details remain in-place and not resized. Such streaking constitutes a program malfunction.

The possibility that this can take place would also be the reason why the first argument fed into my ‘painter.drawImage()‘ invocation above, always needs to be ‘rect()‘, and must therefore always match the real geometry of the widget. There cannot be any empty regions belonging to the resized widget, but not repainted, from a complete ‘QImage’ object in my example.


 

 

(Update 8/20/2020, 12h30: )

The question could be visualized of what happens, if the stretchy widget was shrunk to the point, where it leaves pixels on the screen, not belonging to it. Do those pixels also ‘stay behind’ and ‘look defective’? And the answer is No, because regions outside the resized widget, are managed by the other elements of the application window, or by other widgets. Those regions are eventually cleared, because of widgets outside the stretchy widget being contemplated.

Now, what I have also done is to update my compressed files ‘Creator_Test3.tar.gz‘ and ‘Creator_Test3.zip‘, so that the exercise also tests the concept of a ‘stretchy widget’, not just of ‘displaying a large image’.


 

 

(Update 8/20/2020, 13h00: )

Because it’s both possible and likely, that the application is running under some form of display manager and/or desktop compositor, where I wrote above, that screen-pixels are being exported by the ‘QLabel’ class, that the ‘QPainter’ class can draw to, I have deliberately ignored the fact that those pixels would be pixels which were allocated by the display manager and/or desktop compositor, and not, physical memory locations of the display device.

On top of that, if the mundane method was used to make a scaled copy of the image, then the buffers created by the display manager and/or desktop compositor would continue to exist, and continue to take up memory, just not ‘in userland’. Thus, by using the method described in this posting, the reader could merely reduce the number of buffers by one.

 


 

(Update 8/20/2020, 17h10: )

4:)

There is another truth about how C++ works, which I’ve withheld so far in this posting. Given the class of any class object – and in the case of virtual classes, this means ‘the real class’, not, ‘the statically bound class’ – It’s Always Possible to compute the exact size of an object in bytes. The purpose behind this is to achieve, that the base-address of an object can have a constant added to it by the compiler, in order to find any ‘member variable’, where member variables are also referred to as ‘properties’.

But, a pixel-map can have a completely unpredictable number of pixels. And so, the only way this can be resolved in C++ is, that even a ‘QPixmap’ or a ‘QImage’ object, must be an object with a constructor and a non-trivial destructor, so that some of the properties are mere addresses, of data that is allocated on the heap when the object is constructed, and that is deallocated again when the object is destroyed.

Therefore, even if the function-call to ‘QLabel::setPixmap()‘ contains such a Naked Constructor of a ‘QPixmap’ object, all that ever gets allocated directly to the ‘QPixmap’ object, is a structure of properties. Hence, the threat to the stack-limit as such is minimal.

Dirk

 

Print Friendly, PDF & Email

One thought on “How to make a QLabel widget display a QImage object, without first converting it to a QPixmap.”

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>