Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Resizing and rotating a QGraphicsItem results in odd shape

I can't understand how scaling and rotation are applied to QGraphicsItem.

I need to be able to apply rotation and scaling (not necessarily keeping aspect ratio) and I get fully unexpected results.

Rotation must be around the item center. I seem to have no problem doing that - yet if I try to debug the bounding rectangle, I get seemingly wrong values.

If I don't keep aspect ratio, instead of rotation I get a very weird skew, and I have been struggling for quite a while to find the cause and correct it. I hope anybody can find a solution.

For many items - like rectangles - my solution was to give up on resize - and just replace the item with a new one of given size. I even did that for pixmap (though it probably will affect performance a lot).
But I don't know how to do that for text, or a few other types (svg...).
I am trying to understand how scaling is applied, on rotated items, and how to get it applied correctly.

The code below is an experiment where I scale and rotate a text item, and the result... see attached image

#include <QApplication>
#include <QGraphicsView>
#include <QGraphicsTextItem>

void experimentScaling(QGraphicsScene* s)
{
    QGraphicsTextItem* ref = new QGraphicsTextItem();  // a reference, not resized
    ref->setPlainText("hello world");
    s->addItem(ref);
    ref->setDefaultTextColor(Qt::red);
    ref->setRotation(45);

    QGraphicsTextItem* t = new QGraphicsTextItem();    // text item to be experimented on
    t->setPlainText("hello world");
    s->addItem(t);

    QTransform transform;                    // scale
    transform.scale(10, 1);
    t->setTransform(transform);
    t->update();

    QPointF _center = t->boundingRect().center();
    qDebug("%f %f %f %f", t->boundingRect().left(), t->boundingRect().top(), t->boundingRect().right(), t->boundingRect().bottom());   // seems to be unscaled...

    t->setTransformOriginPoint(_center);    // rotation must be around item center - and seems to work even though the bounding rect gives wrong values above
    t->setRotation(45);        // skewed
    t->update();
}

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    QGraphicsScene s;
    QGraphicsView view(&s);
    s.setSceneRect(-20, -20, 800, 600);
    view.show();
    experimentScaling(&s);
    return app.exec();
}

Reference (red) text rotated 45 degrees, text rotated 45 degrees and resized 10,1:

reference text rotated 45 degrees, text rotated 45 degrees and resized 10,1

The resized (black) text should have the same height as the reference (red) - yet is much taller;
The bounding rectangle is no longer a rectangle - it is skewed;
The angle looks much smaller than 45;

Added a resized but not rotated reference as well:

enter image description here

Please help me understand why this behavior is happening and what can I do about it.

I have tried looking into QGraphicsRotation but I can't figure out how to apply it... All I get is a move instead of rotation.

like image 991
Thalia Avatar asked Aug 24 '15 16:08

Thalia


People also ask

How do I rotate a shape?

You can rotate shapes by degrees, flip them vertically, flip them horizontally, or rotate them using the rotation handle. Select the shapes that you want to rotate.

How to resize a shape in SketchUp?

Select all the shapes that you want to resize. Drag a selection handle until the shape is the size that you want. To resize the shape proportionally, drag a corner handle. Select the shape that you want to resize.

How do I move a shape to the next position?

To move a shape to the next position to which it can snap, such as a grid line or ruler tick mark, press an arrow key. Note: If there is no position to which the shape can snap, pressing an arrow key moves the shape a single tick on the ruler.

How to resize a shape in AutoCAD?

To resize the shape proportionally, drag a corner handle. Select the shape that you want to resize. Drag an endpoint to the length you want. Select all the shapes that you want to move. On the View tab, in the Show group, click Task Panes and select Size & Position.


Video Answer


1 Answers

As documented, the item's transformations are mathematically applied in a certain order - this is the order you'd be multiplying the transform matrices in and is, conceptually, the reverse of the order you'd normally think of.

  1. The transform is applied. The origin point must be included in the transform itself, by applying translations during the transform.
  2. The transformations are applied - each of them can specify its own center.
  3. rotation then scale are applied, both relative to transformOriginPoint.

When you set transform to scaling, and set rotation, the rotation is performed before scaling. The scaling applies to the rotated result - it simply stretches the rotated version horizontally in your case.

