Coverage Report

Created: 2026-01-25 07:18

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/qtsvg/src/svg/animation/qsvganimatedproperty.cpp
Line
Count
Source
1
// Copyright (C) 2024 The Qt Company Ltd.
2
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4
#include "qsvganimatedproperty_p.h"
5
#include <QtCore/qpoint.h>
6
#include <QtGui/qcolor.h>
7
#include <QtGui/qtransform.h>
8
#include <QtCore/qloggingcategory.h>
9
#include <QtCore/qglobalstatic.h>
10
11
QT_BEGIN_NAMESPACE
12
13
Q_STATIC_LOGGING_CATEGORY(lcSvgAnimatedProperty, "qt.svg.animation.properties")
14
15
typedef QHash<QString, QSvgAbstractAnimatedProperty::Type> AnimatableHashType;
16
Q_GLOBAL_STATIC(AnimatableHashType, animatableProperties)
17
18
static void initHash()
19
0
{
20
0
    animatableProperties->insert(QStringLiteral("fill"), QSvgAbstractAnimatedProperty::Color);
21
0
    animatableProperties->insert(QStringLiteral("fill-opacity"), QSvgAbstractAnimatedProperty::Float);
22
0
    animatableProperties->insert(QStringLiteral("stroke-opacity"), QSvgAbstractAnimatedProperty::Float);
23
0
    animatableProperties->insert(QStringLiteral("stroke"), QSvgAbstractAnimatedProperty::Color);
24
0
    animatableProperties->insert(QStringLiteral("opacity"), QSvgAbstractAnimatedProperty::Float);
25
0
    animatableProperties->insert(QStringLiteral("transform"), QSvgAbstractAnimatedProperty::Transform);
26
0
    animatableProperties->insert(QStringLiteral("offset-distance"), QSvgAbstractAnimatedProperty::Float);
27
0
}
28
29
static qreal q_lerp(qreal a, qreal b, qreal t)
30
0
{
31
0
    return a + (b - a) * t;
32
0
}
33
34
static QPointF pointInterpolator(QPointF v1, QPointF v2, qreal t)
35
0
{
36
0
    qreal x = q_lerp(v1.x(), v2.x(), t);
37
0
    qreal y = q_lerp(v1.y(), v2.y(), t);
38
39
0
    return QPointF(x, y);
40
0
}
41
42
43
QSvgAbstractAnimatedProperty::QSvgAbstractAnimatedProperty(const QString &name, Type type)
44
0
    : m_propertyName(name)
45
0
    , m_type(type)
46
0
{
47
0
}
48
49
QSvgAbstractAnimatedProperty::~QSvgAbstractAnimatedProperty()
50
0
{
51
0
}
52
53
void QSvgAbstractAnimatedProperty::setKeyFrames(const QList<qreal> &keyFrames)
54
0
{
55
0
    m_keyFrames = keyFrames;
56
0
}
57
58
void QSvgAbstractAnimatedProperty::appendKeyFrame(qreal keyFrame)
59
0
{
60
0
    m_keyFrames.append(keyFrame);
61
0
}
62
63
QList<qreal> QSvgAbstractAnimatedProperty::keyFrames() const
64
0
{
65
0
    return m_keyFrames;
66
0
}
67
68
void QSvgAbstractAnimatedProperty::setPropertyName(const QString &name)
69
0
{
70
0
    m_propertyName = name;
71
0
}
72
73
QStringView QSvgAbstractAnimatedProperty::propertyName() const
74
0
{
75
0
    return m_propertyName;
76
0
}
77
78
QSvgAbstractAnimatedProperty::Type QSvgAbstractAnimatedProperty::type() const
79
0
{
80
0
    return m_type;
81
0
}
82
83
QVariant QSvgAbstractAnimatedProperty::interpolatedValue() const
84
0
{
85
0
    return m_interpolatedValue;
86
0
}
87
88
QSvgAbstractAnimatedProperty *QSvgAbstractAnimatedProperty::createAnimatedProperty(const QString &name)
89
0
{
90
0
    if (animatableProperties->isEmpty())
91
0
        initHash();
92
93
0
    if (!animatableProperties->contains(name)) {
94
0
        qCDebug(lcSvgAnimatedProperty) << "Property : " << name << " is not animatable";
95
0
        return nullptr;
96
0
    }
97
98
0
    QSvgAbstractAnimatedProperty::Type type = animatableProperties->value(name);
99
0
    QSvgAbstractAnimatedProperty *prop = nullptr;
100
101
0
    switch (type) {
102
0
    case QSvgAbstractAnimatedProperty::Color:
103
0
        prop = new QSvgAnimatedPropertyColor(name);
104
0
        break;
105
0
    case QSvgAbstractAnimatedProperty::Transform:
106
0
        prop = new QSvgAnimatedPropertyTransform(name);
107
0
        break;
108
0
    case QSvgAbstractAnimatedProperty::Float:
109
0
        prop = new QSvgAnimatedPropertyFloat(name);
110
0
    default:
111
0
        break;
112
0
    }
113
114
0
    return prop;
115
0
}
116
117
QSvgAnimatedPropertyColor::QSvgAnimatedPropertyColor(const QString &name)
118
0
    : QSvgAbstractAnimatedProperty(name, QSvgAbstractAnimatedProperty::Color)
