2

I have an image which I need to display as the background of a QLabel. This is my code (culled from Qt documentation here):

#include <QtWidgets>    
#include "imageviewer.h"

ImageViewer::ImageViewer()
{
    imageLabel = new QLabel;
    imageLabel->setBackgroundRole(QPalette::Base);
    imageLabel->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored);
    imageLabel->setScaledContents(true);
    setCentralWidget(imageLabel);

    createActions();
    createMenus();

    resize(570,357);
}


bool ImageViewer::loadFile(const QString &fileName)
{
    QImageReader reader(fileName);
    const QImage image = reader.read();
    if (image.isNull()) {
        QMessageBox::information(this, QGuiApplication::applicationDisplayName(),
                                 tr("Cannot load %1.").arg(QDir::toNativeSeparators(fileName)));
        setWindowFilePath(QString());
        imageLabel->setPixmap(QPixmap());
        imageLabel->adjustSize();
        return false;
    }
    imageLabel->setPixmap(QPixmap::fromImage(image).scaled(size(),Qt::KeepAspectRatio,Qt::SmoothTransformation));
    return true;
}



void ImageViewer::open()
{
    QStringList mimeTypeFilters;
    foreach (const QByteArray &mimeTypeName, QImageReader::supportedMimeTypes())
        mimeTypeFilters.append(mimeTypeName);
    mimeTypeFilters.sort();
    const QStringList picturesLocations = QStandardPaths::standardLocations(QStandardPaths::PicturesLocation);
    QFileDialog dialog(this, tr("Open File"),
                       picturesLocations.isEmpty() ? QDir::currentPath() : picturesLocations.last());
    dialog.setAcceptMode(QFileDialog::AcceptOpen);
    dialog.setMimeTypeFilters(mimeTypeFilters);
    dialog.selectMimeTypeFilter("image/jpeg");

    while (dialog.exec() == QDialog::Accepted && !loadFile(dialog.selectedFiles().first())) {}
}

void ImageViewer::createActions()
{
    openAct = new QAction(tr("&Open..."), this);
    openAct->setShortcut(tr("Ctrl+O"));
    connect(openAct, SIGNAL(triggered()), this, SLOT(open()));

}

void ImageViewer::createMenus()
{
    fileMenu = new QMenu(tr("&File"), this);
    fileMenu->addAction(openAct);

    menuBar()->addMenu(fileMenu);

}

Header file:

#ifndef IMAGEVIEWER_H
#define IMAGEVIEWER_H

#include <QMainWindow>

class QAction;
class QLabel;
class QMenu;
class QScrollArea;
class QScrollBar;

class ImageViewer : public QMainWindow
{
    Q_OBJECT

public:
    ImageViewer();
    bool loadFile(const QString &);

private slots:
    void open();

private:
    void createActions();
    void createMenus();

    QLabel *imageLabel;
    QAction *openAct;
    QMenu *fileMenu;
};

#endif

Main:

#include <QApplication>
#include <QCommandLineParser>

#include "imageviewer.h"

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    QGuiApplication::setApplicationDisplayName(ImageViewer::tr("Image Viewer"));
    ImageViewer imageViewer;
    imageViewer.show();
    return app.exec();
}

As you can see, the viewport size is 570 by 357. The image I am using is this (size is 2560 by 1600, both have an aspect ratio of almost 1.6, too big to upload here, so will upload a screenshot of the pic):

enter image description here

But when I open the app and add the image there, the image I get in the QLabel is pretty blurred:

enter image description here

How do I make the image in the label as well defined as the actual image (it is in bmp format, so you have to change the mime type to bmp in the file open dialog in Mac)?

When I do this task through QPainter, i.e making a QImage out of the image file, then passing it to the painter and calling update it comes up fine. But I need to be able to zoom the image inline when clicked (which I am doing by calling resize() on the QLabel), and the QPainter way does not let me zoom the image.

Platform - OS X 10.10 (Retina Display), Qt 5.3.1, 32 bit.

