GLEGram 12.5 — Initial public release

Based on Swiftgram 12.5 (Telegram iOS 12.5).
All GLEGram features ported and organized in GLEGram/ folder.

Features: Ghost Mode, Saved Deleted Messages, Content Protection Bypass,
Font Replacement, Fake Profile, Chat Export, Plugin System, and more.

See CHANGELOG_12.5.md for full details.
This commit is contained in:
Leeksov
2026-04-06 09:48:12 +03:00
commit 4647310322
39685 changed files with 11052678 additions and 0 deletions
@@ -0,0 +1,5 @@
#include "NodeProperty.hpp"
namespace lottie {
}
@@ -0,0 +1,54 @@
#ifndef NodeProperty_hpp
#define NodeProperty_hpp
#include "Lottie/Public/Primitives/AnyValue.hpp"
#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/AnyNodeProperty.hpp"
#include "Lottie/Public/DynamicProperties/AnyValueProvider.hpp"
#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueContainer.hpp"
namespace lottie {
/// A node property that holds a reference to a T ValueProvider and a T ValueContainer.
template<typename T>
class NodeProperty: public AnyNodeProperty {
public:
NodeProperty(std::shared_ptr<ValueProvider<T>> provider) :
_typedContainer(provider->value(0.0)),
_valueProvider(provider) {
_typedContainer.setNeedsUpdate();
}
public:
virtual AnyValue::Type valueType() const override {
return AnyValueType<T>::type();
}
virtual T value() {
return _typedContainer.outputValue();
}
virtual bool needsUpdate(float frame) const override {
return _typedContainer.needsUpdate() || _valueProvider->hasUpdate(frame);
}
virtual void setProvider(std::shared_ptr<AnyValueProvider> provider) override {
/*if (provider->valueType() != valueType()) {
return;
}
_valueProvider = provider;
_typedContainer.setNeedsUpdate();*/
}
virtual void update(float frame) override {
_typedContainer.setValue(_valueProvider->value(frame), frame);
}
private:
ValueContainer<T> _typedContainer;
std::shared_ptr<ValueProvider<T>> _valueProvider;
//std::shared_ptr<AnyValueProvider> _originalValueProvider;
};
}
#endif /* NodeProperty_hpp */
@@ -0,0 +1,5 @@
#include "AnyNodeProperty.hpp"
namespace lottie {
}
@@ -0,0 +1,33 @@
#ifndef AnyNodeProperty_hpp
#define AnyNodeProperty_hpp
#include "Lottie/Public/Primitives/AnyValue.hpp"
#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/AnyValueContainer.hpp"
#include "Lottie/Public/DynamicProperties/AnyValueProvider.hpp"
#include <memory>
namespace lottie {
/// A property of a node. The node property holds a provider and a container
class AnyNodeProperty {
public:
virtual ~AnyNodeProperty() = default;
public:
/// Returns true if the property needs to recompute its stored value
virtual bool needsUpdate(float frame) const = 0;
/// Updates the property for the frame
virtual void update(float frame) = 0;
/// The Type of the value provider
virtual AnyValue::Type valueType() const = 0;
/// Sets the value provider for the property.
virtual void setProvider(std::shared_ptr<AnyValueProvider> provider) = 0;
};
}
#endif /* AnyNodeProperty_hpp */
@@ -0,0 +1,5 @@
#include "AnyValueContainer.hpp"
namespace lottie {
}
@@ -0,0 +1,25 @@
#ifndef AnyValueContainer_hpp
#define AnyValueContainer_hpp
#include "Lottie/Public/Primitives/AnyValue.hpp"
namespace lottie {
class AnyValueContainer {
public:
/// The stored value of the container
virtual AnyValue value() const = 0;
/// Notifies the provider that it should update its container
virtual void setNeedsUpdate() = 0;
/// When true the container needs to have its value updated by its provider
virtual bool needsUpdate() const = 0;
/// The frame time of the last provided update
virtual float lastUpdateFrame() const = 0;
};
}
#endif /* AnyValueContainer_hpp */
@@ -0,0 +1,13 @@
#ifndef HasRenderUpdates_hpp
#define HasRenderUpdates_hpp
namespace lottie {
class HasRenderUpdates {
public:
virtual bool hasRenderUpdates(float forFrame) = 0;
};
}
#endif /* HasRenderUpdates_hpp */
@@ -0,0 +1,14 @@
#ifndef HasUpdate_hpp
#define HasUpdate_hpp
namespace lottie {
class HasUpdate {
public:
/// The last frame in which this node was updated.
virtual bool hasUpdate() = 0;
};
}
#endif /* HasUpdate_hpp */
@@ -0,0 +1,5 @@
#include "KeypathSearchable.hpp"
namespace lottie {
}
@@ -0,0 +1,36 @@
#ifndef KeypathSearchable_hpp
#define KeypathSearchable_hpp
#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/AnyNodeProperty.hpp"
#include "Lottie/Public/Primitives/CALayer.hpp"
#include <string>
#include <vector>
#include <map>
#include <memory>
namespace lottie {
class KeypathSearchable;
class HasChildKeypaths {
public:
/// Children Keypaths
virtual std::vector<std::shared_ptr<KeypathSearchable>> const &childKeypaths() const = 0;
};
/// Protocol that provides keypath search functionality. Returns all node properties associated with a keypath.
class KeypathSearchable: virtual public HasChildKeypaths {
public:
/// The name of the Keypath
virtual std::string keypathName() const = 0;
/// A list of properties belonging to the keypath.
virtual std::map<std::string, std::shared_ptr<AnyNodeProperty>> keypathProperties() const = 0;
virtual std::shared_ptr<CALayer> keypathLayer() const = 0;
};
}
#endif /* KeypathSearchable_hpp */
@@ -0,0 +1,5 @@
#include "NodePropertyMap.hpp"
namespace lottie {
}
@@ -0,0 +1,41 @@
#ifndef NodePropertyMap_hpp
#define NodePropertyMap_hpp
#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/AnyNodeProperty.hpp"
#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/KeypathSearchable.hpp"
#include "Lottie/Public/Primitives/CALayer.hpp"
#include <vector>
namespace lottie {
class NodePropertyMap: virtual public HasChildKeypaths {
public:
virtual std::vector<std::shared_ptr<AnyNodeProperty>> &properties() = 0;
bool needsLocalUpdate(float frame) {
for (auto &property : properties()) {
if (property->needsUpdate(frame)) {
return true;
}
}
return false;
}
void updateNodeProperties(float frame) {
for (auto &property : properties()) {
property->update(frame);
}
}
};
class KeypathSearchableNodePropertyMap: virtual public NodePropertyMap, virtual public KeypathSearchable {
public:
virtual std::shared_ptr<CALayer> keypathLayer() const override {
return nullptr;
}
};
}
#endif /* NodePropertyMap_hpp */
@@ -0,0 +1,5 @@
#include "ValueContainer.hpp"
namespace lottie {
}
@@ -0,0 +1,58 @@
#ifndef ValueContainer_hpp
#define ValueContainer_hpp
#include "Lottie/Public/Primitives/AnyValue.hpp"
#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/AnyValueContainer.hpp"
namespace lottie {
/// A container for a node value that is Typed to T.
template<typename T>
class ValueContainer: public AnyValueContainer {
public:
ValueContainer(T value) :
_outputValue(value) {
}
public:
float _lastUpdateFrame = std::numeric_limits<float>::infinity();
bool _needsUpdate = true;
virtual AnyValue value() const override {
return AnyValue(_outputValue);
}
virtual bool needsUpdate() const override {
return _needsUpdate;
}
virtual float lastUpdateFrame() const override {
return _lastUpdateFrame;
}
T _outputValue;
T outputValue() {
return _outputValue;
}
void setOutputValue(T value) {
_outputValue = value;
_needsUpdate = false;
}
void setValue(AnyValue value, float forFrame) {
if (value.type() == AnyValueType<T>::type()) {
_needsUpdate = false;
_lastUpdateFrame = forFrame;
_outputValue = value.get<T>();
}
}
virtual void setNeedsUpdate() override {
_needsUpdate = true;
}
};
}
#endif /* ValueContainer_hpp */
@@ -0,0 +1,5 @@
#include "DashPatternInterpolator.hpp"
namespace lottie {
}
@@ -0,0 +1,48 @@
#ifndef DashPatternInterpolator_hpp
#define DashPatternInterpolator_hpp
#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/KeyframeInterpolator.hpp"
#include "Lottie/Public/Primitives/DashPattern.hpp"
namespace lottie {
/// A value provider that produces an array of values from an array of Keyframe Interpolators
class DashPatternInterpolator: public ValueProvider<DashPattern>, public std::enable_shared_from_this<DashPatternInterpolator> {
public:
/// Initialize with an array of array of keyframes.
DashPatternInterpolator(std::vector<std::vector<Keyframe<Vector1D>>> const &keyframeGroups) {
for (const auto &keyframeGroup : keyframeGroups) {
_keyframeInterpolators.push_back(std::make_shared<KeyframeInterpolator<Vector1D>>(keyframeGroup));
}
}
virtual ~DashPatternInterpolator() = default;
virtual AnyValue::Type valueType() const override {
return AnyValueType<DashPattern>::type();
}
virtual DashPattern value(AnimationFrameTime frame) override {
std::vector<float> values;
for (const auto &interpolator : _keyframeInterpolators) {
values.push_back(interpolator->value(frame).value);
}
return DashPattern(std::move(values));
}
virtual bool hasUpdate(float frame) const override {
for (const auto &interpolator : _keyframeInterpolators) {
if (interpolator->hasUpdate(frame)) {
return true;
}
}
return false;
}
private:
std::vector<std::shared_ptr<KeyframeInterpolator<Vector1D>>> _keyframeInterpolators;
};
}
#endif /* DashPatternInterpolator_hpp */
@@ -0,0 +1,5 @@
#include "KeyframeInterpolator.hpp"
namespace lottie {
}
@@ -0,0 +1,452 @@
#ifndef KeyframeInterpolator_hpp
#define KeyframeInterpolator_hpp
#include "Lottie/Public/DynamicProperties/AnyValueProvider.hpp"
namespace lottie {
/// A value provider that produces a value at Time from a group of keyframes
template<typename T>
class KeyframeInterpolator: public ValueProvider<T>, public std::enable_shared_from_this<KeyframeInterpolator<T>> {
public:
KeyframeInterpolator(std::vector<Keyframe<T>> const &keyframes_) :
keyframes(keyframes_) {
assert(!keyframes.empty());
}
virtual ~KeyframeInterpolator() {
}
public:
std::vector<Keyframe<T>> keyframes;
virtual AnyValue::Type valueType() const override {
return AnyValueType<T>::type();
}
virtual T value(AnimationFrameTime frame) override {
// First set the keyframe span for the frame.
updateSpanIndices(frame);
lastUpdatedFrame = frame;
// If only one keyframe return its value
if (leadingKeyframe.has_value() &&
trailingKeyframe.has_value())
{
/// We have leading and trailing keyframe.
auto progress = leadingKeyframe->interpolatedProgress(trailingKeyframe.value(), frame);
return leadingKeyframe->interpolate(trailingKeyframe.value(), progress);
} else if (leadingKeyframe.has_value()) {
return leadingKeyframe->value;
} else if (trailingKeyframe.has_value()) {
return trailingKeyframe->value;
} else {
/// Satisfy the compiler.
return keyframes[0].value;
}
}
/// Returns true to trigger a frame update for this interpolator.
///
/// An interpolator will be asked if it needs to update every frame.
/// If the interpolator needs updating it will be asked to compute its value for
/// the given frame.
///
/// Cases a keyframe should not be updated:
/// - If time is in span and leading keyframe is hold
/// - If time is after the last keyframe.
/// - If time is before the first keyframe
///
/// Cases for updating a keyframe:
/// - If time is in the span, and is not a hold
/// - If time is outside of the span, and there are more keyframes
/// - If a value delegate is set
/// - If leading and trailing are both nil.
virtual bool hasUpdate(float frame) const override {
if (!lastUpdatedFrame.has_value()) {
return true;
}
if (leadingKeyframe.has_value() &&
!trailingKeyframe.has_value() &&
leadingKeyframe->time < frame)
{
/// Frame is after bounds of keyframes
return false;
}
if (trailingKeyframe.has_value() &&
!leadingKeyframe.has_value() &&
frame < trailingKeyframe->time)
{
/// Frame is before bounds of keyframes
return false;
}
if (leadingKeyframe.has_value() &&
trailingKeyframe.has_value() &&
leadingKeyframe->isHold &&
leadingKeyframe->time < frame &&
frame < trailingKeyframe->time)
{
return false;
}
return true;
}
// MARK: Fileprivate
std::optional<float> lastUpdatedFrame;
std::optional<int> leadingIndex;
std::optional<int> trailingIndex;
std::optional<Keyframe<T>> leadingKeyframe;
std::optional<Keyframe<T>> trailingKeyframe;
/// Finds the appropriate Leading and Trailing keyframe index for the given time.
void updateSpanIndices(float frame) {
if (keyframes.empty()) {
leadingIndex = std::nullopt;
trailingIndex = std::nullopt;
leadingKeyframe = std::nullopt;
trailingKeyframe = std::nullopt;
return;
}
// This function searches through the array to find the span of two keyframes
// that contain the current time.
//
// We could use Array.first(where:) but that would search through the entire array
// each frame.
// Instead we track the last used index and search either forwards or
// backwards from there. This reduces the iterations and complexity from
//
// O(n), where n is the length of the sequence to
// O(n), where n is the number of items after or before the last used index.
//
if (keyframes.size() == 1) {
/// Only one keyframe. Set it as first and move on.
leadingIndex = 0;
trailingIndex = std::nullopt;
leadingKeyframe = keyframes[0];
trailingKeyframe = std::nullopt;
return;
}
/// Sets the initial keyframes. This is often only needed for the first check.
if
(!leadingIndex.has_value() &&
!trailingIndex.has_value())
{
if (frame < keyframes[0].time) {
/// Time is before the first keyframe. Set it as the trailing.
trailingIndex = 0;
} else {
/// Time is after the first keyframe. Set the keyframe and the trailing.
leadingIndex = 0;
trailingIndex = 1;
}
}
if
(trailingIndex.has_value() &&
keyframes[trailingIndex.value()].time <= frame)
{
/// Time is after the current span. Iterate forward.
auto newLeading = trailingIndex.value();
bool keyframeFound = false;
while (!keyframeFound) {
leadingIndex = newLeading;
if (newLeading + 1 >= 0 && newLeading + 1 < keyframes.size()) {
trailingIndex = newLeading + 1;
} else {
trailingIndex = std::nullopt;
}
if (!trailingIndex.has_value()) {
/// We have reached the end of our keyframes. Time is after the last keyframe.
keyframeFound = true;
continue;
}
if (frame < keyframes[trailingIndex.value()].time) {
/// Keyframe in current span.
keyframeFound = true;
continue;
}
/// Advance the array.
newLeading = trailingIndex.value();
}
} else if
(leadingIndex.has_value() &&
frame < keyframes[leadingIndex.value()].time)
{
/// Time is before the current span. Iterate backwards
auto newTrailing = leadingIndex.value();
bool keyframeFound = false;
while (!keyframeFound) {
if (newTrailing - 1 >= 0 && newTrailing - 1 < keyframes.size()) {
leadingIndex = newTrailing - 1;
} else {
leadingIndex = std::nullopt;
}
trailingIndex = newTrailing;
if (!leadingIndex.has_value()) {
/// We have reached the end of our keyframes. Time is after the last keyframe.
keyframeFound = true;
continue;
}
if (keyframes[leadingIndex.value()].time <= frame) {
/// Keyframe in current span.
keyframeFound = true;
continue;
}
/// Step back
newTrailing = leadingIndex.value();
}
}
if (const auto keyFrame = leadingIndex) {
leadingKeyframe = keyframes[keyFrame.value()];
} else {
leadingKeyframe = std::nullopt;
}
if (const auto keyFrame = trailingIndex) {
trailingKeyframe = keyframes[keyFrame.value()];
} else {
trailingKeyframe = std::nullopt;
}
}
};
class BezierPathKeyframeInterpolator {
public:
BezierPathKeyframeInterpolator(std::vector<Keyframe<BezierPath>> const &keyframes_) :
keyframes(keyframes_) {
assert(!keyframes.empty());
}
public:
std::vector<Keyframe<BezierPath>> keyframes;
void update(AnimationFrameTime frame, BezierPath &outPath) {
// First set the keyframe span for the frame.
updateSpanIndices(frame);
lastUpdatedFrame = frame;
// If only one keyframe return its value
if (leadingKeyframe.has_value() &&
trailingKeyframe.has_value())
{
/// We have leading and trailing keyframe.
auto progress = leadingKeyframe->interpolatedProgress(trailingKeyframe.value(), frame);
interpolateInplace(leadingKeyframe.value(), trailingKeyframe.value(), progress, outPath);
} else if (leadingKeyframe.has_value()) {
setInplace(leadingKeyframe.value(), outPath);
} else if (trailingKeyframe.has_value()) {
setInplace(trailingKeyframe.value(), outPath);
} else {
/// Satisfy the compiler.
setInplace(keyframes[0], outPath);
}
}
/// Returns true to trigger a frame update for this interpolator.
///
/// An interpolator will be asked if it needs to update every frame.
/// If the interpolator needs updating it will be asked to compute its value for
/// the given frame.
///
/// Cases a keyframe should not be updated:
/// - If time is in span and leading keyframe is hold
/// - If time is after the last keyframe.
/// - If time is before the first keyframe
///
/// Cases for updating a keyframe:
/// - If time is in the span, and is not a hold
/// - If time is outside of the span, and there are more keyframes
/// - If a value delegate is set
/// - If leading and trailing are both nil.
bool hasUpdate(float frame) const {
if (!lastUpdatedFrame.has_value()) {
return true;
}
if (leadingKeyframe.has_value() &&
!trailingKeyframe.has_value() &&
leadingKeyframe->time < frame)
{
/// Frame is after bounds of keyframes
return false;
}
if (trailingKeyframe.has_value() &&
!leadingKeyframe.has_value() &&
frame < trailingKeyframe->time)
{
/// Frame is before bounds of keyframes
return false;
}
if (leadingKeyframe.has_value() &&
trailingKeyframe.has_value() &&
leadingKeyframe->isHold &&
leadingKeyframe->time < frame &&
frame < trailingKeyframe->time)
{
return false;
}
return true;
}
// MARK: Fileprivate
std::optional<float> lastUpdatedFrame;
std::optional<int> leadingIndex;
std::optional<int> trailingIndex;
std::optional<Keyframe<BezierPath>> leadingKeyframe;
std::optional<Keyframe<BezierPath>> trailingKeyframe;
/// Finds the appropriate Leading and Trailing keyframe index for the given time.
void updateSpanIndices(float frame) {
if (keyframes.empty()) {
leadingIndex = std::nullopt;
trailingIndex = std::nullopt;
leadingKeyframe = std::nullopt;
trailingKeyframe = std::nullopt;
return;
}
// This function searches through the array to find the span of two keyframes
// that contain the current time.
//
// We could use Array.first(where:) but that would search through the entire array
// each frame.
// Instead we track the last used index and search either forwards or
// backwards from there. This reduces the iterations and complexity from
//
// O(n), where n is the length of the sequence to
// O(n), where n is the number of items after or before the last used index.
//
if (keyframes.size() == 1) {
/// Only one keyframe. Set it as first and move on.
leadingIndex = 0;
trailingIndex = std::nullopt;
leadingKeyframe = keyframes[0];
trailingKeyframe = std::nullopt;
return;
}
/// Sets the initial keyframes. This is often only needed for the first check.
if
(!leadingIndex.has_value() &&
!trailingIndex.has_value())
{
if (frame < keyframes[0].time) {
/// Time is before the first keyframe. Set it as the trailing.
trailingIndex = 0;
} else {
/// Time is after the first keyframe. Set the keyframe and the trailing.
leadingIndex = 0;
trailingIndex = 1;
}
}
if
(trailingIndex.has_value() &&
keyframes[trailingIndex.value()].time <= frame)
{
/// Time is after the current span. Iterate forward.
auto newLeading = trailingIndex.value();
bool keyframeFound = false;
while (!keyframeFound) {
leadingIndex = newLeading;
if (newLeading + 1 >= 0 && newLeading + 1 < keyframes.size()) {
trailingIndex = newLeading + 1;
} else {
trailingIndex = std::nullopt;
}
if (!trailingIndex.has_value()) {
/// We have reached the end of our keyframes. Time is after the last keyframe.
keyframeFound = true;
continue;
}
if (frame < keyframes[trailingIndex.value()].time) {
/// Keyframe in current span.
keyframeFound = true;
continue;
}
/// Advance the array.
newLeading = trailingIndex.value();
}
} else if
(leadingIndex.has_value() &&
frame < keyframes[leadingIndex.value()].time)
{
/// Time is before the current span. Iterate backwards
auto newTrailing = leadingIndex.value();
bool keyframeFound = false;
while (!keyframeFound) {
if (newTrailing - 1 >= 0 && newTrailing - 1 < keyframes.size()) {
leadingIndex = newTrailing - 1;
} else {
leadingIndex = std::nullopt;
}
trailingIndex = newTrailing;
if (!leadingIndex.has_value()) {
/// We have reached the end of our keyframes. Time is after the last keyframe.
keyframeFound = true;
continue;
}
if (keyframes[leadingIndex.value()].time <= frame) {
/// Keyframe in current span.
keyframeFound = true;
continue;
}
/// Step back
newTrailing = leadingIndex.value();
}
}
if (const auto keyFrame = leadingIndex) {
leadingKeyframe = keyframes[keyFrame.value()];
} else {
leadingKeyframe = std::nullopt;
}
if (const auto keyFrame = trailingIndex) {
trailingKeyframe = keyframes[keyFrame.value()];
} else {
trailingKeyframe = std::nullopt;
}
}
private:
void setInplace(Keyframe<BezierPath> const &from, BezierPath &outPath) {
ValueInterpolator<BezierPath>::setInplace(from.value, outPath);
}
void interpolateInplace(Keyframe<BezierPath> const &from, Keyframe<BezierPath> const &to, float progress, BezierPath &outPath) {
std::optional<Vector2D> spatialOutTangent2d;
if (from.spatialOutTangent) {
spatialOutTangent2d = Vector2D(from.spatialOutTangent->x, from.spatialOutTangent->y);
}
std::optional<Vector2D> spatialInTangent2d;
if (to.spatialInTangent) {
spatialInTangent2d = Vector2D(to.spatialInTangent->x, to.spatialInTangent->y);
}
ValueInterpolator<BezierPath>::interpolateInplace(from.value, to.value, progress, spatialOutTangent2d, spatialInTangent2d, outPath);
}
};
}
#endif /* KeyframeInterpolator_hpp */
@@ -0,0 +1,5 @@
#include "SingleValueProvider.hpp"
namespace lottie {
}
@@ -0,0 +1,42 @@
#ifndef SingleValueProvider_hpp
#define SingleValueProvider_hpp
#include "Lottie/Public/DynamicProperties/AnyValueProvider.hpp"
namespace lottie {
/// Returns a value for every frame.
template<typename T>
class SingleValueProvider: public ValueProvider<T> {
public:
SingleValueProvider(T const &value) :
_value(value) {
}
virtual ~SingleValueProvider() = default;
void setValue(T const &value) {
_value = value;
_hasUpdate = true;
}
virtual T value(AnimationFrameTime frame) override {
return _value;
}
virtual AnyValue::Type valueType() const override {
return AnyValueType<T>::type();
}
virtual bool hasUpdate(float frame) const override {
return _hasUpdate;
}
private:
T _value;
bool _hasUpdate = true;
};
}
#endif /* SingleValueProvider_hpp */
@@ -0,0 +1,65 @@
#ifndef PassThroughOutputNode_hpp
#define PassThroughOutputNode_hpp
#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/HasRenderUpdates.hpp"
#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/HasUpdate.hpp"
#include "Lottie/Private/MainThread/NodeRenderSystem/Protocols/NodeOutput.hpp"
namespace lottie {
class PassThroughOutputNode: virtual public NodeOutput, virtual public HasRenderUpdates, virtual public HasUpdate {
public:
PassThroughOutputNode(std::shared_ptr<NodeOutput> parent) :
_parent(parent) {
}
virtual ~PassThroughOutputNode() = default;
virtual std::shared_ptr<NodeOutput> parent() override {
return _parent;
}
virtual bool isEnabled() const override {
return _isEnabled;
}
virtual void setIsEnabled(bool isEnabled) override {
_isEnabled = isEnabled;
}
virtual bool hasUpdate() override {
return _hasUpdate;
}
void setHasUpdate(bool hasUpdate) {
_hasUpdate = hasUpdate;
}
virtual bool hasOutputUpdates(float forFrame) override {
/// Changes to this node do not affect downstream nodes.
bool parentUpdate = false;
if (_parent) {
parentUpdate = _parent->hasOutputUpdates(forFrame);
}
/// Changes to upstream nodes do, however, affect this nodes state.
_hasUpdate = _hasUpdate || parentUpdate;
return parentUpdate;
}
virtual bool hasRenderUpdates(float forFrame) override {
/// Return true if there are upstream updates or if this node has updates
bool upstreamUpdates = false;
if (_parent) {
upstreamUpdates = _parent->hasOutputUpdates(forFrame);
}
_hasUpdate = _hasUpdate || upstreamUpdates;
return _hasUpdate;
}
private:
std::shared_ptr<NodeOutput> _parent;
bool _hasUpdate = false;
bool _isEnabled = true;
};
}
#endif /* PassThroughOutputNode_hpp */
@@ -0,0 +1,33 @@
#ifndef StrokeNode_hpp
#define StrokeNode_hpp
#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/NodePropertyMap.hpp"
#include "Lottie/Private/Model/ShapeItems/Stroke.hpp"
#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/NodeProperty.hpp"
#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/KeyframeInterpolator.hpp"
#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/SingleValueProvider.hpp"
#include "Lottie/Private/MainThread/NodeRenderSystem/Protocols/AnimatorNode.hpp"
#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/DashPatternInterpolator.hpp"
namespace lottie {
class StrokeShapeDashConfiguration {
public:
StrokeShapeDashConfiguration(std::vector<DashElement> const &elements) {
for (const auto &dash : elements) {
if (dash.type == DashElementType::Offset) {
dashPhase = dash.value.keyframes;
} else {
dashPatterns.push_back(dash.value.keyframes);
}
}
}
public:
std::vector<std::vector<Keyframe<Vector1D>>> dashPatterns;
std::vector<Keyframe<Vector1D>> dashPhase;
};
}
#endif /* StrokeNode_hpp */
@@ -0,0 +1,361 @@
#ifndef TextAnimatorNode_hpp
#define TextAnimatorNode_hpp
#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/NodePropertyMap.hpp"
#include "Lottie/Private/Model/Text/TextAnimator.hpp"
#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/NodeProperty.hpp"
#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/KeyframeInterpolator.hpp"
#include "Lottie/Private/MainThread/NodeRenderSystem/Protocols/NodeOutput.hpp"
#include "Lottie/Private/MainThread/NodeRenderSystem/Protocols/AnimatorNode.hpp"
namespace lottie {
class TextAnimatorNodeProperties: public KeypathSearchableNodePropertyMap {
public:
TextAnimatorNodeProperties(std::shared_ptr<TextAnimator> const &textAnimator) {
_keypathName = textAnimator->name.value_or("");
if (textAnimator->anchor) {
_anchor = std::make_shared<NodeProperty<Vector3D>>(std::make_shared<KeyframeInterpolator<Vector3D>>(textAnimator->anchor->keyframes));
_keypathProperties.insert(std::make_pair("Anchor", _anchor));
}
if (textAnimator->position) {
_position = std::make_shared<NodeProperty<Vector3D>>(std::make_shared<KeyframeInterpolator<Vector3D>>(textAnimator->position->keyframes));
_keypathProperties.insert(std::make_pair("Position", _position));
}
if (textAnimator->scale) {
_scale = std::make_shared<NodeProperty<Vector3D>>(std::make_shared<KeyframeInterpolator<Vector3D>>(textAnimator->scale->keyframes));
_keypathProperties.insert(std::make_pair("Scale", _scale));
}
if (textAnimator->skew) {
_skew = std::make_shared<NodeProperty<Vector1D>>(std::make_shared<KeyframeInterpolator<Vector1D>>(textAnimator->skew->keyframes));
_keypathProperties.insert(std::make_pair("Skew", _skew));
}
if (textAnimator->skewAxis) {
_skewAxis = std::make_shared<NodeProperty<Vector1D>>(std::make_shared<KeyframeInterpolator<Vector1D>>(textAnimator->skewAxis->keyframes));
_keypathProperties.insert(std::make_pair("Skew Axis", _skewAxis));
}
if (textAnimator->rotation) {
_rotation = std::make_shared<NodeProperty<Vector1D>>(std::make_shared<KeyframeInterpolator<Vector1D>>(textAnimator->rotation->keyframes));
_keypathProperties.insert(std::make_pair("Rotation", _rotation));
}
if (textAnimator->rotation) {
_opacity = std::make_shared<NodeProperty<Vector1D>>(std::make_shared<KeyframeInterpolator<Vector1D>>(textAnimator->opacity->keyframes));
_keypathProperties.insert(std::make_pair("Opacity", _opacity));
}
if (textAnimator->strokeColor) {
_strokeColor = std::make_shared<NodeProperty<Color>>(std::make_shared<KeyframeInterpolator<Color>>(textAnimator->strokeColor->keyframes));
_keypathProperties.insert(std::make_pair("Stroke Color", _strokeColor));
}
if (textAnimator->fillColor) {
_fillColor = std::make_shared<NodeProperty<Color>>(std::make_shared<KeyframeInterpolator<Color>>(textAnimator->fillColor->keyframes));
_keypathProperties.insert(std::make_pair("Fill Color", _fillColor));
}
if (textAnimator->strokeWidth) {
_strokeWidth = std::make_shared<NodeProperty<Vector1D>>(std::make_shared<KeyframeInterpolator<Vector1D>>(textAnimator->strokeWidth->keyframes));
_keypathProperties.insert(std::make_pair("Stroke Width", _strokeWidth));
}
if (textAnimator->tracking) {
_tracking = std::make_shared<NodeProperty<Vector1D>>(std::make_shared<KeyframeInterpolator<Vector1D>>(textAnimator->tracking->keyframes));
_keypathProperties.insert(std::make_pair("Tracking", _tracking));
}
for (const auto &it : _keypathProperties) {
_properties.push_back(it.second);
}
}
virtual ~TextAnimatorNodeProperties() = default;
virtual std::string keypathName() const override {
return _keypathName;
}
virtual std::map<std::string, std::shared_ptr<AnyNodeProperty>> keypathProperties() const override {
return _keypathProperties;
}
virtual std::vector<std::shared_ptr<AnyNodeProperty>> &properties() override {
return _properties;
}
virtual std::vector<std::shared_ptr<KeypathSearchable>> const &childKeypaths() const override {
return _childKeypaths;
}
Transform2D caTransform() {
Vector2D anchor = Vector2D::Zero();
if (_anchor) {
auto anchor3d = _anchor->value();
anchor = Vector2D(anchor3d.x, anchor3d.y);
}
Vector2D position = Vector2D::Zero();
if (_position) {
auto position3d = _position->value();
position = Vector2D(position3d.x, position3d.y);
}
Vector2D scale = Vector2D(100.0, 100.0);
if (_scale) {
auto scale3d = _scale->value();
scale = Vector2D(scale3d.x, scale3d.y);
}
float rotation = 0.0;
if (_rotation) {
rotation = _rotation->value().value;
}
std::optional<float> skew;
if (_skew) {
skew = _skew->value().value;
}
std::optional<float> skewAxis;
if (_skewAxis) {
skewAxis = _skewAxis->value().value;
}
return Transform2D::makeTransform(
anchor,
position,
scale,
rotation,
skew,
skewAxis
);
}
virtual std::shared_ptr<CALayer> keypathLayer() const override {
return nullptr;
}
float opacity() {
if (_opacity) {
return _opacity->value().value;
} else {
return 100.0;
}
}
std::optional<Color> strokeColor() {
if (_strokeColor) {
return _strokeColor->value();
} else {
return std::nullopt;
}
}
std::optional<Color> fillColor() {
if (_fillColor) {
return _fillColor->value();
} else {
return std::nullopt;
}
}
float tracking() {
if (_tracking) {
return _tracking->value().value;
} else {
return 1.0;
}
}
float strokeWidth() {
if (_strokeWidth) {
return _strokeWidth->value().value;
} else {
return 0.0;
}
}
private:
std::string _keypathName;
std::shared_ptr<NodeProperty<Vector3D>> _anchor;
std::shared_ptr<NodeProperty<Vector3D>> _position;
std::shared_ptr<NodeProperty<Vector3D>> _scale;
std::shared_ptr<NodeProperty<Vector1D>> _skew;
std::shared_ptr<NodeProperty<Vector1D>> _skewAxis;
std::shared_ptr<NodeProperty<Vector1D>> _rotation;
std::shared_ptr<NodeProperty<Vector1D>> _opacity;
std::shared_ptr<NodeProperty<Color>> _strokeColor;
std::shared_ptr<NodeProperty<Color>> _fillColor;
std::shared_ptr<NodeProperty<Vector1D>> _strokeWidth;
std::shared_ptr<NodeProperty<Vector1D>> _tracking;
std::map<std::string, std::shared_ptr<AnyNodeProperty>> _keypathProperties;
std::vector<std::shared_ptr<KeypathSearchable>> _childKeypaths;
std::vector<std::shared_ptr<AnyNodeProperty>> _properties;
};
class TextOutputNode: virtual public NodeOutput {
public:
TextOutputNode(std::shared_ptr<TextOutputNode> parent) :
_parentTextNode(parent) {
}
virtual ~TextOutputNode() = default;
virtual std::shared_ptr<NodeOutput> parent() override {
return _parentTextNode;
}
Transform2D xform() {
if (_xform.has_value()) {
return _xform.value();
} else if (_parentTextNode) {
return _parentTextNode->xform();
} else {
return Transform2D::identity();
}
}
void setXform(Transform2D const &xform) {
_xform = xform;
}
float opacity() {
if (_opacity.has_value()) {
return _opacity.value();
} else if (_parentTextNode) {
return _parentTextNode->opacity();
} else {
return 1.0;
}
}
void setOpacity(float opacity) {
_opacity = opacity;
}
std::optional<Color> strokeColor() {
if (_strokeColor.has_value()) {
return _strokeColor.value();
} else if (_parentTextNode) {
return _parentTextNode->strokeColor();
} else {
return std::nullopt;
}
}
void setStrokeColor(std::optional<Color> strokeColor) {
_strokeColor = strokeColor;
}
std::optional<Color> fillColor() {
if (_fillColor.has_value()) {
return _fillColor.value();
} else if (_parentTextNode) {
return _parentTextNode->fillColor();
} else {
return std::nullopt;
}
}
void setFillColor(std::optional<Color> fillColor) {
_fillColor = fillColor;
}
float tracking() {
if (_tracking.has_value()) {
return _tracking.value();
} else if (_parentTextNode) {
return _parentTextNode->tracking();
} else {
return 0.0;
}
}
void setTracking(float tracking) {
_tracking = tracking;
}
float strokeWidth() {
if (_strokeWidth.has_value()) {
return _strokeWidth.value();
} else if (_parentTextNode) {
return _parentTextNode->strokeWidth();
} else {
return 0.0;
}
}
void setStrokeWidth(float strokeWidth) {
_strokeWidth = strokeWidth;
}
virtual bool hasOutputUpdates(float frame) override {
// TODO Fix This
return true;
}
virtual bool isEnabled() const override {
return _isEnabled;
}
virtual void setIsEnabled(bool isEnabled) override {
_isEnabled = isEnabled;
}
private:
std::shared_ptr<TextOutputNode> _parentTextNode;
bool _isEnabled = true;
std::optional<Transform2D> _xform;
std::optional<float> _opacity;
std::optional<Color> _strokeColor;
std::optional<Color> _fillColor;
std::optional<float> _tracking;
std::optional<float> _strokeWidth;
};
class TextAnimatorNode: public AnimatorNode {
public:
TextAnimatorNode(std::shared_ptr<TextAnimatorNode> const &parentNode, std::shared_ptr<TextAnimator> const &textAnimator) :
AnimatorNode(parentNode) {
std::shared_ptr<TextOutputNode> parentOutputNode;
if (parentNode) {
parentOutputNode = parentNode->_textOutputNode;
}
_textOutputNode = std::make_shared<TextOutputNode>(parentOutputNode);
_textAnimatorProperties = std::make_shared<TextAnimatorNodeProperties>(textAnimator);
}
virtual ~TextAnimatorNode() = default;
virtual std::shared_ptr<NodeOutput> outputNode() override {
return _textOutputNode;
}
virtual std::shared_ptr<KeypathSearchableNodePropertyMap> propertyMap() const override {
return _textAnimatorProperties;
}
virtual bool localUpdatesPermeateDownstream() override {
return true;
}
virtual void rebuildOutputs(float frame) override {
_textOutputNode->setXform(_textAnimatorProperties->caTransform());
_textOutputNode->setOpacity(((float)_textAnimatorProperties->opacity()) * 0.01f);
_textOutputNode->setStrokeColor(_textAnimatorProperties->strokeColor());
_textOutputNode->setFillColor(_textAnimatorProperties->fillColor());
_textOutputNode->setTracking(_textAnimatorProperties->tracking());
_textOutputNode->setStrokeWidth(_textAnimatorProperties->strokeWidth());
}
private:
std::shared_ptr<TextOutputNode> _textOutputNode;
std::shared_ptr<TextAnimatorNodeProperties> _textAnimatorProperties;
};
}
#endif /* TextAnimatorNode_hpp */
@@ -0,0 +1,238 @@
#ifndef AnimatorNode_hpp
#define AnimatorNode_hpp
#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/KeypathSearchable.hpp"
#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/NodePropertyMap.hpp"
#include "Lottie/Private/MainThread/NodeRenderSystem/Protocols/NodeOutput.hpp"
#include <memory>
#include <optional>
namespace lottie {
class LayerTransformNode;
class PathNode;
class RenderNode;
/// The Animator Node is the base node in the render system tree.
///
/// It defines a single node that has an output path and option input node.
/// At animation time the root animation node is asked to update its contents for
/// the current frame.
/// The node reaches up its chain of nodes until the first node that does not need
/// updating is found. Then each node updates its contents down the render pipeline.
/// Each node adds its local path to its input path and passes it forward.
///
/// An animator node holds a group of interpolators. These interpolators determine
/// if the node needs an update for the current frame.
///
class AnimatorNode: public KeypathSearchable {
public:
AnimatorNode(std::shared_ptr<AnimatorNode> const &parentNode) :
_parentNode(parentNode) {
}
AnimatorNode(const AnimatorNode&) = delete;
AnimatorNode& operator=(AnimatorNode&) = delete;
/// The available properties of the Node.
///
/// These properties are automatically updated each frame.
/// These properties are also settable and gettable through the dynamic
/// property system.
///
virtual std::shared_ptr<KeypathSearchableNodePropertyMap> propertyMap() const = 0;
/// The upstream input node
std::shared_ptr<AnimatorNode> parentNode() {
return _parentNode;
}
void setParentNode(std::shared_ptr<AnimatorNode> const &parentNode) {
_parentNode = parentNode;
}
/// The output of the node.
virtual std::shared_ptr<NodeOutput> outputNode() = 0;
/// Update the outputs of the node. Called if local contents were update or if outputsNeedUpdate returns true.
virtual void rebuildOutputs(float frame) = 0;
/// Setters for marking current node state.
bool isEnabled() {
return _isEnabled;
}
virtual void setIsEnabled(bool isEnabled) {
_isEnabled = isEnabled;
}
bool hasLocalUpdates() {
return _hasLocalUpdates;
}
virtual void setHasLocalUpdates(bool hasLocalUpdates) {
_hasLocalUpdates = hasLocalUpdates;
}
bool hasUpstreamUpdates() {
return _hasUpstreamUpdates;
}
virtual void setHasUpstreamUpdates(bool hasUpstreamUpdates) {
_hasUpstreamUpdates = hasUpstreamUpdates;
}
std::optional<float> lastUpdateFrame() {
return _lastUpdateFrame;
}
virtual void setLastUpdateFrame(std::optional<float> lastUpdateFrame) {
_lastUpdateFrame = lastUpdateFrame;
}
/// Marks if updates to this node affect nodes downstream.
virtual bool localUpdatesPermeateDownstream() {
/// Optional override
return true;
}
virtual bool forceUpstreamOutputUpdates() {
/// Optional
return false;
}
/// Called at the end of this nodes update cycle. Always called. Optional.
virtual bool performAdditionalLocalUpdates(float frame, bool forceLocalUpdate) {
/// Optional
return forceLocalUpdate;
}
virtual void performAdditionalOutputUpdates(float frame, bool forceOutputUpdate) {
/// Optional
}
/// The default simply returns `hasLocalUpdates`
virtual bool shouldRebuildOutputs(float frame) {
return hasLocalUpdates();
}
virtual bool updateOutputs(float frame, bool forceOutputUpdate) {
if (!isEnabled()) {
setLastUpdateFrame(frame);
if (const auto parentNodeValue = parentNode()) {
return parentNodeValue->updateOutputs(frame, forceOutputUpdate);
} else {
return false;
}
}
if (!forceOutputUpdate && lastUpdateFrame().has_value() && lastUpdateFrame().value() == frame) {
/// This node has already updated for this frame. Go ahead and return the results.
return hasUpstreamUpdates() || hasLocalUpdates();
}
/// Ask if this node should force output updates upstream.
bool forceUpstreamUpdates = forceOutputUpdate || forceUpstreamOutputUpdates();
/// Perform upstream output updates. Optionally mark upstream updates if any.
if (const auto parentNodeValue = parentNode()) {
setHasUpstreamUpdates(parentNodeValue->updateOutputs(frame, forceUpstreamUpdates) || hasUpstreamUpdates());
} else {
setHasUpstreamUpdates(hasUpstreamUpdates());
}
/// Perform additional local output updates
performAdditionalOutputUpdates(frame, forceUpstreamUpdates);
/// If there are local updates, or if updates have been force, rebuild outputs
if (forceUpstreamUpdates || shouldRebuildOutputs(frame)) {
setLastUpdateFrame(frame);
rebuildOutputs(frame);
}
return hasUpstreamUpdates() || hasLocalUpdates();
}
/// Rebuilds the content of this node, and upstream nodes if necessary.
virtual bool updateContents(float frame, bool forceLocalUpdate) {
if (!isEnabled()) {
// Disabled node, pass through.
if (const auto parentNodeValue = parentNode()) {
return parentNodeValue->updateContents(frame, forceLocalUpdate);
} else {
return false;
}
}
if (forceLocalUpdate == false && lastUpdateFrame().has_value() && lastUpdateFrame().value() == frame) {
/// This node has already updated for this frame. Go ahead and return the results.
return localUpdatesPermeateDownstream() ? hasUpstreamUpdates() || hasLocalUpdates() : hasUpstreamUpdates();
}
/// Are there local updates? If so mark the node.
setHasLocalUpdates(forceLocalUpdate ? forceLocalUpdate : propertyMap()->needsLocalUpdate(frame));
/// Were there upstream updates? If so mark the node
if (const auto parentNodeValue = parentNode()) {
setHasUpstreamUpdates(parentNodeValue->updateContents(frame, forceLocalUpdate));
} else {
setHasUpstreamUpdates(false);
}
/// Perform property updates if necessary.
if (hasLocalUpdates()) {
/// Rebuild local properties
propertyMap()->updateNodeProperties(frame);
}
/// Ask the node to perform any other updates it might have.
setHasUpstreamUpdates(performAdditionalLocalUpdates(frame, forceLocalUpdate) || hasUpstreamUpdates());
/// If the node can update nodes downstream, notify them, otherwise pass on any upstream updates downstream.
return localUpdatesPermeateDownstream() ? hasUpstreamUpdates() || hasLocalUpdates() : hasUpstreamUpdates();
}
bool updateTree(float frame, bool forceUpdates) {
if (updateContents(frame, forceUpdates)) {
return updateOutputs(frame, forceUpdates);
} else {
return false;
}
}
/// The name of the Keypath
virtual std::string keypathName() const override {
return propertyMap()->keypathName();
}
/// A list of properties belonging to the keypath.
virtual std::map<std::string, std::shared_ptr<AnyNodeProperty>> keypathProperties() const override {
return propertyMap()->keypathProperties();
}
/// Children Keypaths
virtual std::vector<std::shared_ptr<KeypathSearchable>> const &childKeypaths() const override {
return propertyMap()->childKeypaths();
}
virtual std::shared_ptr<CALayer> keypathLayer() const override {
return nullptr;
}
public:
virtual LayerTransformNode *asLayerTransformNode() {
return nullptr;
}
virtual PathNode *asPathNode() {
return nullptr;
}
virtual RenderNode *asRenderNode() {
return nullptr;
}
private:
std::shared_ptr<AnimatorNode> _parentNode;
bool _isEnabled = true;
bool _hasLocalUpdates = false;
bool _hasUpstreamUpdates = false;
std::optional<float> _lastUpdateFrame;
};
}
#endif /* AnimatorNode_hpp */
@@ -0,0 +1,24 @@
#ifndef NodeOutput_hpp
#define NodeOutput_hpp
#include <memory>
namespace lottie {
/// Defines the basic outputs of an animator node.
///
class NodeOutput {
public:
/// The parent node.
virtual std::shared_ptr<NodeOutput> parent() = 0;
/// Returns true if there are any updates upstream.
virtual bool hasOutputUpdates(float forFrame) = 0;
virtual bool isEnabled() const = 0;
virtual void setIsEnabled(bool isEnabled) = 0;
};
}
#endif /* NodeOutput_hpp */
@@ -0,0 +1,103 @@
#include "GetGradientParameters.hpp"
#include <LottieCpp/Color.h>
#include "Lottie/Public/Keyframes/Interpolatable.hpp"
#include "Lottie/Public/Keyframes/ValueInterpolators.hpp"
namespace lottie {
void getGradientParameters(int numberOfColors, GradientColorSet const &colors, std::vector<Color> &outColors, std::vector<float> &outLocations) {
std::vector<Color> alphaColors;
std::vector<float> alphaValues;
std::vector<float> alphaLocations;
std::vector<Color> gradientColors;
std::vector<float> colorLocations;
for (int i = 0; i < numberOfColors; i++) {
int ix = i * 4;
if (colors.colors.size() > ix) {
Color color(
colors.colors[ix + 1],
colors.colors[ix + 2],
colors.colors[ix + 3],
1
);
gradientColors.push_back(color);
colorLocations.push_back(colors.colors[ix]);
}
}
bool drawMask = false;
for (int i = numberOfColors * 4; i < (int)colors.colors.size(); i += 2) {
float alpha = colors.colors[i + 1];
if (alpha < 1.0) {
drawMask = true;
}
alphaLocations.push_back(colors.colors[i]);
alphaColors.push_back(Color(alpha, alpha, alpha, 1.0));
alphaValues.push_back(alpha);
}
if (drawMask) {
std::vector<float> locations;
for (size_t i = 0; i < std::min(gradientColors.size(), colorLocations.size()); i++) {
if (std::find(locations.begin(), locations.end(), colorLocations[i]) == locations.end()) {
locations.push_back(colorLocations[i]);
}
}
for (size_t i = 0; i < std::min(alphaValues.size(), alphaLocations.size()); i++) {
if (std::find(locations.begin(), locations.end(), alphaLocations[i]) == locations.end()) {
locations.push_back(alphaLocations[i]);
}
}
std::sort(locations.begin(), locations.end());
if (locations[0] != 0.0) {
locations.insert(locations.begin(), 0.0);
}
if (locations[locations.size() - 1] != 1.0) {
locations.push_back(1.0);
}
std::vector<Color> colors;
for (const auto location : locations) {
Color color = gradientColors[0];
for (size_t i = 0; i < std::min(gradientColors.size(), colorLocations.size()) - 1; i++) {
if (location >= colorLocations[i] && location <= colorLocations[i + 1]) {
float localLocation = 0.0;
if (colorLocations[i] != colorLocations[i + 1]) {
localLocation = remapFloat(location, colorLocations[i], colorLocations[i + 1], 0.0, 1.0);
}
color = ValueInterpolator<Color>::interpolate(gradientColors[i], gradientColors[i + 1], localLocation, std::nullopt, std::nullopt);
break;
}
}
float alpha = 1.0;
for (size_t i = 0; i < std::min(alphaValues.size(), alphaLocations.size()) - 1; i++) {
if (location >= alphaLocations[i] && location <= alphaLocations[i + 1]) {
float localLocation = 0.0;
if (alphaLocations[i] != alphaLocations[i + 1]) {
localLocation = remapFloat(location, alphaLocations[i], alphaLocations[i + 1], 0.0, 1.0);
}
alpha = ValueInterpolator<float>::interpolate(alphaValues[i], alphaValues[i + 1], localLocation, std::nullopt, std::nullopt);
break;
}
}
color.a = alpha;
colors.push_back(color);
}
gradientColors = colors;
colorLocations = locations;
}
outColors = gradientColors;
outLocations = colorLocations;
}
}
@@ -0,0 +1,14 @@
#ifndef ShapeRenderLayer_hpp
#define ShapeRenderLayer_hpp
#include <LottieCpp/Color.h>
#include "Lottie/Private/MainThread/NodeRenderSystem/Protocols/NodeOutput.hpp"
#include "Lottie/Public/Primitives/GradientColorSet.hpp"
namespace lottie {
void getGradientParameters(int numberOfColors, GradientColorSet const &colors, std::vector<Color> &outColors, std::vector<float> &outLocations);
}
#endif /* ShapeRenderLayer_hpp */