Search code examples
qtscale

Qt - How to create Image that scale with window, and keeps aspect ratio?


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?


Solution

  • 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)