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