Printing QWidget (render) outside of GUI thread - c++

I'm trying to render a QWidget on a QPrinter device without GUI blocking :
My print method looks like this :
void MyClass::print() {
QPrinter *printer = new QPrinter(QPrinter::HighResolution);
printer->setPageSize(QPrinter::A5);
printer->setPageOrientation(QPageLayout::Portrait);
printer->setColorMode(QPrinter::Color);
QPrintDialog *dialog = new QPrintDialog(printer);
if (dialog->exec() == QDialog::Accepted) {
MyWidget *_widget = new MyWidget( /* args */);
QPainter *painter = new QPainter;
painter->begin(printer);
double xscale = printer->pageRect().width() / double(_widget ->width());
double yscale = printer->pageRect().height() / double(_widget ->height());
double scale = qMin(xscale, yscale);
_widget ->setMinimumWidth((printer->pageRect().width() / scale));
_widget ->setMinimumHeight(printer->pageRect().height() / scale);
painter->translate(printer->paperRect().x() + printer->pageRect().width() / 2, printer->paperRect().y() + printer->pageRect().height() / 2);
painter->scale(scale, scale);
painter->translate(-_widget ->width() / 2, -_widget ->height() / 2);
_widget ->render(painter);
painter->end();
}
emit done();
}
With this function i have about 1-2 sec block state so i want to use QThread for this issue But Qt Doc says :
Although QObject is reentrant, the GUI classes, notably QWidget and
all its subclasses, are not reentrant. They can only be used from the
main thread. As noted earlier, QCoreApplication::exec() must also be
called from that thread.
And also :
In practice, the impossibility of using GUI classes in other threads
than the main thread can easily be worked around by putting
time-consuming operations in a separate worker thread and displaying
the results on screen in the main thread when the worker thread is
finished
I've modified Mandelbrot Example but there is nothing to show on screen in my case. my Widget should be rendered (time-consuming operation) and sent to printer that's all.
So do you have anything in mind for my situation ?

If the widget's paintEvent doesn't do much computation, then it'll be very fast to render the widget to a QPicture. A QPicture is just a record of all painter calls. You can then replay them on a printer in a concurrent job.
Alternatively, you can ensure that the widget is not used from the main thread (by staying invisible and not having a parent), and then it's ok to call render from any thread.

Related

QStateMachine event loop with animation

