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