119
0
{
120
0
}
121
122
void QSvgAnimatedPropertyColor::setColors(const QList<QColor> &colors)
123
0
{
124
0
    m_colors = colors;
125
0
}
126
127
void QSvgAnimatedPropertyColor::appendColor(const QColor &color)
128
0
{
129
0
    m_colors.append(color);
130
0
}
131
132
QList<QColor> QSvgAnimatedPropertyColor::colors() const
133
0
{
134
0
    return m_colors;
135
0
}
136
137
void QSvgAnimatedPropertyColor::interpolate(uint index, qreal t) const
138
0
{
139
0
    QColor c1 = m_colors.at(index - 1);
140
0
    QColor c2 = m_colors.at(index);
141
142
0
    int alpha  = q_lerp(c1.alpha(), c2.alpha(), t);
143
0
    int red    = q_lerp(c1.red(), c2.red(), t);
144
0
    int green  = q_lerp(c1.green(), c2.green(), t);
145
0
    int blue   = q_lerp(c1.blue(), c2.blue(), t);
146
147
0
    m_interpolatedValue = QColor(red, green, blue, alpha);
148
0
}
149
150
QSvgAnimatedPropertyFloat::QSvgAnimatedPropertyFloat(const QString &name)
151
0
    : QSvgAbstractAnimatedProperty(name, QSvgAbstractAnimatedProperty::Float)
152
0
{
153
0
}
154
155
void QSvgAnimatedPropertyFloat::setValues(const QList<qreal> &values)
156
0
{
157
0
    m_values = values;
158
0
}
159
160
void QSvgAnimatedPropertyFloat::appendValue(const qreal value)
161
0
{
162
0
    m_values.append(value);
163
0
}
164
165
QList<qreal> QSvgAnimatedPropertyFloat::values() const
166
0
{
167
0
    return m_values;
168
0
}
169
170
void QSvgAnimatedPropertyFloat::interpolate(uint index, qreal t) const
171
0
{
172
0
    if (index >= (uint)m_keyFrames.size()) {
173
0
        qCWarning(lcSvgAnimatedProperty) << "Invalid index for key frames";
174
0
        return;
175
0
    }
176
177
0
    qreal float1 = m_values.at(index - 1);
178
0
    qreal float2 = m_values.at(index);
179
180
0
    m_interpolatedValue = q_lerp(float1, float2, t);
181
0
}
182
183
QSvgAnimatedPropertyTransform::QSvgAnimatedPropertyTransform(const QString &name)
184
0
    : QSvgAbstractAnimatedProperty(name, QSvgAbstractAnimatedProperty::Transform)