I want to create a endless loop with QStateMachine in which I also need animations.
QColor leastTransparent, mostTransparent = color();
leastTransparent.setAlpha(250);
mostTransparent.setAlpha(150);
QState *s1 = new QState();
s1->assignProperty(this, "color", leastTransparent);
QState *s2 = new QState();
s2->assignProperty(this, "color", mostTransparent);
QSignalTransition *transition = s1->addTransition( this, SIGNAL(triggerSignal()),s2);
QSignalTransition *transition2 = s2->addTransition(s2, SIGNAL(entered),s1);
QPropertyAnimation* animation = new QPropertyAnimation( this, "color");
animation->setDuration( 5000 );
transition->addAnimation(animation);
QPropertyAnimation* animation2 = new QPropertyAnimation( this, "color");
animation2->setDuration(10000);
transition2->addAnimation(animation2);
m_stateMachineAnimation->addState(s1);
m_stateMachineAnimation->addState(s2);
m_stateMachineAnimation->setInitialState(s1);
m_stateMachineAnimation->setGlobalRestorePolicy(QStateMachine::RestoreProperties);
m_stateMachineAnimation->start();
What I expect here is the color will get more opaque for first 5 seconds after "triggerSignal". And state will be "s2". And than "s2" 's enter signal be triggered and it will get more and more transparent for 10 seconds.
But instead I am having s2 trigger immediately without waiting 5 seconds right after the "triggerSignal" and than immediately s1 is being triggered again without waiting 10 seconds.
Why my duration is not taken into account by QStateMachine. How can I achieve such a animation with QStateMachine
You seem to expect the animation to create some sort of an in-between state. It does no such thing. The transition merely triggers the animation. You transition immediately from s2 to s1, giving no time for animations to finish. Instead, you need to explicitly trigger the subsequent transition when the final values of the properties are set. The QState::propertiesAssigned signal is most useful for this purpose. Alternatively, you could use the animation's finished() signal.
In the example below, click within the window to start the animation loop:
// https://github.com/KubaO/stackoverflown/tree/master/questions/statemachine-animation-42682462
#include <QtWidgets>
const char kColor[] = "color";
class Widget : public QWidget {
Q_OBJECT
Q_PROPERTY(QColor color MEMBER m_color NOTIFY colorChanged)
QColor m_color{Qt::blue};
QStateMachine m_machine{this};
QState s0{&m_machine}, s1{&m_machine}, s2{&m_machine};
QEventTransition t01{this, QEvent::MouseButtonPress};
QPropertyAnimation anim_s1{this, kColor}, anim_s2{this, kColor};
void paintEvent(QPaintEvent *) override {
QPainter{this}.fillRect(rect(), m_color);
}
Q_SIGNAL void colorChanged(const QColor &);
public:
Widget() {
connect(this, &Widget::colorChanged, [this]{ update(); });
s1.assignProperty(this, kColor, QColor{Qt::red});
s2.assignProperty(this, kColor, QColor{Qt::green});
t01.setTargetState(&s1);
s0.addTransition(&t01); t01.addAnimation(&anim_s1);
s1.addTransition(&s1, &QState::propertiesAssigned, &s2)->addAnimation(&anim_s2);
s2.addTransition(&s2, &QState::propertiesAssigned, &s1)->addAnimation(&anim_s1);
anim_s1.setDuration(1000);
anim_s2.setDuration(2000);
m_machine.setInitialState(&s0);
m_machine.start();
}
};
int main(int argc, char ** argv) {
QApplication app{argc, argv};
Widget w;
w.setFixedSize(300, 200);
w.show();
return app.exec();
}
#include "main.moc"
As an aside, this demonstrates that the animation interpolates RGB values, causing the color to go dark between red and blue as the values go from (1,0,0) through (.5,.5,0) onto (0,1,0). For human consumption, it'd make more sense to interpolate HSV, so that the value (brightness) stays the same, and only the hue (what we humans really call "color") changes.

Qt showing live image, escape from massive signals

