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:
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:
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.
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.
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.
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.
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.
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.
transform
is applied. The origin point must be included in the transform itself, by applying translations during the transform.transformations
are applied - each of them can specify its own center.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:
Stack the transforms in correct order and pass them to transform
, or.
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:
// 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();
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With