Recreate QMovie upon resizing the widget - c++

What is the 'proper' way to recreate a QMovie widget when it gets resized?
class Gif : public QPushButton
{
Q_OBJECT
public:
QMovie* movie = nullptr;
QTimer *timer = new QTimer(this);
int widget_width = 0;
int widget_height = 0;
Gif(QWidget* parent = 0) : QPushButton(parent) { }
void paintEvent(QPaintEvent* p)
{
// Check if the widget has been resized, if so
// delete the QLabel/QMovie and recreate them.
if (widget_width)
{
if (widget_width != width())
{
QLabel* label = this->findChild<QLabel*>("label");
label->deleteLater();
movie->deleteLater();
movie = nullptr;
}
}
// Load the gif into the QMovie/QLabel.
if (!movie)
{
auto StyleSheet = styleSheet();
QVector<QString> matches;
QRegularExpression re(R"(image:\s*url\((.*)\);)");
QRegularExpressionMatch m = re.match(StyleSheet);
for (int i = 0; i <= m.lastCapturedIndex(); i++)
matches.append(m.captured(i));
movie = new QMovie(matches[1]);
if (!movie->isValid())
qDebug() << "failed to create the QMovie.";
QLabel* label = new QLabel(this);
label->setObjectName("label");
widget_width = width();
widget_height = height();
label->setGeometry(0, 0, widget_width, widget_height);
label->setMovie(movie);
label->show();
movie->setScaledSize(QSize().scaled(widget_width, widget_height, Qt::IgnoreAspectRatio));
movie->setSpeed(150);
movie->start();
}
// Pause for 2 seconds after getting into the
// last frame.
if (movie->currentFrameNumber() == (movie->frameCount() -1))
{
movie->stop();
connect(timer, SIGNAL(timeout()), this, SLOT(resume()));
timer->start(2000);
}
}
public slots:
void resume()
{
movie->start();
}
};
Is it 'safe' to call deleteLater() and then assign movie to nullptr? won't cause any mem leak / UB?
if (widget_width != width())
{
QLabel* label = this->findChild<QLabel*>("label");
label->deleteLater();
movie->deleteLater();
movie = nullptr;
}
Also, is there any alternative to QMovie for playing a gif in a gui? it uses a lot of CPU when the gif is medium/high size

Sorry I can not offer the answer to your main question.
With your second question, "a lot of CPU usage" when playing QMovie. I found a method to set the CacheMode of QMovie. It will help to reduce the CPU usage of QMovie playing gif.
movie.setCacheMode(QMovie.CacheMode.CacheAll)

Related

Auto-resizing QStackedWidget

