Qt updating displayed QImage - c++

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

Related

Render QWidget which is larger than 32767 px

I try to render a widget whose size is 3000x50000.
#include <QApplication>
#include <QPainter>
class widget: public QWidget{
protected:
void paintEvent(QPaintEvent *event) override{
QPainter painter(this);
painter.setPen(QPen(Qt::black, 12, Qt::DashDotLine, Qt::RoundCap));
painter.drawLine(QPoint(0,0),rect().bottomRight());
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
widget wid;
wid.setGeometry(0,0,3000,500000);
QPixmap pixmap(3000,50000);
wid.render(&pixmap);
pixmap.save("PATH_TO_IMAGE");
exit(EXIT_SUCCESS);
return a.exec();
}
but in output I have this image result after 32767px image was not rendered.
Maybe you have any idea how to fix it?
The limitation comes from the internal raster paint engine (see qoutlinemapper_p.h: const int QT_RASTER_COORD_LIMIT = 32767;). Upgrade to Qt6 and use a 64bit build - then this limit is updated to 8388607 (2^23-1) pixels.
The better solution which I make is to render the widget to QImage step by step (step should be lower the 32767px) and then copy bits of QImage(usingQImage::bits()) into one buffer(QVector). And after all these steps you can create one QImage(using QImage(&buffer.first())

Using drawComplexControl to retrieve QSliderHandle image

Today I was trying to take a picture of the QSlider handle in order to use it in an adpated QSlider widget with two handles.
This is somewhat similar to the following question: Range slider in Qt (two handles in a QSlider)
For a full fledged solution I can not use a simple image files. I tried to create the image of the QSlider by using the drawComplexControl function, but it leaves me basically with a black image.
What I'm doing wrong here? It seems so simple to me, but it is just not working.
#include <QApplication>
#include <QPushButton>
#include <QPainter>
#include <QStyleOptionSlider>
int main(int argc, char** args) {
QApplication app(argc, args);
auto slider = new QSlider;
slider->setOrientation(Qt::Orientation::Horizontal);
slider->show();
auto btn = new QPushButton("Create Image");
QObject::connect(btn, &QPushButton::clicked, [&] {
auto style = QApplication::style();
QStyleOptionSlider sliderOptions;
QPixmap pix(slider->size());
auto painter = new QPainter();
painter->begin(&pix);
style->drawComplexControl(QStyle::CC_Slider, &sliderOptions, painter, slider);
pix.save("SliderImage.png");
auto handleRect = style->subControlRect(QStyle::ComplexControl::CC_Slider, &sliderOptions, QStyle::SubControl::SC_SliderHandle, slider);
QPixmap handlePix = pix.copy(handleRect);
handlePix.save("SliderHandleImage.png");
painter->end();
});
btn->show();
app.exec();
}
The solution was very simple. I just forgot to add:
sliderOption.initFrom(slider);

Change background color of a QLabel for a specific time

I have in my Qt code a QLabel with a defined background color.
I would in a function to change the background color for one second only and then set it back to the original color.
I thought about using a sleep() function but is there a way to do that without blocking the rest of the program activities ?
Thanks!
You have to use a QTimer::singleShot(...) and QPalette:
#include <QApplication>
#include <QLabel>
#include <QTimer>
class Label: public QLabel{
public:
using QLabel::QLabel;
void changeBackgroundColor(const QColor & color){
QPalette pal = palette();
pal.setColor(QPalette::Window, color);
setPalette(pal);
}
void changeBackgroundColorWithTimer(const QColor & color, int timeout=1000){
QColor defaultColor = palette().color(QPalette::Window);
changeBackgroundColor(color);
QTimer::singleShot(timeout, [this, defaultColor](){
changeBackgroundColor(defaultColor);
});
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Label label;
label.setText("Hello World");
label.show();
label.changeBackgroundColorWithTimer(Qt::green, 2000);
return a.exec();
}
Maybe you can use QTimer to wait a second. Use timer->start(1000) to wait a second and create a SLOT in your class that recive the Qtimer::timeOut signal to changeback the label background color.

QLabel with pixmap image getting blurred

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.

Qt splashscreen won't close

I have a QSplashScreen made that runs through a bunch of images to resemble a gif and closes when the main window opens. This works fine on windows, but when I run it on mac it gets funky. Instead of closing when it's gone through all the pictures like it should it starts going through the images in revers order when clicked.
Here is header (splashscreen.h):
class SplashScreen : public QObject
{
Q_OBJECT
public:
explicit SplashScreen(QObject *parent = 0);
private:
QString filename0;
QString filename1;
QString filename;
int frameNum;
Qt::WindowFlags flags;
private slots:
void showFrame(void);
};
and here is implementation (splashscreen.cpp):
SplashScreen::SplashScreen(QObject *parent) :
QObject(parent)
{
QTimer *timer = new QTimer;
timer->singleShot(0, this, SLOT(showFrame()));
frameNum = 0;
}
void SplashScreen::showFrame(void)
{
QSplashScreen *splash = new QSplashScreen;
QTimer *timer = new QTimer;
frameNum++;
QString filename0 = ""; //first and second half of file path
QString filename1 = "";
splash->showMessage("message here", Qt::AlignBottom, Qt::black);
filename = filename0 + QString::number(frameNum) +filename1; // the number for the file is added here
splash->setPixmap(QPixmap(filename)); // then shown in the splashscreen
splash->show();
if (frameNum < 90)
{
timer->singleShot(75, this, SLOT(showFrame()));
}
else if (frameNum == 90)
{
splash->close();
flags |= Qt::WindowStaysOnBottomHint;
return;
}
}
and here is main file (main.cpp):
int main(int argc, char *argv[])
{
Application app(argc, argv);
SplashScreen *splash = new SplashScreen;
QSplashScreen *splashy = new QSplashScreen;
View view; //main window
QTimer::singleShot(10000, splashy, SLOT(close()));
splashy->hide();
QTimer::singleShot(10000, &view, SLOT(show()));
return app.exec();
}
I've got several different ways to close the splash screen but none of them seem to be working. Is this a bug in macs or is there something I can fix in my code?
There are created 90 different QSplashScreen objects. Only the 90th object is closed.
So, it is the main reason for observed behavior.
If you create a new splash screen QSplashScreen *splash = new QSplashScreen; for each frame then the previous screen should be closed and deleted. It is possible to store QSplashScreen *splash as a class member. Otherwise there is a memory leak.
You may consider to use only one instance of QSplashScreen splash as a private SplashScreen class member. The rest of the code may be unchanged (after replacement splash-> by splash.). It will be automatically deleted with deletion of SplashScreen.
Other issues
QTimer should not be instantiated each time to use its static member function. Each call of showFrame() and SplashScreen() creates a new QTimer object that is never deleted and never used.
The splashy also does not make any sense in main(). All three lines related to splashy may be deleted. Actual splash screens are triggered by new SplashScreen. By the way, it is also a leak. In that case it makes sense to instantiate it directly on the main() function stack: SplashScreen splash;
It looks that the private member SplashScreen::flags is not used.