从源码解析 QGraphicsItem 旋转、缩放、平移、transform等变换操作,利用QGraphicsTransform实现变形动画

QGraphicsItem 有3种方式进行变换:1. 最简单方便的是使用 setRotation() 、setScale();2. 使用 setTransform() 进行复杂变换;3. 还可以使用 setTransformations()进行多项组合变换及自定义变换。同时应用这三种方式将产生叠加效果,并以 QTransform 形式返回。由于QTransform 是以矩阵为基础进行运算,所以叠加时运算的顺序将影响最后的运算结果。 QGraphicsItem 叠加变换时按照如下顺序进行:首先,应用 setTransform() 设置的 transform;其次,叠加运算 setTransformations() 设置的 transformations(按照 list 中的顺序执行);然后,叠加运算 setRotation() 设置的 rotation;最后,叠加运算 setScale()设置的 scale。这些规则体现在 qgraphicsitem_p.h 中的 QGraphicsItemPrivate 结构体中,源码如下:

复制代码
struct QGraphicsItemPrivate::TransformData
{
    QTransform transform;// 对应setTransform()设置的值
    qreal scale; // 对应setScale()设置的值
    qreal rotation; // 对应setRotation()设置的值
    qreal xOrigin;
    qreal yOrigin;
    QList<QGraphicsTransform *> graphicsTransforms; // 对应setTransformations()设置的设置的值
    bool onlyTransform;

    TransformData() :
        scale(1.0), rotation(0.0),
        xOrigin(0.0), yOrigin(0.0),
        onlyTransform(true)
    { }

    QTransform computedFullTransform(QTransform *postmultiplyTransform = nullptr) const
    {
        // 此处进行叠加运算
        if (onlyTransform) {
            if (!postmultiplyTransform || postmultiplyTransform->isIdentity())
                return transform;
            if (transform.isIdentity())
                return *postmultiplyTransform;
            return transform * *postmultiplyTransform;
        }

        QTransform x(transform);
        if (!graphicsTransforms.isEmpty()) {
            QMatrix4x4 m;
            for (int i = 0; i < graphicsTransforms.size(); ++i)
                graphicsTransforms.at(i)->applyTo(&m);
            x *= m.toTransform();
        }
        x.translate(xOrigin, yOrigin);
        x.rotate(rotation);
        x.scale(scale, scale);
        x.translate(-xOrigin, -yOrigin);
        if (postmultiplyTransform)
            x *= *postmultiplyTransform;
        return x;
    }
};

从源码中可以看出,transform、scale、rotation、graphicsTransforms单独存储。通过setRotation()设置的值存储在rotation,通过setScale()设置的值存储在scale。而rotation()和scale()返回的值就是QGraphicsItemPrivate中的变量rotation和scale。从qgraphicsitem.cpp源码中可以看到rotation和scale的存储方法。

复制代码
qreal QGraphicsItem::rotation() const
{
    if (!d_ptr->transformData)
        return 0;
    return d_ptr->transformData->rotation;
}

void QGraphicsItem::setRotation(qreal angle)
{
    prepareGeometryChange();
    qreal newRotation = angle;

    if (d_ptr->flags & ItemSendsGeometryChanges) {
        // Notify the item that the rotation is changing.
        const QVariant newRotationVariant(itemChange(ItemRotationChange, angle));
        newRotation = newRotationVariant.toReal();
    }

    if (!d_ptr->transformData)
        d_ptr->transformData = new QGraphicsItemPrivate::TransformData;

    if (d_ptr->transformData->rotation == newRotation)
        return;

    d_ptr->transformData->rotation = newRotation;
    d_ptr->transformData->onlyTransform = false;
    d_ptr->dirtySceneTransform = 1;

    // Send post-notification.
    if (d_ptr->flags & ItemSendsGeometryChanges)
        itemChange(ItemRotationHasChanged, newRotation);

    if (d_ptr->isObject)
        emit static_cast<QGraphicsObject *>(this)->rotationChanged();

    d_ptr->transformChanged();
}

如果需要获取当前图元旋转的角度,从 rotation() 方法中的得到的角度只是通过 setRotation() 方法设置的角度,该角度不包含 transform 和 graphicsTransforms 中隐含的角度。当然,如果图元只是进行了角度变换,可以从 sceneTransform() 方法获取 Transform,利用文章《Qt 从 QTransform 逆向解出 Translate/Scale/Rotate(平移/缩放/旋转)分析》介绍的方法可以逆向解析出图元旋转的角度。

如果需要获取图元每次变化的角度、缩放大小及平移信息,可以只使用 setTransformations() 方法来控制图元进行变化,因为 QGraphicsTransform 的子类可以完全由开发者自己控制,所执行的变换都可以很容易的计算出来。例如:QGraphicsTransform 的子类 QGraphicsRotation 提供了 angle() 和 setAngle(qreal) 方法;QGraphicsScale 提供了 setXScale(qreal) 、xScale() const 、setYScale(qreal) 、yScale() const 等方法。

控制图元变换时 setTransformations() 方法更灵活一些,特别是对图元进行变换动画时,编写 QGraphicsTransform 的子类,很容易控制动画。具体代码可以参考GitHub项目 Compelling Data Designer 中 BIDesigner/animation

/tranfromanimation 的实现方法。通过扩展 QGraphicsTransform 实现子类 GraphicsRotationZ 、 GraphicsRotationY 、 GraphicsRotationX 、 QGraphicsTranslation 实现了旋转、水平翻转、垂直翻转、按路径移动、缩放等动画效果。

相关推荐
地平线开发者36 分钟前
C++ 部署的性能优化方法
c++·算法·自动驾驶
Yingye Zhu(HPXXZYY)1 小时前
洛谷P12238 [蓝桥杯 2023 国 Java A] 单词分类
c++·算法·蓝桥杯
工藤新一¹1 小时前
C++/SDL 进阶游戏开发 —— 双人塔防(代号:村庄保卫战 16)
c++·游戏引擎·sdl·c++游戏开发·实践项目
whoarethenext2 小时前
c网络库libevent的http常用函数的使用(附带源码)
网络·c++·http·libevent
泽02024 小时前
C++入门(缺省参数/函数/引用)
开发语言·c++
mozun20208 小时前
VS BUG(6) LINK : fatal error LNK1158: 无法运行“rc.exe”
c++·bug·vs·链接器·资源文件
whoarethenext9 小时前
初始https附带c/c++源码使用curl库调用
服务器·c++·qt·https·curl
enyp8010 小时前
麒麟系统(基于Ubuntu)上使用Qt编译时遇到“type_traits文件未找到”的错误
linux·qt·ubuntu
cloues break.10 小时前
C++进阶----多态
开发语言·c++
道剑剑非道11 小时前
QT开发技术【qcustomplot 曲线与鼠标十字功能】
开发语言·qt·计算机外设