I have a QThread running, trying to decode image from a camera:
struct ImageQueue
{
enum {NumItems = 5};
tbb::concurrent_bounded_queue<DecodedImage> camera_queue_; // decoded image
tbb::concurrent_bounded_queue<DecodedImage> display_queue_; // widget display image
ImageQueue(int camid){camera_queue_.set_capacity(NumItems);display_queue_.set_capacity(NumItems)}
};
std::shared_ptr<ImageQueue> camera_queue;
void Worker::process()
{
while(1)
{
if(quit_)
break;
DecodedImage tmp_img;
camera_queue->camera_queue_.pop(tmp_img);
camera_queue->display_queue_.push(tmp_img);
emit imageReady();
}
emit finished();
}
And this thread is part of the Camera Class:
void Camera::Start()
{
work_ = new Worker();
work_->moveToThread(workerThread_);
QObject::connect(workerThread_, &QThread::finished, work_, &QObject::deleteLater);
QObject::connect(this, &Camera::operate, work_, &Worker::process);
QObject::connect(this, &Camera::stopDecode, work_, &Worker::stop);
QObject::connect(work_, &Worker::imageReady, this, &Camera::DisplayImage);
workerThread_->start();
emit operate();
}
On the widget display side:
class CVImageWidget : public QGraphicsView
{
Q_OBJECT
public:
void display(DecodedImage& tmp_img);
~CVImageWidget();
private:
QGraphicsScene *scene_;
};
CVImageWidget widget;
void Camera::DisplayImage()
{
if(camera_queue != nullptr)
{
DecodedImage tmp_img;
camera_queue->display_queue_.pop(tmp_img);
widget->display(tmp_img);
}
}
void CVImageWidget::display(DecodedImage& tmp_img)
{
if(!tmp_img.isNull())
{
scene_->clear();
scene_->addPixmap(QPixmap::fromImage(tmp_img));
}
}
My question is:
Is there a way to save me from the massive imageReady signals ? I use this signal to display image because the image has to be shown in main thread otherwise the image will not be displayed. But the massive amount of signals will make the GUI response become slower.
Is there a way to fix that ? Thanks.
You do not want to be altering a graphics scene each time an image arrives. You can just as well use a QLabel and its setPixmap method, or a simple custom widget.
There is no need for a display queue: you're only interested in the most recent frame. If the UI thread lags behind the camera thread, you want to drop obsolete frames.
You have to rethink if the camera_queue needs to be a concurrent queue, since you access it from a single thread only, and whether you need that queue at all.
The typical image producer-consumer would work as follows: a Camera class interfaces with a camera and emits a hasFrame(const DecodedImage &) signal each time it got a new frame. No need for a queue: any listening object thread's event queues are concurrent queues already.
A display widget simply accepts the images to display:
class Display : public QWidget {
Q_OBJECT
DecodedImage m_image;
protected:
void paintEvent(QPaintEvent *event) {
QPainter painter{this};
painter.drawImage(0, 0, m_image);
}
public:
Display(QWidget *parent = nullptr) : QWidget{parent} {}
QSize sizeHint() const override { return m_image.size(); }
Q_SLOT void setImage(const QImage & image) {
auto oldSize = m_image.size();
m_image = image;
if (oldSize != m_image.size())
updateGeometry();
update();
}
};
You then connect the image source to the display widget and you're done. The paintEvent or other GUI thread operations can take as long as you wish, even if the camera produces images much faster than the display can consume them, you'll still show the most recent image each time the widget gets a chance to repaint itself. See this answer for a demonstration of that fact. See this answer for a similar demonstration that works on a fixed-pipeline OpenGL backend and uses the GPU to scale the image.
Mutex approach should be faster than emiting Qt signals. I've once tried to skip Qt signals by using STL condition_variable and it worked just fine for me.

Animation looks laggy in QOpenGLWidget

I have no experience in writing a game and this week I'm trying writing a player of a music game's map (finally may become a game?) in QT; met problem and I think I need some help.
I want to show animation in 60 FPS on QOpenGLWidget. It's just some circles move in the widget, and CPU usage is low. But it looks laggy.
I enabled VSync by set the default surface format's swap behavior to doublebuffer/triplebuffer and has an interval of 1 which I think it means 60 FPS.
I implement the paintGL() method and draw the content by QPainter which QT's 2D drawing example does.
The step to compute the positions of each circle is placed outsides the paintGL method, and will run before paintGL is called.
This is the flow of the program runs:
read the script
start a timer
post a event to call "tick" procedure
"tick" procedure runs, and request update the window.
paintGL runs, draw the frame
before exit the paintGL method, a event to call "tick" is posted
I think now it waits for VSync and swap buffer
"tick" is called, go to step 4
the code:
class CgssFumenPlayer : public QOpenGLWidget
{
Q_OBJECT
...
bool Load();
public slots:
void onTick();
protected:
....
void paintGL() override;
QElapsedTimer elapsedTimer;
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QSurfaceFormat fmt;
fmt.setSwapBehavior(QSurfaceFormat::TripleBuffer);
fmt.setSwapInterval(1);
QSurfaceFormat::setDefaultFormat(fmt);
CgssFumenPlayer w;
w.Load();
w.setFixedSize(704, 396);
w.show();
return a.exec();
}
bool CgssFumenPlayer::Load()
{
....
elapsedTimer.start();
QMetaObject::invokeMethod(this, "onTick", Qt::QueuedConnection);
}
void CgssFumenPlayer::onTick()
{
playerContext.currentTime = elapsedTimer.elapsed() / 1000.0;
double f = playerContext.currentTime / (1.0 / 60);
playerContext.currentTime = (int)f * (1.0 / 60);
fumen->Compute(&playerContext);
update();
}
void CgssFumenPlayer::paintGL()
{
QPainter painter;
painter.begin(this);
painter.setRenderHint(QPainter::Antialiasing);
painter.setWindow(0, 0, windowWidth, windowHeight);
painter.fillRect(QRectF(0, 0, windowWidth, windowHeight), QColor().black());
DrawButtons(painter);
DrawIcons(painter, &playerContext);
painter.end();
QMetaObject::invokeMethod(this, "onTick", Qt::QueuedConnection);
}
I tried these ways to get more information:
print current time by qDebug() each time entering the paintGL method.
It seems sometimes frame is dropped; it looks very obvious, and he interval to last time it's called is more than 30ms.
move the mouse in/out the window duration animation. It became laggy in higher possibility.
collect the time cost in compute position, seems only a very short time.
run this program in android, just the same or even more laggy.
game which are much more complex runs fluently on my computer. I think the hardware is fast enough. ( i7-4800M, GTX 765M )
restart the program again and again. it's now fluent (less or no frame-dropping happened), now laggy... I can't find the pattern.
Also, adjust the animation to 30 FPS cause it always looks laggy.
How can I deal with the problem?
(p.s. I hope it can run on android as well)
this is the full source code
https://github.com/sorayuki/CGSSPlayer/releases (cgssplayer.zip, not the source code)
(cgss-fumen.cpp makes no difference in this problem I think)
It can build in QTCreator (5.6) with no other dependency.
(for QT 5.5, it require to add
CONFIG += c++11
into the .pro file)

