将基于像素的图形绘制到 QWidget

发布于 2024-10-07 12:18:04 字数 3469 浏览 0 评论 0原文

我有一个应用程序需要以指定的帧速率逐像素绘制(模拟旧机器)。需要注意的是,主机引擎在后台线程中运行,以确保 UI 在模拟过程中保持响应和可用。

目前,我正在尝试使用这样的东西:

class QVideo : public QWidget {
public:
    QVideo(QWidget *parent, Qt::WindowFlags f) : QWidget(parent, f), screen_image_(256, 240, QImage::Format_RGB32) {

    }

    void draw_frame(void *data) {
        // render data into screen_image_
    }

    void start_frame() {
        // do any pre-rendering prep work that needs to be done before
        // each frame
    }

    void end_frame() {
        update(); // force a paint event
    }

    void paintEvent(QPaintEvent *) {
        QPainter p(this);
        p.drawImage(rect(), screen_image_, screen_image_.rect());
    }

    QImage screen_image_;
};

这大多是有效的,而且令人惊讶的是不是很慢。然而,有一个问题。更新函数安排一个paintEvent,它可能不会立即发生。事实上,根据 Qt 文档,一堆 paintEvent 可能会被“组合”。

我看到的负面影响是,经过几分钟的模拟后,屏幕停止更新(尽管模拟仍在运行,但图像似乎冻结),直到我执行一些强制屏幕更新的操作,例如将窗口切换为最大化和最大化。

我尝试过使用 QTimer 和其他类似的机制来实现 GUI 线程中的渲染效果,以便我可以强制立即更新,但这导致了不可接受的性能问题。

有没有更好的方法以固定的间隔不断地将像素绘制到小部件上。首选纯 Qt 解决方案。

编辑:自从有些人 选择有一个态度而不是阅读整个问题,我会澄清这个问题。我无法使用 QWidget::repaint ,因为它有一个限制,必须从与事件循环相同的线程调用它。否则,不会发生更新,而是收到如下 qDebug 消息:

QPixmap: It is not safe to use pixmaps outside the GUI thread
QPixmap: It is not safe to use pixmaps outside the GUI thread
QWidget::repaint: Recursive repaint detected
QPainter::begin: A paint device can only be painted by one painter at a time.
QWidget::repaint: It is dangerous to leave painters active on a widget outside of the PaintEvent
QWidget::repaint: It is dangerous to leave painters active on a widget outside of the PaintEvent

编辑: 为了演示问题,我创建了这个简单的示例代码:

QVideo.h

#include <QWidget>
#include <QPainter>

class QVideo : public QWidget {
    Q_OBJECT;
public:
    QVideo(QWidget *parent = 0, Qt::WindowFlags f = 0) : QWidget(parent, f), screen_image_(256, 240, QImage::Format_RGB32) {

    }

    void draw_frame(void *data) {
        // render data into screen_image_
        // I am using fill here, but in the real thing I am rendering
        // on a pixel by pixel basis
        screen_image_.fill(rand());
    }

    void start_frame() {
        // do any pre-rendering prep work that needs to be done before
        // each frame
    }

    void end_frame() {
        //update(); // force a paint event
        repaint();
    }

    void paintEvent(QPaintEvent *) {
        QPainter p(this);
        p.drawImage(rect(), screen_image_, screen_image_.rect());
    }

    QImage screen_image_;
};

main.cc:

#include <QApplication>
#include <QThread>
#include <cstdio>
#include "QVideo.h"


struct Thread : public QThread {

    Thread(QVideo *v) : v_(v) {
    }

    void run() {
        while(1) {
            v_->start_frame();
            v_->draw_frame(0); // contents doesn't matter for this example
            v_->end_frame();
            QThread::sleep(1);
        }
    }

    QVideo *v_;
};

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);
    QVideo w;
    w.show();

    Thread t(&w);
    t.start();

    return app.exec();
}

我绝对愿意探索选项不使用临时 QImage 进行渲染。它是 Qt 中唯一一个似乎具有直接像素写入接口的类。

I have an application which needs to draw on a pixel by pixel basis at a specified frame rate (simulating an old machine). One caveat is that the main machine engine runs in a background thread in order to ensure that the UI remains responsive and usable during simulation.

Currently, I am toying with using something like this:

class QVideo : public QWidget {
public:
    QVideo(QWidget *parent, Qt::WindowFlags f) : QWidget(parent, f), screen_image_(256, 240, QImage::Format_RGB32) {

    }

