QLabel with pixmap image getting blurred - c++

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):
But when I open the app and add the image there, the image I get in the QLabel is pretty blurred:
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.

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:
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).
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.

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.

Related

Is there QLabel::setScaledContents equivalent function for QGraphicsScene in Qt?

I'm using QGraphicsView and QGraphicsScene to display an uploaded image and then show some drawing on it. I'm uploading and image like so:
void MeasuresWidget::on_openAction_triggered()
{
QString fileName = QFileDialog::getOpenFileName(this,tr("Open File"), QDir::currentPath());
if (!fileName.isEmpty())
{
QImage image(fileName);
if (image.isNull())
{
QMessageBox::information(this, tr("Measures Application"), tr("Cannot load %1.").arg(fileName));
return;
}
scene->clear();
scene->addPixmap(QPixmap::fromImage(image).scaledToWidth(w, Qt::SmoothTransformation));
}
}
The problem i'm facing is that if i upload an image that is smaller than the one that was uploaded before, there appears to be empty space, i.e. scene maintains the size of previous image(the bigger one) and is bigger than current one. I tried maintaining original size of scene in individual variable and using setSceneRect() in each upload action:
//in constructor
originalRect = ui->graphicsView->rect();
//in upload action
scene->setSceneRect(originalRect);
but result is that size of scene always stays the same and, if it's bigger than the actual image, cuts it. I used QLabel to display an image before and i used QLabel::setScaledContents() function and it worked fine for me. So, my question is can i achieve the same behaviour with QGraphicsScene?
Update 1:
Application behaves the way i want if i create new scene every upload action. The code now looks like:
void MeasuresWidget::on_openAction_triggered()
{
scene = new QGraphicsScene(this);
ui->graphicsView->setScene(scene);
QString fileName = QFileDialog::getOpenFileName(this,tr("Open File"), QDir::currentPath());
if (!fileName.isEmpty())
{
QImage image(fileName);
if (image.isNull())
{
QMessageBox::information(this, tr("Image Viewer"), tr("Cannot load %1.").arg(fileName));
return;
}
scene->clear();
scene->addPixmap(QPixmap::fromImage(image).scaledToWidth(w, Qt::SmoothTransformation));
}
}
Is this ok? Can i achieve the behaviour i want without a need to create new scene every upload action?
You just have to resize the scene when you insert your pixmap based on its size.
If you define a new class inheriting from QGraphicsScene, you can handle it easily:
class GraphicsScene: public QGraphicsScene
{
public:
GraphicsScene(QRect const& rect, QObject* parent=nullptr): QGraphicsScene(rect, parent),
background(nullptr)
{}
QGraphicsPixmapItem *addPixmap(const QPixmap &pixmap)
{
// We already have a background. Remove it
if (background)
{
removeItem(background);
delete background;
}
background = QGraphicsScene::addPixmap(pixmap);
// Move the pixmap
background->setPos(0, 0);
// Change the scene rect based on the size of the pixmap
setSceneRect(background->boundingRect());
return background;
}
private:
QGraphicsPixmapItem* background;
};
GraphicsScene* scene = new GraphicsScene(QRect());
QGraphicsView* view = new QGraphicsView();
view->setScene(scene);
view->show();
QPixmap pix1(QSize(2000, 2000));
pix1.fill(Qt::red);
QPixmap pix2(QSize(100, 300));
pix2.fill(Qt::green);
// The scene will be 2000x2000
QTimer::singleShot(1000, [=]() { scene->addPixmap(pix1); });
// The scene will be 100x300
QTimer::singleShot(10000, [=]() { scene->addPixmap(pix2); });

How do I change qt label text to look like window title text

