Revision control
Copy as Markdown
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
// Qt-Security score:significant reason:default
#include "qsvganimatedproperty_p.h"
#include <QtCore/qpoint.h>
#include <QtGui/qcolor.h>
#include <QtGui/qtransform.h>
#include <QtCore/qlatin1stringview.h>
#include <QtCore/qloggingcategory.h>
#include <QtCore/qstring.h>
#include <optional>
QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
Q_STATIC_LOGGING_CATEGORY(lcSvgAnimatedProperty, "qt.svg.animation.properties")
static std::optional<QSvgAbstractAnimatedProperty::Type> name2type(const QString &name)
{
// Perfect hashing:
//
// the length of the string uniquely identifies the property name
// (compiler guarantees, otherwise it would complain about duplicate case
// labels):
constexpr auto hash = [](QStringView s) {
return s.size();
};
switch (hash(name)) {
#define CASE(str, type) \
case hash(u"" #str): \
if (name == #str ## _L1) \
return QSvgAbstractAnimatedProperty:: type ; \
break; \
/* end */
CASE(fill, Color)
CASE(fill-opacity, Float)
CASE(stroke-opacity, Float)
CASE(stroke, Color)
CASE(opacity, Float)
CASE(transform, Transform)
CASE(offset-distance, Float)
#undef CASE
};
return std::nullopt;
}
static qreal q_lerp(qreal a, qreal b, qreal t)
{
return a + (b - a) * t;
}
static QPointF pointInterpolator(QPointF v1, QPointF v2, qreal t)
{
qreal x = q_lerp(v1.x(), v2.x(), t);
qreal y = q_lerp(v1.y(), v2.y(), t);
return QPointF(x, y);
}
QSvgAbstractAnimatedProperty::QSvgAbstractAnimatedProperty(const QString &name, Type type)
: m_propertyName(name)
, m_type(type)
{
}
QSvgAbstractAnimatedProperty::~QSvgAbstractAnimatedProperty()
{
}
void QSvgAbstractAnimatedProperty::setKeyFrames(const QList<qreal> &keyFrames)
{
m_keyFrames = keyFrames;
}
void QSvgAbstractAnimatedProperty::appendKeyFrame(qreal keyFrame)
{
m_keyFrames.append(keyFrame);
}
QList<qreal> QSvgAbstractAnimatedProperty::keyFrames() const
{
return m_keyFrames;
}
void QSvgAbstractAnimatedProperty::appendEasing(QSvgEasingInterfacePtr easing)
{
m_easings.push_back(std::move(easing));
}
const QSvgEasingInterface *QSvgAbstractAnimatedProperty::easingAt(unsigned int i) const
{
return i < m_easings.size() ? m_easings[i].get() : nullptr;
}
void QSvgAbstractAnimatedProperty::setPropertyName(const QString &name)
{
m_propertyName = name;
}
QStringView QSvgAbstractAnimatedProperty::propertyName() const
{
return m_propertyName;
}
QSvgAbstractAnimatedProperty::Type QSvgAbstractAnimatedProperty::type() const
{
return m_type;
}
QVariant QSvgAbstractAnimatedProperty::interpolatedValue() const
{
return m_interpolatedValue;
}
QSvgAbstractAnimatedProperty *QSvgAbstractAnimatedProperty::createAnimatedProperty(const QString &name)
{
const std::optional<Type> type = name2type(name);
if (!type) {
qCDebug(lcSvgAnimatedProperty) << "Property : " << name << " is not animatable";
return nullptr;
}
QSvgAbstractAnimatedProperty *prop = nullptr;
switch (*type) {
case QSvgAbstractAnimatedProperty::Color:
prop = new QSvgAnimatedPropertyColor(name);
break;
case QSvgAbstractAnimatedProperty::Transform:
prop = new QSvgAnimatedPropertyTransform(name);
break;
case QSvgAbstractAnimatedProperty::Float:
prop = new QSvgAnimatedPropertyFloat(name);
default:
break;
}
return prop;
}
QSvgAnimatedPropertyColor::QSvgAnimatedPropertyColor(const QString &name)
: QSvgAbstractAnimatedProperty(name, QSvgAbstractAnimatedProperty::Color)
{
}
QSvgAnimatedPropertyColor::~QSvgAnimatedPropertyColor()
= default;
void QSvgAnimatedPropertyColor::setColors(const QList<QColor> &colors)
{
m_colors = colors;
}
void QSvgAnimatedPropertyColor::appendColor(const QColor &color)
{
m_colors.append(color);
}
QList<QColor> QSvgAnimatedPropertyColor::colors() const
{
return m_colors;
}
void QSvgAnimatedPropertyColor::interpolate(uint index, qreal t) const
{
QColor c1 = m_colors.at(index - 1);
QColor c2 = m_colors.at(index);
int alpha = q_lerp(c1.alpha(), c2.alpha(), t);
int red = q_lerp(c1.red(), c2.red(), t);
int green = q_lerp(c1.green(), c2.green(), t);
int blue = q_lerp(c1.blue(), c2.blue(), t);
m_interpolatedValue = QColor(red, green, blue, alpha);
}
QSvgAnimatedPropertyFloat::QSvgAnimatedPropertyFloat(const QString &name)
: QSvgAbstractAnimatedProperty(name, QSvgAbstractAnimatedProperty::Float)
{
}
QSvgAnimatedPropertyFloat::~QSvgAnimatedPropertyFloat()
= default;
void QSvgAnimatedPropertyFloat::setValues(const QList<qreal> &values)
{
m_values = values;
}
void QSvgAnimatedPropertyFloat::appendValue(const qreal value)
{
m_values.append(value);
}
QList<qreal> QSvgAnimatedPropertyFloat::values() const
{
return m_values;
}
void QSvgAnimatedPropertyFloat::interpolate(uint index, qreal t) const
{
if (index >= (uint)m_keyFrames.size()) {
qCWarning(lcSvgAnimatedProperty) << "Invalid index for key frames";
return;
}
qreal float1 = m_values.at(index - 1);
qreal float2 = m_values.at(index);
m_interpolatedValue = q_lerp(float1, float2, t);
}
QSvgAnimatedPropertyTransform::QSvgAnimatedPropertyTransform(const QString &name)
: QSvgAbstractAnimatedProperty(name, QSvgAbstractAnimatedProperty::Transform)
{
}
QSvgAnimatedPropertyTransform::~QSvgAnimatedPropertyTransform()
= default;
void QSvgAnimatedPropertyTransform::setTransformCount(quint32 count)
{
m_transformCount = count;
}
quint32 QSvgAnimatedPropertyTransform::transformCount() const
{
return m_transformCount;
}
void QSvgAnimatedPropertyTransform::appendComponents(const QList<TransformComponent> &components)
{
m_components.append(components);
}
QList<QSvgAnimatedPropertyTransform::TransformComponent> QSvgAnimatedPropertyTransform::components() const
{
return m_components;
}
// this function iterates over all TransformComponents in two consecutive
// key frames and interpolate between all TransformComponents. Moreover,
// it requires all key frames to have the same number of TransformComponents.
// This must be ensured by the parser itself, and it is handled in validateTransform
// function in qsvgcsshandler.cpp and in createAnimateTransformNode function
// in qsvghandler.cpp.
void QSvgAnimatedPropertyTransform::interpolate(uint index, qreal t) const
{
if (index >= (uint)m_keyFrames.size()) {
qCWarning(lcSvgAnimatedProperty) << "Invalid index for key frames";
return;
}
if (!m_transformCount ||
((m_components.size() / qsizetype(m_transformCount)) != m_keyFrames.size())) {
return;
}
QTransform transform = QTransform();
qsizetype startIndex = (index - 1) * qsizetype(m_transformCount);
qsizetype endIndex = index * qsizetype(m_transformCount);
for (quint32 i = 0; i < m_transformCount; i++) {
TransformComponent tc1 = m_components.at(startIndex + i);
TransformComponent tc2 = m_components.at(endIndex + i);
if (tc1.type == tc2.type) {
if (tc1.type == TransformComponent::Translate) {
QPointF t1 = QPointF(tc1.values.at(0), tc1.values.at(1));
QPointF t2 = QPointF(tc2.values.at(0), tc2.values.at(1));
QPointF tr = pointInterpolator(t1, t2, t);
transform.translate(tr.x(), tr.y());
} else if (tc1.type == TransformComponent::Scale) {
QPointF s1 = QPointF(tc1.values.at(0), tc1.values.at(1));
QPointF s2 = QPointF(tc2.values.at(0), tc2.values.at(1));
QPointF sr = pointInterpolator(s1, s2, t);
transform.scale(sr.x(), sr.y());
} else if (tc1.type == TransformComponent::Rotate) {
QPointF cor1 = QPointF(tc1.values.at(1), tc1.values.at(2));
QPointF cor2 = QPointF(tc2.values.at(1), tc2.values.at(2));
QPointF corResult = pointInterpolator(cor1, cor2, t);
qreal angle1 = tc1.values.at(0);
qreal angle2 = tc2.values.at(0);
qreal angleResult = q_lerp(angle1, angle2, t);
transform.translate(corResult.x(), corResult.y());
transform.rotate(angleResult);
transform.translate(-corResult.x(), -corResult.y());
} else if (tc1.type == TransformComponent::Skew) {
QPointF skew1 = QPointF(tc1.values.at(0), tc1.values.at(1));
QPointF skew2 = QPointF(tc2.values.at(0), tc2.values.at(1));
QPointF skewResult = pointInterpolator(skew1, skew2, t);
transform.shear(qTan(qDegreesToRadians(skewResult.x())),
qTan(qDegreesToRadians(skewResult.y())));
}
}
}
m_interpolatedValue = transform;
}
QT_END_NAMESPACE