调整和旋转QGraphicsItem导致形状奇怪

7

我无法理解如何对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:

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

调整大小后的文本(黑色)应与参考文本(红色)具有相同的高度,但实际上比参考文本高得多;
边界矩形不再是一个矩形-它是倾斜的;
角度看起来比45度小得多;

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

enter image description here

请帮助我理解为什么会出现这种行为,以及我能做些什么来解决它。
我已经尝试查看QGraphicsRotation,但是我无法弄清如何应用它...我只得到了移动而不是旋转。

transform.scale(10, 1) 正确吗?不应该是 transform.scale(10, 10) 吗? - juzzlin
@juzzlin:我不想保持纵横比。我想尝试在一个方向或两个方向上拉伸文本。 - Thalia
2个回答

10

根据文档,物品的变换是按照一定顺序进行数学运算的 - 这个顺序是你需要将变换矩阵相乘的顺序,概念上来说,这个顺序是你通常想到的顺序的相反

  1. 应用transform。原点必须在变换本身中包含,通过在变换期间应用平移来实现。
  2. 应用transformations - 每个变换都可以指定自己的中心。
  3. 然后应用rotationscale,两者都相对于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);

为了使用转换获得正确的结果:
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);

最后,这是一个示例:

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

太好了,非常感谢您清晰明了的解释 - 还向我展示了如何使用变换(QGraphicsRotationQGraphicsScale)。 - Thalia
我也遇到了与您的示例类似的问题,当我尝试将缩放“固定”在左上角时,对象也会移动 - 我正在尝试将其向后平移(使用m31,m32),但结果不一致...有没有一种方法可以补偿平移而不破坏基于中心的旋转? - Thalia
@Thalia,请问坐标系的缩放是在哪个点进行的呢?这些系统可以是父级或文本项。 - Kuba hasn't forgotten Monica
物品的坐标系,boundingRect().topLeft()... 我在这个问题中添加了一个示例:http://stackoverflow.com/questions/32356453/scaling-a-rotated-item-based-on-top-left-moves-the-item - Thalia

2

我可以通过使用两个单独的QTransform,并将它们相乘来使其工作。注意,变换的顺序很重要:

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

QTransform transform2;
transform2.rotate(45);

t->setTransform(transform1 * transform2);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接