In my program I am displaying a dialog window to provide information to the user. I am using the QT Label element to do this and I would like to make the text look more "organic" or carved in, like the title text of a window than simple black on gray. Does anyone have any suggestions? I am using QT Creator and coding in c++ for Linux.
here is an image of what I am trying to acomplish
I prepared an MCVE to demonstrate the options I suggested in my comments.
Changing style of QLabel is rather simple and straight forward.
Using the QStylePainter doesn't quite match what I actually expected. I left in the sample code what I got so far. (May be, somebody could leave a helpful hint.) I will investigate in this topic later and edit this answer as soon as I got some satisfying progress on this topic.
The sample code:
#include <QtWidgets>
class TitleBar: public QWidget {
private:
QStyleOptionTitleBar _option;
//QString _text;
public:
explicit TitleBar(
const QString &text = QString(), QWidget *pQParent = nullptr);
protected:
virtual QSize sizeHint() const;
virtual void resizeEvent(QResizeEvent *pQEvent);
virtual void paintEvent(QPaintEvent *pQEvent);
};
int main(int argc, char **argv)
{
qDebug() << "Qt Version: " << QT_VERSION_STR;
// main application
#undef qApp // undef macro qApp out of the way
QApplication qApp(argc, argv);
// setup of GUI
QWidget qWin;
QVBoxLayout qBox;
// a boring label
QLabel qLbl1(QString::fromUtf8("Rather boring"));
qLbl1.setAlignment(Qt::AlignCenter);
qBox.addWidget(&qLbl1);
// a label with frame
QLabel qLbl2(QString::fromUtf8("Label with Frame"));
qLbl2.setFrameStyle(QLabel::Panel | QLabel::Raised);
qLbl2.setLineWidth(2);
qLbl2.setAlignment(Qt::AlignCenter);
qBox.addWidget(&qLbl2);
// a label with rich-text
QLabel qLbl3(
QString::fromUtf8(
"<body bgcolor='#28f'>" // doesn't have the effect
"<font size='+2' color='#f8e' face='Old English Text MT'>"
"Label <i>with</i> <b>Rich-Text</b>"
"</font>"
"</body>")); // background is set by style-sheet instead
qLbl3.setTextFormat(Qt::RichText); // might be auto-detected...
qLbl3.setStyleSheet("QLabel { background-color: #28f; }");
qLbl3.setAlignment(Qt::AlignCenter);
qLbl3.show();
qBox.addWidget(&qLbl3);
// a home-brew title bar
TitleBar qTitle("A Home-Brew Title Bar");
qBox.addWidget(&qTitle);
// finish setup of GUI
qWin.setLayout(&qBox);
qWin.show();
// run application
return qApp.exec();
}
TitleBar::TitleBar(const QString &text, QWidget *pQParent):
QWidget(pQParent)
{
_option.initFrom(this);
_option.titleBarFlags = Qt::Window;
_option.text = text;
}
QSize TitleBar::sizeHint() const
{
#if 0 // does not provide the expected result
return _option.rect.size();
#elif 0 // does not provide the expected result
return style()->proxy()->subControlRect(QStyle::CC_TitleBar,
&_option, QStyle::SC_TitleBarLabel).size();
#else
return QSize(0,
style()->proxy()->pixelMetric(QStyle::PM_TitleBarHeight,
&_option, this));
#endif // 0
}
void TitleBar::resizeEvent(QResizeEvent *pQEvent)
{
_option.rect = QRect(_option.rect.topLeft(), pQEvent->size());
}
void TitleBar::paintEvent(QPaintEvent *pQEvent)
{
QPainter qPainter(this);
style()->proxy()->drawComplexControl(QStyle::CC_TitleBar,
&_option, &qPainter, this);
}
I compiled and tested it in VS2013 on Windows 10 (64 bit). Below a snapshot:

Qt Overriding QLabel PaintEvent

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
}
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.
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);
}

Qt updating displayed QImage

