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