1

I've been struggling with this problem for the past couple of days. I want to be able to grow and shrink whatever the assigned Pixmap is in a QLabel as the user resizes the window. The issue is preserving the aspect ratio and image quality. Another user on here suggested that I reimplement the paint event for the label - but I'm still very lost. I'm not even sure if I have overridden the paintEvent correctly. I would kill for a bit of sample code here.

This is where I'm at:

void MyLabel::paintEvent(QPaintEvent * event)
{
    //if this widget is assigned a pixmap
    //paint that pixmap at the size of the parent, aspect ratio preserved
    //otherwise, nothing
}

2 Answers2

1

Here is a possible implementation of a QLabel subclass that scales its pixmap content while keeping its aspect ratio. It is implemented based on the way QLabel::paintEvent is implemented.

If you are using it in a layout you can also set its size policy to QSizePolicy::Expanding, so that additional space in the layout is taken up by the QLabel for a larger display of the pixmap content.

#include <QApplication>
#include <QtWidgets>

class PixmapLabel : public QLabel{
public:
    explicit PixmapLabel(QWidget* parent=nullptr):QLabel(parent){
        //By default, this class scales the pixmap according to the label's size
        setScaledContents(true);
    }
    ~PixmapLabel(){}

protected:
    void paintEvent(QPaintEvent* event);
private:
    //QImage to cache the pixmap()
    //to avoid constructing a new QImage on every scale operation
    QImage cachedImage;
    //used to cache the last scaled pixmap
    //to avoid calling scale again when the size is still at the same
    QPixmap scaledPixmap;
    //key for the currently cached QImage and QPixmap
    //used to make sure the label was not set to another QPixmap
    qint64 cacheKey{0};
};

//based on the implementation of QLabel::paintEvent
void PixmapLabel::paintEvent(QPaintEvent *event){
    //if this is assigned to a pixmap
    if(pixmap() && !pixmap()->isNull()){
        QStyle* style= PixmapLabel::style();
        QPainter painter(this);
        drawFrame(&painter);
        QRect cr = contentsRect();
        cr.adjust(margin(), margin(), -margin(), -margin());
        int align= QStyle::visualAlignment(layoutDirection(), alignment());
        QPixmap pix;
        if(hasScaledContents()){ //if scaling is enabled
            QSize scaledSize= cr.size() * devicePixelRatioF();
            //if scaledPixmap is invalid
            if(scaledPixmap.isNull() || scaledPixmap.size()!=scaledSize
                    || pixmap()->cacheKey()!=cacheKey){
                //if cachedImage is also invalid
                if(pixmap()->cacheKey() != cacheKey){
                    //reconstruct cachedImage
                    cachedImage= pixmap()->toImage();
                }
                QImage scaledImage= cachedImage.scaled(
                            scaledSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
                scaledPixmap= QPixmap::fromImage(scaledImage);
                scaledPixmap.setDevicePixelRatio(devicePixelRatioF());
            }
            pix= scaledPixmap;
        } else { // no scaling, Just use pixmap()
            pix= *pixmap();
        }
        QStyleOption opt;
        opt.initFrom(this);
        if(!isEnabled())
            pix= style->generatedIconPixmap(QIcon::Disabled, pix, &opt);
        style->drawItemPixmap(&painter, cr, align, pix);
    } else { //otherwise (if the label is not assigned to a pixmap)
        //call base paintEvent
        QLabel::paintEvent(event);
    }
}

//DEMO program
QPixmap generatePixmap(QSize size) {
    QPixmap pixmap(size);
    pixmap.fill(Qt::white);
    QPainter p(&pixmap);
    p.setRenderHint(QPainter::Antialiasing);
    p.setPen(QPen(Qt::black, 10));
    p.drawEllipse(pixmap.rect());
    p.setPen(QPen(Qt::red, 2));
    p.drawLine(pixmap.rect().topLeft(), pixmap.rect().bottomRight());
    p.drawLine(pixmap.rect().topRight(), pixmap.rect().bottomLeft());
    return pixmap;
}


int main(int argc, char *argv[])

{
    QApplication a(argc, argv);


    QPixmap pixmap= generatePixmap(QSize(1280, 960));
    PixmapLabel label;
    label.setPixmap(pixmap);
    label.setAlignment(Qt::AlignCenter);
    label.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
    label.setMinimumSize(320, 240);
    label.show();

    return a.exec();
}

I think this is better than the solution in this answer, as the QLabel here takes care of resizing its pixmap. So, there is no need to resize it manually every time the parent widget is resized and everytime a new pixmap is set on it.

Mike
  • 8,055
  • 1
  • 30
  • 44
  • Thank you for your help, this cleared everything up. – Frank Fritz the Blitz Oct 21 '16 at 14:50
  • I think it has memory leak , because QLabel has not virtual destructure. – H.M Aug 31 '22 at 07:17
  • @H.M, `QLabel`'s destructor **is** virtual, see [here](https://doc.qt.io/qt-5/qlabel.html#dtor.QLabel). The destructor has to be virtual because the class inherits from `QWidget` and `QObject`, and the lifetime of such objects is often managed polymorphically in the form of [`QObject` trees](https://doc.qt.io/qt-5/objecttrees.html). – Mike Sep 04 '22 at 18:22
  • Also, as a matter of fact, the demo example I provided doesn't require the destructor to be virtual, since it never destroys a `PixmapLabel` object through a base class pointer... – Mike Sep 04 '22 at 18:25
  • [QLabel source code](https://codebrowser.dev/qt5/qtbase/src/widgets/widgets/qlabel.h.html#_ZN6QLabelD1Ev) @Mike , As we can see source codes , it is not virtual. – H.M Sep 04 '22 at 18:30
  • @Mike your right if we explicit pass parent in constructor's parent , we have not memory leak. – H.M Sep 04 '22 at 18:35
  • Hi @H.M, thanks for sharing your concerns :). However, I have to disagree: `QObject` is a base class of `QLabel`, and `QObject` has a virtual dtor, see [here](https://codebrowser.dev/qt5/qtbase/src/corelib/kernel/qobject.h.html#_ZN7QObjectD1Ev)... – Mike Sep 04 '22 at 18:35
0

First of all I wanted to say thanks to Mike.

Additionally I would like to add to his answer from 21.10.2016 - however I am not able to add a comment as of yet - thus here is a full fledged answer. [If anyone is able to move this to the comment section, feel free.]

I also added an overwrite of the other constructor of QLabel and the window flags [with default arguments as in QLabel] and added the Q_OBJECT macro, as to be compatible to the Qt framework:

Header:

class PixmapLabel : public QLabel
{
    Q_OBJECT
public:
    explicit PixmapLabel(QWidget* parent=nullptr, Qt::WindowFlags f = Qt::WindowFlags());
    explicit PixmapLabel(const QString &text, QWidget *parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags());
   ...
}

Module:

PixmapLabel::PixmapLabel(QString const & text, QWidget * parent, Qt::WindowFlags f) :
    QLabel(text, parent, f)
{
    //By default, this class scales the pixmap according to the label's size
     setScaledContents(true);
}
J. Wilde
  • 31
  • 3