I'm quite a n00b with Qt, but I have seen a lot of posts where people can't get a QGraphicsView to update as expected. I am trying to make very simple widget that will display two arrays of data as images overlapped with some alpha blending. From what I gather the best way to do this is to create a QImage to hold the image data, and each time I want to update the overlay display, you convert the QImage into a QPixmap, and then use that to update the pixmap of a QGraphicsPixmapItem (which is in a QGraphicsScene, which is in a QGraphicsView).
As a minimum working example, I have it setup to generate a random red image and random green image, and with random blending between them. It's setup on a timer to generate new data and, ideally, update the view. No matter how many updates/repaints I drop around, I can't seem to get it to update properly. The timer seems to be working, and the random data generation seems to be working as well, but the scene only updates if I physically change the size of the window.
Here is my code, in a few blocks. First DisplayWidget.h
#include <QtWidgets/QWidget>
#include "ui_displaywidget.h"
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QGraphicsPixmapItem>
#include <QPushButton>
#include "math.h"
#include <QVBoxLayout>
#include <qsize.h>
#include <qtimer.h>
#define FULLSCALE 255
#define IM_X_MIN -5.0
#define IM_X_MAX 5.0
#define IM_Z_MIN 0.0
#define IM_Z_MAX 15.0
#define IM_PIXEL_WIDTH 200
#define IM_PIXEL_HEIGHT IM_PIXEL_WIDTH * (IM_Z_MAX-IM_Z_MIN)/(IM_X_MAX - IM_X_MIN)
#define BORDER_WIDTH 10
#define RAND_SEED 7
class DisplayWidget : public QWidget
{
Q_OBJECT
public:
DisplayWidget(int width, int height, QWidget *parent = 0);
~DisplayWidget();
void SetData(float * data, float minVal, float maxVal);
void SetTransparency(float * alpha, float minVal, float maxVal);
private:
//Ui::DisplayWidgetClass ui;
QGraphicsView * view;
QGraphicsScene * scene;
QGraphicsPixmapItem * bModeItem;
QGraphicsPixmapItem * dModeItem;
QImage * bImage;
QImage * dImage;
QTimer * frameGrab;
void CreateWidgets();
void SetupGui();
int w, h;
public slots:
void GenerateNewData();
};
And here's the relevant parts of my .cpp. GenerateNewData() is the function that doesn't seem to produce updates to the scene. This is where I have tried view/scene/item updates/repaints.
DisplayWidget::DisplayWidget(int width, int height, QWidget *parent): QWidget(parent)
{
w = width;
h = height;
CreateWidgets();
SetupGui();
// seed the random number generator
srand(RAND_SEED);
GenerateNewData();
}
void DisplayWidget::CreateWidgets()
{
view = new QGraphicsView(this);
scene = new QGraphicsScene(this);
bModeItem = scene->addPixmap(QPixmap());
dModeItem = scene->addPixmap(QPixmap());
bImage = new QImage(w, h, QImage::Format_ARGB32);
dImage = new QImage(w, h, QImage::Format_ARGB32);
frameGrab = new QTimer(this);
}
void DisplayWidget::SetupGui()
{
QVBoxLayout * layout = new QVBoxLayout(this);
layout->addWidget(view);
setLayout(layout);
scene->setSceneRect(0, 0, IM_PIXEL_WIDTH, IM_PIXEL_HEIGHT);
view->setGeometry(0, 0, IM_PIXEL_WIDTH + 2*BORDER_WIDTH, IM_PIXEL_HEIGHT + 2*BORDER_WIDTH);
view->setScene(scene);
bModeItem->setPixmap(QPixmap::fromImage(*bImage).scaled(QSize(IM_PIXEL_WIDTH, IM_PIXEL_HEIGHT)));
dModeItem->setPixmap(QPixmap::fromImage(*dImage).scaled(QSize(IM_PIXEL_WIDTH, IM_PIXEL_HEIGHT)));
connect(frameGrab, SIGNAL(timeout()),this, SLOT(GenerateNewData()));
frameGrab->start(500);
}
void DisplayWidget::GenerateNewData()
{
QRgb * bImageData = (QRgb *)bImage->scanLine(0);
QRgb * dImageData = (QRgb *)dImage->scanLine(0);
for (int i; i < w * h; i++)
{
bImageData[i] = qRgba(rand() % FULLSCALE, 0, 0, FULLSCALE);
dImageData[i] = qRgba(0, 255,0, rand() % FULLSCALE);
}
bModeItem->setPixmap(QPixmap::fromImage(*bImage).scaled(QSize(IM_PIXEL_WIDTH, IM_PIXEL_HEIGHT)));
dModeItem->setPixmap(QPixmap::fromImage(*dImage).scaled(QSize(IM_PIXEL_WIDTH, IM_PIXEL_HEIGHT)));
}
And here's my main.
#include "displaywidget.h"
#include <QtWidgets/QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
DisplayWidget w(25,25);
w.show();
return a.exec();
}
I would appreciate any help!
Related
I'm trying to create a simple frame in Qt with a tick and some text. I made two new label implementations because I wanted the labels to dynamically fill all the available space but when I resize the window the sizes are off, as shown by the qDebug output, which represents the size of the image label:
Resized: 244 , 244 <-- Window first created
Resized: 305 , 305 <-- Window maximized
Resized: 135 , 135 <-- Window restored to original size
As you can see, when the window is restored to its original size the image is not. The last size should be 244, 244.
The code which describes the behaviour of the two widgets is the following:
"widgets.h":
/*
* This file includes many custom widgets.
*/
#ifndef APOCRYPHA_WIDGETS
#define APOCRYPHA_WIDGETS
#include <QWidget>
#include <QLabel>
#include <QTimer>
#include <QPixmap>
#include <QResizeEvent>
#include <QPaintEvent>
class AutoTextLabel : public QLabel {
Q_OBJECT
public:
explicit AutoTextLabel(QWidget* parent);
AutoTextLabel(QWidget* parent, QString text);
protected:
void resizeEvent(QResizeEvent* event) override;
private:
QTimer* resizeTimer;
private slots:
void onResizeEnd();
};
class AutoImageLabel : public QLabel {
Q_OBJECT
public:
explicit AutoImageLabel(QWidget* parent);
AutoImageLabel(QWidget* parent, const QPixmap& pixmap);
void setFillOrientation(int orientation);
QSize sizeHint() const override;
public slots:
void setPixmap(const QPixmap &newPix);
void resizeEvent(QResizeEvent* event) override;
protected:
// void paintEvent(QPaintEvent* event) override;
private:
int fillOrientation;
int widthForHeight(int h) const;
int heightForWidth(int w) const override;
QPixmap scaledPixmap() const;
QPixmap labelPixmap;
};
#endif //APOCRYPHA_WIDGETS
"widgets.cpp":
/*
* This file includes many custom widgets.
*/
#include "widgets.h"
#include <QPainter>
#include <QDebug>
AutoTextLabel::AutoTextLabel(QWidget *parent, QString text) : QLabel(text, parent){
// Enable antialiasing
QFont aaFont(font());
aaFont.setStyleStrategy(QFont::PreferAntialias);
setFont(aaFont);
// This timer is used to fire a slot when a window is resized
resizeTimer = new QTimer();
resizeTimer->setSingleShot(true);
connect(resizeTimer, SIGNAL(timeout()), SLOT(onResizeEnd()));
}
AutoTextLabel::AutoTextLabel(QWidget *parent) : AutoTextLabel(parent, "") {}
void AutoTextLabel::resizeEvent(QResizeEvent *event) {
QWidget::resizeEvent(event);
// Only fire when 25ms have passed since the last resize.
resizeTimer->start(25);
}
void AutoTextLabel::onResizeEnd() {
QFont updatedFont(font());
// Resize Text
if (!text().isEmpty()){
int fontSize = 1;
updatedFont.setPixelSize(fontSize);
QRect boundingRectangle;
// Update bounding rectangle
if (wordWrap())
boundingRectangle = QFontMetrics(updatedFont).boundingRect(contentsRect(), Qt::TextWordWrap, text());
else
boundingRectangle = QFontMetrics(updatedFont).boundingRect(text());
while (boundingRectangle.height() <= contentsRect().height()) {
fontSize++;
updatedFont.setPixelSize(fontSize);
// Update bounding rectangle
if (wordWrap())
boundingRectangle = QFontMetrics(updatedFont).boundingRect(contentsRect(), Qt::TextWordWrap, text());
else
boundingRectangle = QFontMetrics(updatedFont).boundingRect(text());
}
updatedFont.setPixelSize(fontSize - 1);
setFont(updatedFont);
}
}
/* Auto Image Label */
AutoImageLabel::AutoImageLabel(QWidget *parent, const QPixmap &pixmap) : QLabel(parent) {
setMinimumSize(1, 1);
setScaledContents(false);
setPixmap(pixmap);
}
AutoImageLabel::AutoImageLabel(QWidget *parent) : QLabel(parent) {
setScaledContents(false);
}
void AutoImageLabel::resizeEvent(QResizeEvent *event) {
QWidget::resizeEvent(event);
if(!labelPixmap.isNull())
QLabel::setPixmap(scaledPixmap());
qDebug() << "Resized: " << scaledPixmap().width() << ", " << scaledPixmap().height();
}
int AutoImageLabel::widthForHeight(int h) const {
return labelPixmap.isNull() ? width() : (labelPixmap.width() * h) / labelPixmap.height();
}
int AutoImageLabel::heightForWidth(int w) const {
return labelPixmap.isNull() ? height() : (labelPixmap.height() * w) / labelPixmap.width();
}
void AutoImageLabel::setFillOrientation(int orientation) {
this->fillOrientation = orientation;
}
QSize AutoImageLabel::sizeHint() const {
if (fillOrientation == Qt::Horizontal)
return QSize(width(), heightForWidth(width()));
else
return QSize(widthForHeight(height()), height());
}
QPixmap AutoImageLabel::scaledPixmap() const {
return labelPixmap.scaled(sizeHint(), Qt::KeepAspectRatio, Qt::SmoothTransformation);
}
void AutoImageLabel::setPixmap(const QPixmap &newPix) {
labelPixmap = newPix;
QLabel::setPixmap(scaledPixmap());
}
"other_frames.h":
//
// Created by Riccardo on 18/09/2017.
//
#ifndef APOCRYPHA_OTHER_FRAMES_H
#define APOCRYPHA_OTHER_FRAMES_H
#include <QFrame>
#include <QLabel>
#include <QGridLayout>
#include <QWidget>
#include <QResizeEvent>
#include <QPixmap>
#include <QTimer>
#include "widgets.h"
class ConfirmationFrame : public QFrame {
Q_OBJECT
public:
explicit ConfirmationFrame(QWidget* parent);
ConfirmationFrame(QWidget* parent, const QString& text);
private:
QGridLayout* layout;
AutoImageLabel* imageLabel;
AutoTextLabel* textLabel;
};
#endif //APOCRYPHA_OTHER_FRAMES_H
"other_frames.cpp":
//
// Created by Riccardo on 18/09/2017.
//
#include "other_frames.h"
#include <QDebug>
ConfirmationFrame::ConfirmationFrame(QWidget* parent, const QString &text) : QFrame(parent) {
textLabel = new AutoTextLabel(this, text);
QPixmap pix(":/images/check-tick.png");
imageLabel = new AutoImageLabel(this, pix);
textLabel->setAlignment(Qt::AlignCenter);
imageLabel->setAlignment(Qt::AlignCenter);
textLabel->setWordWrap(true);
// Green Background
setStyleSheet("background-color: rgba(106, 242, 94, 1);");
layout = new QGridLayout();
layout->setSpacing(0);
layout->setContentsMargins(32, 32, 32, 32);
layout->setRowStretch(0, 1);
layout->setRowStretch(1, 1);
layout->addWidget(imageLabel, 0, 1);
layout->addWidget(textLabel, 1, 1);
setLayout(layout);
}
ConfirmationFrame::ConfirmationFrame(QWidget *parent) : ConfirmationFrame(parent, "") {
}
"window_main.h":
#ifndef WINDOW_MAIN_H
#define WINDOW_MAIN_H
#include <QMainWindow>
#include <QMenuBar>
#include <QMenu>
#include <QGridLayout>
#include <QFrame>
#include <QScreen>
class MainWindow : public QMainWindow {
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
QFrame *mainFrame;
void center(QScreen* screen);
void autoSetSize(QScreen* screen);
private:
void createMenu();
// Components
QGridLayout *mainLayout;
QMenuBar *menuBar;
QMenu *fileMenu;
};
#endif // WINDOW_MAIN
"window_main.cpp":
#include "window_main.h"
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) {
mainFrame = new QFrame();
mainLayout = new QGridLayout();
mainLayout->setSpacing(0);
mainLayout->setContentsMargins(0, 0, 0, 0);
createMenu();
mainFrame->setStyleSheet("background-color: red;");
mainFrame->setLayout(mainLayout);
setCentralWidget(mainFrame);
}
void MainWindow::createMenu(){
menuBar = new QMenuBar;
fileMenu = new QMenu(tr("&File"), this);
menuBar->addMenu(fileMenu);
setMenuBar(menuBar);
}
void MainWindow::center(QScreen *screen) {
QSize size = screen->availableSize();
int x = size.width() / 2 - width() / 2;
int y = size.height() / 2 - height() / 2;
move(x, y);
}
void MainWindow::autoSetSize(QScreen *screen) {
QSize screenSize = screen->availableSize();
// TODO Math.round
setMinimumSize(QSize((int)(screenSize.width() / 1.25), (int)(screenSize.height() / 1.25)));
}
"main.cpp":
#include <QApplication>
#include <iostream>
#include <QFile>
#include "quiz/choice.h"
#include "quiz/question.h"
#include "quiz/quizmaker.h"
#include <QSettings>
#include <QStandardPaths>
#include <QDebug>
#include <src/user_interface/other_frames.h>
#include "user_interface/window_main.h"
#include <QScreen>
#include <QFontDatabase>
int main(int argc, char *argv[]) {
QApplication a(argc, argv);
// Set Application Parameters
QCoreApplication::setOrganizationName("Riccardo Fagiolo");
QCoreApplication::setOrganizationDomain("kopharex.me");
QCoreApplication::setApplicationName("Apocrypha");
// Set application font
const int id = QFontDatabase::addApplicationFont(":/fonts/montserrat/Montserrat-Regular.otf");
QString family = QFontDatabase::applicationFontFamilies(id).at(0);
QFont font(family);
font.setStyleStrategy(QFont::PreferAntialias);
a.setFont(font);
// App Settings
QSettings settings;
settings.setValue("data_dir", QStandardPaths::writableLocation(QStandardPaths::AppDataLocation));
// Create UI
auto* window = new MainWindow();
ConfirmationFrame* cframe = new ConfirmationFrame(window, "But I must explain to you how all this mistaken idea of denouncing pleasure and praising pain was born and I will give you a complete account of the system, and expound the actual teachings of the great explorer of the truth, the master-builder of human happiness. No one rejects, dislikes, or avoids pleasure itself, because it is pleasure, but because those who do not know how to pursue pleasure rationally encounter consequences that are extremely painful. Nor again is there anyone who loves or pursues or desires to obtain pain of itself, because it is pain, but because occasionally circumstances occur in which toil and pain can procure him some great pleasure. To take a trivial example, which of us ever undertakes laborious physical exercise, except to obtain some advantage from it? But who has any right to find fault with a man who chooses to enjoy a pleasure that has no annoying consequences, or one who avoids a pain that produces no resultant pleasure?");
window->mainFrame->layout()->addWidget(cframe);
window->autoSetSize(a.primaryScreen());
//cframe->updateTextLabel();
window->show();
window->center(a.primaryScreen());
// [...] - Nothing related to user interface.
return a.exec();
}
Here is a screenshot of the current MainWindow and ConfirmationFrame to give you an idea of what i'm trying to accomplish:
Window Screenshot
All comments regarding the code are welcome.
Thanks for any help,
Riccardo
Hello I tried to fix the resizing issue with an hack.
Before starting the timer to resize the text, just reduce its font to a 1 pixel font:
void AutoTextLabel::resizeEvent(QResizeEvent *event) {
QWidget::resizeEvent(event);
// set a very small font, then start the timer
QFont updatedFont(font());
updatedFont.setPixelSize(1);
setFont(updatedFont);
// Only fire when 25ms have passed since the last resize.
resizeTimer->start(25);
}
Can the effect be acceptable in your opinion?
I have been stuck on this issue for 2 days now. I'm using the Qt plugin for Visual Studio 2013 on Window 7-64 bit.
I have been trying to display a pair of images in QLabels. I need to manipulate the pixel data regularly, so I store them in QImages, and every time I want to refresh the display I set the QPixmap of a QLabel. The problem is, it only seems to refresh if I change/move the window in some way.
This problems goes away if I just make the QLabels children of my QWidget, but never set a layout. If I then add repaint() or update(), the problem comes back.
(this is a very similar post to one I posted using QGraphicsScene, but the problem seems to be more fundamental than that, so I am reposting)
Here is my code. First the .h
#ifndef DISPLAYWIDGET_H
#define DISPLAYWIDGET_H
#include <QtWidgets/QWidget>
#include <QPixmap>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QGraphicsPixmapItem>
#include <QPushButton>
#include <QHBoxLayout>
#include <QTimer>
#include <QLabel>
#define FULLSCALE 255
#define IM_X_MIN -5.0
#define IM_X_MAX 5.0
#define IM_Z_MIN 0.0
#define IM_Z_MAX 15.0
#define IM_PIXEL_WIDTH 200
#define IM_PIXEL_HEIGHT IM_PIXEL_WIDTH * (IM_Z_MAX-IM_Z_MIN)/(IM_X_MAX - IM_X_MIN)
#define BORDER_WIDTH 10
#define RAND_SEED 7
class DisplayWidget : public QWidget
{
Q_OBJECT
public:
DisplayWidget(int width, int height, QWidget *parent = 0);
~DisplayWidget();
private:
QLabel* bimageLabel;
QLabel* dimageLabel;
QImage* bImage;
QImage* dImage;
QTimer* frameGrab;
QPushButton* debugButton;
void CreateWidgets();
void SetupGui();
int w, h;
public slots:
void GenerateNewData();
};
#endif // DISPLAYWIDGET_H
and the .cpp.
#include "displaywidget.h"
DisplayWidget::DisplayWidget(int width, int height, QWidget *parent): QWidget(parent)
{
//ui.setupUi(this);
w = width;
h = height;
CreateWidgets();
SetupGui();
// seed the random number generator
srand(RAND_SEED);
GenerateNewData();
}
DisplayWidget::~DisplayWidget()
{
}
void DisplayWidget::CreateWidgets()
{
bImage = new QImage(w, h, QImage::Format_ARGB32);
dImage = new QImage(w, h, QImage::Format_ARGB32);
bimageLabel = new QLabel(this);
dimageLabel = new QLabel(this);
debugButton = new QPushButton("DEBUG", this);
bimageLabel->setStyleSheet("QLabel {background-color: black};");
dimageLabel->setStyleSheet("QLabel {background-color: white};");
frameGrab = new QTimer(this);
}
void DisplayWidget::SetupGui()
{
QHBoxLayout * layout = new QHBoxLayout();
setLayout(layout); // commenting this line out makes it refresh
layout->addWidget(bimageLabel);
layout->addWidget(dimageLabel);
layout->addWidget(debugButton);
connect(frameGrab, SIGNAL(timeout()),this, SLOT(GenerateNewData()));
connect(debugButton, SIGNAL(clicked()), this, SLOT(GenerateNewData()));
frameGrab->start(50);
}
void DisplayWidget::GenerateNewData()
{
QRgb * bImageData = (QRgb *)bImage->scanLine(0);
QRgb * dImageData = (QRgb *)dImage->scanLine(0);
for (int i; i < w * h; i++)
{
bImageData[i] = qRgba(rand() % FULLSCALE, 0, 0, FULLSCALE);
dImageData[i] = qRgba(0, 0, rand() % FULLSCALE, FULLSCALE);
}
bimageLabel->setPixmap(QPixmap::fromImage(*bImage).scaled(QSize(IM_PIXEL_WIDTH, IM_PIXEL_HEIGHT)));
dimageLabel->setPixmap(QPixmap::fromImage(*dImage).scaled(QSize(IM_PIXEL_WIDTH, IM_PIXEL_HEIGHT)));
//this->update(); // this breaks it again
}
Losing my mind here. I have very limited experience with Qt, but I believe I have the right approach.
Please help!
I've tested your code on Ubuntu. I can unfortunately not comment on windows. I've modified the code slightly (See comments marked by //#w:):
#include "displaywidget.h"
DisplayWidget::DisplayWidget(int width, int height, QWidget *parent): QWidget(parent)
{
w = width;
h = height;
CreateWidgets();
SetupGui();
// seed the random number generator
srand(RAND_SEED);
GenerateNewData();
}
DisplayWidget::~DisplayWidget()
{
}
void DisplayWidget::CreateWidgets()
{
bImage = new QImage(w, h, QImage::Format_ARGB32);
dImage = new QImage(w, h, QImage::Format_ARGB32);
bimageLabel = new QLabel(this);
bimageLabel->setScaledContents(true);
dimageLabel = new QLabel(this);
dimageLabel->setScaledContents(true);
debugButton = new QPushButton("DEBUG", this);
bimageLabel->setStyleSheet("QLabel {background-color: black};");
dimageLabel->setStyleSheet("QLabel {background-color: grey};");
frameGrab = new QTimer(this);
}
void DisplayWidget::SetupGui()
{
//#w: Adding vertical layout as button below seems like better usage of space...
QVBoxLayout * vlay = new QVBoxLayout();
QHBoxLayout * hlay = new QHBoxLayout();
hlay->addWidget(bimageLabel);
hlay->addWidget(dimageLabel);
vlay->addLayout(hlay);
vlay->addWidget(debugButton);
//#w: Removing size constraints on top layout allows me to resize window and see effect.
vlay->setSizeConstraint(QLayout::SetNoConstraint);
setLayout(vlay); // commenting this line out makes it refresh
connect(frameGrab, SIGNAL(timeout()),this, SLOT(GenerateNewData()));
//#w: I suppose we can chuck the button.... Currently it serves no purpose
connect(debugButton, SIGNAL(clicked()), this, SLOT(GenerateNewData()));
//#w: Timer slower initially, then increase to see where performance degrades.
frameGrab->start(200);
}
void DisplayWidget::GenerateNewData()
{
QRgb * bImageData = (QRgb *)bImage->scanLine(0);
QRgb * dImageData = (QRgb *)dImage->scanLine(0);
//#w: This code just varies the contents by having a b and d selector that
// alternates colour... Simple stuff...
int bSelect = rand() % 3,
dSelect = (bSelect==2) ? 0 : bSelect+1;
for (int i = 0; i < w * h; i++)
{
//#w: 3 colours, only two being selected - can be improved, I suppose.
QRgb rgb[3] =
{
(bSelect == 0) || (dSelect==0) ? qRgba(rand() % FULLSCALE, 0, 0, FULLSCALE) : 0,
(bSelect == 1) || (dSelect==1) ? qRgba(0, rand() % FULLSCALE, 0, FULLSCALE) : 0,
(bSelect == 2) || (dSelect==2) ? qRgba(0, 0, rand() % FULLSCALE, FULLSCALE) : 0,
};
bImageData[i] = rgb[bSelect];
dImageData[i] = rgb[dSelect];
}
//#w: Removed scaling, as it depends on layout...
bimageLabel->setPixmap(QPixmap::fromImage(*bImage));
dimageLabel->setPixmap(QPixmap::fromImage(*dImage));
}
Initially the labels take its size from the pixmaps. Thereafter the labels adhere to the layout, which adheres to resizing of the form (main/parent widget)
QtForum solved this one for me, and boy is it embarassing.
I forgot to initialize the loop counter. Fixed everything.
My app display a long scientific vertically-scrollable picture (1024 x 99999999... px) as a sequence of QPixmap 1024x128 blocks. This allows me to scroll a picture with minimal CPU-cost by picking needed blocks from a table: block_id = y_coord/128. Also, QPixmap is preferred "pixel container" for fast screen output.
But now I have a stream of new data coming to the application and need the new data to be added and displayed at the bottom of the long picture. Minimal portion: 1024x1 (a line). Also, I would like to display each new line as soon as possible (close to real-time). Each new portion of 128 lines will be "packed" to QPixmap, but until I received enough data I cannot build a whole block.
What approach should I consider for displaying the new data?
This video gives an idea of "adding new lines of data", except in my case the flow goes up: http://www.youtube.com/watch?v=Dy3zyQNK7jM
You can simply, directly, modify the bottom row of QPixmaps and update() the window (if the bottom row is in range).
You might find using a QImage is more efficient for half-baked rows, depending on how quickly you update/repaint.
On contemporary Qt, when using the raster backend, QPixmap offers no benefits compared to QImage. Everything is rendered to a big QImage backing buffer that then gets blitted to the screen. So just use QImage.
You can have a QImage that is 128 pixels high, but you only draw the part of it that was already filled with data. The part without data is either not drawn, or hangs below the visible area of the window, and is thus effectively invisible.
Here is a quick example I put together. I don't know if it is the most efficient, but it shows off the basic idea you are looking at:
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QGraphicsView>
#include <QGraphicsPixmapItem>
#include <QVector>
#include <QGraphicsScene>
#include <QTimerEvent>
#define TILE_HEIGHT 128
#define TILE_WIDTH 1024
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = 0);
~MainWindow();
QPixmap generateLine();
public slots:
void timerEvent(QTimerEvent *);
private:
QGraphicsView * m_view;
QGraphicsScene * m_scene;
QVector <QGraphicsPixmapItem *> m_tiles;
QVector <QGraphicsPixmapItem *> m_lineBuffer;
};
#endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h"
#include <QPixmap>
#include <QtGlobal>
#include <QDateTime>
#include <QTimer>
#include <QPaintEngine>
#include <QDebug>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
this->setFixedWidth(TILE_WIDTH);
this->setCentralWidget(m_view = new QGraphicsView());
m_scene = new QGraphicsScene;
m_view->setScene(m_scene);
QPixmap p(TILE_WIDTH, TILE_HEIGHT);
p.fill(Qt::black);
m_tiles.append(new QGraphicsPixmapItem(p));
m_tiles.last()->setPos(0,0);
m_scene->addItem(m_tiles.last());
qsrand(QDateTime::currentMSecsSinceEpoch());
this->startTimer(0);
}
MainWindow::~MainWindow()
{
}
void MainWindow::timerEvent(QTimerEvent *)
{
// if your generated data is on another thread, you may want to do some thread
// synchronization with a Mutex and a Mutex Locker so you don't stomp on your
// buffers
// static bool busy = false;
// static int skipCount = 0;
// if(busy)
// {
// skipCount++;
// qDebug() << "Skipped Line count =" << skipCount;
// return;
// }
// busy = true;
// grab a new line
QPixmap linePix = generateLine();
int y = m_tiles.size()*TILE_HEIGHT + m_lineBuffer.size()*1;
// append it to the line buffer
m_lineBuffer.append(new QGraphicsPixmapItem(linePix));
// add it to the scene
m_scene->addItem(m_lineBuffer.last());
m_lineBuffer.last()->setPos(0, y);
// scroll it into view
m_view->ensureVisible(m_lineBuffer.last());
if(m_lineBuffer.size() >= TILE_HEIGHT)
{
// when the line buffer is "full"
// or ready to be made into a tile
// compile all the qpixmaps into a single "tile"
static QRectF source(0,0, TILE_WIDTH, 1);
QPixmap tile(TILE_WIDTH, TILE_HEIGHT);
QPainter painter;
painter.begin(&tile);
for(int i = 0; i < m_lineBuffer.size(); i++)
{
painter.drawPixmap(QRectF(0, i, TILE_WIDTH, 1),
m_lineBuffer.at(i)->pixmap(),
source);
}
painter.end();
// add it into the tiles list
m_tiles.append(new QGraphicsPixmapItem(tile));
// add it to the scene
m_tiles.last()->setPos(0, (m_tiles.size() - 1)*TILE_HEIGHT);
m_scene->addItem(m_tiles.last());
// scroll it into view
m_view->ensureVisible(m_tiles.last());
// Clean up the line buffer
foreach(QGraphicsPixmapItem * pi, m_lineBuffer)
{
m_scene->removeItem(pi);
delete pi;
}
m_lineBuffer.clear();
}
// busy = false;
}
QPixmap MainWindow::generateLine()
{
// create a random pixmap of TILE_WIDTH x 1
static int img_width = TILE_WIDTH;
QImage img(img_width,1, QImage::Format_RGB16);
for(int i = 0; i< img_width; i++)
{
img.setPixel(i, 0, qrand()%65536);
}
return QPixmap::fromImage(img);
}
main.cpp
#include <QApplication>
#include "mainwindow.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.showMaximized();
return a.exec();
}
I want to animate small (100x20) image by changing the color of its pixels by the same value. For example, increase red-channel value by 1 every frame and then decrease back. The image has alpha channel, the animation speed is 30...100 fps (platform dependent; 30 is enough for linux, but windows requires ~70 to look smooth).
As i know, drawing is faster when done in QImage, but displaying is faster with QPixmap.
I like QGraphicsEffects, and QPropertyAnimations. White doesn't colorize, but black does.
#include <QLabel>
#include <QPixmap>
#include <QGraphicsColorizeEffect>
#include <QTimerEvent>
#include <QPropertyAnimation>
#include <QShowEvent>
#include <QDebug>
class Widget : public QLabel
{
Q_OBJECT
Q_PROPERTY(qreal redness READ getRedness WRITE setRedness)
public:
Widget(QWidget *parent = 0)
{
QPixmap p(300, 300);
p.fill(Qt::black);
this->setPixmap(p);
colorize = new QGraphicsColorizeEffect();
colorize->setColor(Qt::red);
redness = 0;
colorize->setStrength(redness);
this->setGraphicsEffect(colorize);
animation = new QPropertyAnimation(this,"redness");
animation->setDuration(2000);
animation->setLoopCount(10);
animation->setStartValue(0.0);
animation->setEndValue(1.0);
animation->setEasingCurve(QEasingCurve::CosineCurve);
animation->start();
}
~Widget(){}
qreal getRedness()
{
return redness;
}
void setRedness(qreal val)
{
redness = val;
colorize->setStrength(redness);
this->update();
// qDebug() << redness;
}
public slots:
void showEvent(QShowEvent *)
{
animation->start();
}
private:
qreal redness;
QGraphicsColorizeEffect * colorize;
QPropertyAnimation * animation;
};
and here is the main.cpp
#include <QApplication>
#include "widget.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
Hope that helps.
When I was using QGridLayout to display my widgets, only the widget was shown and the part of the image that was transparent was not shown. Now I switched to using QGraphicsScene and QGraphicsView, and now my images have a gray background wherever they used to be transparent.
void Piece::paintEvent(QPaintEvent *)
{
string image = ":/images/" + color + piece + ".png";
pixmap.load(image.c_str());
//pixmap.setMask(pixmap.createMaskFromColor(QColor(240, 240, 240)));
QPainter paint(this);
paint.drawPixmap(0, 0, pixmap);
}
That's how the image is displayed on my widget. When I used the code,
layout->addWidget(0,0,1,1);
the background is transparent. But when I use,
scene->addWidget(piece);
The widget has a gray background. How can I make it transparent? The full code can be found here if necessary (probably won't be necessary): https://github.com/gsingh93/Chess
EDIT: I can't figure this problem out at all... I tried using setAutoFillBackground(false); but that didn't work. So my last hope was converting my whole class from a QWidget to a QGrahhicsItem. That didn't work and the background of the image is still gray instead of transparent. If you can't figure out what's wrong with this code can someone please post or link me to an example of how to display an image with a transparent background using QGraphicsScene? Here is the original code, followed by the QGraphicsItem code, followed by my main function.
#include "headers/piece.h"
#include <QPainter>
#include <QMouseEvent>
#include <QBitmap>
#include <QCursor>
using namespace std;
Piece::Piece(string color, string piece, QWidget *parent) :
QWidget(parent)
{
this->piece = piece;
this->color = color;
this->setMaximumHeight(36);
this->setMaximumWidth(36);
x = 0;
y = 0;
setMouseTracking(false);
}
void Piece::paintEvent(QPaintEvent *)
{
string image = ":/images/" + color + piece + ".png";
pixmap.load(image.c_str());
//pixmap.setMask(pixmap.createMaskFromColor(QColor(240, 240, 240)));
QPainter paint(this);
paint.drawPixmap(0, 0, pixmap);
}
void Piece::setPosition(int file, int rank)
{
pixmap.load(":/images/whitepawn.png");
QImage image = pixmap.toImage();
x = (file-1)*50 + 18;// - image.width()/2;
y = (rank-1)*50 + 18;// - image.height()/2;
move(x, y);
}
void Piece::mouseMoveEvent(QMouseEvent *event)
{
if(event->buttons() == Qt::LeftButton)
{
x = event->globalX()-18;
y = event->globalY()-18;
move(x,y);
}
}
.
#include "piece2.h"
#include <QPainter>
#include <QMouseEvent>
#include <QBitmap>
#include <QCursor>
#include <QGraphicsSceneMouseEvent>
using namespace std;
Piece2::Piece2(string color, string piece, QObject *parent) :
QGraphicsItem()
{
this->piece = piece;
this->color = color;
//this->setMaximumHeight(36);
//this->setMaximumWidth(36);
x = 0;
y = 0;
//setMouseTracking(false);
}
void Piece2::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
string image = ":/images/" + color + piece + ".png";
pixmap.load(image.c_str());
//pixmap.setMask(pixmap.createMaskFromColor(QColor(240, 240, 240)));
//QPainter paint(this);
painter->drawPixmap(0, 0, pixmap);
}
void Piece2::setPosition(int file, int rank)
{
pixmap.load(":/images/whitepawn.png");
QImage image = pixmap.toImage();
x = (file-1)*50 + 18;// - image.width()/2;
y = (rank-1)*50 + 18;// - image.height()/2;
setPos(x, y);
}
void Piece2::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
if(event->buttons() == Qt::LeftButton)
{
// x = event->globalX()-18;
// y = event->globalY()-18;
setPos(x,y);
}
}
.
#include <QtGui>
#include <QGraphicsScene>
#include <QGraphicsView>
#include "headers/board.h"
#include "headers/pawn.h"
#include "headers/knight.h"
#include "headers/bishop.h"
#include "headers/rook.h"
#include "headers/king.h"
#include "headers/queen.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QGraphicsScene *scene = new QGraphicsScene();
QGraphicsView *view = new QGraphicsView();
Board board;
scene->addWidget(&board);
scene->addWidget(board.pawn2);
board.pawn2->setPosition(1,1);
//view->viewport()->setPalette(QColor(Qt::transparent));
//view->viewport()->setAutoFillBackground(false);
view->setScene(scene);
//view->setBackgroundRole(QPalette::NoRole);
view->show();
return app.exec();
}
Did you try using a stylesheet to set the background transparency?
yourWidget->setStyleSheet("background-color: transparent;");
I confirm your problem. Additional, laurents solution can't work in Linux. But for me worked next action:
Before put your QWidget to QGraphicsScene by addWidget() method, set him next flag:
board->setAttribute( Qt::WA_TranslucentBackground );
Detailed explanation is here (ru): https://webhamster.ru/mytetrashare/index/mtb0/1625823664d5m6pmpy7s