调整大小和旋转 QGraphicsItem 会导致奇怪的形状

Resizing and rotating a QGraphicsItem results in odd shape

我无法理解如何将缩放和旋转应用于 QGraphicsItem

我需要能够应用旋转和缩放(不一定保持纵横比),但我得到了完全出乎意料的结果。

旋转必须围绕项目中心。我这样做似乎没有问题 - 但是如果我尝试调试边界矩形,我会得到看似错误的值。

如果我不保持纵横比,而不是旋转,我会得到一个非常奇怪的倾斜,我已经花了很长时间来寻找原因并纠正它。我希望任何人都可以找到解决方案。

对于许多项目(如矩形),我的解决方案是放弃调整大小 - 只需将项目替换为给定大小的新项目。 (虽然它可能会对性能产生很大影响)。
但我不知道如何为文本或其他一些类型 (svg...) 执行此操作。
我正在尝试了解如何在旋转的项目上应用缩放,以及如何正确应用它。

下面的代码是我缩放和旋转文本项的实验,结果...见附图

#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();
}

参考(红色)文本旋转 45 度,文本旋转 45 度并调整大小 10,1:

调整后的(黑色)文本应与参考文本(红色)具有相同的高度 - 但要高得多;
边界矩形不再是矩形 - 它是倾斜的;
角度看起来远小于45;

添加了一个调整大小但未旋转的参考:

请帮助我理解为什么会发生这种行为以及我能做些什么。

我已经尝试研究 QGraphicsRotation 但我不知道如何应用它...我得到的只是移动而不是旋转。

我能够通过使用两个单独的 QTransform 并将它们相乘来完成这项工作。请注意,转换顺序很重要:

QTransform transform1;
transform1.scale(10, 1);

QTransform transform2;
transform2.rotate(45);

t->setTransform(transform1 * transform2);

如文档所述,项目的转换在数学上按特定顺序应用 - 这是您乘以转换矩阵的顺序,从概念上讲,是 reverse您通常会想到的顺序。

  1. 应用transform。通过在变换期间应用平移,原点必须包含在变换本身中。
  2. 应用了 transformations - 每个都可以指定自己的中心。
  3. rotation 然后 scale 被应用,两者都相对于 transformOriginPoint.

当你设置transform缩放,设置rotation时,旋转先于缩放。缩放适用于旋转后的结果 - 它只是在您的情况下水平拉伸旋转后的版本。

您需要以某种方式强制执行相反的操作顺序。只有两种方法可以做到这一点:

  1. 以正确的顺序堆叠转换并将它们传递给 transform,或者。

  2. 将正确转换列表传递给 transformations

我将演示如何以交互方式进行操作,您可以在其中使用滑块调整变换参数。

使用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);

使用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);

最后,例子:

// https://github.com/KubaO/Whosebugn/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();
}