QT QGraphicsView rotation

Disclaimer: I am pretty much a beginner with QT.
I've been struggling for some time to rotate a QGraphicsView (no 3D rotation) but, despite what i do, it doesn't work. I have tried:
QTransform transform;
transform.rotate(45);
ui->graphicsView->setTransform(transform);
or more simply:
ui->graphicsView->rotate(45);
These seem like very straightforward ways to do it that should work, but for some reason, whenever i run it, the QGraphicsView doesn't rotate at all. If possible, i'd like some direct and easy to understand code snippets, and/or what i'm doing wrong.
EDIT: This is the code in the widget cpp file i have problems with. It should be a simple timer with an animated hourglass icon. It gets repeated every .5 seconds.
void Widget::timerEvent(QTimerEvent *event)
{
++timeFlag;
++timerFlag;
if (timerFlag < 115){
animateTimer = QString("\":/new/100/timerFrames/timerIconFrame%1.png\"").arg(timerFlag);
QPixmap pix(animateTimer);
pixmapitem.setPixmap(pix);
scene.addItem(&pixmapitem);
ui->graphicsView_2->setScene(&scene);
}
if (timerFlag >= 115 && timerFlag < 119){
//
}
if(timerFlag == 119){
ui->graphicsView_2->setStyleSheet("border-image:url(:/new/100/timerIconPix.PNG);border:0px;}");
}
if(timerFlag == 120){
timerFlag = 0;
}
if (timeFlag==2){
timeFlag = 0;
if(sec>=10){
ui->label_2->setText(QString("%1:%2").arg(min).arg(sec));
} else {
ui->label_2->setText(QString("%1:0%2").arg(min).arg(sec));
}
++sec;
if (sec == 60) {
sec = 0;
++min;
}
}
}
You're merely decorating the QGraphicsView using the style mechanism. You could have used a plain QWidget instead, since you don't use any graphics view functionality. None of the images in the stylesheet are what the view actually displays. The image must be on the scene displayed by the view.
Set the image on a QGraphicsPixmapItem, add that item to a scene, set the scene on the view, and then the transformations will work. You can then keep replacing the pixmap in the timer handler.
Finally, you must also check the timer id in the timerEvent. I assume that you're using a QBasicTimer, say called m_timer, you'd then check as follows:
void Widget::timerEvent(QTimerEvent * ev) {
if (ev->timerId() != m_timer.timerId()) return;
... // rest of the code
}
As you can see, the code that you've not included in the original question was absolutely essential! Without it, the question was wholly off-topic.
You need to implement a QGraphicsView, a QGraphicsScene and then add something that inherits from QGraphicsItem to that scene to rotate.
Here is an example that rotates a QWidget in a QGraphicsView:
QGraphicsView* view = new QGraphicsView(parent);
QGraphicsScene* scene = new QGraphicsScene(view);
view->setScene(scene);
// Widget to rotate - important to not parent it
QWidget* widget = new QWidget();
QProxyWidget proxy_widget = scene_->addWidget(widget);
QPropertyAnimation* animation = new QPropertyAnimation(proxy_widget, "rotation");
animation->setDuration(5000);
animation->setStartValue(0);
animation->setEndValue(360);
animation->setEasingCurve(QEasingCurve::Linear);
animation->start(QAbstractAnimation::DeleteWhenStopped);

