c++qtqt5qgraphicstextitemqpropertyanimation

QPropertyAnimation of a QGraphicsTextItem with a frame make the text shaky


I am animating a QGraphicsTextItem that I have added a frame around. During the animation the text seems to shake slightly inside the frame, which is very annoying.

An example code:

class MovingFrameText : public QGraphicsTextItem
{
    Q_OBJECT;

   public:
      MovingFrameText( ) : QGraphicsTextItem(0)
      {
         setPlainText ( "human ");
         QFont f =  font();
         f.setPixelSize(40);
         setFont(f);
         setFlags(QGraphicsItem::ItemIsMovable);
      }

      QRectF boundingRect() const
      {
         return QGraphicsTextItem::boundingRect().adjusted(-2,-2,+2,+2);
      }

      void paint(QPainter *painter,const QStyleOptionGraphicsItem *option,QWidget *widget)
      {
         QGraphicsTextItem::paint(painter,option,widget);
         painter->setPen(Qt::black);
         painter->drawRect(boundingRect());
      }

};

int main(int argc, char *argv[])
{
   QApplication app(argc,argv);


   MovingFrameText t;
   t.setPos(640,680);

   QGraphicsScene scene;
   scene.addItem(&t);

   QGraphicsView view(&scene);
   view.resize(640, 680);
   view.show();

    auto moveAnimation = new QPropertyAnimation( &t,  "pos" );
    moveAnimation->setDuration( 10000 );
    moveAnimation->setStartValue( QPointF(640, 680) );
    moveAnimation->setEndValue(  QPointF(0, 0) );
    moveAnimation->setEasingCurve( QEasingCurve::Linear );
    moveAnimation->start(QAbstractAnimation::DeleteWhenStopped);

 return app.exec();
}

Is there any way to smooth the animation?


Solution

  • Solution

    You can substantially improve the animation by:

    1. Using QVariantAnimation instead of QPropertyAnimation and calling QGraphicsItem::update on each iteration
    2. Buffering the painting using an additional QPainter with a QPixmap as a canvas for all painting operations and then painting the canvas using the painter passed to the paint method

    Note: The QGraphicsTextItem will still shake a bit, but at least it will behave as one object instead of several independent ones.

    Example

    Here is an example I have prepared for you of how your code could be changed in order to implement the proposed solution:

    class MovingFrameText : public QGraphicsTextItem
    {
    public:
        MovingFrameText(const QString &text, QGraphicsItem *parent = nullptr)
            : QGraphicsTextItem(parent)
        {
            QFont f(font());
    
            f.setPixelSize(40);
    
            setFont(f);
            setPlainText(text);
        }
    
        void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
        {
            painter->setClipping(true);
            painter->setClipRect(option->rect);
            painter->setRenderHint(QPainter::SmoothPixmapTransform);
    
            QPixmap canvas(option->rect.size());
            QPainter canvasPainter;
    
            canvas.fill(Qt::transparent);
    
            canvasPainter.begin(&canvas);
    
            canvasPainter.setFont(font());
            canvasPainter.drawRect(option->rect.adjusted(0, 0, -1, -1));
            canvasPainter.drawText(option->rect, toPlainText());
    
            painter->drawPixmap(0, 0, canvas);
        }
    };
    
    int main(int argc, char *argv[])
    {
        QApplication app(argc,argv);
    
        QGraphicsView view;
        auto *t = new MovingFrameText("human");
    
        view.setScene(new QGraphicsScene(&view));
        view.setAlignment(Qt::AlignLeft | Qt::AlignTop);
        view.setSceneRect(0, 0, 640, 680);
        view.scene()->addItem(t);
        view.show();
    
        auto *moveAnimation = new QVariantAnimation();
    
        moveAnimation->setDuration(10000);
        moveAnimation->setStartValue(QPointF(640, 680));
        moveAnimation->setEndValue(QPointF(0, 0));
        moveAnimation->start(QAbstractAnimation::DeleteWhenStopped);
    
        QObject::connect(moveAnimation, &QVariantAnimation::valueChanged, [t](const QVariant &value){
            t->setPos(value.toPointF());
            t->update();
        });
    
        return app.exec();
    }