4

I'm trying to create image in QT (inside label) that would change size according to changes in window size, but also would keep aspect ratio.

What's the best way to do it?

Macke
  • 22,774
  • 6
  • 76
  • 108
Shefy Gur-ary
  • 546
  • 6
  • 18
  • Please show what you've tried so far. You'll also find [`QSize::scaled`](http://doc.qt.io/qt-5/qsize.html#scaled-1) useful. – G.M. Mar 16 '17 at 12:08
  • I tried using setSizePolicy after lay-outing inside a grid. But I'm looking for something that works, not fixing something that doesn't – Shefy Gur-ary Mar 16 '17 at 12:38

1 Answers1

4

You tagged this question with linux. I develop on Windows 10 - the closest to Linux I have at hand is cygwin. Thus, I solved it in VS2013 but, hey, this is C++ with Qt. It should be portable...

Actually, QPixmap::scaled() has everything built-in what's necessary for scaling by keeping the aspect ratio. Thus, my solution is rather short plugging the QLabel and QPixmap together.

// standard C++ header:
#include <iostream>
#include <string>

// Qt header:
#include <QApplication>
#include <QResizeEvent>
#include <QLabel>
#include <QMainWindow>
#include <QPixmap>
#include <QTimer>

using namespace std;

class LabelImage: public QLabel {

  private:
    QPixmap _qPixmap, _qPixmapScaled;

  public:
    void setPixmap(const QPixmap &qPixmap) { setPixmap(qPixmap, size()); }

  protected:
    virtual void resizeEvent(QResizeEvent *pQEvent);

  private:
    void setPixmap(const QPixmap &qPixmap, const QSize &size);
};

void LabelImage::resizeEvent(QResizeEvent *pQEvent)
{
  QLabel::resizeEvent(pQEvent);
  setPixmap(_qPixmap, pQEvent->size());
}

void LabelImage::setPixmap(const QPixmap &qPixmap, const QSize &size)
{
  _qPixmap = qPixmap;
  _qPixmapScaled = _qPixmap.scaled(size, Qt::KeepAspectRatio);
  QLabel::setPixmap(_qPixmapScaled);
}

int main(int argc, char **argv)
{
  cout << QT_VERSION_STR << endl;
  // main application
#undef qApp // undef macro qApp out of the way
  QApplication qApp(argc, argv);
  // setup GUI
  QMainWindow qWin;
#if 0 // does not consider aspect ratio
  QLabel qLblImg;
  qLblImg.setScaledContents(true);
#else // (not) 0
  LabelImage qLblImg;
#endif // 0
  qLblImg.setAlignment(Qt::AlignCenter);
  qLblImg.setSizePolicy(
    QSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored));
  QPixmap qPM;
  if (qPM.load("cats.jpg")) qLblImg.setPixmap(qPM);
  else {
    qLblImg.setText(
      QString::fromLatin1("Sorry. Cannot find file 'cats.jpg'."));
  }
  qWin.setCentralWidget(&qLblImg);
  qWin.show();
  // run application
  return qApp.exec();
}

Notes:

  1. I overloaded the QLabel::setPixmap() method and store actually two versions of the pixmap - the original and the scaled. I'm not sure if this is necessary - it's the first time I used QPixmap.

  2. While reading the Qt docs I found QLabel::setScaledContents(). I gave it a try but it does not consider the aspect ratio of the pixmap. I couldn't find a way to set this as extra option. (May be, I did not search enough. I disabled this code but left it in to remember this as "wrong direction".)

  3. In an intermediate version, I was able to enlarge the application (and scaling was fine) but I could not shrink it. Googling a little bit I found SO: Enable QLabel to shrink even if it truncates text. This solved the issue.

  4. To keep the sample short, I hardcoded the image file name. It is actually unnecessary to say that the current directory of the application must be the one where the file is located. (This is probably no issue in Linux but I had to adjust the debug settings in VS2013 appropriately.)

Below is a snapshot of my test appl.:

Snapshot of testQLabelImage.exe

This should work (of course) with any image file which can be loaded into Qt. However, to make the sample complete (and because Internet and pictures of cats belong really together) I provide the sample image also (for download).

cats.jpg

The left is Max, the right is Moritz. (Or vice versa?)

Edit:

According to the feedback of Shefy Gur-ary, this didn't work properly in a QLayout. Thus, I modified the original version and added a QGridLayout to my sample code to examine this topic:

// standard C++ header:
#include <iostream>
#include <string>

// Qt header:
#include <QApplication>
#include <QGridLayout>
#include <QGroupBox>
#include <QLabel>
#include <QMainWindow>
#include <QPixmap>
#include <QResizeEvent>
#include <QTimer>