Qt: how to make mainWindow automatically resize when centralwidget is resized?

I would like to have my CentralWidget a certain size. What do I need to do to make my mainWindow resize along it's central widget? here the code that doesn't work:
int main (int argc, char **argv) {
QApplication app(argc, argv);
QGLFormat glFormat;
glFormat.setVersion(4,2);
glFormat.setProfile( QGLFormat::CompatibilityProfile);
QGLWidget* render_qglwidget = new MyWidget(glFormat);
QGLContext* glContext = (QGLContext *) render_qglwidget->context();
glContext->makeCurrent();
QMainWindow* mainWindow = new MyMainWindow();
render_qglwidget->resize(720, 486);
mainWindow->setSizePolicy(QSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding));
mainWindow->setCentralWidget(render_qglwidget);
render_qglwidget->resize(720, 486);
mainWindow->show();
return app.exec();
}
the window that opens will be very small.
i can set the size of the mainwindow using
mainWindow->resize(720, 486);
and the centralwidget will also change it's size. but the central widget will be slightly squashed because the toolbar of the mainWindow also lies within those 486 pixels.
How to let the mainWindow resize automatically?
You can reimplement QMainWindow::event() to resize the window automatically whenever the size of the central widget changes:
bool MyMainWindow::event(QEvent *ev) {
if(ev->type() == QEvent::LayoutRequest) {
setFixedSize(sizeHint());
}
return result = QMainWindow::event(ev);
}
You also have to use setFixedSize() for the central widget instead of just resize(). The latter is almost useless when the widget is placed inside a layout (which is a QMainWindowLayout here).
Set the size of the central widget. Then get the sizehint for the main window and set it to it's size.
(sizeHint(), setFixedSize()).
It is not possible to set the size of the QMainWindow based up its central widget. It is always the other way around.
As you said, use QMainWindow::resize(), adding the toolbar and statusbar height to your central widget to get the final result.
Be also sure to resize in a "delayed init" (i.e. a QTimer with a timeout of 0), so that the height of the toolbar and statusbar are accurate.
If you want your main window to be unresizable by the user, use QMainWindow::setWindowFlags(Qt::FramelessWindowHint) to disable resize by mouse dragging.
I used the following code to solve a similar problem. I wanted to explicitly resize my main window so that the central widget has a given size.
def centralWidgetResize(self, x, y):
# If the window is not visible, it doesn't keep its layout up to date, so force it.
if not self.isVisible():
self.layout().update()
# Does nothing if the layout is already up to date (and the window is visible).
self.layout().activate()
size = self.size()
childsize = self.centralWidget().size()
dx = size.width() - childsize.width()
dy = size.height() - childsize.height()
self.resize(x + dx, y + dy)
It's Python but the C++ code should be a straightforward translation. It also works with weird toolbar placements or multiple toolbars. Note that the actual updating of the size only happens when the window is shown, if it is needed immediately and the window is hidden do another layout().update(); layout().activate().