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