SexyBeast
  • 7,913
  • 28
  • 108
  • 196
  • QLabel is probably using FastTransformation. try: imageLabel->setPixmap(QPixmap::fromImage(image).scaled(size(),Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); If this seems to solve your issue, do your own widget with SmoothTransform or use graphicsscene – aep Nov 02 '15 at 20:45
  • Looking at the screenshots - it might be Retina-related issue - the Qt screen is pixalated. Also, if you are scaling big image down YMMV, regarding quality and aep's comment is right - either rescale it explicitly and/or do draw it manually in a subclassed widget/graphicsscene item. – user362515 Nov 03 '15 at 07:49
  • I have edited the code following aep's comment. I am now doing the same. Still same result. – SexyBeast Nov 03 '15 at 08:50

2 Answers2

3

You are running into a known bug in QLabel on a Retina display Mac in Qt 5.3: https://bugreports.qt.io/browse/QTBUG-42503

From your image, it looks like you are simply getting a non-retina version of your image when you use QLabel, but if you code it manually in the QPainter you're getting the full resolution of your source QImage. If thats the case, then this is indeed caused by this bug.

You have two solutions:

  1. Roll your own solution, don't use QLabel. There is no reason you can't easily "zoom the image inline" without using QLabel (just use a custom QWidget class with an overrided PaintEvent).

  2. Upgrade to a version of Qt where this bug has been fixed. This is probably the right solution anyway, unless there are any regressions in the latest version that cause a problem in your application. According to the bug report, this issues was fixed in v5.5.0, so the latest v5.5.1 should work fine.

An example of the first approach (I'll leave the header out the header file for brevity, its pretty simple):

void ImageWidget::setImage(QImage image)
{
    //Set the image and invalidate our cached pixmap
    m_image = image;
    m_cachedPixmap = QPixmap();
    update();
}

void ImageWidget::paintEvent(QPaintEvent *)
{
    if ( !m_image.isNull() )
    {
        QSize scaledSize = size() * devicePixelRatio();
        if (m_cachedPixmap.size() != scaledSize)
        {
            QImage scaledImage = m_image.scaled(scaledSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
            m_cachedPixmap = QPixmap::fromImage(scaledImage);
            m_cachedPixmap.setDevicePixelRatio(devicePixelRatio());
        }

        QPainter p(this);
        p.drawPixmap(0, 0, m_cachedPixmap);
    }
}

This is simply a very basic widget that just draws a QImage to its full size, respecting the DevicePixelRatio, and hence taking advantage of Retina resolution. Note: coded this on a non-retina machine, so I can't guarantee that it works on Retina, but I got the basic implementation from the Qt 5.5.1 fix for QLabel. Like QLabel, it also caches the Pixmap so it doesn't have to re-scale every time paintEvent is called, unless the widget has actually been resized.

ScottG
  • 773
  • 6
  • 18
  • Thanks. The second solution is not feasible. Can you give some pointer (some sample code) to how to implement it in the first method you described? – SexyBeast Nov 05 '15 at 20:30
  • Actually, this line: `imageLabel->setPixmap(QPixmap::fromImage(image).scaled(size(),Qt::IgnoreAspectRatio,Qt::SmoothTransformation));` is wrong and would be causing the issue even if the bug I linked didn't exist. You should be passing simply `QPixmap::fromImage(image))` to setPixmap. Then, if it weren't for the bug, you'd get a high-resolution image. – ScottG Nov 06 '15 at 09:26
  • I have tried that as well. Same result. I have tried many things and combinations, I just put the latest one here. If you want to see you can check out the code yourself with that line changed as you said. You will get the same result. – SexyBeast Nov 06 '15 at 10:10
  • Right, but what I said was "Then, if it weren't for the bug, you'd get a high-resolution image." So setting the pixmap scaled will always cause the poor quality you see, while setting the image directly will only cause the poor quality when you hit the bug that I linked to, which you are. Just wanted to clarify that. – ScottG Nov 06 '15 at 12:37
  • Okay, but you said that I can do it otherwise through a custom `QWidget` with overridden `paintEvent` that will allow me to zoom the image inline? Can you show me how? – SexyBeast Nov 06 '15 at 12:50
  • Updated my answer to include a basic implementation. I'd still recommend you upgrade to the latest version of Qt though! – ScottG Nov 06 '15 at 14:12
  • That is of course, the most recommended way! But our team has a truckload of dependencies, all using 5.3! So until they all migrate to 5.5, we can't! – SexyBeast Nov 06 '15 at 14:53
  • Umm yes, it works, but I already mentioned that setting the background via `update` and `paintEvent` works. I am now not able to zoom the image by calling `resize()`, again just like I said. :( – SexyBeast Nov 06 '15 at 15:17
  • Why not?? Use ImageWidget in place of your QLabel. When you call resize() on it, it will behave the same way as QLabel does. – ScottG Nov 06 '15 at 17:51
  • I don't want to resize the Window (which happens if I call `resize()` on the ImageWidget), I want to resize only the image. – SexyBeast Nov 06 '15 at 17:58
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/94450/discussion-between-scottg-and-cupidvogel). – ScottG Nov 06 '15 at 17:59
0

You compress the image more than 4-fold (2560 -> 570) and it seems there are small details in the image impossible to retain after shriking this much.

Actually, you get more or less what you can expect after shrinking an image so much.

Aaron
  • 1,181
  • 7
  • 15
  • Instead of doing it via `pixmap`, if I simply assign the `QPainter` object with a `QImage` reference holding this image and update, it comes up fine. – SexyBeast Nov 03 '15 at 10:29