185
0
{
186
187
0
}
188
189
void QSvgAnimatedPropertyTransform::setTransformCount(quint32 count)
190
0
{
191
0
    m_transformCount = count;
192
0
}
193
194
quint32 QSvgAnimatedPropertyTransform::transformCount() const
195
0
{
196
0
    return m_transformCount;
197
0
}
198
199
void QSvgAnimatedPropertyTransform::appendComponents(const QList<TransformComponent> &components)
200
0
{
201
0
    m_components.append(components);
202
0
}
203
204
QList<QSvgAnimatedPropertyTransform::TransformComponent> QSvgAnimatedPropertyTransform::components() const
205
0
{
206
0
    return m_components;
207
0
}
208
209
// this function iterates over all TransformComponents in two consecutive
210
// key frames and interpolate between all TransformComponents. Moreover,
211
// it requires all key frames to have the same number of TransformComponents.
212
// This must be ensured by the parser itself, and it is handled in validateTransform
213
// function in qsvgcsshandler.cpp and in createAnimateTransformNode function
214
// in qsvghandler.cpp.
215
void QSvgAnimatedPropertyTransform::interpolate(uint index, qreal t) const
216
0
{
217
0
    if (index >= (uint)m_keyFrames.size()) {
218
0
        qCWarning(lcSvgAnimatedProperty) << "Invalid index for key frames";
219
0
        return;
220
0
    }
221
222
0
    if (!m_transformCount ||
223
0
        ((m_components.size() / qsizetype(m_transformCount)) != m_keyFrames.size())) {
224
0
        return;
225
0
    }
226
227
0
    QTransform transform = QTransform();
228
229
0
    qsizetype startIndex = (index - 1) * qsizetype(m_transformCount);
230
0
    qsizetype endIndex = index * qsizetype(m_transformCount);
231
232
0
    for (quint32 i = 0; i < m_transformCount; i++) {
233
0
        TransformComponent tc1 = m_components.at(startIndex + i);
234
0
        TransformComponent tc2 = m_components.at(endIndex + i);
235
0
        if (tc1.type == tc2.type) {
236
0
            if (tc1.type == TransformComponent::Translate) {
237
0
                QPointF t1 = QPointF(tc1.values.at(0), tc1.values.at(1));
238
0
                QPointF t2 = QPointF(tc2.values.at(0), tc2.values.at(1));
239
0
                QPointF tr = pointInterpolator(t1, t2, t);
240
0
                transform.translate(tr.x(), tr.y());
241
0
            } else if (tc1.type == TransformComponent::Scale) {
242
0
                QPointF s1 = QPointF(tc1.values.at(0), tc1.values.at(1));
243
0
                QPointF s2 = QPointF(tc2.values.at(0), tc2.values.at(1));
244
0
                QPointF sr = pointInterpolator(s1, s2, t);
245
0
                transform.scale(sr.x(), sr.y());
246
0
            } else if (tc1.type == TransformComponent::Rotate) {
247
0
                QPointF cor1 = QPointF(tc1.values.at(1), tc1.values.at(2));
248
0
                QPointF cor2 = QPointF(tc2.values.at(1), tc2.values.at(2));
249
0
                QPointF corResult = pointInterpolator(cor1, cor2, t);
250
0
                qreal angle1 = tc1.values.at(0);
251
0
                qreal angle2 = tc2.values.at(0);
252
0
                qreal angleResult = q_lerp(angle1, angle2, t);
253
0
                transform.translate(corResult.x(), corResult.y());
254
0
                transform.rotate(angleResult);
255
0
                transform.translate(-corResult.x(), -corResult.y());
256
0
            } else if (tc1.type == TransformComponent::Skew) {
257
0
                QPointF skew1 = QPointF(tc1.values.at(0), tc1.values.at(1));
258
0
                QPointF skew2 = QPointF(tc2.values.at(0), tc2.values.at(1));
259
0
                QPointF skewResult = pointInterpolator(skew1, skew2, t);
260
0
                transform.shear(qTan(qDegreesToRadians(skewResult.x())),
261
0
                                qTan(qDegreesToRadians(skewResult.y())));
262
0
            }
263
0
        }
264
0
    }
265
266
0
    m_interpolatedValue = transform;
267
0
}
268
269
QT_END_NAMESPACE