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