You need to somehow enforce the reverse order of operations. The only two ways to do that are:

  1. Stack the transforms in correct order and pass them to transform, or.

  2. Pass a list of correct transformations to transformations.

I'll demonstrate how to do it either way, in an interactive fashion where you can adjust the transform parameters using sliders.

To obtain the correct result using transform:

QGraphicsItem * item = ....;
QTransform t;
QPointF xlate = item->boundingRect().center();
t.translate(xlate.x(), xlate.y());
t.rotate(angle);
t.scale(xScale, yScale);
t.translate(-xlate.x(), -xlate.y());
item->setTransform(t);

To obtain the correct result using transformations:

QGraphicsItem * item = ....;
QGraphicsRotation rot;
QGraphicsScale scale;
auto center = item->boundingRect().center();
rot.setOrigin(QVector3D(center));
scale.setOrigin(QVector3D(center()));
item->setTransformations(QList<QGraphicsTransform*>() << &rot << &scale);

Finally, the example:

screenshot

// https://github.com/KubaO/stackoverflown/tree/master/questions/graphics-transform-32186798
#include <QtWidgets>

struct Controller {
public:
   QSlider angle, xScale, yScale;
   Controller(QGridLayout & grid, int col) {
      angle.setRange(-180, 180);
      xScale.setRange(1, 10);
      yScale.setRange(1, 10);
      grid.addWidget(&angle, 0, col + 0);
      grid.addWidget(&xScale, 0, col + 1);
      grid.addWidget(&yScale, 0, col + 2);
   }
   template <typename F> void connect(F && f) { connect(f, f, std::forward<F>(f)); }
   template <typename Fa, typename Fx, typename Fy> void connect(Fa && a, Fx && x, Fy && y) {
      QObject::connect(&angle, &QSlider::valueChanged, std::forward<Fa>(a));
      QObject::connect(&xScale, &QSlider::valueChanged, std::forward<Fx>(x));
      QObject::connect(&yScale, &QSlider::valueChanged, std::forward<Fy>(y));
   }
   QTransform xform(QPointF xlate) {
      QTransform t;
      t.translate(xlate.x(), xlate.y());
      t.rotate(angle.value());
      t.scale(xScale.value(), yScale.value());
      t.translate(-xlate.x(), -xlate.y());
      return t;
   }
};

int main(int argc, char **argv)
{
   auto text = QStringLiteral("Hello, World!");
   QApplication app(argc, argv);
   QGraphicsScene scene;
   QWidget w;
   QGridLayout layout(&w);
   QGraphicsView view(&scene);
   Controller left(layout, 0), right(layout, 4);
   layout.addWidget(&view, 0, 3);

   auto ref = new QGraphicsTextItem(text);         // a reference, not resized
   ref->setDefaultTextColor(Qt::red);
   ref->setTransformOriginPoint(ref->boundingRect().center());
   ref->setRotation(45);
   scene.addItem(ref);

   auto leftItem = new QGraphicsTextItem(text);    // controlled from the left
   leftItem->setDefaultTextColor(Qt::green);
   scene.addItem(leftItem);

   auto rightItem = new QGraphicsTextItem(text);   // controlled from the right
   rightItem->setDefaultTextColor(Qt::blue);
   scene.addItem(rightItem);

   QGraphicsRotation rot;
   QGraphicsScale scale;
   rightItem->setTransformations(QList<QGraphicsTransform*>() << &rot << &scale);
   rot.setOrigin(QVector3D(rightItem->boundingRect().center()));
   scale.setOrigin(QVector3D(rightItem->boundingRect().center()));

   left.connect([leftItem, &left]{ leftItem->setTransform(left.xform(leftItem->boundingRect().center()));});
   right.connect([&rot](int a){ rot.setAngle(a); },
                 [&scale](int s){ scale.setXScale(s); }, [&scale](int s){ scale.setYScale(s); });
   right.angle.setValue(45);
   right.xScale.setValue(3);
   right.yScale.setValue(1);

   view.ensureVisible(scene.sceneRect());
   w.show();
   return app.exec();
}
like image 155
Kuba hasn't forgotten Monica Avatar answered Nov 06 '22 02:11

Kuba hasn't forgotten Monica