I have a class StackedWidget which inherits from QStackedWidget. The standard behaviour of QStackedWidget is that it adopts the size of its biggest element. What I want to do is force it to resize to its current element. I have already tried a couple of solutions found here and none of them work including e.g. setting size policies or calling hide() on all the widgets that go out of sight.
I think problem may reside in two possibilities:
1. Something is terribly wrong with StackedWidget. 2. The problem of resizing does not pertain directly to StackedWidget but to some layout or another widget that StackedWidget is nested in.
Concerning the first possibility, here I provide the StackedWidget class.
Please take a look at what my StackedWidget's declaration looks like:
class StackedWidget : public QStackedWidget
{
Q_OBJECT
private:
QComboBox* widgetChooser = nullptr;
public:
StackedWidget(QWidget* parent) : QStackedWidget(parent) {}
void setWidgetChooser(QComboBox* widgetChooser);
void addWidget(QWidget* widget);
private:
void makeConnection();
signals:
public slots:
void setCurrentIndex(const int& index);
};
Here goes the definition:
void StackedWidget::setWidgetChooser(QComboBox* widgetChooser)
{
if (widgetChooser == nullptr) throw RuntimeError("widgetChooser is nullptr");
this->widgetChooser = widgetChooser;
makeConnection();
}
void StackedWidget::addWidget(QWidget* widget)
{
widget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
QStackedWidget::addWidget(widget);
}
void StackedWidget::makeConnection()
{
connect(widgetChooser, SIGNAL(currentIndexChanged(int)), this, SLOT(setCurrentIndex(int)));
}
void StackedWidget::setCurrentIndex(const int& index)
{
qDebug() << "Index changed to " << index;
QWidget* pWidget = widget(index);
Q_ASSERT(pWidget);
QStackedWidget::setCurrentWidget(pWidget);
pWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
pWidget->adjustSize();
adjustSize();
}
Concering the second possibility, here goes how I use StackedWidget.
void SchemaWidget::TempBuildAlt(const SchemaElement* son, QVBoxLayout* parentLayout)
{
if (auto schemaAlternative = dynamic_cast<const SchemaAlternatives*>(son))
{
QComboBox* widgetBox = BuildWidgetChooserBox(schemaAlternative);
StackedWidget* stackedWidget = BuildStackedWidget(schemaAlternative);
stackedWidget->setWidgetChooser(widgetBox);
QHBoxLayout* boxLayout = new QHBoxLayout;
boxLayout->addWidget(new QLabel(schemaAlternative->Name, this));
boxLayout->addWidget(widgetBox);
QVBoxLayout* layout = new QVBoxLayout;
layout->addLayout(boxLayout);
layout->addWidget(stackedWidget);
parentLayout->addLayout(layout);
} else throw RuntimeError("Cannot cast son to SchemaAlternatives.");
}
QComboBox* SchemaWidget::BuildWidgetChooserBox(const SchemaAlternatives* schema)
{
QComboBox* alternativesBox = new QComboBox(this);
QStringListModel* listModel = new QStringListModel(this);
QStringList list;
for (int i = 1; i <= schema->Sons.High(); ++i)
{
if (const SchemaTree* son = dynamic_cast<const SchemaTree*>(schema->Sons(i).Get()))
{
list << son->Name;
}
else throw RuntimeError("Son cannot be cast to SchemaTree.");
}
listModel->setStringList(list);
alternativesBox->setModel(listModel);
return alternativesBox;
}
StackedWidget* SchemaWidget::BuildStackedWidget(const SchemaAlternatives* schema)
{
StackedWidget* stackedWidget = new StackedWidget(this);
for (int i = 1; i <= schema->Sons.High(); ++i)
{
if (const SchemaTree* alternativeSon = dynamic_cast<const SchemaTree*>(schema->Sons(i).Get()))
{
QWidget* widget = new QWidget(this);
QVBoxLayout* widgetLayout = new QVBoxLayout;
BuildWidget(alternativeSon, widgetLayout);
widget->setLayout(widgetLayout);
stackedWidget->addWidget(widget);
}
else throw RuntimeError("Son could not be cast to SchemaTree.");
}
return stackedWidget;
}
I want to say thanks in advance to anyone who will be willing to spend some time on my problem. I do appreciate it. Thanks. You're great.

QPixmap load many images in Qt

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;

Custom Widgets which inherits from QTreeWidget crashes while destructing?

