Coverage Report

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