Coverage Report

Created: 2026-03-12 07:14

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