Recently I've met some problems about QT.As we have a lot of custom widgets (inherit from QT's basic widgets such as QWidget,QTreeWidget...),they worked fine at runtime,BUT,IT IS EASY TO CRASH when exiting program or destructing them,I don't know IF THERE ANYTHING WRONG or inappropriate while implementing a widget which inherits from a basic widget.
Here is an example:
ChatTreeWidget.cpp:
#include "ChatTreeWidget.h"
#include <QPicture>
#include <QDebug>
ChatTreeWidget::ChatTreeWidget(QWidget *parent)
:QTreeWidget(parent)
{
setRootIsDecorated(false);
setIndentation(0);
setHeaderHidden(true);
setIconSize(QSize(30, 30));
setFocusPolicy(Qt::NoFocus);
setObjectName(QString("chatTreeWidget"));
setStyleSheet("#chatTreeWidget{border:0px;}"
"QTreeWidget::item{border-bottom:1px solid #d9d9d9;padding-left:0px;}"
"QTreeWidget::item:hover{background-color: #def0ff;}"
"QTreeWidget::item:selected{background-color:#c2e2fd;}");
connect(this, &QTreeWidget::itemExpanded, this, [=](QTreeWidgetItem *item)
{
auto indexvar = item->data(0, Qt::UserRole).toInt();
indexvar -= 10000;
if (indexvar > m_iconList.size() || indexvar < 0)
return;
auto lbl = m_iconList.at(indexvar);
lbl->setPixmap(QPixmap(":/Widgets/Resources/chattree/sanjiao1.png"));
});
connect(this, &QTreeWidget::itemCollapsed, this, [=](QTreeWidgetItem *item)
{
auto indexvar = item->data(0, Qt::UserRole).toInt();
indexvar -= 10000;
if (indexvar > m_iconList.size() || indexvar < 0)
return;
auto lbl = m_iconList.at(indexvar);
lbl->setPixmap(QPixmap(":/Widgets/Resources/chattree/sanjiao2.png"));
});
connect(this, &QTreeWidget::itemClicked, this, [=](QTreeWidgetItem *item, int column) {
auto indexvar = item->data(0, Qt::UserRole).toInt();
indexvar -= 10000;
if (indexvar > m_iconList.size() || indexvar < 0)
return;
if (item->isExpanded())
{
setItemExpanded(item, false);
}
else
{
setItemExpanded(item, true);
}
});
setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
}
ChatTreeWidget::~ChatTreeWidget()
{
qDebug() << "123123";
}
QTreeWidgetItem* ChatTreeWidget::addTreeWidgetTopItem(QString str)
{
return insertTreeWidgetTopItem(topLevelItemCount(), str);
}
QTreeWidgetItem* ChatTreeWidget::insertTreeWidgetTopItem(int index, QString str)
{
QWidget *widget = new QWidget(this);
QLabel *textLabel = new QLabel(widget);
QHBoxLayout *hlayout = new QHBoxLayout(widget);
QLabel *iconLabel = new QLabel(widget);
QSpacerItem *spacer = new QSpacerItem(20, 20, QSizePolicy::Expanding, QSizePolicy::Minimum);
textLabel->setText(str);
textLabel->setStyleSheet(u8"font-family: \"Microsoft YaHei\"");
hlayout->addWidget(textLabel);
hlayout->addSpacerItem(spacer);
iconLabel->setPixmap(QPixmap(":/Widgets/Resources/chattree/sanjiao2.png"));
hlayout->addWidget(iconLabel);
hlayout->setContentsMargins(5, 0, 10, 0);
widget->setLayout(hlayout);
m_iconList.append(iconLabel);
QTreeWidgetItem *rootItem = new QTreeWidgetItem();
rootItem->setData(0, Qt::UserRole, m_index);
rootItem->setSizeHint(0, QSize(30, 30));
m_index++;
this->insertTopLevelItem(index, rootItem);
setItemWidget(rootItem, 0, widget);
rootItem->setBackground(0, Qt::green);
setExpandsOnDoubleClick(false);
return rootItem;
}
void ChatTreeWidget::setRootItemStyleSheet(QTreeWidgetItem *item,QString css)
{
auto widgets = itemWidget(item, 0);
widgets->setStyleSheet(css);
}
void ChatTreeWidget::setRootItemStyleSheet(QString css)
{
for (qint32 i = 0; i < topLevelItemCount(); i++)
{
auto rootItem = topLevelItem(i);
auto widgets = itemWidget(rootItem,0);
widgets->setStyleSheet(css);
}
}
this code is trying to implement a custom-treewidget to display chat members,so it inherits the QTreeWidget (once I thought I should implement it with QTreeView,but as I'm stupid and lazy,I used QTreeWidget Directly),and uses a special style-sheet to change display style.and as it's a public widget,it offers some interfaces to add top-level items.there is an example image:
when I destruct this widget (I' sure that this widget is token from UI or layout) and even sometimes QT's automatic destructing will cause a crash.It happens very frequently but not everytime.
the two main ways of destroying the widget are:
1.Add it to some Ui,and let it managed by that UI.
2.delete by hand:
chatTreeWidget->setParent(0);
chatTreeWidget->disconnect();
chatTreeWidget->deleteLater();
both of them can cause a random crash.
Is there anything wrong? or it's just a bug?
Thanks for any kind of help!

Memory leak using opencv : VideoCapture

I have developed an application with Qt Creator 2.4.1 (Qt 4.8.4) and OpenCV 2.4.2 that reads images from a folder and displays them.
It uses cv::VideoCapture and QGraphicsScene/QGraphicsView. It runs well, however I encounter a memory leak : if I look at the consumed memory in task manager, memory goes up each time a new image is read and end up crashing.
My main window was created with Qt Designer, it's a class that inherits QMainWindow. There is a QGraphicsView view_src on it and also a push button : buttonStart
Here is a sample of code : Class declaration :
using namespace std;
using namespace cv;
namespace Ui {
class FenetrePrinc;
}
class FenetrePrinc : public QMainWindow {
Q_OBJECT
public:
explicit FenetrePrinc(QWidget *parent = 0);
~FenetrePrinc();
public slots:
virtual void start();
virtual void tick();
virtual void stop_timer();
private:
Ui::FenetrePrinc *ui;
QString filename;
QGraphicsScene *scene_src;
QGraphicsItem *img_src;
VideoCapture sequence;
Mat src;
};
Class definition :
FenetrePrinc::FenetrePrinc(QWidget *parent) : QMainWindow(parent), ui(new Ui::FenetrePrinc){
ui->setupUi(this);
scene_src = new QGraphicsScene();
timer = new QTimer(this);
img_src = scene_src->addPixmap(QPixmap("vide.jpg"));
ui->view_src->setScene(scene_src);
connect(ui->buttonStart, SIGNAL(clicked()), this, SLOT(start()));
}
FenetrePrinc::~FenetrePrinc(){
delete scene_src;
delete img_src;
delete ui;
}
void FenetrePrinc::start(){
if(src.empty())
sequence.open(filename.toStdString());
connect(timer, SIGNAL(timeout()), this, SLOT(tick()));
timer->start(1000/24); //24 frames per second
disconnect(ui->buttonStart, SIGNAL(clicked()), this, SLOT(start()));
connect(ui->buttonStart, SIGNAL(clicked()), this, SLOT(stop_timer()));
}
void FenetrePrinc::tick(){
sequence >> src;
if(src.empty())
{
sequence.release();
stop_timer();
return;
}
scene_src->removeItem(img_src);
img_src = scene_src->addPixmap(convert16uc1(src));
src.release();
}
void FenetrePrinc::stop_timer(){
timer->stop();
disconnect(timer, SIGNAL(timeout()), this, SLOT(tick()));
disconnect(ui->buttonStart, SIGNAL(clicked()), this, SLOT(stop_timer()));
connect(ui->buttonStart, SIGNAL(clicked()), this, SLOT(start()));
}
I don't understand why does memory usage goes up and up each time an image is read, I do release the image each time it is read, and release sequence once finished. But maybe I missed something ?
EDIT : The function QPixmap convert16uc1(Mat img) is the cause of the memory leak. I have to use this function because I am working with 16bits grayscale images, which Qt cannot read. I open images and perform image processing with OpenCV and display the images with Qt.
The code of the function is the following :
QPixmap FenetrePrinc::convert16uc1(const cv::Mat& source)
{
quint16* pSource = (quint16*) source.data;
int pixelCounts = source.cols * source.rows;
QImage dest(source.cols, source.rows, QImage::Format_RGB32);
char* pDest = (char*) dest.bits();
for (int i = 0; i < pixelCounts; i++)
{
quint8 value = (quint8) ((*(pSource)) >> 8);
*(pDest++) = value; // B
*(pDest++) = value; // G
*(pDest++) = value; // R
*(pDest++) = 0; // Alpha
pSource++;
}
return QPixmap::fromImage(dest);
}
Most probably it is convert16uc1.
If you can't post convert16uc1 here, try saving the image temporarily in opencv using imwrite and loading the image in Qt. If the the memleak disappears. analyze convert16uc1.
Or don't call convert16uc1(src) but call addPixmap using some other constant image previously loaded in Qt.
I found what causes the problem and how to fix it, reading this thread.
From the Qt documentation:
void QGraphicsScene::removeItem ( QGraphicsItem * item )
Removes the item item and all its children from the scene. The ownership of item is passed on to the caller (i.e., QGraphicsScene will no longer delete item when destroyed).
See also addItem().
Once QGraphicsScene::removeItem(QGraphicsItem *item)` has been called, QGraphicsScene will no longer delete the item whenn destroyed.
Fix : call delete img_src after removeItem(img_src) : in function FenetrePrinc::tick() :
void FenetrePrinc::tick(){
sequence >> src;
if(src.empty())
{
sequence.release();
stop_timer();
return;
}
scene_src->removeItem(img_src);
delete img_src;
img_src = scene_src->addPixmap(convert16uc1(src));
src.release();
}

Why doesnt the old widget get removed?

Because of the mouseEvent, I would expect the red and blue widgets to switch places on every click. Instead the red switches to the blue and then it never switches back, why?
Frame *red = NULL;
Frame *blue = NULL;
bool isRed = true;
Frame::Frame(QWidget *parent) :
QFrame(parent)
{
}
Frame::~Frame(){
printf("deleted.\n");
fflush(0);
}
void QLayout_clear(QLayout* layout, bool deleteWidgets){
QLayoutItem* item;
QLayout* childLayout;
while ((item = layout->takeAt(0)) != NULL){
QWidget* widget = item->widget();
if (widget != NULL){
layout->removeWidget(widget);
if (deleteWidgets){
delete widget;
}
} else if (childLayout = item->layout()){
QLayout_clear(childLayout, deleteWidgets);
}
//delete item;
}
}
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
QVBoxLayout *layout = new QVBoxLayout;
this->setLayout(layout);
red = new Frame;
red->setFixedSize(100,100);
red->setStyleSheet("background-color:red");
blue = new Frame;
blue->setFixedSize(100,100);
blue->setStyleSheet("background-color:blue");
layout->addWidget(red);
}
void Widget::mouseReleaseEvent(QMouseEvent *){
printf("clicked.\n");
fflush(0);
QVBoxLayout *layout = (QVBoxLayout *)this->layout();
if (1){ //it doesnt matter if this is 1 or 0
delete layout;
layout = new QVBoxLayout;
this->setLayout(layout);
} else {
QLayout_clear(layout, false);
}
if (isRed){
layout->addWidget(blue);
isRed = false;
} else {
layout->addWidget(red);
isRed = true;
}
}
Note: here I am using a simple QFrame for the widgets to switch, in my application the widgets are much more complicated and I cannot recreate them every time I want to swap.
I believe it is not good practice from performance point of view to delete and recreate layers each time you want to switch frames, it would be easier to add both frames to the layer and then set visible only frame you want to appear. Also, boolean isRed is not necessary as info about visibility is contained within each frame itself...
Try this:
QFrame *red = NULL;
QFrame *blue = NULL;
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
QVBoxLayout *layout = new QVBoxLayout(this);
red = new QFrame(this);
red->setFixedSize(100,100);
red->setStyleSheet("background-color:red");
red->setHidden(false);
blue = new QFrame(this);
blue->setFixedSize(100,100);
blue->setStyleSheet("background-color:blue");
blue->setHidden(true);
layout->addWidget(red);
layout->addWidget(blue);
this->setLayout(layout);
}
void Widget::mouseReleaseEvent(QMouseEvent *){
printf("clicked.\n");
fflush(0);
red->setHidden(!red->isHidden());
blue->setHidden(!blue->isHidden());
}
The layout destructs its children. Because addWidget takes ownership so delete layout will delete red and blue widgets too.