c++ - 使用 QPainter 流畅绘图

标签 c++ qt animation

如何使用 Qt QPainter 平滑地增加一个角度/长度变化的弧?这是我刚刚从 Qt 的 Analog Clock Window Example 创建的最少代码.

代码在 50 毫秒内随机更改 m_value +- 5。这是为了模拟我想要实现的实际行为。圆弧从 12 点钟位置开始并逆时针增长。 m_value 缩放以适应 360 度(12 点到 12 点)。

我的目标是根据给定的(模拟)值实时平滑地改变弧长,而不管输入值抖动如何。

我想完成两件事:

  1. 圆弧的平滑重绘。当前代码直接重绘了 当时的值(value)。我什至不使用子角度值。结果 在弧线末端视觉上有噪音。
  2. 使用 V-Sync 更新绘图。为了不浪费 非显示重绘的计算能力。我不知道怎么办 通过 V-Sync 触发 render 事件,所以我设置了 33 毫秒 定时器。当 m_value 的变化时间少于 30 毫秒时,这是必需的。

我不想要的

  • QtQuick。我正在寻找 QPainter 的方法来做到这一点。

我使用的平台:

  • Qt 5.x
  • 在 Debian Linux 上(如果重要的话)

#include <QtGui>

#include "rasterwindow.h"

class SmoothArc : public RasterWindow
{
public:
    SmoothArc();

protected:
    void timerEvent(QTimerEvent *) Q_DECL_OVERRIDE;
    void render(QPainter *p) Q_DECL_OVERRIDE;

private:
    int m_timerId;
    int m_valueTimerId;
    int m_value = 50;
};

SmoothArc::SmoothArc()
{
    setTitle("Smooth Arc");
    resize(200, 200);

    m_timerId = startTimer(33);
    m_valueTimerId = startTimer(100);
}

void SmoothArc::timerEvent(QTimerEvent *event)
{
    if (event->timerId() == m_timerId)
        renderLater();
    if (event->timerId() == m_valueTimerId) {
        m_value = m_value + (qrand() % 11 - 5);
        if (m_value > 100) m_value = 100;
        if (m_value < 0) m_value = 0;
    }
}

void SmoothArc::render(QPainter *p)
{
    p->setRenderHint(QPainter::Antialiasing);
    int side = qMin(width(), height());
    p->scale(side / 200.0, side / 200.0);

    QRectF rect(10, 10, 180, 180);
    QPen pen = p->pen();
    pen.setWidth(10);
    p->setPen(pen);
    p->drawArc(rect, 90*16, (360*(m_value/100.0))*16);
}

int main(int argc, char **argv)
{
    QGuiApplication app(argc, argv);
    SmoothArc arc;
    arc.show();
    return app.exec();
}

完整代码位于 https://github.com/yashi/smooth-arc .像下面这样的常规构建过程应该可以工作。

git clone https://github.com/yashi/smooth-arc.git
cd smooth-arc
qmake
make
./smooth-arc

最佳答案

我不熟悉仅使用 Qt Gui 的方法来解决这个问题,所以我将展示如何使用 Qt Widgets 来解决这个问题。

  1. Smooth redraw of the arc. The current code directly redraw the value at the time. I does not even use sub angle value. The result is visually noisy at the end of the arc.

您可以使用 Qt 的 animation framework插入属性更改:

#include <QtWidgets>

class SmoothArc : public QWidget
{
    Q_OBJECT
    Q_PROPERTY(qreal value READ value WRITE setValue NOTIFY valueChanged)

public:
    SmoothArc();

    qreal value() const;
    void setValue(qreal value);

signals:
    void valueChanged();

protected:
    void timerEvent(QTimerEvent *) Q_DECL_OVERRIDE;
    void paintEvent(QPaintEvent *) Q_DECL_OVERRIDE;

private:
    int m_valueTimerId;
    qreal m_value;
    QPropertyAnimation m_animation;
};

SmoothArc::SmoothArc()
{
    resize(200, 200);

    m_valueTimerId = startTimer(100);
    m_value = 50;
    m_animation.setTargetObject(this);
    m_animation.setPropertyName("value");
}

qreal SmoothArc::value() const
{
    return m_value;
}

void SmoothArc::setValue(qreal value)
{
    if (qFuzzyCompare(value, m_value))
        return;

    m_value = value;
    update();
    emit valueChanged();
}

void SmoothArc::timerEvent(QTimerEvent *event)
{
    if (event->timerId() == m_valueTimerId) {
        qreal newValue = m_value + (qrand() % 11 - 5);
        if (newValue > 100) newValue = 100;
        if (newValue < 0) newValue = 0;

        if (m_animation.state() == QPropertyAnimation::Running)
            m_animation.stop();
        m_animation.setStartValue(m_value);
        m_animation.setEndValue(newValue);
        m_animation.start();
    }
}

void SmoothArc::paintEvent(QPaintEvent *)
{
    QPainter p(this);
    p.setRenderHint(QPainter::Antialiasing);
    int side = qMin(width(), height());
    p.scale(side / 200.0, side / 200.0);

    QRectF rect(10, 10, 180, 180);
    QPen pen = p.pen();
    pen.setWidth(10);
    p.setPen(pen);
    p.drawArc(rect, 90*16, (360*(m_value/100.0))*16);
}

int main(int argc, char **argv)
{
    QApplication app(argc, argv);
    SmoothArc arc;
    arc.show();
    return app.exec();
}

#include "main.moc"

arc animation gif

  1. Update the drawing in along with V-Sync. So that I don't waste computation power for non-displayed redraw. I don't know how to trigger render event by V-Sync, so I've setup 33-millisecond timer. This is needed when m_value changes in less than 30 msec.

如果您使用 update(),我认为 Qt 应该会为您处理这个问题:

Updates the widget unless updates are disabled or the widget is hidden.

This function does not cause an immediate repaint; instead it schedules a paint event for processing when Qt returns to the main event loop. This permits Qt to optimize for more speed and less flicker than a call to repaint() does.

Calling update() several times normally results in just one paintEvent() call.

关于c++ - 使用 QPainter 流畅绘图,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35532459/

相关文章:

c++ - Arduino 使用 attachInterrupt() 测量 PWM

c++ - 在模板派生类中继承具有类型别名的构造函数

c++类前向声明​​和不完整类型的无效使用

javascript - 减慢补间动画的速度

iphone 开发 : how to create an animation by using NSTimer

c++ - Hinnant 的堆栈分配器和异常

c++ - 如何使用 CMake 强制 C++ 编译器使用不同安装包的版本之一?

c++ - 无法将简单的读/写串行端口代码从 boost::asio::serial_port 迁移到 QSerialPort

c++ - 将对象移动到不同的线程

ios - 根据动画期间的变化干扰一些UI属性