/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 |