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); });
Related
I use QLabel to show frame captured by camera.
//subThread: capture frame from camera
...
m_videoCapture = new cv::VideoCapture(pipeline, CAP_GSTREAMER);
while(m_videoCapture.isOpened())
{
m_videoCapture->read(m_frame);
if (m_frame)
{
//callback method
m_funcFrame(m_frame, pUser);
}
}
//MainThread
//callback method--show image on QLabel
showImage(cv::Mat& frame)
{
m_image = QImage(frame.data, frame.cols, frame.rows, frame.step, QImage::Format_RGB888);
QPixmap pixmap = QPixmap::fromImage(m_image).copy();
m_pixmap = pixmap.scaled(width, height, Qt::IgnoreAspectRatio, Qt::SmoothTransformat).copy;
ui->m_label->setPixmap(m_pixmap);
}
it work fine but QLabel's pixmap don't update any more on matter how I move camera after 5 or 6 hours. I have to restart my application to let it back to normal.
I am trying to load images from a folder and view the previous and next images by clicking a button on the GUI (similar to Windows Image Viewer). The names of the images in this folder are xxxx_00.jpg to xxxx_99.jpg, so I use index++ and index-- to change the filename when clicking the buttons.
My codes work well for displaying the first image, but when I click a button to view the previous or the next images, it always shows
QPixmap::scaled: Pixmap is a null pixmap
and returns an empty image (the first image disappeared but new image didn't display).
Here's my code:
In mainwindow.cpp
void MainWindow::on_pushButton_4_clicked() //previous image
{
if (index < 1)
{
index = 99;
QLabel label;
label.setText("Go back");
label.show();
}
else
{
index--;
}
RefreshFilename();
loadimage();
}
void MainWindow::on_pushButton_5_clicked() //next image
{
if (index > 98)
{
index = 0;
QLabel label;
label.setText("ALL SET");
label.show();
}
else
{
index = index + 1;
}
RefreshFilename();
loadimage();
}
void MainWindow::loadimage()
{
// image.load(filename);
// im = image.scaled(500,500,Qt::KeepAspectRatio);
imageObject = new QImage();
imageObject->load(filename);
image = QPixmap::fromImage(*imageObject);
im = image.scaled(400,400,Qt::KeepAspectRatio);
scene = new QGraphicsScene(this);
scene->addPixmap(image);
scene->setSceneRect(image.rect());
ui->mainimage->setScene(scene);
}
I have spent 2 whole days on debugging this, but still have no idea. I am looking forward to any advice and support!
BTW The Refreshfilename Function works fine, so I did not paste it here.
Cause
Since I do not know what RefreshFilename(); does, I cannot tell exactly what the cause is.
However, I see a major flaw in your code, i.e. you create a new scene each time MainWindow::loadimage is called and this causes a memory leak.
When you provide more details I will be more specific here.
Solution
Set the scene once and add a QGraphicsPixmapItem to it, then in the loadImage update the pixmap of the item.
Keep the current number in a class attribute.
Again, I will be more specific once details are added.
Example
In any case (waiting for you to provide a MVCE), I have prepared a working example according to your task description:
#define IMAGE_COUNT 99
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
m_imgNum(0),
m_item(new QGraphicsPixmapItem())
{
auto *widget = new QWidget(this);
auto *layoutMain = new QVBoxLayout(widget);
auto *layoutButtons = new QHBoxLayout();
auto *btnPrev = new QPushButton(tr("Previous"), this);
auto *btnNext = new QPushButton(tr("Next"), this);
auto *view = new QGraphicsView(this);
view->setScene(new QGraphicsScene(this));
view->scene()->addItem(m_item);
layoutButtons->addStretch();
layoutButtons->addWidget(btnPrev);
layoutButtons->addWidget(btnNext);
layoutButtons->addStretch();
layoutMain->addWidget(view);
layoutMain->addLayout(layoutButtons);
setCentralWidget(widget);
resize(640, 480);
loadImage();
connect(btnPrev, &QPushButton::clicked, [this](){
if (m_imgNum > 0)
m_imgNum--;
else
m_imgNum = IMAGE_COUNT;
loadImage();
});
connect(btnNext, &QPushButton::clicked, [this](){
if (m_imgNum < IMAGE_COUNT)
m_imgNum++;
else
m_imgNum = 0;
loadImage();
});
}
void MainWindow::loadImage()
{
m_item->setPixmap(QString("images/image_%1.jpg").arg(m_imgNum, 2, 10, QChar('0')));
}
where m_imgNum, m_item and loadImage are declared in the header as:
private:
inline void loadImage();
int m_imgNum;
QGraphicsPixmapItem *m_item;
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);
}
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.
I need to display a button on top of an image. Something similar to
The background is a QPixmap/QImage and the button is a QPushbutton. I need to be able to dynamically change the image - so I am not sure if a stylesheet would be suitable for the task. I tried this, but could not get it to work.
Any solutions?
Subclass QWidget and implement paintEent where you can paint your image at the background. Set and change background image by stylesheet also possible.
Add layout with button to this widget.
There are something like this:
class WidgetWithButton
: public QWidget
{
Q_OBJECT
QImage m_bgImage;
public:
WidgetWithButton(QWidget* aParent)
: QWidget(aParent)
{
QHBoxLayout* l = new QHBoxLayout(this);
QPushButton* myButton = new QPushButton(tr("Close"));
l->addWidget( myButton, 0, Qt::AlignCenter );
}
void setImage(const QImage& aImage)
{
m_image = aImage;
update();
}
protected:
virtual void paintEvent(QPaintEvent* aPainEvent)
{
if (m_image.isValid())
{
QPainter painter(this);
painter.drawImage(rect(), m_image);
}
else
QWidget::paintEvent(aPainEvent);
}
};