1

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?

plover
  • 439
  • 5
  • 17
  • As an aside, why are you defining a new `pos` property. `QGraphicsTextItem` already has a `pos` property inherited from [`QGraphicsObject`](http://doc.qt.io/qt-5/qgraphicsobject.html#pos-prop). – G.M. Oct 17 '18 at 13:34
  • it was just leftover from another test code I did. I will edit it out, but it doesn't change the result, the text shakes in the box. – plover Oct 17 '18 at 13:38

2 Answers2

2

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();
}
Community
  • 1
  • 1
scopchanov
  • 7,966
  • 10
  • 40
  • 68
  • Thanks for your answer. I tested it with a solution I was working on and it seems to have a similar effect. by adding ' Q_PROPERTY( QPointF pos READ pos WRITE setPosition )' and flooring the QPointF. I will add a solution for that as well. – plover Oct 17 '18 at 13:09
  • @plover, have you tested the code as it is provided by me? Then the flickering is removed. Also, adding `Q_PROPERTY( QPointF pos READ pos WRITE setPosition )` doesn't lead to flicker. – scopchanov Oct 17 '18 at 13:17
  • Yes I've tested my new solution and your solution side by side. Both show promising results. I have to test it in my code base (this is only a tiny example code), to see what is better there. – plover Oct 17 '18 at 13:21
  • @plover, just copying the content of `MovingFrameText::paint` should be enough. – scopchanov Oct 17 '18 at 13:23
1

@scopchanov answer is a very good one. I added here a solution I tested as well, and seems to work. I will need further time to inspect which one I think is better, but I will leave it here for others to try, if you end with a similar issue.

class MovingFrameText : public QGraphicsTextItem
{

   Q_PROPERTY( QPointF pos READ pos WRITE setPosition )
   public:

    void setPosition(QPointF pos)
    {
       setPos(floor(pos.x()+0.5),floor(pos.y()+.5) );
    }

};
plover
  • 439
  • 5
  • 17