Coverage Report

Created: 2026-05-31 06:50

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