using namespace std;

class LabelImage: public QLabel {

  private:
    QPixmap _qPixmap, _qPixmapScaled;

  public:
    void setPixmap(const QPixmap &qPixmap) { setPixmap(qPixmap, size()); }

  protected:
    virtual void resizeEvent(QResizeEvent *pQEvent);

  private:
    void setPixmap(const QPixmap &qPixmap, const QSize &size);
};

void LabelImage::resizeEvent(QResizeEvent *pQEvent)
{
  QLabel::resizeEvent(pQEvent);
  setPixmap(_qPixmap, pQEvent->size());
}

void LabelImage::setPixmap(const QPixmap &qPixmap, const QSize &size)
{
  _qPixmap = qPixmap;
  _qPixmapScaled = _qPixmap.scaled(size, Qt::KeepAspectRatio);
  QLabel::setPixmap(_qPixmapScaled);
}

int main(int argc, char **argv)
{
  cout << QT_VERSION_STR << endl;
  // main application
#undef qApp // undef macro qApp out of the way
  QApplication qApp(argc, argv);
  // setup GUI
  QMainWindow qWin;
  QGroupBox qBox;
  QGridLayout qGrid;
  // a macro for the keyboard lazy:
#define Q_LBL_WITH_POS(ROW, COL) \
  QLabel qLbl##ROW##COL(QString::fromLatin1(#ROW", "#COL)); \
  /*qLbl##ROW##COL.setFrameStyle(QLabel::Raised | QLabel::Box);*/ \
  qGrid.addWidget(&qLbl##ROW##COL, ROW, COL, Qt::AlignCenter)
  Q_LBL_WITH_POS(0, 0);
  Q_LBL_WITH_POS(0, 1);
  Q_LBL_WITH_POS(0, 2);
  Q_LBL_WITH_POS(1, 0);
  LabelImage qLblImg;
  qLblImg.setFrameStyle(QLabel::Raised | QLabel::Box);
  qLblImg.setAlignment(Qt::AlignCenter);
  //qLblImg.setMinimumSize(QSize(1, 1)); // seems to be not necessary
  qLblImg.setSizePolicy(
    QSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored));
  QPixmap qPM;
  if (qPM.load("cats.jpg")) qLblImg.setPixmap(qPM);
  else {
    qLblImg.setText(
      QString::fromLatin1("Sorry. Cannot find file 'cats.jpg'."));
  }
  qGrid.addWidget(&qLblImg, 1, 1, Qt::AlignCenter);
  qGrid.setRowStretch(1, 1); // tell QGridLayout to stretch this cell...
  qGrid.setColumnStretch(1, 1); // ...prior to other cells (w/ stretch 0)
  Q_LBL_WITH_POS(1, 2);
  Q_LBL_WITH_POS(2, 0);
  Q_LBL_WITH_POS(2, 1);
  Q_LBL_WITH_POS(2, 2);
  qBox.setLayout(&qGrid);
  qWin.setCentralWidget(&qBox);
  qWin.show();
  // run application
  return qApp.exec();
}

Notes:

  1. The aspect ratio of image was still correct but the resizing didn't work anymore. Thus, I added QGrid::setRowStretch() and QGrid::setColumnStretch(). Unfortunately, this didn't change much.

  2. I googled this topic and found SO: Change resize behavior in Qt layouts. Actually, This didn't help really also but made me suspective that the QGridLayout could be the actual source of this layout issue.

  3. For better visualization of this layout issue, I added frames to all my widgets. To my surprise, it worked suddenly.

I assume that the layout in the QGridLayout works somehow not like expected (although I wouldn't dare to call it a bug). However, the workaround to make a frame around the image is something I could live with. (Actually, it looks not that bad.)

A snapshot of the updated code sample:

Snapshot of testQLabelImage.exe (V2.0)

Community
  • 1
  • 1
Scheff's Cat
  • 16,517
  • 5
  • 25
  • 45
  • Nice. Took me some time to make it work, but it works like charm. – Shefy Gur-ary Mar 19 '17 at 11:29
  • - Can you also tell me how to do it for label inside another container and layout, and not as the center widget? couldn't find a way to do so. – Shefy Gur-ary Mar 20 '17 at 08:31
  • 2
    @ShefyGur-ary Just found out there doesn't seem to be an auto-notify about answered questions. Here it is: I updated my sample and got it somehow working for `QGridLayout` also. I would like to hear whether this works in the Linux port also. – Scheff's Cat Mar 20 '17 at 18:32