    void draw_frame(void *data) {
        // render data into screen_image_
    }

    void start_frame() {
        // do any pre-rendering prep work that needs to be done before
        // each frame
    }

    void end_frame() {
        update(); // force a paint event
    }

    void paintEvent(QPaintEvent *) {
        QPainter p(this);
        p.drawImage(rect(), screen_image_, screen_image_.rect());
    }

    QImage screen_image_;
};

This is mostly effective, and surprisingly not very slow. However, there is an issue. The update function schedules a paintEvent, it may not hapen right away. In fact, a bunch of paintEvent's may get "combined" according to the Qt documentation.

The negative effect that I am seeing is that after a few minutes of simulation, the screen stops updating (image appears frozen though simulation is still running) until I do something that forces a screen update for example switching the window in and out of maximized.

I have experimented with using QTimer's and other similar mechanism to have the effect of the rendering being in the GUI thread so that I can force immediate updates, but this resulted in unacceptable performance issues.

Is there a better way to draw pixels onto a widget constantly at a fixed interval. Pure Qt solutions are preferred.

EDIT: Since some people choose to have an attitude instead of reading the whole question, I will clarify the issue. I cannot use QWidget::repaint because it has a limitation in that it must be called from the same thread as the event loop. Otherwise, no update occurs and instead I get qDebug messages such as these:

QPixmap: It is not safe to use pixmaps outside the GUI thread
QPixmap: It is not safe to use pixmaps outside the GUI thread
QWidget::repaint: Recursive repaint detected
QPainter::begin: A paint device can only be painted by one painter at a time.
QWidget::repaint: It is dangerous to leave painters active on a widget outside of the PaintEvent
QWidget::repaint: It is dangerous to leave painters active on a widget outside of the PaintEvent

EDIT: to demonstrate the issue I have created this simple example code:

QVideo.h

#include <QWidget>
#include <QPainter>

class QVideo : public QWidget {
    Q_OBJECT;
public:
    QVideo(QWidget *parent = 0, Qt::WindowFlags f = 0) : QWidget(parent, f), screen_image_(256, 240, QImage::Format_RGB32) {

    }

    void draw_frame(void *data) {
        // render data into screen_image_
        // I am using fill here, but in the real thing I am rendering
        // on a pixel by pixel basis
        screen_image_.fill(rand());
    }

    void start_frame() {
        // do any pre-rendering prep work that needs to be done before
        // each frame
    }

    void end_frame() {
        //update(); // force a paint event
        repaint();
    }

    void paintEvent(QPaintEvent *) {
        QPainter p(this);
        p.drawImage(rect(), screen_image_, screen_image_.rect());
    }

    QImage screen_image_;
};

main.cc:

#include <QApplication>
#include <QThread>
#include <cstdio>
#include "QVideo.h"


struct Thread : public QThread {

    Thread(QVideo *v) : v_(v) {
    }

    void run() {
        while(1) {
            v_->start_frame();
            v_->draw_frame(0); // contents doesn't matter for this example
            v_->end_frame();
            QThread::sleep(1);
        }
    }

    QVideo *v_;
};

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);
    QVideo w;
    w.show();

    Thread t(&w);
    t.start();

    return app.exec();
}

I am definitely willing to explore options which don't use a temporary QImage to render. It is just the only class in Qt which seems to have a direct pixel writing interface.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

尴尬癌患者 2024-10-14 12:18:04

尝试从线程向事件循环小部件中调用 repaint() 的插槽发出信号,然后该信号将立即执行。我在我的绘图程序中做了类似的事情,它在一个线程中执行主要计算,然后告诉小部件何时需要重新绘制()数据。

Try emitting a signal from the thread to a slot in the event loop widget that calls repaint(), which will then execute right away. I am doing something like this in my graphing program, which executes the main calculations in one thread, then tells the widget when it is time to repaint() the data.

海风掠过北极光 2024-10-14 12:18:04

在类似的情况下,我所做的仍然是使用 QTimer,但执行多个模拟步骤,而不是仅执行一个。您甚至可以让程序自动调整模拟步骤的数量,以便能够获得您喜欢的屏幕更新每秒帧数。

In similar cases what I did was still using a QTimer, but doing several simulation steps instead of just one. You can even make the program auto-tuning the number of simulation steps to be able to get whatever frames per seconds you like for the screen update.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文