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.
Related
I am using the code below to try to rotate the pointer simulating the second hand of the clock, but when he turns it cuts, the square of the background is fixed and it seems I'm not able to rotate all:
QPixmap shipPixels(":/new/prefix1/imagem/ponteiro.png");
QPixmap rotatePixmap(shipPixels.size());
rotatePixmap.fill(Qt::transparent);
QPainter p(&rotatePixmap);
p.translate(rotatePixmap.size().width() / 2, rotatePixmap.size().height() / 2);
p.rotate(90);
p.translate(-rotatePixmap.size().width() / 2, -rotatePixmap.size().height() / 2);
p.drawPixmap(0, 0, shipPixels);
p.end();
shipPixels = rotatePixmap;
ui->label->setPixmap(rotatePixmap);
The pointer looks like this:
Now with it rotated 90 ยบ
Qt Analog Clock example:
http://qt-project.org/doc/qt-5/qtwidgets-widgets-analogclock-example.html
Maybe before rotating QPixmaps, try drawing a line. After the line is in place and drawing correctly work backwards from there.
UPDATE:
Some sample code for rotating images.
widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QPaintEvent>
#include <QPixmap>
#include <QTime>
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = 0);
signals:
public slots:
void paintEvent(QPaintEvent *);
private:
QPixmap bg;
QPixmap second_hand;
QTime time;
};
#endif // WIDGET_H
widget.cpp
#include "widget.h"
#include <QPainter>
#include <QTimer>
#include <QTime>
Widget::Widget(QWidget *parent) :
QWidget(parent)
{
time.restart();
this->resize(256, 256);
// both images are 256x256 in this example
bg.load("./images/bg.png");
second_hand.load("./images/second_hand.png");
QTimer * t = new QTimer;
t->setSingleShot(false);
t->setInterval(15);
QObject::connect(t,SIGNAL(timeout()), this, SLOT(update()));
t->start();
}
void Widget::paintEvent(QPaintEvent * e)
{
QPainter p(this);
p.drawPixmap(QPoint(0,0),bg);
qreal seconds = ((qreal)(time.elapsed() % 60000))/1000;
p.translate(this->width()/2, this->height()/2);
p.rotate(seconds/60*360);
p.drawPixmap(QPoint(-this->width()/2, -this->height()/2),second_hand);
}
main.cpp
#include "widget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
Hope that helps.
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 make my widget always have square size. Following this answer, I have overridden QWidget::heightForWidth(), and I also call setHeightForWidth(true) in the constructor, as suggested by #peppe. The size policy is set to Preferred,Preferred (for both horizontal size and vertical size).
However, heightForWidth() is not being called. Is there anything I am doing wrong?
This is the declaration of heightForWidth() in my Widget class:
virtual int heightForWidth(int) const;
This happens on Linux and Windows.
Your widget needs to be in a layout. The below works on both Qt 4 and 5.
In Qt 4, it will only force the toplevel window's minimum size if it's in a layout.
In Qt 5, it doesn't force the toplevel window size. There may be a flag for that or it's a bug but I don't recall at the moment.
#include <QApplication>
#include <QWidget>
#include <QPainter>
#include <QDebug>
#include <QVBoxLayout>
#include <QFrame>
class Widget : public QWidget {
mutable int m_ctr;
public:
Widget(QWidget *parent = 0) : QWidget(parent), m_ctr(0) {
QSizePolicy p(sizePolicy());
p.setHeightForWidth(true);
setSizePolicy(p);
}
int heightForWidth(int width) const {
m_ctr ++;
QApplication::postEvent(const_cast<Widget*>(this), new QEvent(QEvent::UpdateRequest));
return qMax(width*2, 100);
}
QSize sizeHint() const {
return QSize(300, heightForWidth(300));
}
void paintEvent(QPaintEvent *) {
QPainter p(this);
p.drawRect(rect().adjusted(0, 0, -1, -1));
p.drawText(rect(), QString("h4w called %1 times").arg(m_ctr));
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QWidget w;
QVBoxLayout * l = new QVBoxLayout(&w);
l->addWidget(new Widget);
QFrame * btm = new QFrame;
btm->setFrameShape(QFrame::Panel);
btm->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
l->addWidget(btm);
w.show();
return a.exec();
}
To stay square if the widget is in a layout you must reimplement
bool hasHeightForWidth() const{ return true; }
int heightForWidth(int w) const { return w; }
functions of the layout class.
I am putting a QProgressBar inside a QSplashScreen by subclassing QSplashScreen. It overrides the drawContents() method.
I thought I had set the geometry correctly, but it renders at both the top and bottom of the screen. I don't know why. Perhaps there's another way to align it. The numbers are correct, as the image is 380x284, so a 19 height progress bar should be 265 pixels down.
Sorry for crappy picture, splash screen wasn't showing up with print screen button. It's just a 1 color white splash screen at the moment, but as you can see, progress bar at top and bottom (they're both the same colors, its the lighting from the camera).
http://i.imgur.com/p1LoJ.jpg
Another issue will be the showMessage() method of QSplashScreen. I want the message to appear above the progress bar, right-aligned... if anyone has any ideas how to do that.
splashscreen.cpp
#include "splashscreen.h"
SplashScreen::SplashScreen(QApplication *app, QWidget *parent) :
QSplashScreen(parent)
{
this->app = app;
this->setPixmap(QPixmap(":/images/splashscreen.png"));
this->setCursor(Qt::BusyCursor);
// if I dont make it a child, it *only* renders at the top
progress = new QProgressBar(this);
progress->setGeometry(0, 265, 380, 19); // puts it at bottom
progress->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
progress->setValue(0);
progress->setMaximum(100);
progress->setEnabled(true);
this->showMessage("Hello", Qt::AlignBottom);
connect(progress, SIGNAL(valueChanged(int)), this, SLOT(progressBarUpdated(int)));
}
void SplashScreen::drawContents(QPainter *painter)
{
QSplashScreen::drawContents(painter);
this->progress->render(painter);
}
void SplashScreen::progressBarUpdated(int value)
{
this->repaint();
this->app->processEvents();
}
splashscreen.h
#ifndef SPLASHSCREEN_H
#define SPLASHSCREEN_H
#include <QSplashScreen>
#include <QProgressBar>
#include <QApplication>
class SplashScreen : public QSplashScreen
{
Q_OBJECT
public:
explicit SplashScreen(QApplication *app, QWidget *parent = 0);
QProgressBar *progress;
QWidget *spacer;
QApplication *app;
public slots:
void progressBarUpdated(int value);
protected:
void drawContents(QPainter *painter);
};
#endif // SPLASHSCREEN_H
main.cpp
#include <QtGui/QApplication>
#include <time.h>
#include "splashscreen.h"
#include "mainwindow.h"
int main(int argc, char *argv[])
{
srand(time(0));
QApplication a(argc, argv);
SplashScreen *splash = new SplashScreen(&a);
splash->show();
// snip.. loading a ton of stuff into memory at startup
// if you're testing this you might have to sleep/timer here iono
MainWindow w;
splash->finish(&w);
w.show();
return app.exec();
}
You can paint progress directly, without creating QProgressBar. For example:
sp.h:
#ifndef SPLASHSCREEN_H
#define SPLASHSCREEN_H
#include <QSplashScreen>
#include <QApplication>
class SplashScreen : public QSplashScreen
{
Q_OBJECT
public:
explicit SplashScreen(QApplication *app, QWidget *parent = 0);
int m_progress;
QApplication *app;
public slots:
void setProgress(int value)
{
m_progress = value;
if (m_progress > 100)
m_progress = 100;
if (m_progress < 0)
m_progress = 0;
update();
}
protected:
void drawContents(QPainter *painter);
};
#endif // SPLASHSCREEN_H
sp.cpp
#include "sp.h"
SplashScreen::SplashScreen(QApplication *aApp, QWidget *parent) :
QSplashScreen(parent), app(aApp), m_progress(0)
{
this->setPixmap(QPixmap(":/images/splashscreen.png"));
this->setCursor(Qt::BusyCursor);
this->showMessage("Hello", Qt::AlignBottom);
}
void SplashScreen::drawContents(QPainter *painter)
{
QSplashScreen::drawContents(painter);
// Set style for progressbar...
QStyleOptionProgressBarV2 pbstyle;
pbstyle.initFrom(this);
pbstyle.state = QStyle::State_Enabled;
pbstyle.textVisible = false;
pbstyle.minimum = 0;
pbstyle.maximum = 100;
pbstyle.progress = m_progress;
pbstyle.invertedAppearance = false;
pbstyle.rect = QRect(0, 265, 380, 19); // Where is it.
// Draw it...
style()->drawControl(QStyle::CE_ProgressBar, &pbstyle, painter, this);
}
May be this helps you.
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