17

I made a class called ImageLabel which extends QLabel. I want it to keep the size ratio of the image that it is displaying no matter how stretched out it is. It works fine when you make the window larger. The problem comes in when you try to make the window smaller: it doesnt resize the height, it leaves it stretched out. How do I fix this?


int ImageLabel::heightForWidth(int width) const {
    int height = (this->size.height()*width)/this->size.width();
    return height;
}

QSize ImageLabel::sizeHint() const {
    return this->size;
}

QSize ImageLabel::minimumSizeHint() const {
    return QSize(0, 0);
}

void ImageLabel::setSizePolicy(){
    QSizePolicy policy = QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
    policy.setHeightForWidth(true);
    QLabel::setSizePolicy(policy);
    QLabel::setScaledContents(true);
}

void ImageLabel::setPixmap ( const QPixmap &pixmap ){
    this->size = pixmap.size();
    QLabel::setPixmap(pixmap);
}

int main(int argc, char *argv[]){
    QApplication a(argc, argv);

    QFrame *frame = new QFrame;
    QVBoxLayout *layout = new QVBoxLayout;
    frame->setLayout(layout);

    QPixmap map;
    map.load("test.png");
    ImageLabel *label = new ImageLabel;
    label->setSizePolicy();
    label->setPixmap(map);
    layout->addWidget(label);
    frame->show();

    return a.exec();
}

chacham15
  • 13,719
  • 26
  • 104
  • 207

2 Answers2

18

There are a couple ways to do this, but most of all I would recommend maybe not fighting against the layout system by trying to hint the aspect. As you can see, you are having to try and implement a number methods trying to help the layout.

I can offer two examples. Neither of them use layouts...

The first uses a child QLabel to show the image, and drives its fixed size off of the resize event:

// imagelabel.h

class ImageLabel : public QWidget
{
    Q_OBJECT

public:
    explicit ImageLabel(QWidget *parent = 0);
    const QPixmap* pixmap() const;

public slots:
    void setPixmap(const QPixmap&);

protected:
    void resizeEvent(QResizeEvent *);

private slots:
    void resizeImage();

private:
    QLabel *label;
};

// imagelabel.cpp

ImageLabel::ImageLabel(QWidget *parent) :
    QWidget(parent)
{
    label = new QLabel(this);
    label->setScaledContents(true);
    label->setFixedSize(0,0);
}

void ImageLabel::resizeEvent(QResizeEvent *event) {
    QWidget::resizeEvent(event);
    resizeImage();
}

const QPixmap* ImageLabel::pixmap() const {
    return label->pixmap();
}

void ImageLabel::setPixmap (const QPixmap &pixmap){
    label->setPixmap(pixmap);
    resizeImage();
}

void ImageLabel::resizeImage() {
    QSize pixSize = label->pixmap()->size();
    pixSize.scale(size(), Qt::KeepAspectRatio);
    label->setFixedSize(pixSize);
}

The second example is based off of the answer given by @Arnold_Spence. It is even shorter as it doesn't use a child QLabel. It just draws the pixmap in the paint event:

// imagelabel2.h

class ImageLabel2 : public QWidget
{
    Q_OBJECT

public:
    explicit ImageLabel2(QWidget *parent = 0);
    const QPixmap* pixmap() const;

public slots:
    void setPixmap(const QPixmap&);

protected:
    void paintEvent(QPaintEvent *);

private:
    QPixmap pix;
};

// imagelabel2.cpp

ImageLabel2::ImageLabel2(QWidget *parent) :
    QWidget(parent)
{
}

void ImageLabel2::paintEvent(QPaintEvent *event) {
    QWidget::paintEvent(event);

    if (pix.isNull())
        return;

    QPainter painter(this);
    painter.setRenderHint(QPainter::Antialiasing);

    QSize pixSize = pix.size();
    pixSize.scale(event->rect().size(), Qt::KeepAspectRatio);

    QPixmap scaledPix = pix.scaled(pixSize,
                                   Qt::KeepAspectRatio,
                                   Qt::SmoothTransformation
                                   );

    painter.drawPixmap(QPoint(), scaledPix);

}

const QPixmap* ImageLabel2::pixmap() const {
    return &pix;
}

void ImageLabel2::setPixmap (const QPixmap &pixmap){
    pix = pixmap;
}
jdi
  • 90,542
  • 19
  • 167
  • 203
  • the `paintEvent` method looks simple, i just dont understand how it interacts with the layout system. how does it tell the layout system to only allocate the correct width for the height? it looks like it doesnt tell it anything and it just draws on what it has available. in that case, you could potentially have annoying margins, right? – chacham15 Jan 01 '13 at 04:40
  • 1
    Like I said, it does not use layouts. You can place this `ImageLabel` **into** a layout which will drive its size. But when the `ImageLabel` reacts to those size changes, it will control the exact display of the pixmap. Yes, you can have left-over margins if you make the parent widget really wide. If you want to make it centered, then it is just a little bit of math to get a proper `QPoint` for the `drawPixmap` call. – jdi Jan 01 '13 at 04:49
  • Really, all that should matter here is whether you need it to have some alignment options. Aside from that, you just throw this into a layout with your other widgets. All this widget should do is keep a pixmap scaled properly, aligned to some area of the available space in the widget. – jdi Jan 01 '13 at 04:52
  • I dont like to have any margins at all. I want to make a picture collage with each picture right next to the other (imagine like pinterest). What I was wondering is how does the parent layout know how much space to give it so that it doesnt have insane margins? – chacham15 Jan 01 '13 at 04:58
  • The margins are purely dependent on what you do with the widget in the parent layout. The widget will do its own job to drawn the picture with proper aspect. Margins will occur if you have different picture sizes in equal size cells. What do you expect to happen if you collage 9 pictures in a 3x3 with all different sizes? – jdi Jan 01 '13 at 07:43
  • 2
    To clarify, both of these worked for me, but only after I also did this (in pyQt) to the instance: `imageLabelSizePolicy = QSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding); imageLabelSizePolicy.setHeightForWidth(True); imageLabel.setSizePolicy(imageLabelSizePolicy)`. This may be a Qt n00b comment, but I'm sure another Qt n00b will land on this page at some point in the future. – Michael Scheper May 04 '16 at 15:05
  • Also, the `heightForWidth()` implementation in the question code is missing from this answer, and it turns out to be important to the layout manager. Refer to this: http://stackoverflow.com/questions/37399515/how-to-make-a-widgets-height-a-fixed-proportion-to-its-width/37419473#37419473 – Michael Scheper May 24 '16 at 16:51
3

There is no facility built into QLabel to maintain the aspect ratio of the image. Since Qlabel probably has functionality that you don't need, you should subclass QWidget and override the paintEvent() to render the image the way you want.

Arnold Spence
  • 21,942
  • 7
  • 74
  • 67
  • yes, that is why I added the `heightForWidth` capabilities that are lacking in `Qlabel`. The issue is larger than just painting, it is about layout. – chacham15 Jan 01 '13 at 02:15
  • 1
    You can either have a widget try to give feedback to a layout as to what size it wants to be and always draw the image to the full dimensions of the widget or you can allow the layout to resize your widget any way it wants and always paint the image at the appropriate scale and position withing the bounds of the widget. The former can get complicated, the latter just seems easier. – Arnold Spence Jan 01 '13 at 02:44
  • @chacham15: I actually think its good advice to just do it in a paintEvent, since that allows you to explicitly paint the exact pixmap you want, instead of fighting against the layout. You may be able to do something where you set a fixed size on a composed QLabel, and use a resizeEvent to keep resizing the child. – jdi Jan 01 '13 at 02:48