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,17 @@
#include "CompositionLayer.hpp"
namespace lottie {
InvertedMatteLayer::InvertedMatteLayer(std::shared_ptr<CompositionLayer> inputMatte) :
_inputMatte(inputMatte) {
setSize(inputMatte->size());
addSublayer(_inputMatte);
}
std::shared_ptr<InvertedMatteLayer> makeInvertedMatteLayer(std::shared_ptr<CompositionLayer> compositionLayer) {
auto result = std::make_shared<InvertedMatteLayer>(compositionLayer);
return result;
}
}
@@ -0,0 +1,201 @@
#ifndef CompositionLayer_hpp
#define CompositionLayer_hpp
#include <LottieCpp/Vectors.h>
#include "Lottie/Public/Primitives/CALayer.hpp"
#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/KeypathSearchable.hpp"
#include "Lottie/Private/Model/Layers/LayerModel.hpp"
#include "Lottie/Private/MainThread/LayerContainers/Utility/LayerTransformNode.hpp"
#include "Lottie/Private/MainThread/LayerContainers/CompLayers/MaskContainerLayer.hpp"
#include <memory>
namespace lottie {
class CompositionLayer;
class InvertedMatteLayer;
/// A layer that inverses the alpha output of its input layer.
class InvertedMatteLayer: public CALayer {
public:
InvertedMatteLayer(std::shared_ptr<CompositionLayer> inputMatte);
std::shared_ptr<CompositionLayer> _inputMatte;
virtual bool isInvertedMatte() const override {
return true;
}
};
std::shared_ptr<InvertedMatteLayer> makeInvertedMatteLayer(std::shared_ptr<CompositionLayer> compositionLayer);
/// The base class for a child layer of CompositionContainer
class CompositionLayer: public CALayer, public KeypathSearchable {
public:
CompositionLayer(std::shared_ptr<LayerModel> const &layer, Vector2D size) {
_contentsLayer = std::make_shared<CALayer>();
_transformNode = std::make_shared<LayerTransformNode>(layer->transform);
if (layer->masks.has_value()) {
_maskLayer = std::make_shared<MaskContainerLayer>(layer->masks.value());
} else {
_maskLayer = nullptr;
}
_matteType = layer->matte;
_inFrame = layer->inFrame;
_outFrame = layer->outFrame;
_timeStretch = layer->timeStretch();
_startFrame = layer->startTime;
if (layer->name.has_value()) {
_keypathName = layer->name.value();
} else {
_keypathName = "Layer";
}
_childKeypaths.push_back(_transformNode->transformProperties());
_contentsLayer->setSize(size);
if (layer->blendMode.has_value() && layer->blendMode.value() != BlendMode::Normal) {
setCompositingFilter(layer->blendMode);
}
addSublayer(_contentsLayer);
if (_maskLayer) {
_contentsLayer->setMask(_maskLayer);
}
}
virtual std::string keypathName() const override {
return _keypathName;
}
virtual std::map<std::string, std::shared_ptr<AnyNodeProperty>> keypathProperties() const override {
return {};
}
virtual std::shared_ptr<CALayer> keypathLayer() const override {
return _contentsLayer;
}
void displayWithFrame(float frame, bool forceUpdates, BezierPathsBoundingBoxContext &boundingBoxContext) {
bool layerVisible = isInRangeOrEqual(frame, _inFrame, _outFrame);
if (_transformNode->updateTree(frame, forceUpdates) || _contentsLayer->isHidden() != !layerVisible) {
_contentsLayer->setTransform(_transformNode->globalTransform());
_contentsLayer->setOpacity(_transformNode->opacity());
_contentsLayer->setIsHidden(!layerVisible);
updateContentsLayerParameters();
}
/// Only update contents if current time is within the layers time bounds.
if (layerVisible) {
displayContentsWithFrame(frame, forceUpdates, boundingBoxContext);
if (_maskLayer) {
_maskLayer->updateWithFrame(frame, forceUpdates);
}
}
}
virtual void updateContentsLayerParameters() {
}
virtual void displayContentsWithFrame(float frame, bool forceUpdates, BezierPathsBoundingBoxContext &boundingBoxContext) {
/// To be overridden by subclass
}
virtual std::vector<std::shared_ptr<KeypathSearchable>> const &childKeypaths() const override {
return _childKeypaths;
}
std::shared_ptr<CompositionLayer> _matteLayer;
void setMatteLayer(std::shared_ptr<CompositionLayer> matteLayer) {
_matteLayer = matteLayer;
if (matteLayer) {
if (_matteType.has_value() && _matteType.value() == MatteType::Invert) {
setMask(makeInvertedMatteLayer(matteLayer));
} else {
setMask(matteLayer);
}
} else {
setMask(nullptr);
}
}
std::shared_ptr<CALayer> const &contentsLayer() const {
return _contentsLayer;
}
std::shared_ptr<MaskContainerLayer> const &maskLayer() const {
return _maskLayer;
}
void setMaskLayer(std::shared_ptr<MaskContainerLayer> const &maskLayer) {
_maskLayer = maskLayer;
}
std::optional<MatteType> const &matteType() const {
return _matteType;
}
float inFrame() const {
return _inFrame;
}
float outFrame() const {
return _outFrame;
}
float startFrame() const {
return _startFrame;
}
float timeStretch() const {
return _timeStretch;
}
virtual std::shared_ptr<RenderTreeNode> renderTreeNode(BezierPathsBoundingBoxContext &boundingBoxContext) {
return nullptr;
}
public:
std::shared_ptr<LayerTransformNode> const transformNode() const {
return _transformNode;
}
protected:
std::shared_ptr<CALayer> _contentsLayer;
std::optional<MatteType> _matteType;
private:
std::shared_ptr<LayerTransformNode> _transformNode;
std::shared_ptr<MaskContainerLayer> _maskLayer;
float _inFrame = 0.0;
float _outFrame = 0.0;
float _startFrame = 0.0;
float _timeStretch = 0.0;
// MARK: Keypath Searchable
std::string _keypathName;
public:
virtual bool isImageCompositionLayer() const {
return false;
}
virtual bool isTextCompositionLayer() const {
return false;
}
protected:
std::vector<std::shared_ptr<KeypathSearchable>> _childKeypaths;
};
}
#endif /* CompositionLayer_hpp */
@@ -0,0 +1,5 @@
#include "ImageCompositionLayer.hpp"
namespace lottie {
}
@@ -0,0 +1,43 @@
#ifndef ImageCompositionLayer_hpp
#define ImageCompositionLayer_hpp
#include "Lottie/Private/MainThread/LayerContainers/CompLayers/CompositionLayer.hpp"
#include "Lottie/Private/Model/Assets/ImageAsset.hpp"
#include "Lottie/Private/Model/Layers/ImageLayerModel.hpp"
namespace lottie {
class ImageCompositionLayer: public CompositionLayer {
public:
ImageCompositionLayer(std::shared_ptr<ImageLayerModel> const &imageLayer, Vector2D const &size) :
CompositionLayer(imageLayer, size) {
_imageReferenceID = imageLayer->referenceID;
contentsLayer()->setMasksToBounds(true);
}
std::shared_ptr<Image> image() {
return _image;
}
void setImage(std::shared_ptr<Image> image) {
_image = image;
//contentsLayer()->setContents(image);
}
std::string const &imageReferenceID() {
return _imageReferenceID;
}
public:
virtual bool isImageCompositionLayer() const override {
return true;
}
private:
std::string _imageReferenceID;
std::shared_ptr<Image> _image;
};
}
#endif /* ImageCompositionLayer_hpp */
@@ -0,0 +1,5 @@
#include "MaskContainerLayer.hpp"
namespace lottie {
}
@@ -0,0 +1,178 @@
#ifndef MaskContainerLayer_hpp
#define MaskContainerLayer_hpp
#include "Lottie/Private/Model/Objects/Mask.hpp"
#include "Lottie/Public/Primitives/CALayer.hpp"
#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/NodePropertyMap.hpp"
#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/NodeProperty.hpp"
#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/KeyframeInterpolator.hpp"
namespace lottie {
inline MaskMode usableMaskMode(MaskMode mode) {
switch (mode) {
case MaskMode::Add:
return MaskMode::Add;
case MaskMode::Subtract:
return MaskMode::Subtract;
case MaskMode::Intersect:
return MaskMode::Intersect;
case MaskMode::Lighten:
return MaskMode::Add;
case MaskMode::Darken:
return MaskMode::Darken;
case MaskMode::Difference:
return MaskMode::Intersect;
case MaskMode::None:
return MaskMode::None;
}
}
class MaskNodeProperties: public NodePropertyMap {
public:
MaskNodeProperties(std::shared_ptr<Mask> const &mask) :
_mode(mask->mode()),
_inverted(mask->inverted) {
_opacity = std::make_shared<NodeProperty<Vector1D>>(std::make_shared<KeyframeInterpolator<Vector1D>>(mask->opacity->keyframes));
_shape = std::make_shared<NodeProperty<BezierPath>>(std::make_shared<KeyframeInterpolator<BezierPath>>(mask->shape.keyframes));
_expansion = std::make_shared<NodeProperty<Vector1D>>(std::make_shared<KeyframeInterpolator<Vector1D>>(mask->expansion->keyframes));
_propertyMap.insert(std::make_pair("Opacity", _opacity));
_propertyMap.insert(std::make_pair("Shape", _shape));
_propertyMap.insert(std::make_pair("Expansion", _expansion));
for (const auto &it : _propertyMap) {
_properties.push_back(it.second);
}
}
virtual std::vector<std::shared_ptr<AnyNodeProperty>> &properties() override {
return _properties;
}
virtual std::vector<std::shared_ptr<KeypathSearchable>> const &childKeypaths() const override {
return _childKeypaths;
}
std::shared_ptr<NodeProperty<Vector1D>> const &opacity() const {
return _opacity;
}
std::shared_ptr<NodeProperty<BezierPath>> const &shape() const {
return _shape;
}
std::shared_ptr<NodeProperty<Vector1D>> const &expansion() const {
return _expansion;
}
MaskMode mode() const {
return _mode;
}
bool inverted() const {
return _inverted;
}
private:
std::map<std::string, std::shared_ptr<AnyNodeProperty>> _propertyMap;
std::vector<std::shared_ptr<KeypathSearchable>> _childKeypaths;
std::vector<std::shared_ptr<AnyNodeProperty>> _properties;
MaskMode _mode = MaskMode::Add;
bool _inverted = false;
std::shared_ptr<NodeProperty<Vector1D>> _opacity;
std::shared_ptr<NodeProperty<BezierPath>> _shape;
std::shared_ptr<NodeProperty<Vector1D>> _expansion;
};
class MaskLayer: public CALayer {
public:
MaskLayer(std::shared_ptr<Mask> const &mask) :
_properties(mask) {
/*_maskLayer = std::make_shared<CAShapeLayer>();
addSublayer(_maskLayer);
if (mask->mode() == MaskMode::Add) {
_maskLayer->setFillColor(Color(1.0, 0.0, 0.0, 1.0));
} else {
_maskLayer->setFillColor(Color(0.0, 1.0, 0.0, 1.0));
}
_maskLayer->setFillRule(FillRule::EvenOdd);*/
}
virtual ~MaskLayer() = default;
void updateWithFrame(float frame, bool forceUpdates) {
if (_properties.opacity()->needsUpdate(frame) || forceUpdates) {
_properties.opacity()->update(frame);
setOpacity(_properties.opacity()->value().value);
}
if (_properties.shape()->needsUpdate(frame) || forceUpdates) {
_properties.shape()->update(frame);
_properties.expansion()->update(frame);
/*auto path = _properties.shape()->value().cgPath();
auto usableMode = usableMaskMode(_properties.mode());
if ((usableMode == MaskMode::Subtract && !_properties.inverted()) ||
(usableMode == MaskMode::Add && _properties.inverted())) {
/// Add a bounds rect to invert the mask
auto newPath = CGPath::makePath();
newPath->addRect(CGRect::veryLarge());
newPath->addPath(path);
path = std::static_pointer_cast<CGPath>(newPath);
}
_maskLayer->setPath(path);*/
}
}
private:
MaskNodeProperties _properties;
//std::shared_ptr<CAShapeLayer> _maskLayer;
};
class MaskContainerLayer: public CALayer {
public:
MaskContainerLayer(std::vector<std::shared_ptr<Mask>> const &masks) {
auto containerLayer = std::make_shared<CALayer>();
bool firstObject = true;
for (const auto &mask : masks) {
auto maskLayer = std::make_shared<MaskLayer>(mask);
_maskLayers.push_back(maskLayer);
auto usableMode = usableMaskMode(mask->mode());
if (usableMode == MaskMode::None) {
continue;
} else if (usableMode == MaskMode::Add || firstObject) {
firstObject = false;
containerLayer->addSublayer(maskLayer);
} else {
containerLayer->setMask(maskLayer);
auto newContainer = std::make_shared<CALayer>();
newContainer->addSublayer(containerLayer);
containerLayer = newContainer;
}
}
addSublayer(containerLayer);
}
// MARK: Internal
void updateWithFrame(float frame, bool forceUpdates) {
for (const auto &maskLayer : _maskLayers) {
maskLayer->updateWithFrame(frame, forceUpdates);
}
}
private:
std::vector<std::shared_ptr<MaskLayer>> _maskLayers;
};
}
#endif /* MaskContainerLayer_hpp */
@@ -0,0 +1,5 @@
#include "NullCompositionLayer.hpp"
namespace lottie {
}
@@ -0,0 +1,17 @@
#ifndef NullCompositionLayer_hpp
#define NullCompositionLayer_hpp
#include "Lottie/Private/MainThread/LayerContainers/CompLayers/CompositionLayer.hpp"
namespace lottie {
class NullCompositionLayer: public CompositionLayer {
public:
NullCompositionLayer(std::shared_ptr<LayerModel> const &layer) :
CompositionLayer(layer, Vector2D::Zero()) {
}
};
}
#endif /* NullCompositionLayer_hpp */
@@ -0,0 +1,5 @@
#include "PreCompositionLayer.hpp"
namespace lottie {
}
@@ -0,0 +1,202 @@
#ifndef PreCompositionLayer_hpp
#define PreCompositionLayer_hpp
#include "Lottie/Private/MainThread/LayerContainers/CompLayers/CompositionLayer.hpp"
#include "Lottie/Private/Model/Layers/PreCompLayerModel.hpp"
#include "Lottie/Private/Model/Assets/PrecompAsset.hpp"
#include "Lottie/Private/MainThread/LayerContainers/Utility/LayerImageProvider.hpp"
#include "Lottie/Public/TextProvider/AnimationTextProvider.hpp"
#include "Lottie/Public/FontProvider/AnimationFontProvider.hpp"
#include "Lottie/Private/Model/Assets/AssetLibrary.hpp"
#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/NodeProperty.hpp"
#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/KeyframeInterpolator.hpp"
#include "Lottie/Private/MainThread/LayerContainers/Utility/CompositionLayersInitializer.hpp"
namespace lottie {
class PreCompositionLayer: public CompositionLayer {
public:
PreCompositionLayer(
std::shared_ptr<PreCompLayerModel> const &precomp,
PrecompAsset const &asset,
std::shared_ptr<LayerImageProvider> const &layerImageProvider,
std::shared_ptr<AnimationTextProvider> const &textProvider,
std::shared_ptr<AnimationFontProvider> const &fontProvider,
std::shared_ptr<AssetLibrary> const &assetLibrary,
float frameRate
) : CompositionLayer(precomp, Vector2D(precomp->width, precomp->height)) {
if (precomp->timeRemapping) {
_remappingNode = std::make_shared<NodeProperty<Vector1D>>(std::make_shared<KeyframeInterpolator<Vector1D>>(precomp->timeRemapping->keyframes));
}
_frameRate = frameRate;
setSize(Vector2D(precomp->width, precomp->height));
contentsLayer()->setMasksToBounds(true);
contentsLayer()->setSize(size());
auto layers = initializeCompositionLayers(
asset.layers,
assetLibrary,
layerImageProvider,
textProvider,
fontProvider,
frameRate
);
std::vector<std::shared_ptr<ImageCompositionLayer>> imageLayers;
std::shared_ptr<CompositionLayer> mattedLayer;
for (auto layerIt = layers.rbegin(); layerIt != layers.rend(); layerIt++) {
std::shared_ptr<CompositionLayer> layer = *layerIt;
layer->setSize(size());
_animationLayers.push_back(layer);
if (layer->isImageCompositionLayer()) {
imageLayers.push_back(std::static_pointer_cast<ImageCompositionLayer>(layer));
}
if (mattedLayer) {
/// The previous layer requires this layer to be its matte
mattedLayer->setMatteLayer(layer);
mattedLayer = nullptr;
continue;
}
if (layer->matteType().has_value() && (layer->matteType().value() == MatteType::Add || layer->matteType().value() == MatteType::Invert)) {
/// We have a layer that requires a matte.
mattedLayer = layer;
}
contentsLayer()->addSublayer(layer);
}
for (const auto &layer : layers) {
_childKeypaths.push_back(layer);
}
layerImageProvider->addImageLayers(imageLayers);
}
virtual std::map<std::string, std::shared_ptr<AnyNodeProperty>> keypathProperties() const override {
if (!_remappingNode) {
return {};
}
std::map<std::string, std::shared_ptr<AnyNodeProperty>> result;
result.insert(std::make_pair("Time Remap", _remappingNode));
return result;
}
virtual void displayContentsWithFrame(float frame, bool forceUpdates, BezierPathsBoundingBoxContext &boundingBoxContext) override {
float localFrame = 0.0;
if (_remappingNode) {
_remappingNode->update(frame);
localFrame = _remappingNode->value().value * _frameRate;
} else {
localFrame = (frame - startFrame()) / timeStretch();
}
for (const auto &animationLayer : _animationLayers) {
animationLayer->displayWithFrame(localFrame, forceUpdates, boundingBoxContext);
}
}
virtual std::shared_ptr<RenderTreeNode> renderTreeNode(BezierPathsBoundingBoxContext &boundingBoxContext) override {
if (!_renderTreeNode) {
std::vector<std::shared_ptr<RenderTreeNode>> renderTreeSubnodes;
for (const auto &animationLayer : _animationLayers) {
bool found = false;
for (const auto &sublayer : contentsLayer()->sublayers()) {
if (animationLayer == sublayer) {
found = true;
break;
}
}
if (found) {
auto node = animationLayer->renderTreeNode(boundingBoxContext);
if (node) {
renderTreeSubnodes.push_back(node);
}
}
}
std::vector<std::shared_ptr<RenderTreeNode>> renderTreeValue;
auto renderTreeContentItem = std::make_shared<RenderTreeNode>(
Vector2D(0.0, 0.0),
Transform2D::identity(),
1.0,
false,
false,
renderTreeSubnodes,
nullptr,
false
);
if (renderTreeContentItem) {
renderTreeValue.push_back(renderTreeContentItem);
}
_contentsTreeNode = std::make_shared<RenderTreeNode>(
Vector2D(0.0, 0.0),
Transform2D::identity(),
1.0,
false,
false,
renderTreeValue,
nullptr,
false
);
std::vector<std::shared_ptr<RenderTreeNode>> subnodes;
subnodes.push_back(_contentsTreeNode);
std::shared_ptr<RenderTreeNode> maskNode;
bool invertMask = false;
if (_matteLayer) {
maskNode = _matteLayer->renderTreeNode(boundingBoxContext);
if (maskNode && _matteType.has_value() && _matteType.value() == MatteType::Invert) {
invertMask = true;
}
}
_renderTreeNode = std::make_shared<RenderTreeNode>(
Vector2D(0.0, 0.0),
Transform2D::identity(),
1.0,
false,
false,
subnodes,
maskNode,
invertMask
);
}
_contentsTreeNode->_size = _contentsLayer->size();
_contentsTreeNode->_masksToBounds = _contentsLayer->masksToBounds();
_renderTreeNode->_size = size();
_renderTreeNode->_transform = transform();
_renderTreeNode->_alpha = opacity();
_renderTreeNode->_masksToBounds = masksToBounds();
_renderTreeNode->_isHidden = isHidden();
return _renderTreeNode;
}
virtual void updateContentsLayerParameters() override {
_contentsTreeNode->_transform = _contentsLayer->transform();
_contentsTreeNode->_alpha = _contentsLayer->opacity();
_contentsTreeNode->_isHidden = _contentsLayer->isHidden();
}
private:
float _frameRate = 0.0;
std::shared_ptr<NodeProperty<Vector1D>> _remappingNode;
std::vector<std::shared_ptr<CompositionLayer>> _animationLayers;
std::shared_ptr<RenderTreeNode> _renderTreeNode;
std::shared_ptr<RenderTreeNode> _contentsTreeNode;
};
}
#endif /* PreCompositionLayer_hpp */
@@ -0,0 +1,36 @@
#ifndef ShapeCompositionLayer_hpp
#define ShapeCompositionLayer_hpp
#include "Lottie/Private/MainThread/LayerContainers/CompLayers/CompositionLayer.hpp"
#include "Lottie/Private/Model/Layers/ShapeLayerModel.hpp"
#include "Lottie/Private/Model/Layers/SolidLayerModel.hpp"
#include "Lottie/Private/MainThread/NodeRenderSystem/Protocols/AnimatorNode.hpp"
namespace lottie {
class ShapeLayerPresentationTree;
/// A CompositionLayer responsible for initializing and rendering shapes
class ShapeCompositionLayer: public CompositionLayer {
public:
ShapeCompositionLayer(std::shared_ptr<ShapeLayerModel> const &shapeLayer);
ShapeCompositionLayer(std::shared_ptr<SolidLayerModel> const &solidLayer);
virtual void displayContentsWithFrame(float frame, bool forceUpdates, BezierPathsBoundingBoxContext &boundingBoxContext) override;
virtual std::shared_ptr<RenderTreeNode> renderTreeNode(BezierPathsBoundingBoxContext &boundingBoxContext) override;
void initializeContentsLayerParameters();
virtual void updateContentsLayerParameters() override;
private:
std::shared_ptr<ShapeLayerPresentationTree> _contentTree;
AnimationFrameTime _frameTime = 0.0;
bool _frameTimeInitialized = false;
std::shared_ptr<RenderTreeNode> _renderTreeNode;
std::shared_ptr<RenderTreeNode> _contentRenderTreeNode;
};
}
#endif /* ShapeCompositionLayer_hpp */
@@ -0,0 +1,487 @@
#include "BezierPathUtils.hpp"
namespace lottie {
BezierPath makeEllipseBezierPath(
Vector2D const &size,
Vector2D const &center,
PathDirection direction
) {
const float ControlPointConstant = 0.55228;
Vector2D half = size * 0.5;
if (direction == PathDirection::CounterClockwise) {
half.x = half.x * -1.0;
}
Vector2D q1(center.x, center.y - half.y);
Vector2D q2(center.x + half.x, center.y);
Vector2D q3(center.x, center.y + half.y);
Vector2D q4(center.x - half.x, center.y);
Vector2D cp = half * ControlPointConstant;
BezierPath path(CurveVertex::relative(
q1,
Vector2D(-cp.x, 0),
Vector2D(cp.x, 0)));
path.addVertex(CurveVertex::relative(
q2,
Vector2D(0, -cp.y),
Vector2D(0, cp.y)));
path.addVertex(CurveVertex::relative(
q3,
Vector2D(cp.x, 0),
Vector2D(-cp.x, 0)));
path.addVertex(CurveVertex::relative(
q4,
Vector2D(0, cp.y),
Vector2D(0, -cp.y)));
path.addVertex(CurveVertex::relative(
q1,
Vector2D(-cp.x, 0),
Vector2D(cp.x, 0)));
path.close();
return path;
}
BezierPath makeRectangleBezierPath(
Vector2D const &position,
Vector2D const &inputSize,
float cornerRadius,
PathDirection direction
) {
const float ControlPointConstant = 0.55228;
Vector2D size = inputSize * 0.5;
float radius = std::min(std::min(cornerRadius, (float)size.x), (float)size.y);
BezierPath bezierPath;
std::vector<CurveVertex> points;
if (radius <= 0.0) {
/// No Corners
points = {
/// Lead In
CurveVertex::relative(
Vector2D(size.x, -size.y),
Vector2D::Zero(),
Vector2D::Zero())
.translated(position),
/// Corner 1
CurveVertex::relative(
Vector2D(size.x, size.y),
Vector2D::Zero(),
Vector2D::Zero())
.translated(position),
/// Corner 2
CurveVertex::relative(
Vector2D(-size.x, size.y),
Vector2D::Zero(),
Vector2D::Zero())
.translated(position),
/// Corner 3
CurveVertex::relative(
Vector2D(-size.x, -size.y),
Vector2D::Zero(),
Vector2D::Zero())
.translated(position),
/// Corner 4
CurveVertex::relative(
Vector2D(size.x, -size.y),
Vector2D::Zero(),
Vector2D::Zero())
.translated(position)
};
} else {
float controlPoint = radius * ControlPointConstant;
points = {
/// Lead In
CurveVertex::absolute(
Vector2D(radius, 0),
Vector2D(radius, 0),
Vector2D(radius, 0))
.translated(Vector2D(-radius, radius))
.translated(Vector2D(size.x, -size.y))
.translated(position),
/// Corner 1
CurveVertex::absolute(
Vector2D(radius, 0), // Point
Vector2D(radius, 0), // In tangent
Vector2D(radius, controlPoint))
.translated(Vector2D(-radius, -radius))
.translated(Vector2D(size.x, size.y))
.translated(position),
CurveVertex::absolute(
Vector2D(0, radius), // Point
Vector2D(controlPoint, radius), // In tangent
Vector2D(0, radius)) // Out Tangent
.translated(Vector2D(-radius, -radius))
.translated(Vector2D(size.x, size.y))
.translated(position),
/// Corner 2
CurveVertex::absolute(
Vector2D(0, radius), // Point
Vector2D(0, radius), // In tangent
Vector2D(-controlPoint, radius))// Out tangent
.translated(Vector2D(radius, -radius))
.translated(Vector2D(-size.x, size.y))
.translated(position),
CurveVertex::absolute(
Vector2D(-radius, 0), // Point
Vector2D(-radius, controlPoint), // In tangent
Vector2D(-radius, 0)) // Out tangent
.translated(Vector2D(radius, -radius))
.translated(Vector2D(-size.x, size.y))
.translated(position),
/// Corner 3
CurveVertex::absolute(
Vector2D(-radius, 0), // Point
Vector2D(-radius, 0), // In tangent
Vector2D(-radius, -controlPoint)) // Out tangent
.translated(Vector2D(radius, radius))
.translated(Vector2D(-size.x, -size.y))
.translated(position),
CurveVertex::absolute(
Vector2D(0, -radius), // Point
Vector2D(-controlPoint, -radius), // In tangent
Vector2D(0, -radius)) // Out tangent
.translated(Vector2D(radius, radius))
.translated(Vector2D(-size.x, -size.y))
.translated(position),
/// Corner 4
CurveVertex::absolute(
Vector2D(0, -radius), // Point
Vector2D(0, -radius), // In tangent
Vector2D(controlPoint, -radius)) // Out tangent
.translated(Vector2D(-radius, radius))
.translated(Vector2D(size.x, -size.y))
.translated(position),
CurveVertex::absolute(
Vector2D(radius, 0), // Point
Vector2D(radius, -controlPoint), // In tangent
Vector2D(radius, 0)) // Out tangent
.translated(Vector2D(-radius, radius))
.translated(Vector2D(size.x, -size.y))
.translated(position)
};
}
bool reversed = direction == PathDirection::CounterClockwise;
if (reversed) {
for (auto vertexIt = points.rbegin(); vertexIt != points.rend(); vertexIt++) {
bezierPath.addVertex((*vertexIt).reversed());
}
} else {
for (auto vertexIt = points.begin(); vertexIt != points.end(); vertexIt++) {
bezierPath.addVertex(*vertexIt);
}
}
bezierPath.close();
return bezierPath;
}
/// Magic number needed for building path data
static constexpr float StarNodePolystarConstant = 0.47829;
BezierPath makeStarBezierPath(
Vector2D const &position,
float outerRadius,
float innerRadius,
float inputOuterRoundedness,
float inputInnerRoundedness,
float numberOfPoints,
float rotation,
PathDirection direction
) {
float currentAngle = degreesToRadians(rotation - 90.0);
float anglePerPoint = (2.0 * M_PI) / numberOfPoints;
float halfAnglePerPoint = anglePerPoint / 2.0;
float partialPointAmount = numberOfPoints - floor(numberOfPoints);
float outerRoundedness = inputOuterRoundedness * 0.01;
float innerRoundedness = inputInnerRoundedness * 0.01;
Vector2D point = Vector2D::Zero();
float partialPointRadius = 0.0;
if (partialPointAmount != 0.0) {
currentAngle += halfAnglePerPoint * (1 - partialPointAmount);
partialPointRadius = innerRadius + partialPointAmount * (outerRadius - innerRadius);
point.x = (partialPointRadius * cos(currentAngle));
point.y = (partialPointRadius * sin(currentAngle));
currentAngle += anglePerPoint * partialPointAmount / 2;
} else {
point.x = (outerRadius * cos(currentAngle));
point.y = (outerRadius * sin(currentAngle));
currentAngle += halfAnglePerPoint;
}
std::vector<CurveVertex> vertices;
vertices.push_back(CurveVertex::relative(point + position, Vector2D::Zero(), Vector2D::Zero()));
Vector2D previousPoint = point;
bool longSegment = false;
int numPoints = (int)(ceil(numberOfPoints) * 2.0);
for (int i = 0; i < numPoints; i++) {
float radius = longSegment ? outerRadius : innerRadius;
float dTheta = halfAnglePerPoint;
if (partialPointRadius != 0.0 && i == numPoints - 2) {
dTheta = anglePerPoint * partialPointAmount / 2;
}
if (partialPointRadius != 0.0 && i == numPoints - 1) {
radius = partialPointRadius;
}
previousPoint = point;
point.x = (radius * cos(currentAngle));
point.y = (radius * sin(currentAngle));
if (innerRoundedness == 0.0 && outerRoundedness == 0.0) {
vertices.push_back(CurveVertex::relative(point + position, Vector2D::Zero(), Vector2D::Zero()));
} else {
float cp1Theta = (atan2(previousPoint.y, previousPoint.x) - M_PI / 2.0);
float cp1Dx = cos(cp1Theta);
float cp1Dy = sin(cp1Theta);
float cp2Theta = (atan2(point.y, point.x) - M_PI / 2.0);
float cp2Dx = cos(cp2Theta);
float cp2Dy = sin(cp2Theta);
float cp1Roundedness = longSegment ? innerRoundedness : outerRoundedness;
float cp2Roundedness = longSegment ? outerRoundedness : innerRoundedness;
float cp1Radius = longSegment ? innerRadius : outerRadius;
float cp2Radius = longSegment ? outerRadius : innerRadius;
Vector2D cp1(
cp1Radius * cp1Roundedness * StarNodePolystarConstant * cp1Dx,
cp1Radius * cp1Roundedness * StarNodePolystarConstant * cp1Dy
);
Vector2D cp2(
cp2Radius * cp2Roundedness * StarNodePolystarConstant * cp2Dx,
cp2Radius * cp2Roundedness * StarNodePolystarConstant * cp2Dy
);
if (partialPointAmount != 0.0) {
if (i == 0) {
cp1 = cp1 * partialPointAmount;
} else if (i == numPoints - 1) {
cp2 = cp2 * partialPointAmount;
}
}
auto previousVertex = vertices[vertices.size() - 1];
vertices[vertices.size() - 1] = CurveVertex::absolute(
previousVertex.point,
previousVertex.inTangent,
previousVertex.point - cp1
);
vertices.push_back(CurveVertex::relative(point + position, cp2, Vector2D::Zero()));
}
currentAngle += dTheta;
longSegment = !longSegment;
}
bool reverse = direction == PathDirection::CounterClockwise;
BezierPath path;
if (reverse) {
for (auto vertexIt = vertices.rbegin(); vertexIt != vertices.rend(); vertexIt++) {
path.addVertex((*vertexIt).reversed());
}
} else {
for (auto vertexIt = vertices.begin(); vertexIt != vertices.end(); vertexIt++) {
path.addVertex(*vertexIt);
}
}
path.close();
return path;
}
CompoundBezierPath trimCompoundPath(CompoundBezierPath sourcePath, float start, float end, float offset, TrimType type) {
/// No need to trim, it's a full path
if (start == 0.0 && end == 1.0) {
return sourcePath;
}
/// All paths are empty.
if (start == end) {
return CompoundBezierPath();
}
if (type == TrimType::Simultaneously) {
CompoundBezierPath result;
for (BezierPath &path : sourcePath.paths) {
CompoundBezierPath tempPath;
tempPath.appendPath(path);
auto subPaths = tempPath.trim(start, end, offset);
for (const auto &subPath : subPaths->paths) {
result.appendPath(subPath);
}
}
return result;
}
/// Individual path trimming.
/// Brace yourself for the below code.
/// Normalize lengths with offset.
float startPosition = fmod(start + offset, 1.0);
float endPosition = fmod(end + offset, 1.0);
if (startPosition < 0.0) {
startPosition = 1.0 + startPosition;
}
if (endPosition < 0.0) {
endPosition = 1.0 + endPosition;
}
if (startPosition == 1.0) {
startPosition = 0.0;
}
if (endPosition == 0.0) {
endPosition = 1.0;
}
/// First get the total length of all paths.
float totalLength = 0.0;
for (auto &upstreamPath : sourcePath.paths) {
totalLength += upstreamPath.length();
}
/// Now determine the start and end cut lengths
float startLength = startPosition * totalLength;
float endLength = endPosition * totalLength;
float pathStart = 0.0;
CompoundBezierPath result;
/// Now loop through all path containers
for (auto &pathContainer : sourcePath.paths) {
auto pathEnd = pathStart + pathContainer.length();
if (!isInRange(startLength, pathStart, pathEnd) &&
isInRange(endLength, pathStart, pathEnd)) {
// pathStart|=======E----------------------|pathEnd
// Cut path components, removing after end.
float pathCutLength = endLength - pathStart;
float subpathStart = 0.0;
float subpathEnd = subpathStart + pathContainer.length();
if (pathCutLength < subpathEnd) {
/// This is the subpath that needs to be cut.
float cutLength = pathCutLength - subpathStart;
CompoundBezierPath tempPath;
tempPath.appendPath(pathContainer);
auto newPaths = tempPath.trim(0, cutLength / pathContainer.length(), 0);
for (const auto &newPath : newPaths->paths) {
result.appendPath(newPath);
}
} else {
/// Add to container and move on
result.appendPath(pathContainer);
}
/*if (pathCutLength == subpathEnd) {
/// Right on the end. The next subpath is not included. Break.
break;
}
subpathStart = subpathEnd;*/
} else if (!isInRange(endLength, pathStart, pathEnd) &&
isInRange(startLength, pathStart, pathEnd)) {
// pathStart|-------S======================|pathEnd
//
// Cut path components, removing before beginning.
float pathCutLength = startLength - pathStart;
// Clear paths from container
float subpathStart = 0.0;
float subpathEnd = subpathStart + pathContainer.length();
if (subpathStart < pathCutLength && pathCutLength < subpathEnd) {
/// This is the subpath that needs to be cut.
float cutLength = pathCutLength - subpathStart;
CompoundBezierPath tempPath;
tempPath.appendPath(pathContainer);
auto newPaths = tempPath.trim(cutLength / pathContainer.length(), 1, 0);
for (const auto &newPath : newPaths->paths) {
result.appendPath(newPath);
}
} else if (pathCutLength <= subpathStart) {
result.appendPath(pathContainer);
}
//subpathStart = subpathEnd;
} else if (isInRange(endLength, pathStart, pathEnd) &&
isInRange(startLength, pathStart, pathEnd)) {
// pathStart|-------S============E---------|endLength
// pathStart|=====E----------------S=======|endLength
// trim from path beginning to endLength.
// Cut path components, removing before beginnings.
float startCutLength = startLength - pathStart;
float endCutLength = endLength - pathStart;
float subpathStart = 0.0;
float subpathEnd = subpathStart + pathContainer.length();
if (!isInRange(startCutLength, subpathStart, subpathEnd) &&
!isInRange(endCutLength, subpathStart, subpathEnd))
{
// The whole path is included. Add
// S|==============================|E
result.appendPath(pathContainer);
} else if (isInRange(startCutLength, subpathStart, subpathEnd) &&
!isInRange(endCutLength, subpathStart, subpathEnd)) {
/// The start of the path needs to be trimmed
// |-------S======================|E
float cutLength = startCutLength - subpathStart;
CompoundBezierPath tempPath;
tempPath.appendPath(pathContainer);
auto newPaths = tempPath.trim(cutLength / pathContainer.length(), 1, 0);
for (const auto &newPath : newPaths->paths) {
result.appendPath(newPath);
}
} else if (!isInRange(startCutLength, subpathStart, subpathEnd) &&
isInRange(endCutLength, subpathStart, subpathEnd)) {
// S|=======E----------------------|
float cutLength = endCutLength - subpathStart;
CompoundBezierPath tempPath;
tempPath.appendPath(pathContainer);
auto newPaths = tempPath.trim(0, cutLength / pathContainer.length(), 0);
for (const auto &newPath : newPaths->paths) {
result.appendPath(newPath);
}
} else if (isInRange(startCutLength, subpathStart, subpathEnd) &&
isInRange(endCutLength, subpathStart, subpathEnd)) {
// |-------S============E---------|
float cutFromLength = startCutLength - subpathStart;
float cutToLength = endCutLength - subpathStart;
CompoundBezierPath tempPath;
tempPath.appendPath(pathContainer);
auto newPaths = tempPath.trim(
cutFromLength / pathContainer.length(),
cutToLength / pathContainer.length(),
0
);
for (const auto &newPath : newPaths->paths) {
result.appendPath(newPath);
}
}
} else if ((endLength <= pathStart && pathEnd <= startLength) ||
(startLength <= pathStart && endLength <= pathStart) ||
(pathEnd <= startLength && pathEnd <= endLength)) {
/// The Path needs to be cleared
} else {
result.appendPath(pathContainer);
}
pathStart = pathEnd;
}
return result;
}
}
@@ -0,0 +1,39 @@
#ifndef BezierPaths_h
#define BezierPaths_h
#include "Lottie/Private/Model/ShapeItems/Ellipse.hpp"
#include <LottieCpp/BezierPath.h>
#include "Lottie/Private/Utility/Primitives/CompoundBezierPath.hpp"
#include "Lottie/Private/Model/ShapeItems/Trim.hpp"
namespace lottie {
BezierPath makeEllipseBezierPath(
Vector2D const &size,
Vector2D const &center,
PathDirection direction
);
BezierPath makeRectangleBezierPath(
Vector2D const &position,
Vector2D const &inputSize,
float cornerRadius,
PathDirection direction
);
BezierPath makeStarBezierPath(
Vector2D const &position,
float outerRadius,
float innerRadius,
float inputOuterRoundedness,
float inputInnerRoundedness,
float numberOfPoints,
float rotation,
PathDirection direction
);
CompoundBezierPath trimCompoundPath(CompoundBezierPath sourcePath, float start, float end, float offset, TrimType type);
}
#endif /* BezierPaths_h */
@@ -0,0 +1,5 @@
#include "TextCompositionLayer.hpp"
namespace lottie {
}
@@ -0,0 +1,81 @@
#ifndef TextCompositionLayer_hpp
#define TextCompositionLayer_hpp
#include "Lottie/Private/MainThread/LayerContainers/CompLayers/CompositionLayer.hpp"
#include "Lottie/Private/Model/Layers/TextLayerModel.hpp"
#include "Lottie/Public/TextProvider/AnimationTextProvider.hpp"
#include "Lottie/Public/FontProvider/AnimationFontProvider.hpp"
#include "Lottie/Private/MainThread/NodeRenderSystem/Nodes/Text/TextAnimatorNode.hpp"
namespace lottie {
class TextCompositionLayer: public CompositionLayer {
public:
TextCompositionLayer(std::shared_ptr<TextLayerModel> const &textLayer, std::shared_ptr<AnimationTextProvider> textProvider, std::shared_ptr<AnimationFontProvider> fontProvider) :
CompositionLayer(textLayer, Vector2D::Zero()) {
std::shared_ptr<TextAnimatorNode> rootNode;
for (const auto &animator : textLayer->animators) {
rootNode = std::make_shared<TextAnimatorNode>(rootNode, animator);
}
_rootNode = rootNode;
_textDocument = std::make_shared<KeyframeInterpolator<TextDocument>>(textLayer->text.keyframes);
_textProvider = textProvider;
_fontProvider = fontProvider;
if (_rootNode) {
_childKeypaths.push_back(rootNode);
}
}
std::shared_ptr<AnimationTextProvider> const &textProvider() const {
return _textProvider;
}
void setTextProvider(std::shared_ptr<AnimationTextProvider> const &textProvider) {
_textProvider = textProvider;
}
std::shared_ptr<AnimationFontProvider> const &fontProvider() const {
return _fontProvider;
}
void setFontProvider(std::shared_ptr<AnimationFontProvider> const &fontProvider) {
_fontProvider = fontProvider;
}
virtual void displayContentsWithFrame(float frame, bool forceUpdates, BezierPathsBoundingBoxContext &boundingBoxContext) override {
if (!_textDocument) {
return;
}
bool documentUpdate = _textDocument->hasUpdate(frame);
bool animatorUpdate = false;
if (_rootNode) {
animatorUpdate = _rootNode->updateContents(frame, forceUpdates);
}
if (!(documentUpdate || animatorUpdate)) {
return;
}
if (_rootNode) {
_rootNode->rebuildOutputs(frame);
}
}
public:
virtual bool isTextCompositionLayer() const override {
return true;
}
private:
std::shared_ptr<TextAnimatorNode> _rootNode;
std::shared_ptr<KeyframeInterpolator<TextDocument>> _textDocument;
std::shared_ptr<AnimationTextProvider> _textProvider;
std::shared_ptr<AnimationFontProvider> _fontProvider;
};
}
#endif /* TextCompositionLayer_hpp */
@@ -0,0 +1,5 @@
#include "MainThreadAnimationLayer.hpp"
namespace lottie {
}
@@ -0,0 +1,266 @@
#ifndef MainThreadAnimationLayer_hpp
#define MainThreadAnimationLayer_hpp
#include "Lottie/Public/Primitives/CALayer.hpp"
#include "Lottie/Public/ImageProvider/AnimationImageProvider.hpp"
#include "Lottie/Private/Model/Animation.hpp"
#include "Lottie/Public/TextProvider/AnimationTextProvider.hpp"
#include "Lottie/Public/FontProvider/AnimationFontProvider.hpp"
#include "Lottie/Private/MainThread/LayerContainers/Utility/LayerImageProvider.hpp"
#include "Lottie/Private/MainThread/LayerContainers/Utility/LayerTextProvider.hpp"
#include "Lottie/Private/MainThread/LayerContainers/Utility/CompositionLayersInitializer.hpp"
#include "Lottie/Private/MainThread/LayerContainers/Utility/LayerFontProvider.hpp"
#include "Lottie/Public/DynamicProperties/AnyValueProvider.hpp"
#include "Lottie/Public/DynamicProperties/AnimationKeypath.hpp"
namespace lottie {
class BlankImageProvider: public AnimationImageProvider {
public:
virtual ~BlankImageProvider() = default;
std::shared_ptr<Image> imageForAsset(ImageAsset const &asset) {
return nullptr;
}
};
class MainThreadAnimationLayer: public CALayer {
public:
MainThreadAnimationLayer(
Animation const &animation,
std::shared_ptr<AnimationImageProvider> const &imageProvider,
std::shared_ptr<AnimationTextProvider> const &textProvider,
std::shared_ptr<AnimationFontProvider> const &fontProvider
) {
if (animation.assetLibrary) {
_layerImageProvider = std::make_shared<LayerImageProvider>(imageProvider, animation.assetLibrary->imageAssets);
} else {
std::map<std::string, std::shared_ptr<ImageAsset>> imageAssets;
_layerImageProvider = std::make_shared<LayerImageProvider>(imageProvider, imageAssets);
}
_layerTextProvider = std::make_shared<LayerTextProvider>(textProvider);
_layerFontProvider = std::make_shared<LayerFontProvider>(fontProvider);
setSize(Vector2D(animation.width, animation.height));
auto layers = initializeCompositionLayers(
animation.layers,
animation.assetLibrary,
_layerImageProvider,
textProvider,
fontProvider,
animation.framerate
);
std::vector<std::shared_ptr<ImageCompositionLayer>> imageLayers;
std::vector<std::shared_ptr<TextCompositionLayer>> textLayers;
std::shared_ptr<CompositionLayer> mattedLayer;
for (auto layerIt = layers.rbegin(); layerIt != layers.rend(); layerIt++) {
std::shared_ptr<CompositionLayer> const &layer = *layerIt;
layer->setSize(size());
_animationLayers.push_back(layer);
if (layer->isImageCompositionLayer()) {
imageLayers.push_back(std::static_pointer_cast<ImageCompositionLayer>(layer));
}
if (layer->isTextCompositionLayer()) {
textLayers.push_back(std::static_pointer_cast<TextCompositionLayer>(layer));
}
if (mattedLayer) {
/// The previous layer requires this layer to be its matte
mattedLayer->setMatteLayer(layer);
mattedLayer = nullptr;
continue;
}
if (layer->matteType().has_value() && (layer->matteType() == MatteType::Add || layer->matteType() == MatteType::Invert)) {
/// We have a layer that requires a matte.
mattedLayer = layer;
}
addSublayer(layer);
}
_layerImageProvider->addImageLayers(imageLayers);
_layerImageProvider->reloadImages();
_layerTextProvider->addTextLayers(textLayers);
_layerTextProvider->reloadTexts();
_layerFontProvider->addTextLayers(textLayers);
_layerFontProvider->reloadTexts();
renderTreeNode();
}
void setRespectAnimationFrameRate(bool respectAnimationFrameRate) {
_respectAnimationFrameRate = respectAnimationFrameRate;
}
void display() {
float newFrame = currentFrame();
if (_respectAnimationFrameRate) {
newFrame = floor(newFrame);
}
for (const auto &layer : _animationLayers) {
layer->displayWithFrame(newFrame, false, _boundingBoxContext);
}
}
std::vector<std::shared_ptr<CompositionLayer>> const &animationLayers() const {
return _animationLayers;
}
void reloadImages() {
_layerImageProvider->reloadImages();
}
/// Forces the view to update its drawing.
void forceDisplayUpdate() {
for (const auto &layer : _animationLayers) {
layer->displayWithFrame(currentFrame(), true, _boundingBoxContext);
}
}
void logHierarchyKeypaths() {
printf("Lottie: Logging Animation Keypaths\n");
assert(false);
//animationLayers.forEach({ $0.logKeypaths(for: nil) })
}
void setValueProvider(std::shared_ptr<AnyValueProvider> const &valueProvider, AnimationKeypath const &keypath) {
/*for (const auto &layer : _animationLayers) {
assert(false);
if let foundProperties = layer.nodeProperties(for: keypath) {
for property in foundProperties {
property.setProvider(provider: valueProvider)
}
layer.displayWithFrame(frame: presentation()?.currentFrame ?? currentFrame, forceUpdates: true)
}
}*/
}
std::optional<AnyValue> getValue(AnimationKeypath const &keypath, std::optional<float> atFrame) {
/*for (const auto &layer : _animationLayers) {
assert(false);
if
let foundProperties = layer.nodeProperties(for: keypath),
let first = foundProperties.first
{
return first.valueProvider.value(frame: atFrame ?? currentFrame)
}
}*/
return std::nullopt;
}
std::optional<AnyValue> getOriginalValue(AnimationKeypath const &keypath, std::optional<float> atFrame) {
/*for (const auto &layer : _animationLayers) {
assert(false);
if
let foundProperties = layer.nodeProperties(for: keypath),
let first = foundProperties.first
{
return first.originalValueProvider.value(frame: atFrame ?? currentFrame)
}
}*/
return std::nullopt;
}
std::vector<std::shared_ptr<AnimatorNode>> animatorNodesForKeypath(AnimationKeypath const &keypath) {
std::vector<std::shared_ptr<AnimatorNode>> results;
/*for (const auto &layer : _animationLayers) {
if let nodes = layer.animatorNodes(for: keypath) {
results.append(contentsOf: nodes)
}
}*/
return results;
}
float currentFrame() const {
return _currentFrame;
}
void setCurrentFrame(float currentFrame) {
_currentFrame = currentFrame;
for (size_t i = 0; i < _animationLayers.size(); i++) {
_animationLayers[i]->displayWithFrame(_currentFrame, false, _boundingBoxContext);
}
}
std::shared_ptr<AnimationImageProvider> imageProvider() const {
return _layerImageProvider->imageProvider();
}
void setImageProvider(std::shared_ptr<AnimationImageProvider> const &imageProvider) {
_layerImageProvider->setImageProvider(imageProvider);
}
std::shared_ptr<AnimationTextProvider> textProvider() const {
return _layerTextProvider->textProvider();
}
void setTextProvider(std::shared_ptr<AnimationTextProvider> const &textProvider) {
_layerTextProvider->setTextProvider(textProvider);
}
std::shared_ptr<AnimationFontProvider> fontProvider() const {
return _layerFontProvider->fontProvider();
}
void setFontProvider(std::shared_ptr<AnimationFontProvider> const &fontProvider) {
_layerFontProvider->setFontProvider(fontProvider);
}
virtual std::shared_ptr<RenderTreeNode> renderTreeNode() {
if (!_renderTreeNode) {
std::vector<std::shared_ptr<RenderTreeNode>> subnodes;
for (const auto &animationLayer : _animationLayers) {
bool found = false;
for (const auto &sublayer : sublayers()) {
if (animationLayer == sublayer) {
found = true;
break;
}
}
if (found) {
auto node = animationLayer->renderTreeNode(_boundingBoxContext);
if (node) {
subnodes.push_back(node);
}
}
}
_renderTreeNode = std::make_shared<RenderTreeNode>(
size(),
Transform2D::identity(),
1.0,
false,
false,
subnodes,
nullptr,
false
);
}
return _renderTreeNode;
}
private:
float _currentFrame = 0.0;
std::shared_ptr<AnimationImageProvider> _imageProvider;
std::shared_ptr<AnimationTextProvider> _textProvider;
std::shared_ptr<AnimationFontProvider> _fontProvider;
bool _respectAnimationFrameRate = true;
std::vector<std::shared_ptr<CompositionLayer>> _animationLayers;
std::shared_ptr<LayerImageProvider> _layerImageProvider;
std::shared_ptr<LayerTextProvider> _layerTextProvider;
std::shared_ptr<LayerFontProvider> _layerFontProvider;
std::shared_ptr<RenderTreeNode> _renderTreeNode;
BezierPathsBoundingBoxContext _boundingBoxContext;
};
}
#endif /* MainThreadAnimationLayer_hpp */
@@ -0,0 +1,111 @@
#include "CompositionLayersInitializer.hpp"
#include "Lottie/Private/MainThread/LayerContainers/CompLayers/NullCompositionLayer.hpp"
#include "Lottie/Private/MainThread/LayerContainers/CompLayers/ShapeCompositionLayer.hpp"
#include "Lottie/Private/MainThread/LayerContainers/CompLayers/PreCompositionLayer.hpp"
#include "Lottie/Private/MainThread/LayerContainers/CompLayers/ImageCompositionLayer.hpp"
#include "Lottie/Private/MainThread/LayerContainers/CompLayers/TextCompositionLayer.hpp"
namespace lottie {
std::vector<std::shared_ptr<CompositionLayer>> initializeCompositionLayers(
std::vector<std::shared_ptr<LayerModel>> const &layers,
std::shared_ptr<AssetLibrary> const &assetLibrary,
std::shared_ptr<LayerImageProvider> const &layerImageProvider,
std::shared_ptr<AnimationTextProvider> const &textProvider,
std::shared_ptr<AnimationFontProvider> const &fontProvider,
float frameRate
) {
std::vector<std::shared_ptr<CompositionLayer>> compositionLayers;
std::map<int, std::shared_ptr<CompositionLayer>> layerMap;
std::vector<std::shared_ptr<LayerModel>> childLayers;
for (const auto &layer : layers) {
if (layer->hidden) {
auto genericLayer = std::make_shared<NullCompositionLayer>(layer);
compositionLayers.push_back(genericLayer);
if (layer->index) {
layerMap.insert(std::make_pair(layer->index.value(), genericLayer));
}
} else if (layer->type == LayerType::Shape) {
auto shapeContainer = std::make_shared<ShapeCompositionLayer>(std::static_pointer_cast<ShapeLayerModel>(layer));
compositionLayers.push_back(shapeContainer);
if (layer->index) {
layerMap.insert(std::make_pair(layer->index.value(), shapeContainer));
}
} else if (layer->type == LayerType::Solid) {
auto shapeContainer = std::make_shared<ShapeCompositionLayer>(std::static_pointer_cast<SolidLayerModel>(layer));
compositionLayers.push_back(shapeContainer);
if (layer->index) {
layerMap.insert(std::make_pair(layer->index.value(), shapeContainer));
}
} else if (layer->type == LayerType::Precomp && assetLibrary) {
auto precompLayer = std::static_pointer_cast<PreCompLayerModel>(layer);
auto precompAssetIt = assetLibrary->precompAssets.find(precompLayer->referenceID);
if (precompAssetIt != assetLibrary->precompAssets.end()) {
auto precompContainer = std::make_shared<PreCompositionLayer>(
precompLayer,
*(precompAssetIt->second),
layerImageProvider,
textProvider,
fontProvider,
assetLibrary,
frameRate
);
compositionLayers.push_back(precompContainer);
if (layer->index) {
layerMap.insert(std::make_pair(layer->index.value(), precompContainer));
}
}
} else if (layer->type == LayerType::Image && assetLibrary) {
auto imageLayer = std::static_pointer_cast<ImageLayerModel>(layer);
auto imageAssetIt = assetLibrary->imageAssets.find(imageLayer->referenceID);
if (imageAssetIt != assetLibrary->imageAssets.end()) {
auto imageContainer = std::make_shared<ImageCompositionLayer>(
imageLayer,
Vector2D((*imageAssetIt->second).width, (*imageAssetIt->second).height)
);
compositionLayers.push_back(imageContainer);
if (layer->index) {
layerMap.insert(std::make_pair(layer->index.value(), imageContainer));
}
}
} else if (layer->type == LayerType::Text) {
auto textContainer = std::make_shared<TextCompositionLayer>(std::static_pointer_cast<TextLayerModel>(layer), textProvider, fontProvider);
compositionLayers.push_back(textContainer);
if (layer->index) {
layerMap.insert(std::make_pair(layer->index.value(), textContainer));
}
} else {
auto genericLayer = std::make_shared<NullCompositionLayer>(layer);
compositionLayers.push_back(genericLayer);
if (layer->index) {
layerMap.insert(std::make_pair(layer->index.value(), genericLayer));
}
}
if (layer->parent) {
childLayers.push_back(layer);
}
}
/// Now link children with their parents
for (const auto &layerModel : childLayers) {
if (!layerModel->index.has_value()) {
continue;
}
if (const auto parentID = layerModel->parent) {
auto childLayerIt = layerMap.find(layerModel->index.value());
if (childLayerIt != layerMap.end()) {
auto parentLayerIt = layerMap.find(parentID.value());
if (parentLayerIt != layerMap.end()) {
childLayerIt->second->transformNode()->setParentNode(parentLayerIt->second->transformNode());
}
}
}
}
return compositionLayers;
}
}
@@ -0,0 +1,23 @@
#ifndef CompositionLayersInitializer_hpp
#define CompositionLayersInitializer_hpp
#include "Lottie/Private/MainThread/LayerContainers/CompLayers/CompositionLayer.hpp"
#include "Lottie/Private/Model/Assets/AssetLibrary.hpp"
#include "Lottie/Private/MainThread/LayerContainers/Utility/LayerImageProvider.hpp"
#include "Lottie/Public/TextProvider/AnimationTextProvider.hpp"
#include "Lottie/Public/FontProvider/AnimationFontProvider.hpp"
namespace lottie {
std::vector<std::shared_ptr<CompositionLayer>> initializeCompositionLayers(
std::vector<std::shared_ptr<LayerModel>> const &layers,
std::shared_ptr<AssetLibrary> const &assetLibrary,
std::shared_ptr<LayerImageProvider> const &layerImageProvider,
std::shared_ptr<AnimationTextProvider> const &textProvider,
std::shared_ptr<AnimationFontProvider> const &fontProvider,
float frameRate
);
}
#endif /* CompositionLayersInitializer_hpp */
@@ -0,0 +1,5 @@
#include "LayerFontProvider.hpp"
namespace lottie {
}
@@ -0,0 +1,45 @@
#ifndef LayerFontProvider_hpp
#define LayerFontProvider_hpp
#include "Lottie/Public/FontProvider/AnimationFontProvider.hpp"
#include "Lottie/Private/MainThread/LayerContainers/CompLayers/TextCompositionLayer.hpp"
namespace lottie {
/// Connects a LottieFontProvider to a group of text layers
class LayerFontProvider {
public:
LayerFontProvider(std::shared_ptr<AnimationFontProvider> const &fontProvider) {
_fontProvider = fontProvider;
reloadTexts();
}
std::shared_ptr<AnimationFontProvider> const &fontProvider() const {
return _fontProvider;
}
void setFontProvider(std::shared_ptr<AnimationFontProvider> const &fontProvider) {
_fontProvider = fontProvider;
reloadTexts();
}
void addTextLayers(std::vector<std::shared_ptr<TextCompositionLayer>> const &layers) {
for (const auto &layer : layers) {
_textLayers.push_back(layer);
}
}
void reloadTexts() {
for (const auto &layer : _textLayers) {
layer->setFontProvider(_fontProvider);
}
}
private:
std::vector<std::shared_ptr<TextCompositionLayer>> _textLayers;
std::shared_ptr<AnimationFontProvider> _fontProvider;
};
}
#endif /* LayerFontProvider_hpp */
@@ -0,0 +1,5 @@
#include "LayerImageProvider.hpp"
namespace lottie {
}
@@ -0,0 +1,58 @@
#ifndef LayerImageProvider_hpp
#define LayerImageProvider_hpp
#include "Lottie/Public/ImageProvider/AnimationImageProvider.hpp"
#include "Lottie/Private/Model/Assets/ImageAsset.hpp"
#include "Lottie/Private/MainThread/LayerContainers/CompLayers/ImageCompositionLayer.hpp"
namespace lottie {
/// Connects a LottieImageProvider to a group of image layers
class LayerImageProvider {
public:
LayerImageProvider(std::shared_ptr<AnimationImageProvider> const &imageProvider, std::map<std::string, std::shared_ptr<ImageAsset>> const &assets) :
_imageProvider(imageProvider),
_imageAssets(assets) {
reloadImages();
}
std::shared_ptr<AnimationImageProvider> imageProvider() const {
return _imageProvider;
}
void setImageProvider(std::shared_ptr<AnimationImageProvider> const &imageProvider) {
_imageProvider = imageProvider;
reloadImages();
}
std::vector<std::shared_ptr<ImageCompositionLayer>> const &imageLayers() const {
return _imageLayers;
}
void addImageLayers(std::vector<std::shared_ptr<ImageCompositionLayer>> const &layers) {
for (const auto &layer : layers) {
auto it = _imageAssets.find(layer->imageReferenceID());
if (it != _imageAssets.end()) {
_imageLayers.push_back(layer);
}
}
}
void reloadImages() {
for (const auto &imageLayer : imageLayers()) {
auto it = _imageAssets.find(imageLayer->imageReferenceID());
if (it != _imageAssets.end()) {
imageLayer->setImage(_imageProvider->imageForAsset(*it->second));
}
}
}
private:
std::shared_ptr<AnimationImageProvider> _imageProvider;
std::vector<std::shared_ptr<ImageCompositionLayer>> _imageLayers;
std::map<std::string, std::shared_ptr<ImageAsset>> _imageAssets;
};
}
#endif /* LayerImageProvider_hpp */
@@ -0,0 +1,5 @@
#include "LayerTextProvider.hpp"
namespace lottie {
}
@@ -0,0 +1,45 @@
#ifndef LayerTextProvider_hpp
#define LayerTextProvider_hpp
#include "Lottie/Public/TextProvider/AnimationTextProvider.hpp"
#include "Lottie/Private/MainThread/LayerContainers/CompLayers/TextCompositionLayer.hpp"
namespace lottie {
/// Connects a LottieTextProvider to a group of text layers
class LayerTextProvider {
public:
LayerTextProvider(std::shared_ptr<AnimationTextProvider> const &textProvider) {
_textProvider = textProvider;
reloadTexts();
}
std::shared_ptr<AnimationTextProvider> const &textProvider() const {
return _textProvider;
}
void setTextProvider(std::shared_ptr<AnimationTextProvider> const &textProvider) {
_textProvider = textProvider;
reloadTexts();
}
void addTextLayers(std::vector<std::shared_ptr<TextCompositionLayer>> const &layers) {
for (const auto &layer : layers) {
_textLayers.push_back(layer);
}
}
void reloadTexts() {
for (const auto &layer : _textLayers) {
layer->setTextProvider(_textProvider);
}
}
private:
std::vector<std::shared_ptr<TextCompositionLayer>> _textLayers;
std::shared_ptr<AnimationTextProvider> _textProvider;
};
}
#endif /* LayerTextProvider_hpp */
@@ -0,0 +1,5 @@
#include "LayerTransformNode.hpp"
namespace lottie {
}
@@ -0,0 +1,205 @@
#ifndef LayerTransformNode_hpp
#define LayerTransformNode_hpp
#include "Lottie/Private/Model/Objects/Transform.hpp"
#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/NodePropertyMap.hpp"
#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/KeypathSearchable.hpp"
#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/NodeProperty.hpp"
#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/KeyframeInterpolator.hpp"
#include "Lottie/Private/MainThread/NodeRenderSystem/Protocols/AnimatorNode.hpp"
#include "Lottie/Private/MainThread/NodeRenderSystem/Protocols/NodeOutput.hpp"
#include "Lottie/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/PassThroughOutputNode.hpp"
namespace lottie {
class LayerTransformProperties: public KeypathSearchableNodePropertyMap {
public:
LayerTransformProperties(std::shared_ptr<Transform> transform) {
_anchor = std::make_shared<NodeProperty<Vector3D>>(std::make_shared<KeyframeInterpolator<Vector3D>>(transform->anchorPoint().keyframes));
_scale = std::make_shared<NodeProperty<Vector3D>>(std::make_shared<KeyframeInterpolator<Vector3D>>(transform->scale().keyframes));
_rotation = std::make_shared<NodeProperty<Vector1D>>(std::make_shared<KeyframeInterpolator<Vector1D>>(transform->rotation().keyframes));
_opacity = std::make_shared<NodeProperty<Vector1D>>(std::make_shared<KeyframeInterpolator<Vector1D>>(transform->opacity().keyframes));
std::map<std::string, std::shared_ptr<AnyNodeProperty>> propertyMap;
_keypathProperties.insert(std::make_pair("Anchor Point", _anchor));
_keypathProperties.insert(std::make_pair("Scale", _scale));
_keypathProperties.insert(std::make_pair("Rotation", _rotation));
_keypathProperties.insert(std::make_pair("Opacity", _opacity));
if (transform->positionX().has_value() && transform->positionY().has_value()) {
auto xPosition = std::make_shared<NodeProperty<Vector1D>>(std::make_shared<KeyframeInterpolator<Vector1D>>(transform->positionX()->keyframes));
auto yPosition = std::make_shared<NodeProperty<Vector1D>>(std::make_shared<KeyframeInterpolator<Vector1D>>(transform->positionY()->keyframes));
_keypathProperties.insert(std::make_pair("X Position", xPosition));
_keypathProperties.insert(std::make_pair("Y Position", yPosition));
_positionX = xPosition;
_positionY = yPosition;
_position = nullptr;
} else if (transform->position().has_value()) {
auto position = std::make_shared<NodeProperty<Vector3D>>(std::make_shared<KeyframeInterpolator<Vector3D>>(transform->position()->keyframes));
_keypathProperties.insert(std::make_pair("Position", position));
_position = position;
_positionX = nullptr;
_positionY = nullptr;
} else {
_position = nullptr;
_positionX = nullptr;
_positionY = nullptr;
}
for (const auto &it : _keypathProperties) {
_properties.push_back(it.second);
}
}
virtual ~LayerTransformProperties() = default;
virtual std::vector<std::shared_ptr<AnyNodeProperty>> &properties() override {
return _properties;
}
virtual std::vector<std::shared_ptr<KeypathSearchable>> const &childKeypaths() const override {
return _childKeypaths;
}
virtual std::string keypathName() const override {
return "Transform";
}
virtual std::map<std::string, std::shared_ptr<AnyNodeProperty>> keypathProperties() const override {
return _keypathProperties;
}
virtual std::shared_ptr<CALayer> keypathLayer() const override {
return nullptr;
}
std::shared_ptr<NodeProperty<Vector3D>> const &anchor() {
return _anchor;
}
std::shared_ptr<NodeProperty<Vector3D>> const &scale() {
return _scale;
}
std::shared_ptr<NodeProperty<Vector1D>> const &rotation() {
return _rotation;
}
std::shared_ptr<NodeProperty<Vector3D>> const &position() {
return _position;
}
std::shared_ptr<NodeProperty<Vector1D>> const &positionX() {
return _positionX;
}
std::shared_ptr<NodeProperty<Vector1D>> const &positionY() {
return _positionY;
}
std::shared_ptr<NodeProperty<Vector1D>> const &opacity() {
return _opacity;
}
private:
std::map<std::string, std::shared_ptr<AnyNodeProperty>> _keypathProperties;
std::vector<std::shared_ptr<KeypathSearchable>> _childKeypaths;
std::vector<std::shared_ptr<AnyNodeProperty>> _properties;
std::shared_ptr<NodeProperty<Vector3D>> _anchor;
std::shared_ptr<NodeProperty<Vector3D>> _scale;
std::shared_ptr<NodeProperty<Vector1D>> _rotation;
std::shared_ptr<NodeProperty<Vector3D>> _position;
std::shared_ptr<NodeProperty<Vector1D>> _positionX;
std::shared_ptr<NodeProperty<Vector1D>> _positionY;
std::shared_ptr<NodeProperty<Vector1D>> _opacity;
};
class LayerTransformNode: public AnimatorNode {
public:
LayerTransformNode(std::shared_ptr<Transform> transform) :
AnimatorNode(nullptr),
_transformProperties(std::make_shared<LayerTransformProperties>(transform)) {
_outputNode = std::make_shared<PassThroughOutputNode>(nullptr);
}
virtual ~LayerTransformNode() = default;
virtual std::shared_ptr<NodeOutput> outputNode() override {
return _outputNode;
}
virtual std::shared_ptr<KeypathSearchableNodePropertyMap> propertyMap() const override {
return _transformProperties;
}
virtual bool shouldRebuildOutputs(float frame) override {
return hasLocalUpdates() || hasUpstreamUpdates();
}
virtual void rebuildOutputs(float frame) override {
_opacity = ((float)_transformProperties->opacity()->value().value) * 0.01f;
Vector2D position(0.0, 0.0);
if (_transformProperties->position()) {
auto position3d = _transformProperties->position()->value();
position.x = position3d.x;
position.y = position3d.y;
} else if (_transformProperties->positionX() && _transformProperties->positionY()) {
position = Vector2D(
_transformProperties->positionX()->value().value,
_transformProperties->positionY()->value().value
);
}
Vector3D anchor = _transformProperties->anchor()->value();
Vector3D scale = _transformProperties->scale()->value();
_localTransform = Transform2D::makeTransform(
Vector2D(anchor.x, anchor.y),
position,
Vector2D(scale.x, scale.y),
_transformProperties->rotation()->value().value,
std::nullopt,
std::nullopt
);
if (parentNode() && parentNode()->asLayerTransformNode()) {
_globalTransform = _localTransform * parentNode()->asLayerTransformNode()->_globalTransform;
} else {
_globalTransform = _localTransform;
}
}
std::shared_ptr<LayerTransformProperties> const &transformProperties() {
return _transformProperties;
}
float opacity() {
return _opacity;
}
Transform2D const &globalTransform() {
return _globalTransform;
}
private:
std::shared_ptr<NodeOutput> _outputNode;
std::shared_ptr<LayerTransformProperties> _transformProperties;
float _opacity = 1.0;
Transform2D _localTransform = Transform2D::identity();
Transform2D _globalTransform = Transform2D::identity();
public:
virtual LayerTransformNode *asLayerTransformNode() override {
return this;
}
};
}
#endif /* LayerTransformNode_hpp */