I acquire images from a camera by using a SDK which returns the data in an unsigned char array. However this SDK doesn't offer functionality to display the data, so I tried to implement this by using Qt 4.8 under Ubuntu 12.04.
At the moment my code looks like this:
#include <QtGui/QApplication>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QGraphicsPixmapItem>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QGraphicsScene scene;
QGraphicsView view(&scene);
while (..) // condition which breaks after a number of images where grabbed
{
// Get the image from the third party SDK into an unsigned char array
// named pImageBuffer
QImage image ((unsigned char *) pImageBuffer,
GetWidth(),
GetHeight(),
QImage::Format_Indexed8);
QVector<QRgb> colorTable(256);
for(int i=0;i<256;i++)
colorTable[i] = qRgb(i,i,i);
image.setColorTable(colorTable);
QPixmap pixmap = QPixmap::fromImage(image);
QGraphicsPixmapItem *item = new QGraphicsPixmapItem(pixmap);
scene.addItem(item);
view.show();
a.exec();
}
}
This works like expected, the images are displayed properly. However a.exec() blocks the main thread until I close the QtWindow.
Is there any easy way to modify this, so the window stays open all the time and just updates the displayed image? Performance doesn't matter at all at the moment, but I need to keep the code simple.
While a call to QApplication::processEvents will work, it's just a hack and not the best solution.
Ideally the image grabber should run on a separate thread as an object derived from QObect. This object emits signals of the images that it receives, which are received by an object on the main thread.
The receiving object can then set the image on the QGraphicsPixmapItem object.
Note that the code in the question creates a new QGraphicsPixmapItem for every image that is received from the grabber. Assuming you're wanting to create an animated image, you should only be creating and adding a single QGraphicsPixmapItem to the scene.
Using QThread is very easy and if you've not done it before, I suggest you read this article, which clearly explains what to do, with example code.
class ImageGrabber
{
Q_OBJECT
public:
ImageGrabber(QPixmapItem* item) : _item(item)
{
connect( &timer, SIGNAL(timeout()), this, SLOT(grabImage()) )
_timer.start(33); // 33 ms between timeouts.
}
public slots:
void grabImage()
{
// Update image
QImage image(...);
_item->setPixmap( QPixmap::fromImage(image) );
}
private:
QPixmapItem* _item;
QTimer _timer;
};
int main(...)
{
QApplication a(argc,argv);
...
view.show();
QGraphicsPixmapItem* pixmapItem = scene.addPixmap(QPixmap());
ImageGrabber ig(pixmapItem);
return a.exec();
}

QGraphicsObject doesn't display QPixmap

I'm developing a very simple application on win7 and running on win8.
When I run on win7 it shows the pixmap drawn by my QGraphicsObject subclass. However when I copy the exe and dll's to the my tablet it doesn't show the pixmap but everything else is the same.
Another problem is when I quit the application it says unexpectedly closed.
Here are the related parts
myImage::myImage(QGraphicsObject *parent) :
QGraphicsObject(parent)
{
pxm = new QPixmap("://images/flower.jpg");
setScale(0.5);
}
QRectF myImage::boundingRect() const
{
QRectF rect(0,0,pxm->width(),pxm->height());
return rect;
}
void myImage::paint( QPainter* painter,
const QStyleOptionGraphicsItem* /*option*/,
QWidget* /*widget*/ )
{
painter->drawPixmap( 0, 0, pxm->width(), pxm->height(), *pxm );
}
And here is the main function
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QGraphicsScene scene;
myImage img;
scene.addItem(&img);
QGraphicsView view(&scene);
QWidget window;
window.setFixedHeight(400);
window.setFixedWidth(500);
window.setSizePolicy(QSizePolicy::Fixed,QSizePolicy::Fixed);
QPushButton button("Quit");
QObject::connect(&button,SIGNAL(clicked()),&app,SLOT(quit()));
QVBoxLayout layout;
layout.addWidget(&view);
layout.addWidget(&button);
window.setLayout(&layout);
window.show();
return app.exec();
}
The problem here is that you're using a jpeg image, which isn't native to Qt. In the installation of Qt you'll find a plugins folder with a folder called "imageformats". If you copy the folder of libraries to the path of the executable (assuming Windows), then this should work. A similar discussion is here.
Alternatively, use a different image format to jpeg.