diff --git a/.bazelrc b/.bazelrc index 591c03d767..7e6c513edb 100644 --- a/.bazelrc +++ b/.bazelrc @@ -11,6 +11,8 @@ build --per_file_copt="third-party/webrtc/.*\.cpp$","@-std=c++17" build --per_file_copt="third-party/webrtc/.*\.cc$","@-std=c++17" build --per_file_copt="third-party/webrtc/.*\.mm$","@-std=c++17" build --per_file_copt="submodules/LottieMeshSwift/LottieMeshBinding/Sources/.*\.mm$","@-std=c++17" +build --per_file_copt="submodules/TelegramUI/Components/LottieCpp/Sources/.*\.mm$","@-std=c++17" +build --per_file_copt="submodules/TelegramUI/Components/LottieCpp/Sources/.*\.cpp$","@-std=c++17" build --swiftcopt=-whole-module-optimization diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/BUILD index 0dfda1903f..449ebb8f5b 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/BUILD @@ -84,6 +84,7 @@ swift_library( "//submodules/TelegramUI/Components/Chat/ChatMessageTransitionNode", "//submodules/AnimatedStickerNode", "//submodules/TelegramAnimatedStickerNode", + "//submodules/TelegramUI/Components/LottieMetal", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift index ecd72210c7..c534befd47 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift @@ -74,6 +74,7 @@ import UIKitRuntimeUtils import ChatMessageTransitionNode import AnimatedStickerNode import TelegramAnimatedStickerNode +import LottieMetal private struct BubbleItemAttributes { var isAttachment: Bool @@ -5822,7 +5823,8 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI do { let pathPrefix = item.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(resource.id) - let additionalAnimationNode = DefaultAnimatedStickerNodeImpl() + + let additionalAnimationNode = LottieMetalAnimatedStickerNode() additionalAnimationNode.setup(source: source, width: Int(animationSize.width * 1.6), height: Int(animationSize.height * 1.6), playbackMode: .once, mode: .direct(cachePathPrefix: pathPrefix)) var animationFrame: CGRect if isStickerEffect { diff --git a/submodules/TelegramUI/Components/LottieCpp/BUILD b/submodules/TelegramUI/Components/LottieCpp/BUILD new file mode 100644 index 0000000000..510c938526 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/BUILD @@ -0,0 +1,33 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +objc_library( + name = "LottieCpp", + enable_modules = True, + module_name = "LottieCpp", + srcs = glob([ + "Sources/**/*.m", + "Sources/**/*.mm", + "Sources/**/*.h", + "Sources/**/*.c", + "Sources/**/*.cpp", + "Sources/**/*.hpp", + ]), + copts = [ + "-Werror", + "-I{}/Sources".format(package_name()), + ], + hdrs = glob([ + "PublicHeaders/**/*.h", + ]), + includes = [ + "PublicHeaders", + ], + deps = [ + ], + sdk_frameworks = [ + "Foundation", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/LottieAnimation.h b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/LottieAnimation.h new file mode 100644 index 0000000000..93a988af5b --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/LottieAnimation.h @@ -0,0 +1,27 @@ +#ifndef LottieAnimation_h +#define LottieAnimation_h + +#import + +#import "LottieRenderTree.h" + +#ifdef __cplusplus +extern "C" { +#endif + +@interface LottieAnimation : NSObject + +@property (nonatomic, readonly) NSInteger frameCount; +@property (nonatomic, readonly) CGSize size; + +- (instancetype _Nullable)initWithData:(NSData * _Nonnull)data; + +- (NSData * _Nonnull)toJson; + +@end + +#ifdef __cplusplus +} +#endif + +#endif /* LottieAnimation_h */ diff --git a/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/LottieAnimationContainer.h b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/LottieAnimationContainer.h new file mode 100644 index 0000000000..5944bb0488 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/LottieAnimationContainer.h @@ -0,0 +1,27 @@ +#ifndef LottieAnimationContainer_h +#define LottieAnimationContainer_h + +#import "LottieAnimation.h" +#import "LottieRenderTree.h" +#import "LottieAnimationContainer.h" + +#ifdef __cplusplus +extern "C" { +#endif + +@interface LottieAnimationContainer : NSObject + +@property (nonatomic, strong, readonly) LottieAnimation * _Nonnull animation; + +- (instancetype _Nonnull)initWithAnimation:(LottieAnimation * _Nonnull)animation; + +- (void)update:(NSInteger)frame; +- (LottieRenderNode * _Nonnull)getCurrentRenderTreeForSize:(CGSize)size; + +@end + +#ifdef __cplusplus +} +#endif + +#endif /* LottieAnimationContainer_h */ diff --git a/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/LottieCpp.h b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/LottieCpp.h new file mode 100644 index 0000000000..f877f88faa --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/LottieCpp.h @@ -0,0 +1,12 @@ +#ifndef LottieCpp_h +#define LottieCpp_h + +#import + +#ifdef __cplusplus + + + +#endif + +#endif /* DctHuffman_h */ diff --git a/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/LottieRenderTree.h b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/LottieRenderTree.h new file mode 100644 index 0000000000..d853b6bad5 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/LottieRenderTree.h @@ -0,0 +1,140 @@ +#ifndef LottieRenderTree_h +#define LottieRenderTree_h + +#import + +#ifdef __cplusplus +extern "C" { +#endif + +typedef NS_ENUM(NSUInteger, LottiePathItemType) { + LottiePathItemTypeMoveTo, + LottiePathItemTypeLineTo, + LottiePathItemTypeCurveTo, + LottiePathItemTypeClose +}; + +typedef struct { + LottiePathItemType type; + CGPoint points[4]; +} LottiePathItem; + +typedef struct { + CGFloat r; + CGFloat g; + CGFloat b; + CGFloat a; +} LottieColor; + +typedef NS_ENUM(NSUInteger, LottieFillRule) { + LottieFillRuleEvenOdd, + LottieFillRuleWinding +}; + +typedef NS_ENUM(NSUInteger, LottieGradientType) { + LottieGradientTypeLinear, + LottieGradientTypeRadial +}; + +@interface LottieColorStop : NSObject + +@property (nonatomic, readonly, direct) LottieColor color; +@property (nonatomic, readonly, direct) CGFloat location; + +- (instancetype _Nonnull)init NS_UNAVAILABLE; + +@end + +@interface LottiePath : NSObject + +- (CGRect)boundingBox __attribute__((objc_direct)); +- (void)enumerateItems:(void (^ _Nonnull)(LottiePathItem * _Nonnull))iterate __attribute__((objc_direct)); + +- (instancetype _Nonnull)init NS_UNAVAILABLE; + +@end + +@interface LottieRenderContentShading : NSObject + +@end + +@interface LottieRenderContentSolidShading : LottieRenderContentShading + +@property (nonatomic, readonly, direct) LottieColor color; +@property (nonatomic, readonly, direct) CGFloat opacity; + +- (instancetype _Nonnull)init NS_UNAVAILABLE; + +@end + +@interface LottieRenderContentGradientShading : LottieRenderContentShading + +@property (nonatomic, readonly, direct) CGFloat opacity; +@property (nonatomic, readonly, direct) LottieGradientType gradientType; +@property (nonatomic, strong, readonly, direct) NSArray * _Nonnull colorStops; +@property (nonatomic, readonly, direct) CGPoint start; +@property (nonatomic, readonly, direct) CGPoint end; + +- (instancetype _Nonnull)init NS_UNAVAILABLE; + +@end + +@interface LottieRenderContentFill : NSObject + +@property (nonatomic, strong, readonly, direct) LottieRenderContentShading * _Nonnull shading; +@property (nonatomic, readonly, direct) LottieFillRule fillRule; + +- (instancetype _Nonnull)init NS_UNAVAILABLE; + +@end + +@interface LottieRenderContentStroke : NSObject + +@property (nonatomic, strong, readonly, direct) LottieRenderContentShading * _Nonnull shading; +@property (nonatomic, readonly, direct) CGFloat lineWidth; +@property (nonatomic, readonly, direct) CGLineJoin lineJoin; +@property (nonatomic, readonly, direct) CGLineCap lineCap; +@property (nonatomic, readonly, direct) CGFloat miterLimit; +@property (nonatomic, readonly, direct) CGFloat dashPhase; +@property (nonatomic, strong, readonly, direct) NSArray * _Nullable dashPattern; + +- (instancetype _Nonnull)init NS_UNAVAILABLE; + +@end + +@interface LottieRenderContent : NSObject + +@property (nonatomic, strong, readonly, direct) LottiePath * _Nonnull path; +@property (nonatomic, strong, readonly, direct) LottieRenderContentStroke * _Nullable stroke; +@property (nonatomic, strong, readonly, direct) LottieRenderContentFill * _Nullable fill; + +- (instancetype _Nonnull)init NS_UNAVAILABLE; + +@end + +@interface LottieRenderNode : NSObject + +@property (nonatomic, readonly, direct) CGPoint position; +@property (nonatomic, readonly, direct) CGRect bounds; +@property (nonatomic, readonly, direct) CATransform3D transform; +@property (nonatomic, readonly, direct) CGFloat opacity; +@property (nonatomic, readonly, direct) bool masksToBounds; +@property (nonatomic, readonly, direct) bool isHidden; + +@property (nonatomic, readonly, direct) CGRect globalRect; +@property (nonatomic, readonly, direct) CATransform3D globalTransform; +@property (nonatomic, readonly, direct) LottieRenderContent * _Nullable renderContent; +@property (nonatomic, readonly, direct) bool hasSimpleContents; +@property (nonatomic, readonly, direct) bool isInvertedMatte; +@property (nonatomic, readonly, direct) NSArray * _Nonnull subnodes; +@property (nonatomic, readonly, direct) LottieRenderNode * _Nullable mask; + +- (instancetype _Nonnull)init NS_UNAVAILABLE; + +@end + +#ifdef __cplusplus +} +#endif + +#endif /* LottieRenderTree_h */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/CompositionLayer.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/CompositionLayer.cpp new file mode 100644 index 0000000000..e4fca1e328 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/CompositionLayer.cpp @@ -0,0 +1,29 @@ +#include "CompositionLayer.hpp" + +#include "Lottie/Public/Primitives/RenderTree.hpp" + +namespace lottie { + +InvertedMatteLayer::InvertedMatteLayer(std::shared_ptr inputMatte) : +_inputMatte(inputMatte) { + setBounds(inputMatte->bounds()); + setNeedsDisplay(true); + + addSublayer(_inputMatte); +} + +void InvertedMatteLayer::setup() { + _inputMatte->setLayerDelegate(shared_from_base()); +} + +void InvertedMatteLayer::frameUpdated(double frame) { + setNeedsDisplay(true); +} + +std::shared_ptr makeInvertedMatteLayer(std::shared_ptr compositionLayer) { + auto result = std::make_shared(compositionLayer); + result->setup(); + return result; +} + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/CompositionLayer.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/CompositionLayer.hpp new file mode 100644 index 0000000000..df28991e4e --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/CompositionLayer.hpp @@ -0,0 +1,217 @@ +#ifndef CompositionLayer_hpp +#define CompositionLayer_hpp + +#include "Lottie/Public/Primitives/Vectors.hpp" +#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 "Lottie/Private/MainThread/LayerContainers/CompLayers/CompositionLayerDelegate.hpp" + +#include + +namespace lottie { + +class CompositionLayer; +class InvertedMatteLayer; + +/// A layer that inverses the alpha output of its input layer. +class InvertedMatteLayer: public CALayer, public CompositionLayerDelegate { +public: + InvertedMatteLayer(std::shared_ptr inputMatte); + + void setup(); + + std::shared_ptr _inputMatte; + //let wrapperLayer = CALayer() + + virtual void frameUpdated(double frame) override; + /*virtual bool implementsDraw() const override; + virtual void draw(std::shared_ptr const &context) override;*/ + //virtual std::shared_ptr renderableItem() override; + + virtual bool isInvertedMatte() const override { + return true; + } +}; + +std::shared_ptr makeInvertedMatteLayer(std::shared_ptr compositionLayer); + +/// The base class for a child layer of CompositionContainer +class CompositionLayer: public CALayer, public KeypathSearchable { +public: + CompositionLayer(std::shared_ptr const &layer, Vector2D size) { + _contentsLayer = std::make_shared(); + + _transformNode = std::make_shared(layer->transform); + + if (layer->masks.has_value()) { + _maskLayer = std::make_shared(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->setBounds(CGRect(0.0, 0.0, size.x, size.y)); + + 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> keypathProperties() const override { + return {}; + } + + virtual std::shared_ptr keypathLayer() const override { + return _contentsLayer; + } + + void displayWithFrame(double frame, bool forceUpdates) { + _transformNode->updateTree(frame, forceUpdates); + bool layerVisible = isInRangeOrEqual(frame, _inFrame, _outFrame); + /// Only update contents if current time is within the layers time bounds. + if (layerVisible) { + displayContentsWithFrame(frame, forceUpdates); + if (_maskLayer) { + _maskLayer->updateWithFrame(frame, forceUpdates); + } + } + _contentsLayer->setTransform(_transformNode->globalTransform()); + _contentsLayer->setOpacity(_transformNode->opacity()); + _contentsLayer->setIsHidden(!layerVisible); + + if (const auto delegate = _layerDelegate.lock()) { + delegate->frameUpdated(frame); + } + } + + virtual void displayContentsWithFrame(double frame, bool forceUpdates) { + /// To be overridden by subclass + } + + + virtual std::vector> const &childKeypaths() const override { + return _childKeypaths; + } + + std::shared_ptr _matteLayer; + void setMatteLayer(std::shared_ptr matteLayer) { + _matteLayer = matteLayer; + if (matteLayer) { + if (_matteType.has_value() && _matteType.value() == MatteType::Invert) { + setMask(makeInvertedMatteLayer(matteLayer)); + } else { + setMask(matteLayer); + } + } else { + setMask(nullptr); + } + } + + std::weak_ptr const &layerDelegate() const { + return _layerDelegate; + } + void setLayerDelegate(std::weak_ptr const &layerDelegate) { + _layerDelegate = layerDelegate; + } + + std::shared_ptr const &contentsLayer() const { + return _contentsLayer; + } + + std::shared_ptr const &maskLayer() const { + return _maskLayer; + } + void setMaskLayer(std::shared_ptr const &maskLayer) { + _maskLayer = maskLayer; + } + + std::optional const &matteType() const { + return _matteType; + } + + double inFrame() const { + return _inFrame; + } + double outFrame() const { + return _outFrame; + } + double startFrame() const { + return _startFrame; + } + double timeStretch() const { + return _timeStretch; + } + + virtual std::shared_ptr renderTreeNode() { + return nullptr; + } + +public: + std::shared_ptr const transformNode() const { + return _transformNode; + } + +protected: + std::shared_ptr _contentsLayer; + std::optional _matteType; + +private: + std::weak_ptr _layerDelegate; + + std::shared_ptr _transformNode; + + std::shared_ptr _maskLayer; + + double _inFrame = 0.0; + double _outFrame = 0.0; + double _startFrame = 0.0; + double _timeStretch = 0.0; + + // MARK: Keypath Searchable + + std::string _keypathName; + + //std::shared_ptr _renderTree; + +public: + virtual bool isImageCompositionLayer() const { + return false; + } + + virtual bool isTextCompositionLayer() const { + return false; + } + +protected: + std::vector> _childKeypaths; +}; + +} + +#endif /* CompositionLayer_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/CompositionLayerDelegate.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/CompositionLayerDelegate.hpp new file mode 100644 index 0000000000..59b05a3426 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/CompositionLayerDelegate.hpp @@ -0,0 +1,13 @@ +#ifndef CompositionLayerDelegate_hpp +#define CompositionLayerDelegate_hpp + +namespace lottie { + +class CompositionLayerDelegate { +public: + virtual void frameUpdated(double frame) = 0; +}; + +} + +#endif /* CompositionLayerDelegate_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/ImageCompositionLayer.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/ImageCompositionLayer.cpp new file mode 100644 index 0000000000..ad3b669b24 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/ImageCompositionLayer.cpp @@ -0,0 +1,5 @@ +#include "ImageCompositionLayer.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/ImageCompositionLayer.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/ImageCompositionLayer.hpp new file mode 100644 index 0000000000..8b9d709245 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/ImageCompositionLayer.hpp @@ -0,0 +1,42 @@ +#ifndef ImageCompositionLayer_hpp +#define ImageCompositionLayer_hpp + +#include "Lottie/Private/MainThread/LayerContainers/CompLayers/CompositionLayer.hpp" +#include "Lottie/Private/Model/Layers/ImageLayerModel.hpp" + +namespace lottie { + +class ImageCompositionLayer: public CompositionLayer { +public: + ImageCompositionLayer(std::shared_ptr const &imageLayer, Vector2D const &size) : + CompositionLayer(imageLayer, size) { + _imageReferenceID = imageLayer->referenceID; + + contentsLayer()->setMasksToBounds(true); + } + + std::shared_ptr image() { + return _image; + } + void setImage(std::shared_ptr 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; +}; + +} + +#endif /* ImageCompositionLayer_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/MaskContainerLayer.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/MaskContainerLayer.cpp new file mode 100644 index 0000000000..398d52d57c --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/MaskContainerLayer.cpp @@ -0,0 +1,5 @@ +#include "MaskContainerLayer.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/MaskContainerLayer.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/MaskContainerLayer.hpp new file mode 100644 index 0000000000..9c4e46f41b --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/MaskContainerLayer.hpp @@ -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 const &mask) : + _mode(mask->mode()), + _inverted(mask->inverted) { + _opacity = std::make_shared>(std::make_shared>(mask->opacity->keyframes)); + _shape = std::make_shared>(std::make_shared>(mask->shape.keyframes)); + _expansion = std::make_shared>(std::make_shared>(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> &properties() override { + return _properties; + } + + virtual std::vector> const &childKeypaths() const override { + return _childKeypaths; + } + + std::shared_ptr> const &opacity() const { + return _opacity; + } + + std::shared_ptr> const &shape() const { + return _shape; + } + + std::shared_ptr> const &expansion() const { + return _expansion; + } + + MaskMode mode() const { + return _mode; + } + + bool inverted() const { + return _inverted; + } + +private: + std::map> _propertyMap; + std::vector> _childKeypaths; + + std::vector> _properties; + + MaskMode _mode = MaskMode::Add; + bool _inverted = false; + + std::shared_ptr> _opacity; + std::shared_ptr> _shape; + std::shared_ptr> _expansion; +}; + +class MaskLayer: public CALayer { +public: + MaskLayer(std::shared_ptr const &mask) : + _properties(mask) { + _maskLayer = std::make_shared(); + + 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(double 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(newPath); + } + _maskLayer->setPath(path); + } + } + +private: + MaskNodeProperties _properties; + + std::shared_ptr _maskLayer; +}; + +class MaskContainerLayer: public CALayer { +public: + MaskContainerLayer(std::vector> const &masks) { + auto containerLayer = std::make_shared(); + bool firstObject = true; + for (const auto &mask : masks) { + auto maskLayer = std::make_shared(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(); + newContainer->addSublayer(containerLayer); + containerLayer = newContainer; + } + } + addSublayer(containerLayer); + } + + // MARK: Internal + + void updateWithFrame(double frame, bool forceUpdates) { + for (const auto &maskLayer : _maskLayers) { + maskLayer->updateWithFrame(frame, forceUpdates); + } + } + +private: + std::vector> _maskLayers; +}; + +} + +#endif /* MaskContainerLayer_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/NullCompositionLayer.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/NullCompositionLayer.cpp new file mode 100644 index 0000000000..6c5345cfd6 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/NullCompositionLayer.cpp @@ -0,0 +1,5 @@ +#include "NullCompositionLayer.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/NullCompositionLayer.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/NullCompositionLayer.hpp new file mode 100644 index 0000000000..c3d3dea5cc --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/NullCompositionLayer.hpp @@ -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 const &layer) : + CompositionLayer(layer, Vector2D::Zero()) { + } +}; + +} + +#endif /* NullCompositionLayer_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/PreCompositionLayer.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/PreCompositionLayer.cpp new file mode 100644 index 0000000000..d3428a9b1b --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/PreCompositionLayer.cpp @@ -0,0 +1,5 @@ +#include "PreCompositionLayer.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/PreCompositionLayer.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/PreCompositionLayer.hpp new file mode 100644 index 0000000000..384d96d645 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/PreCompositionLayer.hpp @@ -0,0 +1,200 @@ +#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 const &precomp, + PrecompAsset const &asset, + std::shared_ptr const &layerImageProvider, + std::shared_ptr const &textProvider, + std::shared_ptr const &fontProvider, + std::shared_ptr const &assetLibrary, + double frameRate + ) : CompositionLayer(precomp, Vector2D(precomp->width, precomp->height)) { + if (precomp->timeRemapping) { + _remappingNode = std::make_shared>(std::make_shared>(precomp->timeRemapping->keyframes)); + } + _frameRate = frameRate; + + setBounds(CGRect(0.0, 0.0, precomp->width, precomp->height)); + contentsLayer()->setMasksToBounds(true); + contentsLayer()->setBounds(bounds()); + + auto layers = initializeCompositionLayers( + asset.layers, + assetLibrary, + layerImageProvider, + textProvider, + fontProvider, + frameRate + ); + + std::vector> imageLayers; + + std::shared_ptr mattedLayer; + + for (auto layerIt = layers.rbegin(); layerIt != layers.rend(); layerIt++) { + std::shared_ptr layer = *layerIt; + layer->setBounds(bounds()); + _animationLayers.push_back(layer); + + if (layer->isImageCompositionLayer()) { + imageLayers.push_back(std::static_pointer_cast(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> keypathProperties() const override { + if (!_remappingNode) { + return {}; + } + + std::map> result; + result.insert(std::make_pair("Time Remap", _remappingNode)); + + return result; + } + + virtual void displayContentsWithFrame(double frame, bool forceUpdates) override { + double 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); + } + } + + virtual std::shared_ptr renderTreeNode() override { + if (_contentsLayer->isHidden()) { + return nullptr; + } + + std::shared_ptr maskNode; + bool invertMask = false; + if (_matteLayer) { + maskNode = _matteLayer->renderTreeNode(); + if (maskNode && _matteType.has_value() && _matteType.value() == MatteType::Invert) { + invertMask = true; + } + } + + std::vector> renderTreeValue; + auto renderTreeContentItem = renderTree(); + if (renderTreeContentItem) { + renderTreeValue.push_back(renderTreeContentItem); + } + + std::vector> subnodes; + subnodes.push_back(std::make_shared( + _contentsLayer->bounds(), + _contentsLayer->position(), + _contentsLayer->transform(), + _contentsLayer->opacity(), + _contentsLayer->masksToBounds(), + _contentsLayer->isHidden(), + nullptr, + renderTreeValue, + nullptr, + false + )); + + assert(opacity() == 1.0); + assert(!isHidden()); + assert(!masksToBounds()); + assert(transform().isIdentity()); + assert(position() == Vector2D::Zero()); + + return std::make_shared( + bounds(), + position(), + transform(), + opacity(), + masksToBounds(), + isHidden(), + nullptr, + subnodes, + maskNode, + invertMask + ); + } + + std::shared_ptr renderTree() { + std::vector> result; + + 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(); + if (node) { + result.push_back(node); + } + } + } + + std::vector> subnodes; + return std::make_shared( + CGRect(0.0, 0.0, 0.0, 0.0), + Vector2D(0.0, 0.0), + CATransform3D::identity(), + 1.0, + false, + false, + nullptr, + result, + nullptr, + false + ); + } + +private: + double _frameRate = 0.0; + std::shared_ptr> _remappingNode; + + std::vector> _animationLayers; +}; + +} + +#endif /* PreCompositionLayer_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/ShapeCompositionLayer.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/ShapeCompositionLayer.cpp new file mode 100644 index 0000000000..0caadc12ac --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/ShapeCompositionLayer.cpp @@ -0,0 +1,1364 @@ +#include "ShapeCompositionLayer.hpp" + +#include "Lottie/Private/Model/ShapeItems/Group.hpp" +#include "Lottie/Private/Model/ShapeItems/Ellipse.hpp" +#include "Lottie/Private/Model/ShapeItems/Rectangle.hpp" +#include "Lottie/Private/Model/ShapeItems/Star.hpp" +#include "Lottie/Private/Model/ShapeItems/Shape.hpp" +#include "Lottie/Private/Model/ShapeItems/Trim.hpp" +#include "Lottie/Private/Model/ShapeItems/Stroke.hpp" +#include "Lottie/Private/Model/ShapeItems/GradientStroke.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/RenderLayers/GetGradientParameters.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/StrokeNode.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/DashPatternInterpolator.hpp" +#include "Lottie/Private/MainThread/LayerContainers/CompLayers/ShapeUtils/BezierPathUtils.hpp" +#include "Lottie/Private/Model/ShapeItems/ShapeTransform.hpp" + +namespace lottie { + +class ShapeLayerPresentationTree { +public: + class FillOutput { + public: + FillOutput() { + } + ~FillOutput() = default; + + virtual void update(AnimationFrameTime frameTime) = 0; + virtual std::shared_ptr fill() = 0; + }; + + class SolidFillOutput : public FillOutput { + public: + explicit SolidFillOutput(Fill const &fill) : + rule(fill.fillRule.value_or(FillRule::NonZeroWinding)), + color(fill.color.keyframes), + opacity(fill.opacity.keyframes) { + } + + virtual ~SolidFillOutput() = default; + + virtual void update(AnimationFrameTime frameTime) override { + bool hasUpdates = false; + + if (color.hasUpdate(frameTime)) { + hasUpdates = true; + colorValue = color.value(frameTime); + } + + if (opacity.hasUpdate(frameTime)) { + hasUpdates = true; + opacityValue = opacity.value(frameTime).value; + } + + if (!_fill || hasUpdates) { + auto solid = std::make_shared(colorValue, opacityValue * 0.01); + _fill = std::make_shared( + solid, + rule + ); + } + } + + virtual std::shared_ptr fill() override { + return _fill; + } + + private: + FillRule rule; + + KeyframeInterpolator color; + Color colorValue = Color(0.0, 0.0, 0.0, 0.0); + + KeyframeInterpolator opacity; + double opacityValue = 0.0; + + std::shared_ptr _fill; + }; + + class GradientFillOutput : public FillOutput { + public: + explicit GradientFillOutput(GradientFill const &gradientFill) : + rule(FillRule::NonZeroWinding), + numberOfColors(gradientFill.numberOfColors), + gradientType(gradientFill.gradientType), + colors(gradientFill.colors.keyframes), + startPoint(gradientFill.startPoint.keyframes), + endPoint(gradientFill.endPoint.keyframes), + opacity(gradientFill.opacity.keyframes) { + } + + virtual ~GradientFillOutput() = default; + + virtual void update(AnimationFrameTime frameTime) override { + bool hasUpdates = false; + + if (colors.hasUpdate(frameTime)) { + hasUpdates = true; + colorsValue = colors.value(frameTime); + } + + if (startPoint.hasUpdate(frameTime)) { + hasUpdates = true; + startPointValue = startPoint.value(frameTime); + } + + if (endPoint.hasUpdate(frameTime)) { + hasUpdates = true; + endPointValue = endPoint.value(frameTime); + } + + if (opacity.hasUpdate(frameTime)) { + hasUpdates = true; + opacityValue = opacity.value(frameTime).value; + } + + if (!_fill || hasUpdates) { + std::vector colors; + std::vector locations; + getGradientParameters(numberOfColors, colorsValue, colors, locations); + + auto gradient = std::make_shared( + opacityValue * 0.01, + gradientType, + colors, + locations, + Vector2D(startPointValue.x, startPointValue.y), + Vector2D(endPointValue.x, endPointValue.y) + ); + _fill = std::make_shared( + gradient, + rule + ); + } + } + + virtual std::shared_ptr fill() override { + return _fill; + } + + private: + FillRule rule; + int numberOfColors = 0; + GradientType gradientType; + + KeyframeInterpolator colors; + GradientColorSet colorsValue; + + KeyframeInterpolator startPoint; + Vector3D startPointValue = Vector3D(0.0, 0.0, 0.0); + + KeyframeInterpolator endPoint; + Vector3D endPointValue = Vector3D(0.0, 0.0, 0.0); + + KeyframeInterpolator opacity; + double opacityValue = 0.0; + + std::shared_ptr _fill; + }; + + class StrokeOutput { + public: + StrokeOutput() { + } + ~StrokeOutput() = default; + + virtual void update(AnimationFrameTime frameTime) = 0; + virtual std::shared_ptr stroke() = 0; + }; + + class SolidStrokeOutput : public StrokeOutput { + public: + SolidStrokeOutput(Stroke const &stroke) : + lineJoin(stroke.lineJoin), + lineCap(stroke.lineCap), + miterLimit(stroke.miterLimit.value_or(4.0)), + color(stroke.color.keyframes), + opacity(stroke.opacity.keyframes), + width(stroke.width.keyframes) { + if (stroke.dashPattern.has_value()) { + StrokeShapeDashConfiguration dashConfiguration(stroke.dashPattern.value()); + dashPattern = std::make_unique(dashConfiguration.dashPatterns); + + if (!dashConfiguration.dashPhase.empty()) { + dashPhase = std::make_unique>(dashConfiguration.dashPhase); + } + } + } + + virtual ~SolidStrokeOutput() = default; + + virtual void update(AnimationFrameTime frameTime) override { + bool hasUpdates = false; + + if (color.hasUpdate(frameTime)) { + hasUpdates = true; + colorValue = color.value(frameTime); + } + + if (opacity.hasUpdate(frameTime)) { + hasUpdates = true; + opacityValue = opacity.value(frameTime).value; + } + + if (width.hasUpdate(frameTime)) { + hasUpdates = true; + widthValue = width.value(frameTime).value; + } + + if (dashPattern) { + if (dashPattern->hasUpdate(frameTime)) { + hasUpdates = true; + dashPatternValue = dashPattern->value(frameTime); + } + } + + if (dashPhase) { + if (dashPhase->hasUpdate(frameTime)) { + hasUpdates = true; + dashPhaseValue = dashPhase->value(frameTime).value; + } + } + + if (!_stroke || hasUpdates) { + bool hasNonZeroDashes = false; + if (!dashPatternValue.values.empty()) { + for (const auto &value : dashPatternValue.values) { + if (value != 0) { + hasNonZeroDashes = true; + break; + } + } + } + + auto solid = std::make_shared(colorValue, opacityValue * 0.01); + _stroke = std::make_shared( + solid, + widthValue, + lineJoin, + lineCap, + miterLimit, + hasNonZeroDashes ? dashPhaseValue : 0.0, + hasNonZeroDashes ? dashPatternValue.values : std::vector() + ); + } + } + + virtual std::shared_ptr stroke() override { + return _stroke; + } + + private: + LineJoin lineJoin; + LineCap lineCap; + double miterLimit = 4.0; + + KeyframeInterpolator color; + Color colorValue = Color(0.0, 0.0, 0.0, 0.0); + + KeyframeInterpolator opacity; + double opacityValue = 0.0; + + KeyframeInterpolator width; + double widthValue = 0.0; + + std::unique_ptr dashPattern; + DashPattern dashPatternValue = DashPattern({}); + + std::unique_ptr> dashPhase; + double dashPhaseValue = 0.0; + + std::shared_ptr _stroke; + }; + + class GradientStrokeOutput : public StrokeOutput { + public: + GradientStrokeOutput(GradientStroke const &gradientStroke) : + lineJoin(gradientStroke.lineJoin), + lineCap(gradientStroke.lineCap), + miterLimit(gradientStroke.miterLimit.value_or(4.0)), + numberOfColors(gradientStroke.numberOfColors), + gradientType(gradientStroke.gradientType), + colors(gradientStroke.colors.keyframes), + startPoint(gradientStroke.startPoint.keyframes), + endPoint(gradientStroke.endPoint.keyframes), + opacity(gradientStroke.opacity.keyframes), + width(gradientStroke.width.keyframes) { + if (gradientStroke.dashPattern.has_value()) { + StrokeShapeDashConfiguration dashConfiguration(gradientStroke.dashPattern.value()); + dashPattern = std::make_unique(dashConfiguration.dashPatterns); + + if (!dashConfiguration.dashPhase.empty()) { + dashPhase = std::make_unique>(dashConfiguration.dashPhase); + } + } + } + + virtual ~GradientStrokeOutput() = default; + + virtual void update(AnimationFrameTime frameTime) override { + bool hasUpdates = false; + + if (colors.hasUpdate(frameTime)) { + hasUpdates = true; + colorsValue = colors.value(frameTime); + } + + if (startPoint.hasUpdate(frameTime)) { + hasUpdates = true; + startPointValue = startPoint.value(frameTime); + } + + if (endPoint.hasUpdate(frameTime)) { + hasUpdates = true; + endPointValue = endPoint.value(frameTime); + } + + if (opacity.hasUpdate(frameTime)) { + hasUpdates = true; + opacityValue = opacity.value(frameTime).value; + } + + if (width.hasUpdate(frameTime)) { + hasUpdates = true; + widthValue = width.value(frameTime).value; + } + + if (dashPattern) { + if (dashPattern->hasUpdate(frameTime)) { + hasUpdates = true; + dashPatternValue = dashPattern->value(frameTime); + } + } + + if (dashPhase) { + if (dashPhase->hasUpdate(frameTime)) { + hasUpdates = true; + dashPhaseValue = dashPhase->value(frameTime).value; + } + } + + if (!_stroke || hasUpdates) { + bool hasNonZeroDashes = false; + if (!dashPatternValue.values.empty()) { + for (const auto &value : dashPatternValue.values) { + if (value != 0) { + hasNonZeroDashes = true; + break; + } + } + } + + std::vector colors; + std::vector locations; + getGradientParameters(numberOfColors, colorsValue, colors, locations); + + auto gradient = std::make_shared( + opacityValue * 0.01, + gradientType, + colors, + locations, + Vector2D(startPointValue.x, startPointValue.y), + Vector2D(endPointValue.x, endPointValue.y) + ); + _stroke = std::make_shared( + gradient, + widthValue, + lineJoin, + lineCap, + miterLimit, + hasNonZeroDashes ? dashPhaseValue : 0.0, + hasNonZeroDashes ? dashPatternValue.values : std::vector() + ); + } + } + + virtual std::shared_ptr stroke() override { + return _stroke; + } + + private: + LineJoin lineJoin; + LineCap lineCap; + double miterLimit = 4.0; + + int numberOfColors = 0; + GradientType gradientType; + + KeyframeInterpolator colors; + GradientColorSet colorsValue; + + KeyframeInterpolator startPoint; + Vector3D startPointValue = Vector3D(0.0, 0.0, 0.0); + + KeyframeInterpolator endPoint; + Vector3D endPointValue = Vector3D(0.0, 0.0, 0.0); + + KeyframeInterpolator opacity; + double opacityValue = 0.0; + + KeyframeInterpolator width; + double widthValue = 0.0; + + std::unique_ptr dashPattern; + DashPattern dashPatternValue = DashPattern({}); + + std::unique_ptr> dashPhase; + double dashPhaseValue = 0.0; + + std::shared_ptr _stroke; + }; + + struct TrimParams { + double start = 0.0; + double end = 0.0; + double offset = 0.0; + TrimType type = TrimType::Simultaneously; + size_t subItemLimit = 0; + + TrimParams(double start_, double end_, double offset_, TrimType type_, size_t subItemLimit_) : + start(start_), + end(end_), + offset(offset_), + type(type_), + subItemLimit(subItemLimit_) { + } + }; + + class TrimParamsOutput { + public: + TrimParamsOutput(Trim const &trim, size_t subItemLimit) : + type(trim.trimType), + subItemLimit(subItemLimit), + start(trim.start.keyframes), + end(trim.end.keyframes), + offset(trim.offset.keyframes) { + } + + void update(AnimationFrameTime frameTime) { + if (start.hasUpdate(frameTime)) { + startValue = start.value(frameTime).value; + } + + if (end.hasUpdate(frameTime)) { + endValue = end.value(frameTime).value; + } + + if (offset.hasUpdate(frameTime)) { + offsetValue = offset.value(frameTime).value; + } + } + + TrimParams trimParams() { + double resolvedStartValue = startValue * 0.01; + double resolvedEndValue = endValue * 0.01; + double resolvedStart = std::min(resolvedStartValue, resolvedEndValue); + double resolvedEnd = std::max(resolvedStartValue, resolvedEndValue); + + double resolvedOffset = fmod(offsetValue, 360.0) / 360.0; + + return TrimParams(resolvedStart, resolvedEnd, resolvedOffset, type, subItemLimit); + } + + private: + TrimType type; + size_t subItemLimit = 0; + + KeyframeInterpolator start; + double startValue = 0.0; + + KeyframeInterpolator end; + double endValue = 0.0; + + KeyframeInterpolator offset; + double offsetValue = 0.0; + }; + + struct ShadingVariant { + std::shared_ptr fill; + std::shared_ptr stroke; + size_t subItemLimit = 0; + + std::shared_ptr renderTree; + }; + + struct TransformedPath { + BezierPath path; + CATransform3D transform; + + TransformedPath(BezierPath const &path_, CATransform3D const &transform_) : + path(path_), + transform(transform_) { + } + }; + + class PathOutput { + public: + PathOutput() { + } + virtual ~PathOutput() = default; + + virtual void update(AnimationFrameTime frameTime) = 0; + virtual BezierPath const *currentPath() = 0; + }; + + class StaticPathOutput : public PathOutput { + public: + explicit StaticPathOutput(BezierPath const &path) : + resolvedPath(path) { + } + + virtual void update(AnimationFrameTime frameTime) override { + } + + virtual BezierPath const *currentPath() override { + return &resolvedPath; + } + + private: + BezierPath resolvedPath; + }; + + class ShapePathOutput : public PathOutput { + public: + explicit ShapePathOutput(Shape const &shape) : + path(shape.path.keyframes) { + } + + virtual void update(AnimationFrameTime frameTime) override { + if (!hasValidData || path.hasUpdate(frameTime)) { + path.update(frameTime, resolvedPath); + } + + hasValidData = true; + } + + virtual BezierPath const *currentPath() override { + return &resolvedPath; + } + + private: + bool hasValidData = false; + + BezierPathKeyframeInterpolator path; + + BezierPath resolvedPath; + }; + + class RectanglePathOutput : public PathOutput { + public: + explicit RectanglePathOutput(Rectangle const &rectangle) : + direction(rectangle.direction.value_or(PathDirection::Clockwise)), + position(rectangle.position.keyframes), + size(rectangle.size.keyframes), + cornerRadius(rectangle.cornerRadius.keyframes) { + } + + virtual void update(AnimationFrameTime frameTime) override { + bool hasUpdates = false; + + if (!hasValidData || position.hasUpdate(frameTime)) { + hasUpdates = true; + positionValue = position.value(frameTime); + } + if (!hasValidData || size.hasUpdate(frameTime)) { + hasUpdates = true; + sizeValue = size.value(frameTime); + } + if (!hasValidData || cornerRadius.hasUpdate(frameTime)) { + hasUpdates = true; + cornerRadiusValue = cornerRadius.value(frameTime).value; + } + + if (hasUpdates) { + resolvedPath = makeRectangleBezierPath(Vector2D(positionValue.x, positionValue.y), Vector2D(sizeValue.x, sizeValue.y), cornerRadiusValue, direction); + } + + hasValidData = true; + } + + virtual BezierPath const *currentPath() override { + return &resolvedPath; + } + + private: + bool hasValidData = false; + + PathDirection direction; + + KeyframeInterpolator position; + Vector3D positionValue = Vector3D(0.0, 0.0, 0.0); + + KeyframeInterpolator size; + Vector3D sizeValue = Vector3D(0.0, 0.0, 0.0); + + KeyframeInterpolator cornerRadius; + double cornerRadiusValue = 0.0; + + BezierPath resolvedPath; + }; + + class EllipsePathOutput : public PathOutput { + public: + explicit EllipsePathOutput(Ellipse const &ellipse) : + direction(ellipse.direction.value_or(PathDirection::Clockwise)), + position(ellipse.position.keyframes), + size(ellipse.size.keyframes) { + } + + virtual void update(AnimationFrameTime frameTime) override { + bool hasUpdates = false; + + if (!hasValidData || position.hasUpdate(frameTime)) { + hasUpdates = true; + positionValue = position.value(frameTime); + } + if (!hasValidData || size.hasUpdate(frameTime)) { + hasUpdates = true; + sizeValue = size.value(frameTime); + } + + if (hasUpdates) { + resolvedPath = makeEllipseBezierPath(Vector2D(sizeValue.x, sizeValue.y), Vector2D(positionValue.x, positionValue.y), direction); + } + + hasValidData = true; + } + + virtual BezierPath const *currentPath() override { + return &resolvedPath; + } + + private: + bool hasValidData = false; + + PathDirection direction; + + KeyframeInterpolator position; + Vector3D positionValue = Vector3D(0.0, 0.0, 0.0); + + KeyframeInterpolator size; + Vector3D sizeValue = Vector3D(0.0, 0.0, 0.0); + + BezierPath resolvedPath; + }; + + class StarPathOutput : public PathOutput { + public: + explicit StarPathOutput(Star const &star) : + direction(star.direction.value_or(PathDirection::Clockwise)), + position(star.position.keyframes), + outerRadius(star.outerRadius.keyframes), + outerRoundedness(star.outerRoundness.keyframes), + rotation(star.rotation.keyframes), + points(star.points.keyframes) { + if (star.innerRadius.has_value()) { + innerRadius = std::make_unique>(std::make_shared>(star.innerRadius->keyframes)); + } else { + innerRadius = std::make_unique>(std::make_shared>(Vector1D(0.0))); + } + + if (star.innerRoundness.has_value()) { + innerRoundedness = std::make_unique>(std::make_shared>(star.innerRoundness->keyframes)); + } else { + innerRoundedness = std::make_unique>(std::make_shared>(Vector1D(0.0))); + } + } + + virtual void update(AnimationFrameTime frameTime) override { + bool hasUpdates = false; + + if (!hasValidData || position.hasUpdate(frameTime)) { + hasUpdates = true; + positionValue = position.value(frameTime); + } + + if (!hasValidData || outerRadius.hasUpdate(frameTime)) { + hasUpdates = true; + outerRadiusValue = outerRadius.value(frameTime).value; + } + + innerRadius->update(frameTime); + if (!hasValidData || innerRadiusValue != innerRadius->value().value) { + hasUpdates = true; + innerRadiusValue = innerRadius->value().value; + } + + if (!hasValidData || outerRoundedness.hasUpdate(frameTime)) { + hasUpdates = true; + outerRoundednessValue = outerRoundedness.value(frameTime).value; + } + + innerRoundedness->update(frameTime); + if (!hasValidData || innerRoundednessValue != innerRoundedness->value().value) { + hasUpdates = true; + innerRoundednessValue = innerRoundedness->value().value; + } + + if (!hasValidData || points.hasUpdate(frameTime)) { + hasUpdates = true; + pointsValue = points.value(frameTime).value; + } + + if (!hasValidData || rotation.hasUpdate(frameTime)) { + hasUpdates = true; + rotationValue = rotation.value(frameTime).value; + } + + if (hasUpdates) { + resolvedPath = makeStarBezierPath(Vector2D(positionValue.x, positionValue.y), outerRadiusValue, innerRadiusValue, outerRoundednessValue, innerRoundednessValue, pointsValue, rotationValue, direction); + } + + hasValidData = true; + } + + virtual BezierPath const *currentPath() override { + return &resolvedPath; + } + + private: + bool hasValidData = false; + + PathDirection direction; + + KeyframeInterpolator position; + Vector3D positionValue = Vector3D(0.0, 0.0, 0.0); + + KeyframeInterpolator outerRadius; + double outerRadiusValue = 0.0; + + KeyframeInterpolator outerRoundedness; + double outerRoundednessValue = 0.0; + + std::unique_ptr> innerRadius; + double innerRadiusValue = 0.0; + + std::unique_ptr> innerRoundedness; + double innerRoundednessValue = 0.0; + + KeyframeInterpolator rotation; + double rotationValue = 0.0; + + KeyframeInterpolator points; + double pointsValue = 0.0; + + BezierPath resolvedPath; + }; + + class TransformOutput { + public: + TransformOutput(std::shared_ptr shapeTransform) { + if (shapeTransform->anchor) { + _anchor = std::make_unique>(shapeTransform->anchor->keyframes); + } + if (shapeTransform->position) { + _position = std::make_unique>(shapeTransform->position->keyframes); + } + if (shapeTransform->scale) { + _scale = std::make_unique>(shapeTransform->scale->keyframes); + } + if (shapeTransform->rotation) { + _rotation = std::make_unique>(shapeTransform->rotation->keyframes); + } + if (shapeTransform->skew) { + _skew = std::make_unique>(shapeTransform->skew->keyframes); + } + if (shapeTransform->skewAxis) { + _skewAxis = std::make_unique>(shapeTransform->skewAxis->keyframes); + } + if (shapeTransform->opacity) { + _opacity = std::make_unique>(shapeTransform->opacity->keyframes); + } + } + + void update(AnimationFrameTime frameTime) { + bool hasUpdates = false; + + if (!hasValidData) { + hasUpdates = true; + } + if (_anchor && _anchor->hasUpdate(frameTime)) { + hasUpdates = true; + } + if (_position && _position->hasUpdate(frameTime)) { + hasUpdates = true; + } + if (_scale && _scale->hasUpdate(frameTime)) { + hasUpdates = true; + } + if (_rotation && _rotation->hasUpdate(frameTime)) { + hasUpdates = true; + } + if (_skew && _skew->hasUpdate(frameTime)) { + hasUpdates = true; + } + if (_skewAxis && _skewAxis->hasUpdate(frameTime)) { + hasUpdates = true; + } + if (_opacity && _opacity->hasUpdate(frameTime)) { + hasUpdates = true; + } + + if (hasUpdates) { + //TODO:optimize by storing components + + Vector3D anchorValue(0.0, 0.0, 0.0); + if (_anchor) { + anchorValue = _anchor->value(frameTime); + } + + Vector3D positionValue(0.0, 0.0, 0.0); + if (_position) { + positionValue = _position->value(frameTime); + } + + Vector3D scaleValue(100.0, 100.0, 100.0); + if (_scale) { + scaleValue = _scale->value(frameTime); + } + + double rotationValue = 0.0; + if (_rotation) { + rotationValue = _rotation->value(frameTime).value; + } + + double skewValue = 0.0; + if (_skew) { + skewValue = _skew->value(frameTime).value; + } + + double skewAxisValue = 0.0; + if (_skewAxis) { + skewAxisValue = _skewAxis->value(frameTime).value; + } + + if (_opacity) { + _opacityValue = _opacity->value(frameTime).value * 0.01; + } else { + _opacityValue = 1.0; + } + + _transformValue = CATransform3D::identity().translated(Vector2D(positionValue.x, positionValue.y)).rotated(rotationValue).skewed(-skewValue, skewAxisValue).scaled(Vector2D(scaleValue.x * 0.01, scaleValue.y * 0.01)).translated(Vector2D(-anchorValue.x, -anchorValue.y)); + + hasValidData = true; + } + } + + CATransform3D const &transform() { + return _transformValue; + } + + double opacity() { + return _opacityValue; + } + + private: + bool hasValidData = false; + + std::unique_ptr> _anchor; + std::unique_ptr> _position; + std::unique_ptr> _scale; + std::unique_ptr> _rotation; + std::unique_ptr> _skew; + std::unique_ptr> _skewAxis; + std::unique_ptr> _opacity; + + CATransform3D _transformValue = CATransform3D::identity(); + double _opacityValue = 1.0; + }; + + class ContentItem { + public: + ContentItem() { + } + + public: + bool isGroup = false; + + void setPath(std::unique_ptr &&path_) { + path = std::move(path_); + } + + void setTransform(std::unique_ptr &&transform_) { + transform = std::move(transform_); + } + + std::shared_ptr const &renderTree() const { + return _renderTree; + } + + private: + std::unique_ptr path; + std::unique_ptr transform; + + std::vector shadings; + std::vector> trims; + + std::vector> subItems; + + std::shared_ptr _renderTree; + + private: + std::vector collectPaths(AnimationFrameTime frameTime, size_t subItemLimit, CATransform3D parentTransform) { + std::vector mappedPaths; + + CATransform3D effectiveTransform = parentTransform; + CATransform3D effectiveChildTransform = parentTransform; + + size_t maxSubitem = std::min(subItems.size(), subItemLimit); + + if (path) { + path->update(frameTime); + mappedPaths.emplace_back(*(path->currentPath()), effectiveTransform); + } + + for (size_t i = 0; i < maxSubitem; i++) { + auto &subItem = subItems[i]; + CATransform3D subItemTransform = effectiveChildTransform; + + if (subItem->isGroup && subItem->transform) { + subItem->transform->update(frameTime); + subItemTransform = subItem->transform->transform() * subItemTransform; + } + + std::optional currentTrim; + if (!trims.empty()) { + trims[0]->update(frameTime); + currentTrim = trims[0]->trimParams(); + } + + auto subItemPaths = subItem->collectPaths(frameTime, INT32_MAX, subItemTransform); + + if (currentTrim) { + CompoundBezierPath tempPath; + for (auto &path : subItemPaths) { + tempPath.appendPath(path.path.copyUsingTransform(path.transform)); + } + CompoundBezierPath trimmedPath = trimCompoundPath(tempPath, currentTrim->start, currentTrim->end, currentTrim->offset, currentTrim->type); + for (auto &path : trimmedPath.paths) { + mappedPaths.emplace_back(path, CATransform3D::identity()); + } + } else { + for (auto &path : subItemPaths) { + mappedPaths.emplace_back(path.path, path.transform); + } + } + } + + return mappedPaths; + } + + public: + void addSubItem(std::shared_ptr const &subItem) { + subItems.push_back(subItem); + } + + void addFill(std::shared_ptr fill) { + ShadingVariant shading; + shading.subItemLimit = subItems.size(); + shading.fill = fill; + shadings.insert(shadings.begin(), shading); + } + + void addStroke(std::shared_ptr stroke) { + ShadingVariant shading; + shading.subItemLimit = subItems.size(); + shading.stroke = stroke; + shadings.insert(shadings.begin(), shading); + } + + void addTrim(Trim const &trim) { + trims.push_back(std::make_shared(trim, subItems.size())); + } + + public: + void initializeRenderChildren() { + _renderTree = std::make_shared( + CGRect(0.0, 0.0, 0.0, 0.0), + Vector2D(0.0, 0.0), + CATransform3D::identity(), + 1.0, + false, + false, + nullptr, + std::vector>(), + nullptr, + false + ); + + if (!shadings.empty()) { + for (int i = 0; i < shadings.size(); i++) { + auto &shadingVariant = shadings[i]; + + if (!(shadingVariant.fill || shadingVariant.stroke)) { + continue; + } + + auto shadingRenderTree = std::make_shared( + CGRect(0.0, 0.0, 0.0, 0.0), + Vector2D(0.0, 0.0), + CATransform3D::identity(), + 1.0, + false, + false, + nullptr, + std::vector>(), + nullptr, + false + ); + shadingVariant.renderTree = shadingRenderTree; + _renderTree->_subnodes.push_back(shadingRenderTree); + } + } + + if (isGroup && !subItems.empty()) { + std::vector> subItemNodes; + for (int i = (int)subItems.size() - 1; i >= 0; i--) { + subItems[i]->initializeRenderChildren(); + subItemNodes.push_back(subItems[i]->_renderTree); + } + + if (!subItemNodes.empty()) { + _renderTree->_subnodes.push_back(std::make_shared( + CGRect(0.0, 0.0, 0.0, 0.0), + Vector2D(0.0, 0.0), + CATransform3D::identity(), + 1.0, + false, + false, + nullptr, + subItemNodes, + nullptr, + false + )); + } + } + } + + public: + void renderChildren(AnimationFrameTime frameTime, std::optional parentTrim) { + CATransform3D containerTransform = CATransform3D::identity(); + double containerOpacity = 1.0; + if (transform) { + transform->update(frameTime); + containerTransform = transform->transform(); + containerOpacity = transform->opacity(); + } + _renderTree->_transform = containerTransform; + _renderTree->_alpha = containerOpacity; + + for (int i = 0; i < shadings.size(); i++) { + const auto &shadingVariant = shadings[i]; + + if (!(shadingVariant.fill || shadingVariant.stroke)) { + continue; + } + + CompoundBezierPath compoundPath; + auto paths = collectPaths(frameTime, shadingVariant.subItemLimit, CATransform3D::identity()); + for (const auto &path : paths) { + compoundPath.appendPath(path.path.copyUsingTransform(path.transform)); + } + + //std::optional currentTrim = parentTrim; + //TODO:investigate + /*if (!trims.empty()) { + currentTrim = trims[0]; + }*/ + + if (parentTrim) { + compoundPath = trimCompoundPath(compoundPath, parentTrim->start, parentTrim->end, parentTrim->offset, parentTrim->type); + } + + std::vector resultPaths; + for (const auto &path : compoundPath.paths) { + resultPaths.push_back(path); + } + + std::shared_ptr content; + + std::shared_ptr fill; + if (shadingVariant.fill) { + shadingVariant.fill->update(frameTime); + fill = shadingVariant.fill->fill(); + } + + std::shared_ptr stroke; + if (shadingVariant.stroke) { + shadingVariant.stroke->update(frameTime); + stroke = shadingVariant.stroke->stroke(); + } + + content = std::make_shared( + resultPaths, + stroke, + fill + ); + + shadingVariant.renderTree->_content = content; + } + + if (isGroup && !subItems.empty()) { + for (int i = (int)subItems.size() - 1; i >= 0; i--) { + std::optional childTrim = parentTrim; + for (const auto &trim : trims) { + trim->update(frameTime); + + if (i < (int)trim->trimParams().subItemLimit) { + //TODO:allow combination + //assert(!parentTrim); + childTrim = trim->trimParams(); + } + } + + subItems[i]->renderChildren(frameTime, childTrim); + } + } + } + }; + +public: + ShapeLayerPresentationTree(std::vector> const &items) { + itemTree = std::make_shared(); + itemTree->isGroup = true; + ShapeLayerPresentationTree::renderTreeContent(items, itemTree); + } + + ShapeLayerPresentationTree(std::shared_ptr const &solidLayer) { + itemTree = std::make_shared(); + itemTree->isGroup = true; + + std::vector> items; + items.push_back(std::make_shared( + std::nullopt, + std::nullopt, + std::nullopt, + std::nullopt, + solidLayer->hidden, + std::nullopt, + std::nullopt, + std::nullopt, + std::nullopt, + KeyframeGroup(Vector3D(0.0, 0.0, 0.0)), + KeyframeGroup(Vector3D(solidLayer->width, solidLayer->height, 0.0)), + KeyframeGroup(Vector1D(0.0)) + )); + ShapeLayerPresentationTree::renderTreeContent(items, itemTree); + } + + virtual ~ShapeLayerPresentationTree() = default; + +private: + static void renderTreeContent(std::vector> const &items, std::shared_ptr &itemTree) { + for (const auto &item : items) { + if (item->hidden()) { + continue; + } + + switch (item->type) { + case ShapeType::Fill: { + Fill const &fill = *((Fill *)item.get()); + + itemTree->addFill(std::make_shared(fill)); + + break; + } + case ShapeType::GradientFill: { + GradientFill const &gradientFill = *((GradientFill *)item.get()); + + itemTree->addFill(std::make_shared(gradientFill)); + + break; + } + case ShapeType::Stroke: { + Stroke const &stroke = *((Stroke *)item.get()); + + itemTree->addStroke(std::make_shared(stroke)); + + break; + } + case ShapeType::GradientStroke: { + GradientStroke const &gradientStroke = *((GradientStroke *)item.get()); + + itemTree->addStroke(std::make_shared(gradientStroke)); + + break; + } + case ShapeType::Group: { + Group const &group = *((Group *)item.get()); + + auto groupItem = std::make_shared(); + groupItem->isGroup = true; + + ShapeLayerPresentationTree::renderTreeContent(group.items, groupItem); + + itemTree->addSubItem(groupItem); + + break; + } + case ShapeType::Shape: { + Shape const &shape = *((Shape *)item.get()); + + auto shapeItem = std::make_shared(); + shapeItem->setPath(std::make_unique(shape)); + itemTree->addSubItem(shapeItem); + + break; + } + case ShapeType::Trim: { + Trim const &trim = *((Trim *)item.get()); + + itemTree->addTrim(trim); + + break; + } + case ShapeType::Transform: { + auto transform = std::static_pointer_cast(item); + + itemTree->setTransform(std::make_unique(transform)); + + break; + } + case ShapeType::Ellipse: { + Ellipse const &ellipse = *((Ellipse *)item.get()); + + auto shapeItem = std::make_shared(); + shapeItem->setPath(std::make_unique(ellipse)); + itemTree->addSubItem(shapeItem); + + break; + } + case ShapeType::Merge: { + //assert(false); + break; + } + case ShapeType::Rectangle: { + Rectangle const &rectangle = *((Rectangle *)item.get()); + + auto shapeItem = std::make_shared(); + shapeItem->setPath(std::make_unique(rectangle)); + itemTree->addSubItem(shapeItem); + + break; + } + case ShapeType::Repeater: { + assert(false); + break; + } + case ShapeType::Star: { + Star const &star = *((Star *)item.get()); + + auto shapeItem = std::make_shared(); + shapeItem->setPath(std::make_unique(star)); + itemTree->addSubItem(shapeItem); + + break; + } + case ShapeType::RoundedRectangle: { + //TODO:restore + break; + } + default: { + break; + } + } + } + + itemTree->initializeRenderChildren(); + } + +public: + std::shared_ptr itemTree; +}; + +ShapeCompositionLayer::ShapeCompositionLayer(std::shared_ptr const &shapeLayer) : +CompositionLayer(shapeLayer, Vector2D::Zero()) { + _contentTree = std::make_shared(shapeLayer->items); +} + +ShapeCompositionLayer::ShapeCompositionLayer(std::shared_ptr const &solidLayer) : +CompositionLayer(solidLayer, Vector2D::Zero()) { + _contentTree = std::make_shared(solidLayer); +} + +void ShapeCompositionLayer::displayContentsWithFrame(double frame, bool forceUpdates) { + _frameTime = frame; + _frameTimeInitialized = true; + + _contentTree->itemTree->renderChildren(_frameTime, std::nullopt); +} + +std::shared_ptr ShapeCompositionLayer::renderTreeNode() { + if (_contentsLayer->isHidden()) { + return nullptr; + } + + assert(_frameTimeInitialized); + + std::shared_ptr maskNode; + bool invertMask = false; + if (_matteLayer) { + maskNode = _matteLayer->renderTreeNode(); + if (maskNode && _matteType.has_value() && _matteType.value() == MatteType::Invert) { + invertMask = true; + } + } + + std::vector> renderTreeValue; + renderTreeValue.push_back(_contentTree->itemTree->renderTree()); + + //printf("Name: %s\n", keypathName().c_str()); + /*if (!maskNode && keypathName().find("Shape Layer 3") != -1) { + return std::make_shared( + bounds(), + _contentsLayer->position(), + _contentsLayer->transform(), + _contentsLayer->opacity(), + _contentsLayer->masksToBounds(), + _contentsLayer->isHidden(), + nullptr, + renderTreeValue, + nullptr, + false + ); + }*/ + + std::vector> subnodes; + subnodes.push_back(std::make_shared( + _contentsLayer->bounds(), + _contentsLayer->position(), + _contentsLayer->transform(), + _contentsLayer->opacity(), + _contentsLayer->masksToBounds(), + _contentsLayer->isHidden(), + nullptr, + renderTreeValue, + nullptr, + false + )); + + assert(position() == Vector2D::Zero()); + assert(transform().isIdentity()); + assert(opacity() == 1.0); + assert(!masksToBounds()); + assert(!isHidden()); + + assert(_contentsLayer->bounds() == CGRect(0.0, 0.0, 0.0, 0.0)); + assert(_contentsLayer->position() == Vector2D::Zero()); + assert(!_contentsLayer->masksToBounds()); + + return std::make_shared( + bounds(), + position(), + transform(), + opacity(), + masksToBounds(), + isHidden(), + nullptr, + subnodes, + maskNode, + invertMask + ); +} + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/ShapeCompositionLayer.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/ShapeCompositionLayer.hpp new file mode 100644 index 0000000000..ea88c5d0ac --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/ShapeCompositionLayer.hpp @@ -0,0 +1,31 @@ +#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 const &shapeLayer); + ShapeCompositionLayer(std::shared_ptr const &solidLayer); + + virtual void displayContentsWithFrame(double frame, bool forceUpdates) override; + virtual std::shared_ptr renderTreeNode() override; + +private: + std::shared_ptr _contentTree; + + AnimationFrameTime _frameTime = 0.0; + bool _frameTimeInitialized = false; +}; + +} + +#endif /* ShapeCompositionLayer_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/ShapeUtils/BezierPathUtils.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/ShapeUtils/BezierPathUtils.cpp new file mode 100644 index 0000000000..cd129d2606 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/ShapeUtils/BezierPathUtils.cpp @@ -0,0 +1,487 @@ +#include "BezierPathUtils.hpp" + +namespace lottie { + +BezierPath makeEllipseBezierPath( + Vector2D const &size, + Vector2D const ¢er, + PathDirection direction +) { + const double 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, + double cornerRadius, + PathDirection direction +) { + const double ControlPointConstant = 0.55228; + + Vector2D size = inputSize * 0.5; + double radius = std::min(std::min(cornerRadius, size.x), size.y); + + BezierPath bezierPath; + std::vector 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 { + double 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 double StarNodePolystarConstant = 0.47829; + +BezierPath makeStarBezierPath( + Vector2D const &position, + double outerRadius, + double innerRadius, + double inputOuterRoundedness, + double inputInnerRoundedness, + double numberOfPoints, + double rotation, + PathDirection direction +) { + double currentAngle = degreesToRadians(rotation - 90.0); + double anglePerPoint = (2.0 * M_PI) / numberOfPoints; + double halfAnglePerPoint = anglePerPoint / 2.0; + double partialPointAmount = numberOfPoints - floor(numberOfPoints); + double outerRoundedness = inputOuterRoundedness * 0.01; + double innerRoundedness = inputInnerRoundedness * 0.01; + + Vector2D point = Vector2D::Zero(); + + double 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 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++) { + double radius = longSegment ? outerRadius : innerRadius; + double 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 { + double cp1Theta = (atan2(previousPoint.y, previousPoint.x) - M_PI / 2.0); + double cp1Dx = cos(cp1Theta); + double cp1Dy = sin(cp1Theta); + + double cp2Theta = (atan2(point.y, point.x) - M_PI / 2.0); + double cp2Dx = cos(cp2Theta); + double cp2Dy = sin(cp2Theta); + + double cp1Roundedness = longSegment ? innerRoundedness : outerRoundedness; + double cp2Roundedness = longSegment ? outerRoundedness : innerRoundedness; + double cp1Radius = longSegment ? innerRadius : outerRadius; + double 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, double start, double end, double 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. + double startPosition = fmod(start + offset, 1.0); + double 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. + double totalLength = 0.0; + for (auto &upstreamPath : sourcePath.paths) { + totalLength += upstreamPath.length(); + } + + /// Now determine the start and end cut lengths + double startLength = startPosition * totalLength; + double endLength = endPosition * totalLength; + double 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. + + double pathCutLength = endLength - pathStart; + double subpathStart = 0.0; + double subpathEnd = subpathStart + pathContainer.length(); + if (pathCutLength < subpathEnd) { + /// This is the subpath that needs to be cut. + double 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. + double pathCutLength = startLength - pathStart; + // Clear paths from container + double subpathStart = 0.0; + double subpathEnd = subpathStart + pathContainer.length(); + + if (subpathStart < pathCutLength && pathCutLength < subpathEnd) { + /// This is the subpath that needs to be cut. + double 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. + double startCutLength = startLength - pathStart; + double endCutLength = endLength - pathStart; + + double subpathStart = 0.0; + + double 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 + double 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----------------------| + double 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---------| + double cutFromLength = startCutLength - subpathStart; + double 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; +} + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/ShapeUtils/BezierPathUtils.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/ShapeUtils/BezierPathUtils.hpp new file mode 100644 index 0000000000..9d9d27b612 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/ShapeUtils/BezierPathUtils.hpp @@ -0,0 +1,39 @@ +#ifndef BezierPaths_h +#define BezierPaths_h + +#include "Lottie/Private/Model/ShapeItems/Ellipse.hpp" +#include "Lottie/Private/Utility/Primitives/BezierPath.hpp" +#include "Lottie/Private/Utility/Primitives/CompoundBezierPath.hpp" +#include "Lottie/Private/Model/ShapeItems/Trim.hpp" + +namespace lottie { + +BezierPath makeEllipseBezierPath( + Vector2D const &size, + Vector2D const ¢er, + PathDirection direction +); + +BezierPath makeRectangleBezierPath( + Vector2D const &position, + Vector2D const &inputSize, + double cornerRadius, + PathDirection direction +); + +BezierPath makeStarBezierPath( + Vector2D const &position, + double outerRadius, + double innerRadius, + double inputOuterRoundedness, + double inputInnerRoundedness, + double numberOfPoints, + double rotation, + PathDirection direction +); + +CompoundBezierPath trimCompoundPath(CompoundBezierPath sourcePath, double start, double end, double offset, TrimType type); + +} + +#endif /* BezierPaths_h */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/TextCompositionLayer.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/TextCompositionLayer.cpp new file mode 100644 index 0000000000..5e773d361b --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/TextCompositionLayer.cpp @@ -0,0 +1,5 @@ +#include "TextCompositionLayer.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/TextCompositionLayer.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/TextCompositionLayer.hpp new file mode 100644 index 0000000000..57e0545345 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/TextCompositionLayer.hpp @@ -0,0 +1,124 @@ +#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 const &textLayer, std::shared_ptr textProvider, std::shared_ptr fontProvider) : + CompositionLayer(textLayer, Vector2D::Zero()) { + std::shared_ptr rootNode; + for (const auto &animator : textLayer->animators) { + rootNode = std::make_shared(rootNode, animator); + } + _rootNode = rootNode; + _textDocument = std::make_shared>(textLayer->text.keyframes); + + _textProvider = textProvider; + _fontProvider = fontProvider; + + //_contentsLayer->addSublayer(_textLayer); + + assert(false); + //self.textLayer.masksToBounds = false + //self.textLayer.isGeometryFlipped = true + + if (_rootNode) { + _childKeypaths.push_back(rootNode); + } + } + + std::shared_ptr const &textProvider() const { + return _textProvider; + } + void setTextProvider(std::shared_ptr const &textProvider) { + _textProvider = textProvider; + } + + std::shared_ptr const &fontProvider() const { + return _fontProvider; + } + void setFontProvider(std::shared_ptr const &fontProvider) { + _fontProvider = fontProvider; + } + + virtual void displayContentsWithFrame(double frame, bool forceUpdates) 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); + } + + assert(false); + /*// Get Text Attributes + let text = textDocument.value(frame: frame) as! TextDocument + let strokeColor = rootNode?.textOutputNode.strokeColor ?? text.strokeColorData?.cgColorValue + let strokeWidth = rootNode?.textOutputNode.strokeWidth ?? CGFloat(text.strokeWidth ?? 0) + let tracking = (CGFloat(text.fontSize) * (rootNode?.textOutputNode.tracking ?? CGFloat(text.tracking))) / 1000.0 + let matrix = rootNode?.textOutputNode.xform ?? CATransform3DIdentity + let textString = textProvider.textFor(keypathName: keypathName, sourceText: text.text) + let ctFont = fontProvider.fontFor(family: text.fontFamily, size: CGFloat(text.fontSize)) + + // Set all of the text layer options + textLayer.text = textString + textLayer.font = ctFont + textLayer.alignment = text.justification.textAlignment + textLayer.lineHeight = CGFloat(text.lineHeight) + textLayer.tracking = tracking + + if let fillColor = rootNode?.textOutputNode.fillColor { + textLayer.fillColor = fillColor + } else if let fillColor = text.fillColorData?.cgColorValue { + textLayer.fillColor = fillColor + } else { + textLayer.fillColor = nil + } + + textLayer.preferredSize = text.textFrameSize?.sizeValue + textLayer.strokeOnTop = text.strokeOverFill ?? false + textLayer.strokeWidth = strokeWidth + textLayer.strokeColor = strokeColor + textLayer.sizeToFit() + + textLayer.opacity = Float(rootNode?.textOutputNode.opacity ?? 1) + textLayer.transform = CATransform3DIdentity + textLayer.position = text.textFramePosition?.pointValue ?? CGPoint.zero + textLayer.transform = matrix*/ + } + +public: + virtual bool isTextCompositionLayer() const override { + return true; + } + +private: + std::shared_ptr _rootNode; + std::shared_ptr> _textDocument; + + //std::shared_ptr _textLayer; + std::shared_ptr _textProvider; + std::shared_ptr _fontProvider; +}; + +} + +#endif /* TextCompositionLayer_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/MainThreadAnimationLayer.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/MainThreadAnimationLayer.cpp new file mode 100644 index 0000000000..a205f16eba --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/MainThreadAnimationLayer.cpp @@ -0,0 +1,5 @@ +#include "MainThreadAnimationLayer.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/MainThreadAnimationLayer.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/MainThreadAnimationLayer.hpp new file mode 100644 index 0000000000..dd360588a5 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/MainThreadAnimationLayer.hpp @@ -0,0 +1,274 @@ +#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 imageForAsset(ImageAsset const &asset) { + return nullptr; + } +}; + +class MainThreadAnimationLayer: public CALayer { +public: + MainThreadAnimationLayer( + Animation const &animation, + std::shared_ptr const &imageProvider, + std::shared_ptr const &textProvider, + std::shared_ptr const &fontProvider + ) { + if (animation.assetLibrary) { + _layerImageProvider = std::make_shared(imageProvider, animation.assetLibrary->imageAssets); + } else { + std::map> imageAssets; + _layerImageProvider = std::make_shared(imageProvider, imageAssets); + } + + _layerTextProvider = std::make_shared(textProvider); + _layerFontProvider = std::make_shared(fontProvider); + + setBounds(CGRect(0.0, 0.0, animation.width, animation.height)); + + auto layers = initializeCompositionLayers( + animation.layers, + animation.assetLibrary, + _layerImageProvider, + textProvider, + fontProvider, + animation.framerate + ); + + std::vector> imageLayers; + std::vector> textLayers; + + std::shared_ptr mattedLayer; + + for (auto layerIt = layers.rbegin(); layerIt != layers.rend(); layerIt++) { + std::shared_ptr const &layer = *layerIt; + layer->setBounds(bounds()); + _animationLayers.push_back(layer); + + if (layer->isImageCompositionLayer()) { + imageLayers.push_back(std::static_pointer_cast(layer)); + } + if (layer->isTextCompositionLayer()) { + textLayers.push_back(std::static_pointer_cast(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(); + + setNeedsDisplay(true); + } + + void setRespectAnimationFrameRate(bool respectAnimationFrameRate) { + _respectAnimationFrameRate = respectAnimationFrameRate; + } + + void display() { + double newFrame = currentFrame(); + if (_respectAnimationFrameRate) { + newFrame = floor(newFrame); + } + for (const auto &layer : _animationLayers) { + layer->displayWithFrame(newFrame, false); + } + } + + std::vector> 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); + } + } + + void logHierarchyKeypaths() { + printf("Lottie: Logging Animation Keypaths\n"); + assert(false); + //animationLayers.forEach({ $0.logKeypaths(for: nil) }) + } + + void setValueProvider(std::shared_ptr 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 getValue(AnimationKeypath const &keypath, std::optional 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 getOriginalValue(AnimationKeypath const &keypath, std::optional 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::shared_ptr layerForKeypath(AnimationKeypath const &keyPath) { + assert(false); + /*for layer in animationLayers { + if let foundLayer = layer.layer(for: keypath) { + return foundLayer + } + }*/ + return nullptr; + } + + std::vector> animatorNodesForKeypath(AnimationKeypath const &keypath) { + std::vector> results; + /*for (const auto &layer : _animationLayers) { + if let nodes = layer.animatorNodes(for: keypath) { + results.append(contentsOf: nodes) + } + }*/ + return results; + } + + double currentFrame() const { + return _currentFrame; + } + void setCurrentFrame(double currentFrame) { + _currentFrame = currentFrame; + + for (size_t i = 0; i < _animationLayers.size(); i++) { + _animationLayers[i]->displayWithFrame(_currentFrame, false); + } + } + + std::shared_ptr imageProvider() const { + return _layerImageProvider->imageProvider(); + } + void setImageProvider(std::shared_ptr const &imageProvider) { + _layerImageProvider->setImageProvider(imageProvider); + } + + std::shared_ptr textProvider() const { + return _layerTextProvider->textProvider(); + } + void setTextProvider(std::shared_ptr const &textProvider) { + _layerTextProvider->setTextProvider(textProvider); + } + + std::shared_ptr fontProvider() const { + return _layerFontProvider->fontProvider(); + } + void setFontProvider(std::shared_ptr const &fontProvider) { + _layerFontProvider->setFontProvider(fontProvider); + } + + virtual std::shared_ptr renderTreeNode() { + std::vector> 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(); + if (node) { + subnodes.push_back(node); + } + } + } + + return std::make_shared( + bounds(), + position(), + CATransform3D::identity(), + 1.0, + false, + false, + nullptr, + subnodes, + nullptr, + false + ); + } + +private: + // MARK: Internal + + /// The animatable Current Frame Property + double _currentFrame = 0.0; + + std::shared_ptr _imageProvider; + std::shared_ptr _textProvider; + std::shared_ptr _fontProvider; + + bool _respectAnimationFrameRate = true; + + std::vector> _animationLayers; + + std::shared_ptr _layerImageProvider; + std::shared_ptr _layerTextProvider; + std::shared_ptr _layerFontProvider; +}; + +} + +#endif /* MainThreadAnimationLayer_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/Utility/CompositionLayersInitializer.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/Utility/CompositionLayersInitializer.cpp new file mode 100644 index 0000000000..bec737cb0a --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/Utility/CompositionLayersInitializer.cpp @@ -0,0 +1,112 @@ +#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> initializeCompositionLayers( + std::vector> const &layers, + std::shared_ptr const &assetLibrary, + std::shared_ptr const &layerImageProvider, + std::shared_ptr const &textProvider, + std::shared_ptr const &fontProvider, + double frameRate +) { + std::vector> compositionLayers; + std::map> layerMap; + + /// Organize the assets into a dictionary of [ID : ImageAsset] + std::vector> childLayers; + + for (const auto &layer : layers) { + if (layer->hidden) { + auto genericLayer = std::make_shared(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(std::static_pointer_cast(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(std::static_pointer_cast(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(layer); + auto precompAssetIt = assetLibrary->precompAssets.find(precompLayer->referenceID); + if (precompAssetIt != assetLibrary->precompAssets.end()) { + auto precompContainer = std::make_shared( + 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(layer); + auto imageAssetIt = assetLibrary->imageAssets.find(imageLayer->referenceID); + if (imageAssetIt != assetLibrary->imageAssets.end()) { + auto imageContainer = std::make_shared( + 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(std::static_pointer_cast(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(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; +} + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/Utility/CompositionLayersInitializer.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/Utility/CompositionLayersInitializer.hpp new file mode 100644 index 0000000000..247d8d5631 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/Utility/CompositionLayersInitializer.hpp @@ -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> initializeCompositionLayers( + std::vector> const &layers, + std::shared_ptr const &assetLibrary, + std::shared_ptr const &layerImageProvider, + std::shared_ptr const &textProvider, + std::shared_ptr const &fontProvider, + double frameRate +); + +} + +#endif /* CompositionLayersInitializer_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/Utility/LayerFontProvider.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/Utility/LayerFontProvider.cpp new file mode 100644 index 0000000000..d6123ee219 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/Utility/LayerFontProvider.cpp @@ -0,0 +1,5 @@ +#include "LayerFontProvider.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/Utility/LayerFontProvider.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/Utility/LayerFontProvider.hpp new file mode 100644 index 0000000000..ff9eaae19c --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/Utility/LayerFontProvider.hpp @@ -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 const &fontProvider) { + _fontProvider = fontProvider; + reloadTexts(); + } + + std::shared_ptr const &fontProvider() const { + return _fontProvider; + } + void setFontProvider(std::shared_ptr const &fontProvider) { + _fontProvider = fontProvider; + reloadTexts(); + } + + void addTextLayers(std::vector> const &layers) { + for (const auto &layer : layers) { + _textLayers.push_back(layer); + } + } + + void reloadTexts() { + for (const auto &layer : _textLayers) { + layer->setFontProvider(_fontProvider); + } + } + +private: + std::vector> _textLayers; + + std::shared_ptr _fontProvider; +}; + +} + +#endif /* LayerFontProvider_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/Utility/LayerImageProvider.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/Utility/LayerImageProvider.cpp new file mode 100644 index 0000000000..536c043749 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/Utility/LayerImageProvider.cpp @@ -0,0 +1,5 @@ +#include "LayerImageProvider.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/Utility/LayerImageProvider.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/Utility/LayerImageProvider.hpp new file mode 100644 index 0000000000..d9affbd1cf --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/Utility/LayerImageProvider.hpp @@ -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 const &imageProvider, std::map> const &assets) : + _imageProvider(imageProvider), + _imageAssets(assets) { + reloadImages(); + } + + std::shared_ptr imageProvider() const { + return _imageProvider; + } + void setImageProvider(std::shared_ptr const &imageProvider) { + _imageProvider = imageProvider; + reloadImages(); + } + + std::vector> const &imageLayers() const { + return _imageLayers; + } + + void addImageLayers(std::vector> 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 _imageProvider; + std::vector> _imageLayers; + + std::map> _imageAssets; +}; + +} + +#endif /* LayerImageProvider_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/Utility/LayerTextProvider.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/Utility/LayerTextProvider.cpp new file mode 100644 index 0000000000..22da907bd3 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/Utility/LayerTextProvider.cpp @@ -0,0 +1,5 @@ +#include "LayerTextProvider.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/Utility/LayerTextProvider.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/Utility/LayerTextProvider.hpp new file mode 100644 index 0000000000..82c96ec8f3 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/Utility/LayerTextProvider.hpp @@ -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 const &textProvider) { + _textProvider = textProvider; + reloadTexts(); + } + + std::shared_ptr const &textProvider() const { + return _textProvider; + } + void setTextProvider(std::shared_ptr const &textProvider) { + _textProvider = textProvider; + reloadTexts(); + } + + void addTextLayers(std::vector> const &layers) { + for (const auto &layer : layers) { + _textLayers.push_back(layer); + } + } + + void reloadTexts() { + for (const auto &layer : _textLayers) { + layer->setTextProvider(_textProvider); + } + } + +private: + std::vector> _textLayers; + + std::shared_ptr _textProvider; +}; + +} + +#endif /* LayerTextProvider_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/Utility/LayerTransformNode.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/Utility/LayerTransformNode.cpp new file mode 100644 index 0000000000..8f751a571f --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/Utility/LayerTransformNode.cpp @@ -0,0 +1,5 @@ +#include "LayerTransformNode.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/Utility/LayerTransformNode.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/Utility/LayerTransformNode.hpp new file mode 100644 index 0000000000..de22024145 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/Utility/LayerTransformNode.hpp @@ -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) { + _anchor = std::make_shared>(std::make_shared>(transform->anchorPoint().keyframes)); + _scale = std::make_shared>(std::make_shared>(transform->scale().keyframes)); + _rotation = std::make_shared>(std::make_shared>(transform->rotation().keyframes)); + _opacity = std::make_shared>(std::make_shared>(transform->opacity().keyframes)); + + std::map> 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>(std::make_shared>(transform->positionX()->keyframes)); + auto yPosition = std::make_shared>(std::make_shared>(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>(std::make_shared>(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> &properties() override { + return _properties; + } + + virtual std::vector> const &childKeypaths() const override { + return _childKeypaths; + } + + virtual std::string keypathName() const override { + return "Transform"; + } + + virtual std::map> keypathProperties() const override { + return _keypathProperties; + } + + virtual std::shared_ptr keypathLayer() const override { + return nullptr; + } + + std::shared_ptr> const &anchor() { + return _anchor; + } + + std::shared_ptr> const &scale() { + return _scale; + } + + std::shared_ptr> const &rotation() { + return _rotation; + } + + std::shared_ptr> const &position() { + return _position; + } + + std::shared_ptr> const &positionX() { + return _positionX; + } + + std::shared_ptr> const &positionY() { + return _positionY; + } + + std::shared_ptr> const &opacity() { + return _opacity; + } + +private: + std::map> _keypathProperties; + std::vector> _childKeypaths; + + std::vector> _properties; + + std::shared_ptr> _anchor; + std::shared_ptr> _scale; + std::shared_ptr> _rotation; + std::shared_ptr> _position; + std::shared_ptr> _positionX; + std::shared_ptr> _positionY; + std::shared_ptr> _opacity; +}; + +class LayerTransformNode: public AnimatorNode { +public: + LayerTransformNode(std::shared_ptr transform) : + AnimatorNode(nullptr), + _transformProperties(std::make_shared(transform)) { + _outputNode = std::make_shared(nullptr); + } + + virtual ~LayerTransformNode() = default; + + virtual std::shared_ptr outputNode() override { + return _outputNode; + } + + virtual std::shared_ptr propertyMap() const override { + return _transformProperties; + } + + virtual bool shouldRebuildOutputs(double frame) override { + return hasLocalUpdates() || hasUpstreamUpdates(); + } + + virtual void rebuildOutputs(double 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 = CATransform3D::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 const &transformProperties() { + return _transformProperties; + } + + float opacity() { + return _opacity; + } + + CATransform3D const &globalTransform() { + return _globalTransform; + } + +private: + std::shared_ptr _outputNode; + + std::shared_ptr _transformProperties; + + float _opacity = 1.0; + CATransform3D _localTransform = CATransform3D::identity(); + CATransform3D _globalTransform = CATransform3D::identity(); + +public: + virtual LayerTransformNode *asLayerTransformNode() override { + return this; + } +}; + +} + +#endif /* LayerTransformNode_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/NodeProperty.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/NodeProperty.cpp new file mode 100644 index 0000000000..eb9e3a5837 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/NodeProperty.cpp @@ -0,0 +1,5 @@ +#include "NodeProperty.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/NodeProperty.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/NodeProperty.hpp new file mode 100644 index 0000000000..1a6f995c1f --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/NodeProperty.hpp @@ -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 +class NodeProperty: public AnyNodeProperty { +public: + NodeProperty(std::shared_ptr> provider) : + _typedContainer(provider->value(0.0)), + _valueProvider(provider) { + _typedContainer.setNeedsUpdate(); + } + +public: + virtual AnyValue::Type valueType() const override { + return AnyValueType::type(); + } + + virtual T value() { + return _typedContainer.outputValue(); + } + + virtual bool needsUpdate(double frame) const override { + return _typedContainer.needsUpdate() || _valueProvider->hasUpdate(frame); + } + + virtual void setProvider(std::shared_ptr provider) override { + /*if (provider->valueType() != valueType()) { + return; + } + _valueProvider = provider; + _typedContainer.setNeedsUpdate();*/ + } + + virtual void update(double frame) override { + _typedContainer.setValue(_valueProvider->value(frame), frame); + } + +private: + ValueContainer _typedContainer; + std::shared_ptr> _valueProvider; + //std::shared_ptr _originalValueProvider; +}; + +} + +#endif /* NodeProperty_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/AnyNodeProperty.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/AnyNodeProperty.cpp new file mode 100644 index 0000000000..8609641d49 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/AnyNodeProperty.cpp @@ -0,0 +1,5 @@ +#include "AnyNodeProperty.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/AnyNodeProperty.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/AnyNodeProperty.hpp new file mode 100644 index 0000000000..f317e68b9f --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/AnyNodeProperty.hpp @@ -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 + +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(double frame) const = 0; + + /// Updates the property for the frame + virtual void update(double 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 provider) = 0; +}; + +} + +#endif /* AnyNodeProperty_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/AnyValueContainer.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/AnyValueContainer.cpp new file mode 100644 index 0000000000..b186f2e2f4 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/AnyValueContainer.cpp @@ -0,0 +1,5 @@ +#include "AnyValueContainer.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/AnyValueContainer.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/AnyValueContainer.hpp new file mode 100644 index 0000000000..55e6dac2e2 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/AnyValueContainer.hpp @@ -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 double lastUpdateFrame() const = 0; +}; + +} + +#endif /* AnyValueContainer_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/HasRenderUpdates.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/HasRenderUpdates.hpp new file mode 100644 index 0000000000..cf30fec769 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/HasRenderUpdates.hpp @@ -0,0 +1,13 @@ +#ifndef HasRenderUpdates_hpp +#define HasRenderUpdates_hpp + +namespace lottie { + +class HasRenderUpdates { +public: + virtual bool hasRenderUpdates(double forFrame) = 0; +}; + +} + +#endif /* HasRenderUpdates_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/HasUpdate.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/HasUpdate.hpp new file mode 100644 index 0000000000..f0c35b0530 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/HasUpdate.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 */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/KeypathSearchable.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/KeypathSearchable.cpp new file mode 100644 index 0000000000..62c08facba --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/KeypathSearchable.cpp @@ -0,0 +1,5 @@ +#include "KeypathSearchable.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/KeypathSearchable.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/KeypathSearchable.hpp new file mode 100644 index 0000000000..4586c7ddc2 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/KeypathSearchable.hpp @@ -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 +#include +#include +#include + +namespace lottie { + +class KeypathSearchable; + +class HasChildKeypaths { +public: + /// Children Keypaths + virtual std::vector> 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> keypathProperties() const = 0; + + virtual std::shared_ptr keypathLayer() const = 0; +}; + +} + +#endif /* KeypathSearchable_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/NodePropertyMap.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/NodePropertyMap.cpp new file mode 100644 index 0000000000..100edba8df --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/NodePropertyMap.cpp @@ -0,0 +1,5 @@ +#include "NodePropertyMap.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/NodePropertyMap.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/NodePropertyMap.hpp new file mode 100644 index 0000000000..64eff898f4 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/NodePropertyMap.hpp @@ -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 + +namespace lottie { + +class NodePropertyMap: virtual public HasChildKeypaths { +public: + virtual std::vector> &properties() = 0; + + bool needsLocalUpdate(double frame) { + for (auto &property : properties()) { + if (property->needsUpdate(frame)) { + return true; + } + } + return false; + } + + void updateNodeProperties(double frame) { + for (auto &property : properties()) { + property->update(frame); + } + } +}; + +class KeypathSearchableNodePropertyMap: virtual public NodePropertyMap, virtual public KeypathSearchable { +public: + virtual std::shared_ptr keypathLayer() const override { + return nullptr; + } +}; + +} + +#endif /* NodePropertyMap_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueContainer.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueContainer.cpp new file mode 100644 index 0000000000..2ead9de1ed --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueContainer.cpp @@ -0,0 +1,5 @@ +#include "ValueContainer.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueContainer.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueContainer.hpp new file mode 100644 index 0000000000..54f1548b14 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueContainer.hpp @@ -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 +class ValueContainer: public AnyValueContainer { +public: + ValueContainer(T value) : + _outputValue(value) { + } + +public: + double _lastUpdateFrame = std::numeric_limits::infinity(); + bool _needsUpdate = true; + + virtual AnyValue value() const override { + return AnyValue(_outputValue); + } + + virtual bool needsUpdate() const override { + return _needsUpdate; + } + + virtual double lastUpdateFrame() const override { + return _lastUpdateFrame; + } + + T _outputValue; + + T outputValue() { + return _outputValue; + } + void setOutputValue(T value) { + _outputValue = value; + _needsUpdate = false; + } + + void setValue(AnyValue value, double forFrame) { + if (value.type() == AnyValueType::type()) { + _needsUpdate = false; + _lastUpdateFrame = forFrame; + _outputValue = value.get(); + } + } + + virtual void setNeedsUpdate() override { + _needsUpdate = true; + } +}; + +} + +#endif /* ValueContainer_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/DashPatternInterpolator.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/DashPatternInterpolator.cpp new file mode 100644 index 0000000000..b3f91f556b --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/DashPatternInterpolator.cpp @@ -0,0 +1,5 @@ +#include "DashPatternInterpolator.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/DashPatternInterpolator.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/DashPatternInterpolator.hpp new file mode 100644 index 0000000000..2200c4c113 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/DashPatternInterpolator.hpp @@ -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, public std::enable_shared_from_this { +public: + /// Initialize with an array of array of keyframes. + DashPatternInterpolator(std::vector>> const &keyframeGroups) { + for (const auto &keyframeGroup : keyframeGroups) { + _keyframeInterpolators.push_back(std::make_shared>(keyframeGroup)); + } + } + + virtual ~DashPatternInterpolator() = default; + + virtual AnyValue::Type valueType() const override { + return AnyValueType::type(); + } + + virtual DashPattern value(AnimationFrameTime frame) override { + std::vector values; + for (const auto &interpolator : _keyframeInterpolators) { + values.push_back(interpolator->value(frame).value); + } + return DashPattern(std::move(values)); + } + + virtual bool hasUpdate(double frame) const override { + for (const auto &interpolator : _keyframeInterpolators) { + if (interpolator->hasUpdate(frame)) { + return true; + } + } + return false; + } + +private: + std::vector>> _keyframeInterpolators; +}; + +} + +#endif /* DashPatternInterpolator_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/KeyframeInterpolator.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/KeyframeInterpolator.cpp new file mode 100644 index 0000000000..8ec2364762 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/KeyframeInterpolator.cpp @@ -0,0 +1,5 @@ +#include "KeyframeInterpolator.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/KeyframeInterpolator.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/KeyframeInterpolator.hpp new file mode 100644 index 0000000000..4ea16e331a --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/KeyframeInterpolator.hpp @@ -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 +class KeyframeInterpolator: public ValueProvider, public std::enable_shared_from_this> { +public: + KeyframeInterpolator(std::vector> const &keyframes_) : + keyframes(keyframes_) { + assert(!keyframes.empty()); + } + + virtual ~KeyframeInterpolator() { + } + +public: + std::vector> keyframes; + + virtual AnyValue::Type valueType() const override { + return AnyValueType::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(double 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 lastUpdatedFrame; + + std::optional leadingIndex; + std::optional trailingIndex; + std::optional> leadingKeyframe; + std::optional> trailingKeyframe; + + /// Finds the appropriate Leading and Trailing keyframe index for the given time. + void updateSpanIndices(double 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> const &keyframes_) : + keyframes(keyframes_) { + assert(!keyframes.empty()); + } + +public: + std::vector> 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(double 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 lastUpdatedFrame; + + std::optional leadingIndex; + std::optional trailingIndex; + std::optional> leadingKeyframe; + std::optional> trailingKeyframe; + + /// Finds the appropriate Leading and Trailing keyframe index for the given time. + void updateSpanIndices(double 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 const &from, BezierPath &outPath) { + ValueInterpolator::setInplace(from.value, outPath); + } + + void interpolateInplace(Keyframe const &from, Keyframe const &to, double progress, BezierPath &outPath) { + std::optional spatialOutTangent2d; + if (from.spatialOutTangent) { + spatialOutTangent2d = Vector2D(from.spatialOutTangent->x, from.spatialOutTangent->y); + } + std::optional spatialInTangent2d; + if (to.spatialInTangent) { + spatialInTangent2d = Vector2D(to.spatialInTangent->x, to.spatialInTangent->y); + } + ValueInterpolator::interpolateInplace(from.value, to.value, progress, spatialOutTangent2d, spatialInTangent2d, outPath); + } +}; + +} + +#endif /* KeyframeInterpolator_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/SingleValueProvider.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/SingleValueProvider.cpp new file mode 100644 index 0000000000..face321499 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/SingleValueProvider.cpp @@ -0,0 +1,5 @@ +#include "SingleValueProvider.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/SingleValueProvider.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/SingleValueProvider.hpp new file mode 100644 index 0000000000..dc1212ba28 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/SingleValueProvider.hpp @@ -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 +class SingleValueProvider: public ValueProvider { +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::type(); + } + + virtual bool hasUpdate(double frame) const override { + return _hasUpdate; + } + +private: + T _value; + bool _hasUpdate = true; +}; + +} + +#endif /* SingleValueProvider_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/PassThroughOutputNode.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/PassThroughOutputNode.hpp new file mode 100644 index 0000000000..c1d2db8959 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/PassThroughOutputNode.hpp @@ -0,0 +1,72 @@ +#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 parent) : + _parent(parent) { + } + + virtual ~PassThroughOutputNode() = default; + + virtual std::shared_ptr 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 std::shared_ptr outputPath() override { + if (_parent) { + return _parent->outputPath(); + } + return nullptr; + } + + virtual bool hasOutputUpdates(double 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(double 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 _parent; + bool _hasUpdate = false; + bool _isEnabled = true; +}; + +} + +#endif /* PassThroughOutputNode_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/StrokeNode.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/StrokeNode.hpp new file mode 100644 index 0000000000..731a81149c --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/StrokeNode.hpp @@ -0,0 +1,36 @@ +#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/Protocols/RenderNode.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/DashPatternInterpolator.hpp" + +namespace lottie { + +class StrokeShapeDashConfiguration { +public: + StrokeShapeDashConfiguration(std::vector const &elements) { + /// Converts the `[DashElement]` data model into `lineDashPattern` and `lineDashPhase` + /// representations usable in a `CAShapeLayer` + for (const auto &dash : elements) { + if (dash.type == DashElementType::Offset) { + dashPhase = dash.value.keyframes; + } else { + dashPatterns.push_back(dash.value.keyframes); + } + } + } + +public: + std::vector>> dashPatterns; + std::vector> dashPhase; +}; + +} + +#endif /* StrokeNode_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/Nodes/Text/TextAnimatorNode.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/Nodes/Text/TextAnimatorNode.hpp new file mode 100644 index 0000000000..6acdb2ee20 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/Nodes/Text/TextAnimatorNode.hpp @@ -0,0 +1,367 @@ +#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 const &textAnimator) { + _keypathName = textAnimator->name.value_or(""); + + if (textAnimator->anchor) { + _anchor = std::make_shared>(std::make_shared>(textAnimator->anchor->keyframes)); + _keypathProperties.insert(std::make_pair("Anchor", _anchor)); + } + + if (textAnimator->position) { + _position = std::make_shared>(std::make_shared>(textAnimator->position->keyframes)); + _keypathProperties.insert(std::make_pair("Position", _position)); + } + + if (textAnimator->scale) { + _scale = std::make_shared>(std::make_shared>(textAnimator->scale->keyframes)); + _keypathProperties.insert(std::make_pair("Scale", _scale)); + } + + if (textAnimator->skew) { + _skew = std::make_shared>(std::make_shared>(textAnimator->skew->keyframes)); + _keypathProperties.insert(std::make_pair("Skew", _skew)); + } + + if (textAnimator->skewAxis) { + _skewAxis = std::make_shared>(std::make_shared>(textAnimator->skewAxis->keyframes)); + _keypathProperties.insert(std::make_pair("Skew Axis", _skewAxis)); + } + + if (textAnimator->rotation) { + _rotation = std::make_shared>(std::make_shared>(textAnimator->rotation->keyframes)); + _keypathProperties.insert(std::make_pair("Rotation", _rotation)); + } + + if (textAnimator->rotation) { + _opacity = std::make_shared>(std::make_shared>(textAnimator->opacity->keyframes)); + _keypathProperties.insert(std::make_pair("Opacity", _opacity)); + } + + if (textAnimator->strokeColor) { + _strokeColor = std::make_shared>(std::make_shared>(textAnimator->strokeColor->keyframes)); + _keypathProperties.insert(std::make_pair("Stroke Color", _strokeColor)); + } + + if (textAnimator->fillColor) { + _fillColor = std::make_shared>(std::make_shared>(textAnimator->fillColor->keyframes)); + _keypathProperties.insert(std::make_pair("Fill Color", _fillColor)); + } + + if (textAnimator->strokeWidth) { + _strokeWidth = std::make_shared>(std::make_shared>(textAnimator->strokeWidth->keyframes)); + _keypathProperties.insert(std::make_pair("Stroke Width", _strokeWidth)); + } + + if (textAnimator->tracking) { + _tracking = std::make_shared>(std::make_shared>(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> keypathProperties() const override { + return _keypathProperties; + } + + virtual std::vector> &properties() override { + return _properties; + } + + virtual std::vector> const &childKeypaths() const override { + return _childKeypaths; + } + + CATransform3D 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); + } + + double rotation = 0.0; + if (_rotation) { + rotation = _rotation->value().value; + } + + std::optional skew; + if (_skew) { + skew = _skew->value().value; + } + std::optional skewAxis; + if (_skewAxis) { + skewAxis = _skewAxis->value().value; + } + + return CATransform3D::makeTransform( + anchor, + position, + scale, + rotation, + skew, + skewAxis + ); + } + + virtual std::shared_ptr keypathLayer() const override { + return nullptr; + } + + double opacity() { + if (_opacity) { + return _opacity->value().value; + } else { + return 100.0; + } + } + + std::optional strokeColor() { + if (_strokeColor) { + return _strokeColor->value(); + } else { + return std::nullopt; + } + } + + std::optional fillColor() { + if (_fillColor) { + return _fillColor->value(); + } else { + return std::nullopt; + } + } + + double tracking() { + if (_tracking) { + return _tracking->value().value; + } else { + return 1.0; + } + } + + double strokeWidth() { + if (_strokeWidth) { + return _strokeWidth->value().value; + } else { + return 0.0; + } + } + +private: + std::string _keypathName; + + std::shared_ptr> _anchor; + std::shared_ptr> _position; + std::shared_ptr> _scale; + std::shared_ptr> _skew; + std::shared_ptr> _skewAxis; + std::shared_ptr> _rotation; + std::shared_ptr> _opacity; + std::shared_ptr> _strokeColor; + std::shared_ptr> _fillColor; + std::shared_ptr> _strokeWidth; + std::shared_ptr> _tracking; + + std::map> _keypathProperties; + std::vector> _childKeypaths; + std::vector> _properties; +}; + +class TextOutputNode: virtual public NodeOutput { +public: + TextOutputNode(std::shared_ptr parent) : + _parentTextNode(parent) { + } + + virtual ~TextOutputNode() = default; + + virtual std::shared_ptr parent() override { + return _parentTextNode; + } + + CATransform3D xform() { + if (_xform.has_value()) { + return _xform.value(); + } else if (_parentTextNode) { + return _parentTextNode->xform(); + } else { + return CATransform3D::identity(); + } + } + void setXform(CATransform3D const &xform) { + _xform = xform; + } + + double opacity() { + if (_opacity.has_value()) { + return _opacity.value(); + } else if (_parentTextNode) { + return _parentTextNode->opacity(); + } else { + return 1.0; + } + } + void setOpacity(double opacity) { + _opacity = opacity; + } + + std::optional strokeColor() { + if (_strokeColor.has_value()) { + return _strokeColor.value(); + } else if (_parentTextNode) { + return _parentTextNode->strokeColor(); + } else { + return std::nullopt; + } + } + void setStrokeColor(std::optional strokeColor) { + _strokeColor = strokeColor; + } + + std::optional fillColor() { + if (_fillColor.has_value()) { + return _fillColor.value(); + } else if (_parentTextNode) { + return _parentTextNode->fillColor(); + } else { + return std::nullopt; + } + } + void setFillColor(std::optional fillColor) { + _fillColor = fillColor; + } + + double tracking() { + if (_tracking.has_value()) { + return _tracking.value(); + } else if (_parentTextNode) { + return _parentTextNode->tracking(); + } else { + return 0.0; + } + } + void setTracking(double tracking) { + _tracking = tracking; + } + + double strokeWidth() { + if (_strokeWidth.has_value()) { + return _strokeWidth.value(); + } else if (_parentTextNode) { + return _parentTextNode->strokeWidth(); + } else { + return 0.0; + } + } + void setStrokeWidth(double strokeWidth) { + _strokeWidth = strokeWidth; + } + + virtual bool hasOutputUpdates(double frame) override { + // TODO Fix This + return true; + } + + virtual std::shared_ptr outputPath() override { + return _outputPath; + } + + virtual bool isEnabled() const override { + return _isEnabled; + } + virtual void setIsEnabled(bool isEnabled) override { + _isEnabled = isEnabled; + } + +private: + std::shared_ptr _parentTextNode; + bool _isEnabled = true; + + std::shared_ptr _outputPath; + + std::optional _xform; + std::optional _opacity; + std::optional _strokeColor; + std::optional _fillColor; + std::optional _tracking; + std::optional _strokeWidth; +}; + +class TextAnimatorNode: public AnimatorNode { +public: + TextAnimatorNode(std::shared_ptr const &parentNode, std::shared_ptr const &textAnimator) : + AnimatorNode(parentNode) { + std::shared_ptr parentOutputNode; + if (parentNode) { + parentOutputNode = parentNode->_textOutputNode; + } + _textOutputNode = std::make_shared(parentOutputNode); + + _textAnimatorProperties = std::make_shared(textAnimator); + } + + virtual ~TextAnimatorNode() = default; + + virtual std::shared_ptr outputNode() override { + return _textOutputNode; + } + + virtual std::shared_ptr propertyMap() const override { + return _textAnimatorProperties; + } + + virtual bool localUpdatesPermeateDownstream() override { + return true; + } + + virtual void rebuildOutputs(double 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; + + std::shared_ptr _textAnimatorProperties; +}; + +} + +#endif /* TextAnimatorNode_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/Protocols/AnimatorNode.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/Protocols/AnimatorNode.hpp new file mode 100644 index 0000000000..1d62e6c8d2 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/Protocols/AnimatorNode.hpp @@ -0,0 +1,235 @@ +#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 +#include + +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 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 propertyMap() const = 0; + + /// The upstream input node + std::shared_ptr parentNode() { + return _parentNode; + } + void setParentNode(std::shared_ptr const &parentNode) { + _parentNode = parentNode; + } + + /// The output of the node. + virtual std::shared_ptr outputNode() = 0; + + /// Update the outputs of the node. Called if local contents were update or if outputsNeedUpdate returns true. + virtual void rebuildOutputs(double 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 lastUpdateFrame() { + return _lastUpdateFrame; + } + virtual void setLastUpdateFrame(std::optional 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(double frame, bool forceLocalUpdate) { + /// Optional + return forceLocalUpdate; + } + virtual void performAdditionalOutputUpdates(double frame, bool forceOutputUpdate) { + /// Optional + } + + /// The default simply returns `hasLocalUpdates` + virtual bool shouldRebuildOutputs(double frame) { + return hasLocalUpdates(); + } + + virtual bool updateOutputs(double 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(double 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(); + } + + virtual void updateTree(double frame, bool forceUpdates) { + updateContents(frame, forceUpdates); + updateOutputs(frame, forceUpdates); + } + + /// 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> keypathProperties() const override { + return propertyMap()->keypathProperties(); + } + + /// Children Keypaths + virtual std::vector> const &childKeypaths() const override { + return propertyMap()->childKeypaths(); + } + + virtual std::shared_ptr 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 _parentNode; + bool _isEnabled = true; + bool _hasLocalUpdates = false; + bool _hasUpstreamUpdates = false; + std::optional _lastUpdateFrame; +}; + +} + +#endif /* AnimatorNode_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/Protocols/NodeOutput.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/Protocols/NodeOutput.hpp new file mode 100644 index 0000000000..970a51ae24 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/Protocols/NodeOutput.hpp @@ -0,0 +1,28 @@ +#ifndef NodeOutput_hpp +#define NodeOutput_hpp + +#include "Lottie/Public/Primitives/CGPath.hpp" + +#include + +namespace lottie { + +/// Defines the basic outputs of an animator node. +/// +class NodeOutput { +public: + /// The parent node. + virtual std::shared_ptr parent() = 0; + + /// Returns true if there are any updates upstream. OutputPath must be built before returning. + virtual bool hasOutputUpdates(double forFrame) = 0; + + virtual std::shared_ptr outputPath() = 0; + + virtual bool isEnabled() const = 0; + virtual void setIsEnabled(bool isEnabled) = 0; +}; + +} + +#endif /* NodeOutput_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/Protocols/RenderNode.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/Protocols/RenderNode.hpp new file mode 100644 index 0000000000..5db444d408 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/Protocols/RenderNode.hpp @@ -0,0 +1,73 @@ +#ifndef RenderNode_hpp +#define RenderNode_hpp + +#include "Lottie/Public/Primitives/CALayer.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/Protocols/AnimatorNode.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/Protocols/NodeOutput.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/HasRenderUpdates.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/HasUpdate.hpp" + +namespace lottie { + +class StrokeRenderer; +class FillRenderer; +class GradientStrokeRenderer; +class GradientFillRenderer; + +/// A protocol that defines anything with render instructions +class Renderable: virtual public HasRenderUpdates, virtual public HasUpdate { +public: + enum RenderableType { + Fill, + Stroke, + GradientFill, + GradientStroke + }; + +public: + /// Determines if the renderer requires a custom context for drawing. + /// If yes the shape layer will perform a custom drawing pass. + /// If no the shape layer will be a standard CAShapeLayer + virtual bool shouldRenderInContext() = 0; + + /// Passes in the CAShapeLayer to update + virtual void updateShapeLayer(std::shared_ptr const &layer) = 0; + + /// Asks the renderer what the renderable bounds is for the given box. + virtual CGRect renderBoundsFor(CGRect const &boundingBox) { + /// Optional + return boundingBox; + } + + /// Opportunity for renderers to inject sublayers + virtual void setupSublayers(std::shared_ptr const &layer) = 0; + + virtual RenderableType renderableType() const = 0; + + virtual StrokeRenderer *asStrokeRenderer() { + return nullptr; + } + + virtual FillRenderer *asFillRenderer() { + return nullptr; + } + + virtual GradientStrokeRenderer *asGradientStrokeRenderer() { + return nullptr; + } + + virtual GradientFillRenderer *asGradientFillRenderer() { + return nullptr; + } +}; + +/// A protocol that defines a node that holds render instructions +class RenderNode { +public: + virtual std::shared_ptr renderer() = 0; + virtual std::shared_ptr nodeOutput() = 0; +}; + +} + +#endif /* RenderNode_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/RenderLayers/GetGradientParameters.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/RenderLayers/GetGradientParameters.cpp new file mode 100644 index 0000000000..a1b830cf13 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/RenderLayers/GetGradientParameters.cpp @@ -0,0 +1,99 @@ +#include "GetGradientParameters.hpp" + +namespace lottie { + +void getGradientParameters(int numberOfColors, GradientColorSet const &colors, std::vector &outColors, std::vector &outLocations) { + std::vector alphaColors; + std::vector alphaValues; + std::vector alphaLocations; + + std::vector gradientColors; + std::vector 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) { + double 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 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 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]) { + double localLocation = 0.0; + if (colorLocations[i] != colorLocations[i + 1]) { + localLocation = remapDouble(location, colorLocations[i], colorLocations[i + 1], 0.0, 1.0); + } + color = ValueInterpolator::interpolate(gradientColors[i], gradientColors[i + 1], localLocation, std::nullopt, std::nullopt); + break; + } + } + + double 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]) { + double localLocation = 0.0; + if (alphaLocations[i] != alphaLocations[i + 1]) { + localLocation = remapDouble(location, alphaLocations[i], alphaLocations[i + 1], 0.0, 1.0); + } + alpha = ValueInterpolator::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; +} + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/RenderLayers/GetGradientParameters.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/RenderLayers/GetGradientParameters.hpp new file mode 100644 index 0000000000..f14c578644 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/RenderLayers/GetGradientParameters.hpp @@ -0,0 +1,13 @@ +#ifndef ShapeRenderLayer_hpp +#define ShapeRenderLayer_hpp + +#include "Lottie/Private/MainThread/NodeRenderSystem/Protocols/RenderNode.hpp" +#include "Lottie/Private/MainThread/NodeRenderSystem/Protocols/NodeOutput.hpp" + +namespace lottie { + +void getGradientParameters(int numberOfColors, GradientColorSet const &colors, std::vector &outColors, std::vector &outLocations); + +} + +#endif /* ShapeRenderLayer_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Animation.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Animation.cpp new file mode 100644 index 0000000000..577a56be35 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Animation.cpp @@ -0,0 +1,5 @@ +#include "Animation.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Animation.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Animation.hpp new file mode 100644 index 0000000000..55c0c68225 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Animation.hpp @@ -0,0 +1,314 @@ +#ifndef Animation_hpp +#define Animation_hpp + +#include "Lottie/Public/Primitives/AnimationTime.hpp" +#include "Lottie/Private/Utility/Primitives/CoordinateSpace.hpp" +#include "Lottie/Private/Model/Layers/LayerModel.hpp" +#include "Lottie/Private/Model/Text/Glyph.hpp" +#include "Lottie/Private/Model/Text/Font.hpp" +#include "Lottie/Private/Model/Objects/Marker.hpp" +#include "Lottie/Private/Model/Assets/AssetLibrary.hpp" +#include "Lottie/Private/Model/Objects/FitzModifier.hpp" + +#include "lottiejson11/lottiejson11.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" +#include "Lottie/Private/Model/Layers/LayerModelSerialization.hpp" + +#include +#include +#include +#include + +namespace lottie { + +/// The `Animation` model is the top level model object in Lottie. +/// +/// An `Animation` holds all of the animation data backing a Lottie Animation. +/// Codable, see JSON schema [here](https://github.com/airbnb/lottie-web/tree/master/docs/json). +class Animation { +public: + Animation( + std::optional name_, + std::optional tgs_, + AnimationFrameTime startFrame_, + AnimationFrameTime endFrame_, + double framerate_, + std::string const &version_, + std::optional type_, + int width_, + int height_, + std::vector> const &layers_, + std::optional>> glyphs_, + std::optional> fonts_, + std::shared_ptr assetLibrary_, + std::optional> markers_, + std::optional> fitzModifiers_, + std::optional meta_, + std::optional comps_ + ) : + startFrame(startFrame_), + endFrame(endFrame_), + framerate(framerate_), + name(name_), + version(version_), + tgs(tgs_), + type(type_), + width(width_), + height(height_), + layers(layers_), + glyphs(glyphs_), + fonts(fonts_), + assetLibrary(assetLibrary_), + markers(markers_), + fitzModifiers(fitzModifiers_), + meta(meta_), + comps(comps_) { + if (markers) { + std::map parsedMarkerMap; + for (const auto &marker : markers.value()) { + parsedMarkerMap.insert(std::make_pair(marker.name, marker)); + } + markerMap = std::move(parsedMarkerMap); + } + } + + Animation(const Animation&) = delete; + Animation& operator=(Animation&) = delete; + + static std::shared_ptr fromJson(json11::Json::object const &json) noexcept(false) { + auto name = getOptionalString(json, "nm"); + auto version = getString(json, "v"); + + auto tgs = getOptionalInt(json, "tgs"); + + std::optional type; + if (const auto typeRawValue = getOptionalInt(json, "ddd")) { + if (typeRawValue.value() == 0) { + type = CoordinateSpace::Type2d; + } else { + type = CoordinateSpace::Type3d; + } + } + + AnimationFrameTime startFrame = getDouble(json, "ip"); + AnimationFrameTime endFrame = getDouble(json, "op"); + + double framerate = getDouble(json, "fr"); + + int width = getInt(json, "w"); + int height = getInt(json, "h"); + + auto layerDictionaries = getObjectArray(json, "layers"); + std::vector> layers; + for (size_t i = 0; i < layerDictionaries.size(); i++) { + try { + auto layer = parseLayerModel(layerDictionaries[i]); + layers.push_back(layer); + } catch(...) { + throw LottieParsingException(); + } + } + + std::optional>> glyphs; + if (const auto glyphDictionaries = getOptionalObjectArray(json, "chars")) { + glyphs = std::vector>(); + for (const auto &glyphDictionary : glyphDictionaries.value()) { + glyphs->push_back(std::make_shared(glyphDictionary)); + } + } else { + glyphs = std::nullopt; + } + + std::optional> fonts; + if (const auto fontsDictionary = getOptionalObject(json, "fonts")) { + fonts = std::make_shared(fontsDictionary.value()); + } + + std::shared_ptr assetLibrary; + if (const auto assetLibraryData = getOptionalAny(json, "assets")) { + assetLibrary = std::make_shared(assetLibraryData.value()); + } + + std::optional> markers; + if (const auto markerDictionaries = getOptionalObjectArray(json, "markers")) { + markers = std::vector(); + for (const auto &markerDictionary : markerDictionaries.value()) { + markers->push_back(Marker(markerDictionary)); + } + } + std::optional> fitzModifiers; + if (const auto fitzModifierDictionaries = getOptionalObjectArray(json, "fitz")) { + fitzModifiers = std::vector(); + for (const auto &fitzModifierDictionary : fitzModifierDictionaries.value()) { + fitzModifiers->push_back(FitzModifier(fitzModifierDictionary)); + } + } + + auto meta = getOptionalAny(json, "meta"); + auto comps = getOptionalAny(json, "comps"); + + return std::make_shared( + name, + tgs, + startFrame, + endFrame, + framerate, + version, + type, + width, + height, + std::move(layers), + std::move(glyphs), + std::move(fonts), + assetLibrary, + std::move(markers), + fitzModifiers, + meta, + comps + ); + } + + json11::Json::object toJson() const { + json11::Json::object result; + + if (name.has_value()) { + result.insert(std::make_pair("nm", name.value())); + } + + result.insert(std::make_pair("v", json11::Json(version))); + + if (tgs.has_value()) { + result.insert(std::make_pair("tgs", tgs.value())); + } + + if (type.has_value()) { + switch (type.value()) { + case CoordinateSpace::Type2d: + result.insert(std::make_pair("ddd", json11::Json(0))); + break; + case CoordinateSpace::Type3d: + result.insert(std::make_pair("ddd", json11::Json(1))); + break; + } + } + + result.insert(std::make_pair("ip", json11::Json(startFrame))); + result.insert(std::make_pair("op", json11::Json(endFrame))); + result.insert(std::make_pair("fr", json11::Json(framerate))); + result.insert(std::make_pair("w", json11::Json(width))); + result.insert(std::make_pair("h", json11::Json(height))); + + json11::Json::array layersArray; + for (const auto &layer : layers) { + json11::Json::object layerJson; + layer->toJson(layerJson); + layersArray.push_back(layerJson); + } + result.insert(std::make_pair("layers", json11::Json(layersArray))); + + if (glyphs.has_value()) { + json11::Json::array glyphArray; + for (const auto &glyph : glyphs.value()) { + glyphArray.push_back(glyph->toJson()); + } + result.insert(std::make_pair("chars", json11::Json(glyphArray))); + } + + if (fonts.has_value()) { + result.insert(std::make_pair("fonts", fonts.value()->toJson())); + } + + if (assetLibrary) { + result.insert(std::make_pair("assets", assetLibrary->toJson())); + } + + if (markers.has_value()) { + json11::Json::array markerArray; + for (const auto &marker : markers.value()) { + markerArray.push_back(marker.toJson()); + } + result.insert(std::make_pair("markers", json11::Json(markerArray))); + } + + if (fitzModifiers.has_value()) { + json11::Json::array fitzModifierArray; + for (const auto &fitzModifier : fitzModifiers.value()) { + fitzModifierArray.push_back(fitzModifier.toJson()); + } + result.insert(std::make_pair("fitz", json11::Json(fitzModifierArray))); + } + + if (meta.has_value()) { + result.insert(std::make_pair("meta", meta.value())); + } + if (comps.has_value()) { + result.insert(std::make_pair("comps", comps.value())); + } + + return result; + } + +public: + /// The start time of the composition in frameTime. + AnimationFrameTime startFrame; + + /// The end time of the composition in frameTime. + AnimationFrameTime endFrame; + + /// The frame rate of the composition. + double framerate; + + /// Return all marker names, in order, or an empty list if none are specified + std::vector markerNames() { + if (!markers.has_value()) { + return {}; + } + std::vector result; + for (const auto &marker : markers.value()) { + result.push_back(marker.name); + } + return result; + } + + /// Animation name + std::optional name; + + /// The version of the JSON Schema. + std::string version; + + std::optional tgs; + + /// The coordinate space of the composition. + std::optional type; + + /// The height of the composition in points. + int width; + + /// The width of the composition in points. + int height; + + /// The list of animation layers + std::vector> layers; + + /// The list of glyphs used for text rendering + std::optional>> glyphs; + + /// The list of fonts used for text rendering + std::optional> fonts; + + /// Asset Library + std::shared_ptr assetLibrary; + + /// Markers + std::optional> markers; + std::optional> markerMap; + + std::optional> fitzModifiers; + + std::optional meta; + std::optional comps; +}; + +} + +#endif /* Animation_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Assets/Asset.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Assets/Asset.cpp new file mode 100644 index 0000000000..12f67cdd8a --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Assets/Asset.cpp @@ -0,0 +1,5 @@ +#include "Asset.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Assets/Asset.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Assets/Asset.hpp new file mode 100644 index 0000000000..c071ab7854 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Assets/Asset.hpp @@ -0,0 +1,50 @@ +#ifndef Asset_hpp +#define Asset_hpp + +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +#include +#include + +namespace lottie { + +class Asset { +public: + Asset(std::string id_) : + id(id_) { + } + + explicit Asset(json11::Json::object const &json) noexcept(false) { + auto idData = getAny(json, "id"); + if (idData.is_string()) { + id = idData.string_value(); + } else if (idData.is_number()) { + std::ostringstream idString; + idString << idData.int_value(); + id = idString.str(); + } + + objectName = getOptionalString(json, "nm"); + } + + Asset(const Asset&) = delete; + Asset& operator=(Asset&) = delete; + + virtual void toJson(json11::Json::object &json) const { + json.insert(std::make_pair("id", id)); + + if (objectName.has_value()) { + json.insert(std::make_pair("nm", objectName.value())); + } + } + +public: + /// The ID of the asset + std::string id; + + std::optional objectName; +}; + +} + +#endif /* Asset_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Assets/AssetLibrary.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Assets/AssetLibrary.cpp new file mode 100644 index 0000000000..7feff3231e --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Assets/AssetLibrary.cpp @@ -0,0 +1,5 @@ +#include "AssetLibrary.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Assets/AssetLibrary.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Assets/AssetLibrary.hpp new file mode 100644 index 0000000000..7ff3b07451 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Assets/AssetLibrary.hpp @@ -0,0 +1,71 @@ +#ifndef AssetLibrary_hpp +#define AssetLibrary_hpp + +#include "Lottie/Private/Model/Assets/Asset.hpp" +#include "Lottie/Private/Model/Assets/ImageAsset.hpp" +#include "Lottie/Private/Model/Assets/PrecompAsset.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +#include + +namespace lottie { + +class AssetLibrary { +public: + AssetLibrary( + std::map> const &assets_, + std::map> const &imageAssets_, + std::map> const &precompAssets_ + ) : + assets(assets_), + imageAssets(imageAssets_), + precompAssets(precompAssets_) { + } + + explicit AssetLibrary(json11::Json const &json) noexcept(false) { + if (!json.is_array()) { + throw LottieParsingException(); + } + + for (const auto &item : json.array_items()) { + if (!item.is_object()) { + throw LottieParsingException(); + } + if (item.object_items().find("layers") != item.object_items().end()) { + auto asset = std::make_shared(item.object_items()); + assets.insert(std::make_pair(asset->id, asset)); + assetList.push_back(asset); + precompAssets.insert(std::make_pair(asset->id, asset)); + } else { + auto asset = std::make_shared(item.object_items()); + assets.insert(std::make_pair(asset->id, asset)); + assetList.push_back(asset); + imageAssets.insert(std::make_pair(asset->id, asset)); + } + } + } + + json11::Json::array toJson() const { + json11::Json::array result; + + for (const auto &asset : assetList) { + json11::Json::object assetJson; + asset->toJson(assetJson); + result.push_back(assetJson); + } + + return result; + } + +public: + /// The Assets + std::vector> assetList; + std::map> assets; + + std::map> imageAssets; + std::map> precompAssets; +}; + +} + +#endif /* AssetLibrary_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Assets/ImageAsset.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Assets/ImageAsset.cpp new file mode 100644 index 0000000000..b5a2e57d52 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Assets/ImageAsset.cpp @@ -0,0 +1,5 @@ +#include "ImageAsset.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Assets/ImageAsset.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Assets/ImageAsset.hpp new file mode 100644 index 0000000000..a1e63cbea7 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Assets/ImageAsset.hpp @@ -0,0 +1,119 @@ +#ifndef ImageAsset_hpp +#define ImageAsset_hpp + +#include "Lottie/Private/Model/Assets/Asset.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +namespace lottie { + +class ImageAsset: public Asset { +public: + ImageAsset( + std::string id_, + std::string name_, + std::string directory_, + double width_, + double height_ + ) : Asset(id_), + name(name_), + directory(directory_), + width(width_), + height(height_) { + } + + virtual ~ImageAsset() = default; + + explicit ImageAsset(json11::Json::object const &json) noexcept(false) : + Asset(json) { + name = getString(json, "p"); + directory = getString(json, "u"); + width = getDouble(json, "w"); + height = getDouble(json, "h"); + + _e = getOptionalInt(json, "e"); + _t = getOptionalString(json, "t"); + } + + virtual void toJson(json11::Json::object &json) const override { + Asset::toJson(json); + + json.insert(std::make_pair("p", name)); + json.insert(std::make_pair("u", directory)); + json.insert(std::make_pair("w", width)); + json.insert(std::make_pair("h", height)); + + if (_e.has_value()) { + json.insert(std::make_pair("e", _e.value())); + } + if (_t.has_value()) { + json.insert(std::make_pair("t", _t.value())); + } + } + +public: + /// Image name + std::string name; + + /// Image Directory + std::string directory; + + /// Image Size + double width; + double height; + + std::optional _e; + std::optional _t; +}; + +/*extension Data { + + // MARK: Lifecycle + + /// Initializes `Data` from an `ImageAsset`. + /// + /// Returns nil when the input is not recognized as valid Data URL. + /// - parameter imageAsset: The image asset that contains Data URL. + internal init?(imageAsset: ImageAsset) { + self.init(dataString: imageAsset.name) + } + + /// Initializes `Data` from a [Data URL](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs) String. + /// + /// Returns nil when the input is not recognized as valid Data URL. + /// - parameter dataString: The data string to parse. + /// - parameter options: Options for the string parsing. Default value is `[]`. + internal init?(dataString: String, options: DataURLReadOptions = []) { + guard + dataString.hasPrefix("data:"), + let url = URL(string: dataString) + else { + return nil + } + // The code below is needed because Data(contentsOf:) floods logs + // with messages since url doesn't have a host. This only fixes flooding logs + // when data inside Data URL is base64 encoded. + if + let base64Range = dataString.range(of: ";base64,"), + !options.contains(DataURLReadOptions.legacy) + { + let encodedString = String(dataString[base64Range.upperBound...]) + self.init(base64Encoded: encodedString) + } else { + try? self.init(contentsOf: url) + } + } + + // MARK: Internal + + internal struct DataURLReadOptions: OptionSet { + let rawValue: Int + + /// Will read Data URL using Data(contentsOf:) + static let legacy = DataURLReadOptions(rawValue: 1 << 0) + } + +};*/ + +} + +#endif /* ImageAsset_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Assets/PrecompAsset.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Assets/PrecompAsset.cpp new file mode 100644 index 0000000000..976044565a --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Assets/PrecompAsset.cpp @@ -0,0 +1,5 @@ +#include "PrecompAsset.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Assets/PrecompAsset.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Assets/PrecompAsset.hpp new file mode 100644 index 0000000000..a6869c2081 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Assets/PrecompAsset.hpp @@ -0,0 +1,64 @@ +#ifndef PrecompAsset_hpp +#define PrecompAsset_hpp + +#include "Lottie/Private/Model/Assets/Asset.hpp" +#include "Lottie/Private/Model/Layers/LayerModel.hpp" +#include "Lottie/Private/Model/Layers/LayerModelSerialization.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +#include + +namespace lottie { + +class PrecompAsset: public Asset { +public: + PrecompAsset( + std::string const &id_, + std::vector> const &layers_ + ) : Asset(id_), + layers(layers_) { + } + + virtual ~PrecompAsset() = default; + + explicit PrecompAsset(json11::Json::object const &json) noexcept(false) : + Asset(json) { + frameRate = getOptionalDouble(json, "fr"); + + auto layerDictionaries = getObjectArray(json, "layers"); + for (size_t i = 0; i < layerDictionaries.size(); i++) { + try { + auto layer = parseLayerModel(layerDictionaries[i]); + layers.push_back(layer); + } catch(...) { + throw LottieParsingException(); + } + } + } + + virtual void toJson(json11::Json::object &json) const override { + Asset::toJson(json); + + json11::Json::array layerArray; + for (const auto &layer : layers) { + json11::Json::object layerJson; + layer->toJson(layerJson); + layerArray.push_back(layerJson); + } + json.insert(std::make_pair("layers", layerArray)); + + if (frameRate.has_value()) { + json.insert(std::make_pair("fr", frameRate.value())); + } + } + +public: + /// Layers of the precomp + std::vector> layers; + + std::optional frameRate; +}; + +} + +#endif /* PrecompAsset_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Keyframes/KeyframeGroup.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Keyframes/KeyframeGroup.cpp new file mode 100644 index 0000000000..2f0c1a788a --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Keyframes/KeyframeGroup.cpp @@ -0,0 +1,5 @@ +#include "KeyframeGroup.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Keyframes/KeyframeGroup.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Keyframes/KeyframeGroup.hpp new file mode 100644 index 0000000000..ce591c7655 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Keyframes/KeyframeGroup.hpp @@ -0,0 +1,150 @@ +#ifndef KeyframeGroup_hpp +#define KeyframeGroup_hpp + +#include "Lottie/Public/Keyframes/Keyframe.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +#include + +namespace lottie { + +/// Used for coding/decoding a group of Keyframes by type. +/// +/// Keyframe data is wrapped in a dictionary { "k" : KeyframeData }. +/// The keyframe data can either be an array of keyframes or, if no animation is present, the raw value. +/// This helper object is needed to properly decode the json. + +template +class KeyframeGroup { +public: + KeyframeGroup(std::vector> &&keyframes_) : + keyframes(std::move(keyframes_)), + isSingle(false) { + } + + KeyframeGroup(T const &value_) : + keyframes({ Keyframe(value_, std::nullopt, std::nullopt) }), + isSingle(false) { + } + + KeyframeGroup(json11::Json::object const &json) noexcept(false) { + isAnimated = getOptionalInt(json, "a"); + expression = getOptionalAny(json, "x"); + expressionIndex = getOptionalInt(json, "ix"); + _extraL = getOptionalInt(json, "l"); + + auto containerData = getAny(json, "k"); + + try { + LottieParsingException::Guard expectedException; + T keyframeData = T(containerData); + keyframes.push_back(Keyframe(keyframeData, std::nullopt, std::nullopt)); + isSingle = true; + } catch(...) { + // Decode and array of keyframes. + // + // Body Movin and Lottie deal with keyframes in different ways. + // + // A keyframe object in Body movin defines a span of time with a START + // and an END, from the current keyframe time to the next keyframe time. + // + // A keyframe object in Lottie defines a singular point in time/space. + // This point has an in-tangent and an out-tangent. + // + // To properly decode this we must iterate through keyframes while holding + // reference to the previous keyframe. + + if (!containerData.is_array()) { + throw LottieParsingException(); + } + + std::optional> previousKeyframeData; + for (const auto &containerItem : containerData.array_items()) { + // Ensure that Time and Value are present. + auto keyframeData = KeyframeData(containerItem); + rawKeyframeData.push_back(keyframeData); + + std::optional value; + if (keyframeData.startValue.has_value()) { + value = keyframeData.startValue; + } else if (previousKeyframeData.has_value()) { + value = previousKeyframeData->endValue; + } + if (!value.has_value()) { + throw LottieParsingException(); + } + if (!keyframeData.time.has_value()) { + throw LottieParsingException(); + } + + std::optional inTangent; + std::optional spatialInTangent; + if (previousKeyframeData.has_value()) { + inTangent = previousKeyframeData->inTangent; + spatialInTangent = previousKeyframeData->spatialInTangent; + } + + keyframes.emplace_back( + value.value(), + keyframeData.time.value(), + keyframeData.isHold(), + inTangent, + keyframeData.outTangent, + spatialInTangent, + keyframeData.spatialOutTangent + ); + + previousKeyframeData = keyframeData; + } + + isSingle = false; + } + } + + json11::Json::object toJson() const { + json11::Json::object result; + + assert(!keyframes.empty()); + + if (keyframes.size() == 1 && isSingle) { + result.insert(std::make_pair("k", keyframes[0].value.toJson())); + } else { + json11::Json::array containerData; + + for (const auto &keyframe : rawKeyframeData) { + containerData.push_back(keyframe.toJson()); + } + + result.insert(std::make_pair("k", containerData)); + } + + if (isAnimated.has_value()) { + result.insert(std::make_pair("a", isAnimated.value())); + } + if (expression.has_value()) { + result.insert(std::make_pair("x", expression.value())); + } + if (expressionIndex.has_value()) { + result.insert(std::make_pair("ix", expressionIndex.value())); + } + if (_extraL.has_value()) { + result.insert(std::make_pair("l", _extraL.value())); + } + + return result; + } + +public: + std::vector> keyframes; + std::optional isAnimated; + + std::optional expression; + std::optional expressionIndex; + std::vector> rawKeyframeData; + bool isSingle = false; + std::optional _extraL; +}; + +} + +#endif /* KeyframeGroup_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/ImageLayerModel.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/ImageLayerModel.cpp new file mode 100644 index 0000000000..4251973760 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/ImageLayerModel.cpp @@ -0,0 +1,5 @@ +#include "ImageLayerModel.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/ImageLayerModel.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/ImageLayerModel.hpp new file mode 100644 index 0000000000..2787e4e557 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/ImageLayerModel.hpp @@ -0,0 +1,40 @@ +#ifndef ImageLayerModel_hpp +#define ImageLayerModel_hpp + +#include "Lottie/Private/Model/Layers/LayerModel.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +namespace lottie { + +/// A layer that holds an image. +class ImageLayerModel: public LayerModel { +public: + explicit ImageLayerModel(json11::Json::object const &json) noexcept(false) : + LayerModel(json) { + referenceID = getString(json, "refId"); + + _sc = getOptionalString(json, "sc"); + } + + virtual ~ImageLayerModel() = default; + + virtual void toJson(json11::Json::object &json) const override { + LayerModel::toJson(json); + + json.insert(std::make_pair("refId", referenceID)); + + if (_sc.has_value()) { + json.insert(std::make_pair("sc", _sc.value())); + } + } + +public: + /// The reference ID of the image. + std::string referenceID; + + std::optional _sc; +}; + +} + +#endif /* ImageLayerModel_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/LayerModel.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/LayerModel.cpp new file mode 100644 index 0000000000..6ceabadcf5 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/LayerModel.cpp @@ -0,0 +1,45 @@ +#include "LayerModel.hpp" + +namespace lottie { + +LayerType parseLayerType(json11::Json::object const &json, std::string const &key) { + if (const auto layerTypeValue = getOptionalInt(json, "ty")) { + switch (layerTypeValue.value()) { + case 0: + return LayerType::Precomp; + case 1: + return LayerType::Solid; + case 2: + return LayerType::Image; + case 3: + return LayerType::Null; + case 4: + return LayerType::Shape; + case 5: + return LayerType::Text; + default: + return LayerType::Null; + } + } else { + return LayerType::Null; + } +} + +int serializeLayerType(LayerType value) { + switch (value) { + case LayerType::Precomp: + return 0; + case LayerType::Solid: + return 1; + case LayerType::Image: + return 2; + case LayerType::Null: + return 3; + case LayerType::Shape: + return 4; + case LayerType::Text: + return 5; + } +} + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/LayerModel.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/LayerModel.hpp new file mode 100644 index 0000000000..a5e08d4447 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/LayerModel.hpp @@ -0,0 +1,318 @@ +#ifndef LayerModel_hpp +#define LayerModel_hpp + +#include "Lottie/Private/Model/Objects/Transform.hpp" +#include "Lottie/Private/Model/Objects/Mask.hpp" +#include "Lottie/Private/Utility/Primitives/CoordinateSpace.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +#include +#include +#include + +namespace lottie { + +enum class LayerType { + Precomp, + Solid, + Image, + Null, + Shape, + Text +}; + +LayerType parseLayerType(json11::Json::object const &json, std::string const &key); +int serializeLayerType(LayerType value); + +enum class MatteType: int { + None = 0, + Add = 1, + Invert = 2, + Unknown = 3 +}; + +enum class BlendMode: int { + Normal = 0, + Multiply = 1, + Screen = 2, + Overlay = 3, + Darken = 4, + Lighten = 5, + ColorDodge = 6, + ColorBurn = 7, + HardLight = 8, + SoftLight = 9, + Difference = 10, + Exclusion = 11, + Hue = 12, + Saturation = 13, + Color = 14, + Luminosity = 15 +}; + +/// A base top container for shapes, images, and other view objects. +class LayerModel { +public: + explicit LayerModel(json11::Json::object const &json) noexcept(false) { + name = getOptionalString(json, "nm"); + index = getOptionalInt(json, "ind"); + + type = parseLayerType(json, "ty"); + + autoOrient = getOptionalInt(json, "ao"); + + if (const auto typeRawValue = getOptionalInt(json, "ddd")) { + if (typeRawValue.value() == 0) { + coordinateSpace = CoordinateSpace::Type2d; + } else { + coordinateSpace = CoordinateSpace::Type3d; + } + } else { + coordinateSpace = std::nullopt; + } + + inFrame = getDouble(json, "ip"); + outFrame = getDouble(json, "op"); + startTime = getDouble(json, "st"); + + transform = std::make_shared(getObject(json, "ks")); + parent = getOptionalInt(json, "parent"); + + if (const auto blendModeRawValue = getOptionalInt(json, "bm")) { + switch (blendModeRawValue.value()) { + case 0: + blendMode = BlendMode::Normal; + break; + case 1: + blendMode = BlendMode::Multiply; + break; + case 2: + blendMode = BlendMode::Screen; + break; + case 3: + blendMode = BlendMode::Overlay; + break; + case 4: + blendMode = BlendMode::Darken; + break; + case 5: + blendMode = BlendMode::Lighten; + break; + case 6: + blendMode = BlendMode::ColorDodge; + break; + case 7: + blendMode = BlendMode::ColorBurn; + break; + case 8: + blendMode = BlendMode::HardLight; + break; + case 9: + blendMode = BlendMode::SoftLight; + break; + case 10: + blendMode = BlendMode::Difference; + break; + case 11: + blendMode = BlendMode::Exclusion; + break; + case 12: + blendMode = BlendMode::Hue; + break; + case 13: + blendMode = BlendMode::Saturation; + break; + case 14: + blendMode = BlendMode::Color; + break; + case 15: + blendMode = BlendMode::Luminosity; + break; + default: + throw LottieParsingException(); + } + } + + if (const auto maskDictionaries = getOptionalObjectArray(json, "masksProperties")) { + masks = std::vector>(); + for (const auto &maskDictionary : maskDictionaries.value()) { + masks->push_back(std::make_shared(maskDictionary)); + } + } + + if (const auto timeStretchData = getOptionalDouble(json, "sr")) { + _timeStretch = timeStretchData.value(); + } + + if (const auto matteRawValue = getOptionalInt(json, "tt")) { + switch (matteRawValue.value()) { + case 0: + matte = MatteType::None; + break; + case 1: + matte = MatteType::Add; + break; + case 2: + matte = MatteType::Invert; + break; + case 3: + matte = MatteType::Unknown; + break; + default: + throw LottieParsingException(); + } + } + + if (const auto hiddenData = getOptionalBool(json, "hd")) { + hidden = hiddenData.value(); + } + + hasMask = getOptionalBool(json, "hasMask"); + td = getOptionalInt(json, "td"); + effectsData = getOptionalAny(json, "ef"); + layerClass = getOptionalString(json, "cl"); + _extraHidden = getOptionalAny(json, "hidden"); + } + + LayerModel(const LayerModel&) = delete; + LayerModel& operator=(LayerModel&) = delete; + + virtual ~LayerModel() = default; + + virtual void toJson(json11::Json::object &json) const { + if (name.has_value()) { + json.insert(std::make_pair("nm", name.value())); + } + if (index.has_value()) { + json.insert(std::make_pair("ind", index.value())); + } + + if (autoOrient.has_value()) { + json.insert(std::make_pair("ao", autoOrient.value())); + } + + json.insert(std::make_pair("ty", serializeLayerType(type))); + + if (coordinateSpace.has_value()) { + switch (coordinateSpace.value()) { + case CoordinateSpace::Type2d: + json.insert(std::make_pair("ddd", 0)); + break; + case CoordinateSpace::Type3d: + json.insert(std::make_pair("ddd", 1)); + break; + } + } + + json.insert(std::make_pair("ip", inFrame)); + json.insert(std::make_pair("op", outFrame)); + json.insert(std::make_pair("st", startTime)); + + json.insert(std::make_pair("ks", transform->toJson())); + + if (parent.has_value()) { + json.insert(std::make_pair("parent", parent.value())); + } + + if (blendMode.has_value()) { + json.insert(std::make_pair("bm", (int)blendMode.value())); + } + + if (masks.has_value()) { + json11::Json::array maskArray; + for (const auto &mask : masks.value()) { + maskArray.push_back(mask->toJson()); + } + json.insert(std::make_pair("masksProperties", maskArray)); + } + + if (_timeStretch.has_value()) { + json.insert(std::make_pair("sr", _timeStretch.value())); + } + + if (matte.has_value()) { + json.insert(std::make_pair("tt", (int)matte.value())); + } + + if (hidden.has_value()) { + json.insert(std::make_pair("hd", hidden.value())); + } + + if (hasMask.has_value()) { + json.insert(std::make_pair("hasMask", hasMask.value())); + } + if (td.has_value()) { + json.insert(std::make_pair("td", td.value())); + } + if (effectsData.has_value()) { + json.insert(std::make_pair("ef", effectsData.value())); + } + if (layerClass.has_value()) { + json.insert(std::make_pair("cl", layerClass.value())); + } + if (_extraHidden.has_value()) { + json.insert(std::make_pair("hidden", _extraHidden.value())); + } + } + + double timeStretch() { + if (_timeStretch.has_value()) { + return _timeStretch.value(); + } else { + return 1.0; + } + } + +public: + /// The readable name of the layer + std::optional name; + + /// The index of the layer + std::optional index; + + /// The type of the layer. + LayerType type; + + std::optional autoOrient; + + /// The coordinate space + std::optional coordinateSpace; + + /// The in time of the layer in frames. + double inFrame; + /// The out time of the layer in frames. + double outFrame; + + /// The start time of the layer in frames. + double startTime; + + /// The transform of the layer + std::shared_ptr transform; + + /// The index of the parent layer, if applicable. + std::optional parent; + + /// The blending mode for the layer + std::optional blendMode; + + /// An array of masks for the layer. + std::optional>> masks; + + /// A number that stretches time by a multiplier + std::optional _timeStretch; + + /// The type of matte if any. + std::optional matte; + + std::optional hidden; + + std::optional hasMask; + std::optional td; + std::optional effectsData; + std::optional layerClass; + std::optional _extraHidden; +}; + +} + +#endif /* LayerModel_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/LayerModelSerialization.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/LayerModelSerialization.cpp new file mode 100644 index 0000000000..6183d51be3 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/LayerModelSerialization.cpp @@ -0,0 +1,34 @@ +#include "LayerModelSerialization.hpp" + +#include "Lottie/Private/Model/Layers/PreCompLayerModel.hpp" +#include "Lottie/Private/Model/Layers/SolidLayerModel.hpp" +#include "Lottie/Private/Model/Layers/ImageLayerModel.hpp" +#include "Lottie/Private/Model/Layers/ShapeLayerModel.hpp" +#include "Lottie/Private/Model/Layers/TextLayerModel.hpp" + +namespace lottie { + +std::shared_ptr parseLayerModel(json11::Json::object const &json) noexcept(false) { + LayerType layerType = parseLayerType(json, "ty"); + + switch (layerType) { + case LayerType::Precomp: + return std::make_shared(json); + case LayerType::Solid: + return std::make_shared(json); + case LayerType::Image: + return std::make_shared(json); + case LayerType::Null: + return std::make_shared(json); + case LayerType::Shape: + try { + return std::make_shared(json); + } catch(...) { + throw LottieParsingException(); + } + case LayerType::Text: + return std::make_shared(json); + } +} + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/LayerModelSerialization.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/LayerModelSerialization.hpp new file mode 100644 index 0000000000..6f42ac527d --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/LayerModelSerialization.hpp @@ -0,0 +1,14 @@ +#ifndef LayerModelSerialization_hpp +#define LayerModelSerialization_hpp + +#include "lottiejson11/lottiejson11.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" +#include "Lottie/Private/Model/Layers/LayerModel.hpp" + +namespace lottie { + +std::shared_ptr parseLayerModel(json11::Json::object const &json) noexcept(false); + +} + +#endif /* LayerModelSerialization_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/PreCompLayerModel.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/PreCompLayerModel.cpp new file mode 100644 index 0000000000..2a4c7b1363 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/PreCompLayerModel.cpp @@ -0,0 +1,5 @@ +#include "PreCompLayerModel.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/PreCompLayerModel.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/PreCompLayerModel.hpp new file mode 100644 index 0000000000..7f4956e803 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/PreCompLayerModel.hpp @@ -0,0 +1,57 @@ +#ifndef PreCompLayerModel_hpp +#define PreCompLayerModel_hpp + +#include "Lottie/Private/Model/Layers/LayerModel.hpp" +#include "Lottie/Private/Model/Keyframes/KeyframeGroup.hpp" +#include "Lottie/Public/Primitives/Vectors.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +#include + +namespace lottie { + +/// A layer that holds another animation composition. +class PreCompLayerModel: public LayerModel { +public: + PreCompLayerModel(json11::Json::object const &json) : + LayerModel(json) { + referenceID = getString(json, "refId"); + if (const auto timeRemappingData = getOptionalObject(json, "tm")) { + timeRemapping = KeyframeGroup(timeRemappingData.value()); + } + width = getDouble(json, "w"); + height = getDouble(json, "h"); + } + + virtual ~PreCompLayerModel() = default; + + virtual void toJson(json11::Json::object &json) const override { + LayerModel::toJson(json); + + json.insert(std::make_pair("refId", referenceID)); + + if (timeRemapping.has_value()) { + json.insert(std::make_pair("tm", timeRemapping->toJson())); + } + + json.insert(std::make_pair("w", width)); + json.insert(std::make_pair("h", height)); + } + +public: + /// The reference ID of the precomp. + std::string referenceID; + + /// A value that remaps time over time. + std::optional> timeRemapping; + + /// Precomp Width + double width; + + /// Precomp Height + double height; +}; + +} + +#endif /* PreCompLayerModel_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/ShapeLayerModel.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/ShapeLayerModel.cpp new file mode 100644 index 0000000000..608dca567b --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/ShapeLayerModel.cpp @@ -0,0 +1,5 @@ +#include "ShapeLayerModel.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/ShapeLayerModel.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/ShapeLayerModel.hpp new file mode 100644 index 0000000000..72477ef93b --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/ShapeLayerModel.hpp @@ -0,0 +1,45 @@ +#ifndef ShapeLayerModel_hpp +#define ShapeLayerModel_hpp + +#include "Lottie/Private/Model/Layers/LayerModel.hpp" +#include "Lottie/Private/Model/ShapeItems/ShapeItem.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +#include + +namespace lottie { + +/// A layer that holds vector shape objects. +class ShapeLayerModel: public LayerModel { +public: + ShapeLayerModel(json11::Json::object const &json) noexcept(false) : + LayerModel(json) { + auto shapeItemsData = getObjectArray(json, "shapes"); + for (const auto &shapeItemData : shapeItemsData) { + items.push_back(parseShapeItem(shapeItemData)); + } + } + + virtual ~ShapeLayerModel() = default; + + virtual void toJson(json11::Json::object &json) const override { + LayerModel::toJson(json); + + json11::Json::array shapeItemArray; + for (const auto &item : items) { + json11::Json::object itemJson; + item->toJson(itemJson); + shapeItemArray.push_back(itemJson); + } + + json.insert(std::make_pair("shapes", shapeItemArray)); + } + +public: + /// A list of shape items. + std::vector> items; +}; + +} + +#endif /* ShapeLayerModel_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/SolidLayerModel.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/SolidLayerModel.cpp new file mode 100644 index 0000000000..153c283953 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/SolidLayerModel.cpp @@ -0,0 +1,5 @@ +#include "SolidLayerModel.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/SolidLayerModel.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/SolidLayerModel.hpp new file mode 100644 index 0000000000..3a375d2a51 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/SolidLayerModel.hpp @@ -0,0 +1,42 @@ +#ifndef SolidLayerModel_hpp +#define SolidLayerModel_hpp + +#include "Lottie/Private/Model/Layers/LayerModel.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +namespace lottie { + +/// A layer that holds a solid color. +class SolidLayerModel: public LayerModel { +public: + explicit SolidLayerModel(json11::Json::object const &json) noexcept(false) : + LayerModel(json) { + colorHex = getString(json, "sc"); + width = getDouble(json, "sw"); + height = getDouble(json, "sh"); + } + + virtual ~SolidLayerModel() = default; + + virtual void toJson(json11::Json::object &json) const override { + LayerModel::toJson(json); + + json.insert(std::make_pair("sc", colorHex)); + json.insert(std::make_pair("sw", width)); + json.insert(std::make_pair("sh", height)); + } + +public: + /// The color of the solid in Hex // Change to value provider. + std::string colorHex; + + /// The Width of the color layer + double width; + + /// The height of the color layer + double height; +}; + +} + +#endif /* SolidLayerModel_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/TextLayerModel.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/TextLayerModel.cpp new file mode 100644 index 0000000000..9e2ad034b1 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/TextLayerModel.cpp @@ -0,0 +1,5 @@ +#include "TextLayerModel.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/TextLayerModel.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/TextLayerModel.hpp new file mode 100644 index 0000000000..6ea5e62c28 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/TextLayerModel.hpp @@ -0,0 +1,83 @@ +#ifndef TextLayerModel_hpp +#define TextLayerModel_hpp + +#include "Lottie/Private/Model/Layers/LayerModel.hpp" +#include "Lottie/Private/Model/Keyframes/KeyframeGroup.hpp" +#include "Lottie/Private/Model/Text/TextDocument.hpp" +#include "Lottie/Private/Model/Text/TextAnimator.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +namespace lottie { + +/// A layer that holds text. +class TextLayerModel: public LayerModel { +public: + TextLayerModel(json11::Json::object const &json) : + LayerModel(json), + text(KeyframeGroup(TextDocument( + "", + 0.0, + "", + TextJustification::Left, + 0, + 0.0, + std::nullopt, + std::nullopt, + std::nullopt, + std::nullopt, + std::nullopt, + std::nullopt, + std::nullopt + ))) { + auto textContainer = getObject(json, "t"); + + auto textData = getObject(textContainer, "d"); + text = KeyframeGroup(textData); + + if (auto animatorsData = getOptionalObjectArray(textContainer, "a")) { + for (const auto &animatorData : animatorsData.value()) { + animators.push_back(std::make_shared(animatorData)); + } + } + + _extraM = getOptionalAny(textContainer, "m"); + _extraP = getOptionalAny(textContainer, "p"); + } + + virtual ~TextLayerModel() = default; + + virtual void toJson(json11::Json::object &json) const override { + LayerModel::toJson(json); + + json11::Json::object textContainer; + textContainer.insert(std::make_pair("d", text.toJson())); + if (_extraM.has_value()) { + textContainer.insert(std::make_pair("m", _extraM.value())); + } + if (_extraP.has_value()) { + textContainer.insert(std::make_pair("p", _extraP.value())); + } + json11::Json::array animatorArray; + for (const auto &animator : animators) { + animatorArray.push_back(animator->toJson()); + } + textContainer.insert(std::make_pair("a", animatorArray)); + + json.insert(std::make_pair("t", textContainer)); + } + +public: + /// The text for the layer + KeyframeGroup text; + + /// Text animators + std::vector> animators; + + std::optional _extraM; + std::optional _extraP; + std::optional _extraA; +}; + +} + +#endif /* TextLayerModel_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/DashElement.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/DashElement.cpp new file mode 100644 index 0000000000..a28df19770 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/DashElement.cpp @@ -0,0 +1,5 @@ +#include "DashElement.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/DashElement.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/DashElement.hpp new file mode 100644 index 0000000000..6cd73101ff --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/DashElement.hpp @@ -0,0 +1,78 @@ +#ifndef DashElement_hpp +#define DashElement_hpp + +#include "Lottie/Private/Model/Keyframes/KeyframeGroup.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" +#include "Lottie/Public/Primitives/DashPattern.hpp" + +namespace lottie { + +enum class DashElementType { + Offset, + Dash, + Gap +}; + +class DashElement { +public: + DashElement( + DashElementType type_, + KeyframeGroup const &value_ + ) : + type(type_), + value(value_) { + } + + explicit DashElement(json11::Json::object const &json) noexcept(false) : + type(DashElementType::Offset), + value(KeyframeGroup(Vector1D(0.0))) { + auto typeRawValue = getString(json, "n"); + if (typeRawValue == "o") { + type = DashElementType::Offset; + } else if (typeRawValue == "d") { + type = DashElementType::Dash; + } else if (typeRawValue == "g") { + type = DashElementType::Gap; + } else { + throw LottieParsingException(); + } + + value = KeyframeGroup(getObject(json, "v")); + + name = getOptionalString(json, "nm"); + } + + json11::Json::object toJson() const { + json11::Json::object result; + + switch (type) { + case DashElementType::Offset: + result.insert(std::make_pair("n", "o")); + break; + case DashElementType::Dash: + result.insert(std::make_pair("n", "d")); + break; + case DashElementType::Gap: + result.insert(std::make_pair("n", "g")); + break; + } + + result.insert(std::make_pair("v", value.toJson())); + + if (name.has_value()) { + result.insert(std::make_pair("nm", name.value())); + } + + return result; + } + +public: + DashElementType type; + KeyframeGroup value; + + std::optional name; +}; + +} + +#endif /* DashElement_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/FitzModifier.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/FitzModifier.cpp new file mode 100644 index 0000000000..7b18e01e4a --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/FitzModifier.cpp @@ -0,0 +1,5 @@ +#include "FitzModifier.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/FitzModifier.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/FitzModifier.hpp new file mode 100644 index 0000000000..163cf09c21 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/FitzModifier.hpp @@ -0,0 +1,53 @@ +#ifndef FitzModifier_hpp +#define FitzModifier_hpp + +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +namespace lottie { + +class FitzModifier { +public: + explicit FitzModifier(json11::Json::object const &json) noexcept(false) { + original = getInt(json, "o"); + type12 = getOptionalInt(json, "f12"); + type3 = getOptionalInt(json, "f3"); + type4 = getOptionalInt(json, "f4"); + type5 = getOptionalInt(json, "f5"); + type6 = getOptionalInt(json, "f6"); + } + + json11::Json::object toJson() const { + json11::Json::object result; + + result.insert(std::make_pair("o", (double)original)); + if (type12.has_value()) { + result.insert(std::make_pair("f12", (double)type12.value())); + } + if (type3.has_value()) { + result.insert(std::make_pair("f3", (double)type3.value())); + } + if (type4.has_value()) { + result.insert(std::make_pair("f4", (double)type4.value())); + } + if (type5.has_value()) { + result.insert(std::make_pair("f5", (double)type5.value())); + } + if (type6.has_value()) { + result.insert(std::make_pair("f6", (double)type6.value())); + } + + return result; + } + +public: + double original; + std::optional type12; + std::optional type3; + std::optional type4; + std::optional type5; + std::optional type6; +}; + +} + +#endif /* FitzModifier_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/Marker.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/Marker.cpp new file mode 100644 index 0000000000..ea2664d416 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/Marker.cpp @@ -0,0 +1,5 @@ +#include "Marker.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/Marker.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/Marker.hpp new file mode 100644 index 0000000000..4219996a4b --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/Marker.hpp @@ -0,0 +1,52 @@ +#ifndef Marker_hpp +#define Marker_hpp + +#include "Lottie/Public/Primitives/AnimationTime.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +#include + +namespace lottie { + +class Marker { +public: + Marker( + std::string const &name_, + AnimationFrameTime frameTime_ + ) : + name(name_), + frameTime(frameTime_) { + } + + explicit Marker(json11::Json::object const &json) noexcept(false) { + name = getString(json, "cm"); + frameTime = getDouble(json, "tm"); + dr = getOptionalInt(json, "dr"); + } + + json11::Json::object toJson() const { + json11::Json::object result; + + result.insert(std::make_pair("cm", name)); + result.insert(std::make_pair("tm", frameTime)); + + if (dr.has_value()) { + result.insert(std::make_pair("dr", dr.value())); + } + + return result; + } + +public: + /// The Marker Name + std::string name; + + /// The Frame time of the marker + AnimationFrameTime frameTime; + + std::optional dr; +}; + +} + +#endif /* Marker_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/Mask.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/Mask.cpp new file mode 100644 index 0000000000..8cb64d6709 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/Mask.cpp @@ -0,0 +1,5 @@ +#include "Mask.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/Mask.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/Mask.hpp new file mode 100644 index 0000000000..d453a1c374 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/Mask.hpp @@ -0,0 +1,139 @@ +#ifndef Mask_hpp +#define Mask_hpp + +#include "Lottie/Private/Model/Keyframes/KeyframeGroup.hpp" +#include "Lottie/Private/Utility/Primitives/BezierPath.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +namespace lottie { + +enum class MaskMode { + Add, + Subtract, + Intersect, + Lighten, + Darken, + Difference, + None +}; + +class Mask { +public: + explicit Mask(json11::Json::object const &json) noexcept(false) : + opacity(KeyframeGroup(Vector1D(100.0))), + shape(KeyframeGroup(BezierPath())), + inverted(false), + expansion(KeyframeGroup(Vector1D(0.0))) { + if (const auto modeRawValue = getOptionalString(json, "mode")) { + if (modeRawValue.value() == "a") { + _mode = MaskMode::Add; + } else if (modeRawValue.value() == "s") { + _mode = MaskMode::Subtract; + } else if (modeRawValue.value() == "i") { + _mode = MaskMode::Intersect; + } else if (modeRawValue.value() == "l") { + _mode = MaskMode::Lighten; + } else if (modeRawValue.value() == "d") { + _mode = MaskMode::Darken; + } else if (modeRawValue.value() == "f") { + _mode = MaskMode::Difference; + } else if (modeRawValue.value() == "n") { + _mode = MaskMode::None; + } else { + throw LottieParsingException(); + } + } + + if (const auto opacityData = getOptionalObject(json, "o")) { + opacity = KeyframeGroup(opacityData.value()); + } + + shape = KeyframeGroup(getObject(json, "pt")); + + if (const auto invertedData = getOptionalBool(json, "inv")) { + inverted = invertedData.value(); + } + + if (const auto expansionData = getOptionalObject(json, "x")) { + expansion = KeyframeGroup(expansionData.value()); + } + + name = getOptionalString(json, "nm"); + } + + json11::Json::object toJson() const { + json11::Json::object result; + + if (_mode.has_value()) { + switch (_mode.value()) { + case MaskMode::Add: + result.insert(std::make_pair("mode", "a")); + break; + case MaskMode::Subtract: + result.insert(std::make_pair("mode", "s")); + break; + case MaskMode::Intersect: + result.insert(std::make_pair("mode", "i")); + break; + case MaskMode::Lighten: + result.insert(std::make_pair("mode", "l")); + break; + case MaskMode::Darken: + result.insert(std::make_pair("mode", "d")); + break; + case MaskMode::Difference: + result.insert(std::make_pair("mode", "f")); + break; + case MaskMode::None: + result.insert(std::make_pair("mode", "n")); + break; + } + } + + if (opacity.has_value()) { + result.insert(std::make_pair("o", opacity->toJson())); + } + + result.insert(std::make_pair("pt", shape.toJson())); + + if (inverted.has_value()) { + result.insert(std::make_pair("inv", inverted.value())); + } + + if (expansion.has_value()) { + result.insert(std::make_pair("x", expansion->toJson())); + } + + if (name.has_value()) { + result.insert(std::make_pair("nm", name.value())); + } + + return result; + } + +public: + MaskMode mode() const { + if (_mode.has_value()) { + return _mode.value(); + } else { + return MaskMode::Add; + } + } + +public: + std::optional _mode; + + std::optional> opacity; + + KeyframeGroup shape; + + std::optional inverted; + + std::optional> expansion; + + std::optional name; +}; + +} + +#endif /* Mask_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/Transform.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/Transform.cpp new file mode 100644 index 0000000000..e55154304f --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/Transform.cpp @@ -0,0 +1,5 @@ +#include "Transform.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/Transform.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/Transform.hpp new file mode 100644 index 0000000000..d3bfa4471e --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/Transform.hpp @@ -0,0 +1,267 @@ +#ifndef Transform_hpp +#define Transform_hpp + +#include "Lottie/Private/Model/Keyframes/KeyframeGroup.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +#include + +namespace lottie { + +class Transform { +public: + enum class PositionInternalRepresentation { + None, + TopLevelXY, + TopLevelCombined, + NestedXY + }; + + enum class RotationZInternalRepresentation { + RZ, + R + }; + +public: + Transform( + std::optional> anchorPoint_, + std::optional> position_, + std::optional> positionX_, + std::optional> positionY_, + std::optional> scale_, + std::optional> rotation_, + std::optional> &opacity_, + std::optional> rotationZ_ + ) : + _anchorPoint(anchorPoint_), + _position(position_), + _positionX(positionX_), + _positionY(positionY_), + _scale(scale_), + _rotation(rotation_), + _opacity(opacity_), + _rotationZ(rotationZ_) { + } + + explicit Transform(json11::Json::object const &json) noexcept(false) { + // AnchorPoint + if (const auto anchorPointDictionary = getOptionalObject(json, "a")) { + _anchorPoint = KeyframeGroup(anchorPointDictionary.value()); + } + + try { + auto xDictionary = getOptionalObject(json, "px"); + auto yDictionary = getOptionalObject(json, "py"); + if (xDictionary.has_value() && yDictionary.has_value()) { + _positionX = KeyframeGroup(xDictionary.value()); + _positionY = KeyframeGroup(yDictionary.value()); + _position = std::nullopt; + _positionInternalRepresentation = PositionInternalRepresentation::TopLevelXY; + } else if (const auto positionData = getOptionalObject(json, "p")) { + try { + LottieParsingException::Guard expectedGuard; + + _position = KeyframeGroup(positionData.value()); + _positionX = std::nullopt; + _positionX = std::nullopt; + _positionInternalRepresentation = PositionInternalRepresentation::TopLevelCombined; + } catch(...) { + auto xData = getObject(positionData.value(), "x"); + auto yData = getObject(positionData.value(), "y"); + _positionX = KeyframeGroup(xData); + _positionY = KeyframeGroup(yData); + _position = std::nullopt; + _positionInternalRepresentation = PositionInternalRepresentation::NestedXY; + _extra_positionS = getOptionalBool(positionData.value(), "s"); + } + } else { + _position = std::nullopt; + _positionX = std::nullopt; + _positionY = std::nullopt; + _positionInternalRepresentation = PositionInternalRepresentation::None; + } + } catch(...) { + throw LottieParsingException(); + } + + try { + // Scale + if (const auto scaleData = getOptionalObject(json, "s")) { + _scale = KeyframeGroup(scaleData.value()); + } + + // Rotation + if (const auto rotationZData = getOptionalObject(json, "rz")) { + _rotationZ = KeyframeGroup(rotationZData.value()); + _rotationZInternalRepresentation = RotationZInternalRepresentation::RZ; + } else if (const auto rotationData = getOptionalObject(json, "r")) { + _rotation = KeyframeGroup(rotationData.value()); + _rotationZInternalRepresentation = RotationZInternalRepresentation::R; + } + + // Opacity + if (const auto opacityData = getOptionalObject(json, "o")) { + _opacity = KeyframeGroup(opacityData.value()); + } + } catch(...) { + throw LottieParsingException(); + } + + _extraTy = getOptionalString(json, "ty"); + _extraSa = getOptionalAny(json, "sa"); + _extraSk = getOptionalAny(json, "sk"); + } + + json11::Json::object toJson() const { + json11::Json::object result; + + if (_anchorPoint.has_value()) { + result.insert(std::make_pair("a", _anchorPoint->toJson())); + } + + switch (_positionInternalRepresentation) { + case PositionInternalRepresentation::None: + assert(!_positionX.has_value()); + assert(!_positionY.has_value()); + assert(!_position.has_value()); + break; + case PositionInternalRepresentation::TopLevelXY: + assert(_positionX.has_value()); + assert(_positionY.has_value()); + assert(!_position.has_value()); + result.insert(std::make_pair("x", _positionX->toJson())); + result.insert(std::make_pair("y", _positionY->toJson())); + break; + case PositionInternalRepresentation::TopLevelCombined: + assert(_position.has_value()); + assert(!_positionX.has_value()); + assert(!_positionY.has_value()); + result.insert(std::make_pair("p", _position->toJson())); + break; + case PositionInternalRepresentation::NestedXY: + json11::Json::object nestedPosition; + assert(_positionX.has_value()); + assert(_positionY.has_value()); + assert(!_position.has_value()); + nestedPosition.insert(std::make_pair("x", _positionX->toJson())); + nestedPosition.insert(std::make_pair("y", _positionY->toJson())); + if (_extra_positionS.has_value()) { + nestedPosition.insert(std::make_pair("s", _extra_positionS.value())); + } + result.insert(std::make_pair("p", nestedPosition)); + break; + } + + if (_scale.has_value()) { + result.insert(std::make_pair("s", _scale->toJson())); + } + + if (_rotation.has_value()) { + switch (_rotationZInternalRepresentation) { + case RotationZInternalRepresentation::RZ: + result.insert(std::make_pair("rz", _rotation->toJson())); + break; + case RotationZInternalRepresentation::R: + result.insert(std::make_pair("r", _rotation->toJson())); + break; + } + } + + if (_opacity.has_value()) { + result.insert(std::make_pair("o", _opacity->toJson())); + } + + if (_extraTy.has_value()) { + result.insert(std::make_pair("ty", _extraTy.value())); + } + if (_extraSa.has_value()) { + result.insert(std::make_pair("sa", _extraSa.value())); + } + if (_extraSk.has_value()) { + result.insert(std::make_pair("sk", _extraSk.value())); + } + + return result; + } + + KeyframeGroup anchorPoint() { + if (_anchorPoint.has_value()) { + return _anchorPoint.value(); + } else { + return KeyframeGroup(Vector3D(0.0, 0.0, 0.0)); + } + } + + KeyframeGroup scale() const { + if (_scale) { + return _scale.value(); + } else { + return KeyframeGroup(Vector3D(100.0, 100.0, 100.0)); + } + } + + KeyframeGroup rotation() const { + if (_rotation) { + return _rotation.value(); + } else { + return KeyframeGroup(Vector1D(0.0)); + } + } + + KeyframeGroup opacity() const { + if (_opacity) { + return _opacity.value(); + } else { + return KeyframeGroup(Vector1D(100.0)); + } + } + + std::optional> const &position() const { + return _position; + } + + std::optional> const &positionX() const { + return _positionX; + } + + std::optional> const &positionY() const { + return _positionY; + } + +private: + /// The anchor point of the transform. + std::optional> _anchorPoint; + + /// The position of the transform. This is nil if the position data was split. + std::optional> _position; + + /// The positionX of the transform. This is nil if the position property is set. + std::optional> _positionX; + + /// The positionY of the transform. This is nil if the position property is set. + std::optional> _positionY; + + PositionInternalRepresentation _positionInternalRepresentation = PositionInternalRepresentation::None; + + /// The scale of the transform + std::optional> _scale; + + /// The rotation of the transform. Note: This is single dimensional rotation. + std::optional> _rotation; + + /// The opacity of the transform. + std::optional> _opacity; + + /// Should always be nil. + std::optional> _rotationZ; + RotationZInternalRepresentation _rotationZInternalRepresentation; + + std::optional _extra_positionS; + std::optional _extraTy; + std::optional _extraSa; + std::optional _extraSk; +}; + +} + +#endif /* Transform_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Ellipse.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Ellipse.cpp new file mode 100644 index 0000000000..9378276f24 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Ellipse.cpp @@ -0,0 +1,5 @@ +#include "Ellipse.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Ellipse.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Ellipse.hpp new file mode 100644 index 0000000000..2565dbf817 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Ellipse.hpp @@ -0,0 +1,64 @@ +#ifndef Ellipse_hpp +#define Ellipse_hpp + +#include "Lottie/Private/Model/ShapeItems/ShapeItem.hpp" +#include "Lottie/Private/Model/Keyframes/KeyframeGroup.hpp" +#include "Lottie/Public/Primitives/Vectors.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +namespace lottie { + +enum class PathDirection: int { + Clockwise = 1, + UserSetClockwise = 2, + CounterClockwise = 3 +}; + +/// An item that define an ellipse shape +class Ellipse: public ShapeItem { +public: + explicit Ellipse(json11::Json::object const &json) noexcept(false) : + ShapeItem(json), + position(KeyframeGroup(Vector3D(0.0, 0.0, 0.0))), + size(KeyframeGroup(Vector3D(0.0, 0.0, 0.0))) { + if (const auto directionRawValue = getOptionalInt(json, "d")) { + switch (directionRawValue.value()) { + case 1: + direction = PathDirection::Clockwise; + break; + case 2: + direction = PathDirection::UserSetClockwise; + break; + case 3: + direction = PathDirection::CounterClockwise; + break; + default: + throw LottieParsingException(); + } + } + + position = KeyframeGroup(getObject(json, "p")); + size = KeyframeGroup(getObject(json, "s")); + } + + virtual ~Ellipse() = default; + + virtual void toJson(json11::Json::object &json) const override { + ShapeItem::toJson(json); + + if (direction.has_value()) { + json.insert(std::make_pair("d", (int)direction.value())); + } + json.insert(std::make_pair("p", position.toJson())); + json.insert(std::make_pair("s", size.toJson())); + } + +public: + std::optional direction; + KeyframeGroup position; + KeyframeGroup size; +}; + +} + +#endif /* Ellipse_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Fill.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Fill.cpp new file mode 100644 index 0000000000..992a25a5be --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Fill.cpp @@ -0,0 +1,6 @@ +#include "Fill.hpp" + +namespace lottie { + +} + diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Fill.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Fill.hpp new file mode 100644 index 0000000000..6666c3b238 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Fill.hpp @@ -0,0 +1,76 @@ +#ifndef Fill_hpp +#define Fill_hpp + +#include "Lottie/Private/Model/Keyframes/KeyframeGroup.hpp" +#include "Lottie/Private/Model/ShapeItems/ShapeItem.hpp" +#include "Lottie/Public/Primitives/Color.hpp" +#include "Lottie/Public/Primitives/Vectors.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +namespace lottie { + +enum class FillRule: int { + None = 0, + NonZeroWinding = 1, + EvenOdd = 2 +}; + +class Fill: public ShapeItem { +public: + explicit Fill(json11::Json::object const &json) noexcept(false) : + ShapeItem(json), + opacity(KeyframeGroup(Vector1D(0.0))), + color(KeyframeGroup(Color(0.0, 0.0, 0.0, 0.0))) { + opacity = KeyframeGroup(getObject(json, "o")); + color = KeyframeGroup(getObject(json, "c")); + + if (const auto fillRuleRawValue = getOptionalInt(json, "r")) { + switch (fillRuleRawValue.value()) { + case 0: + fillRule = FillRule::None; + break; + case 1: + fillRule = FillRule::NonZeroWinding; + break; + case 2: + fillRule = FillRule::EvenOdd; + break; + default: + throw LottieParsingException(); + } + } + + fillEnabled = getOptionalBool(json, "fillEnabled"); + } + + virtual ~Fill() = default; + + virtual void toJson(json11::Json::object &json) const override { + ShapeItem::toJson(json); + + json.insert(std::make_pair("o", opacity.toJson())); + json.insert(std::make_pair("c", color.toJson())); + + if (fillRule.has_value()) { + json.insert(std::make_pair("r", (int)fillRule.value())); + } + + if (fillEnabled.has_value()) { + json.insert(std::make_pair("fillEnabled", fillEnabled.value())); + } + } + +public: + KeyframeGroup opacity; + + /// The color keyframes for the fill + KeyframeGroup color; + + std::optional fillRule; + + std::optional fillEnabled; +}; + +} + +#endif /* Fill_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/GradientFill.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/GradientFill.cpp new file mode 100644 index 0000000000..f2ed96d1b9 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/GradientFill.cpp @@ -0,0 +1,5 @@ +#include "GradientFill.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/GradientFill.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/GradientFill.hpp new file mode 100644 index 0000000000..55c7efc9c2 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/GradientFill.hpp @@ -0,0 +1,118 @@ +#ifndef GradientFill_hpp +#define GradientFill_hpp + +#include "Lottie/Private/Model/ShapeItems/ShapeItem.hpp" +#include "Lottie/Private/Model/Keyframes/KeyframeGroup.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" +#include "Lottie/Public/Primitives/GradientColorSet.hpp" + +namespace lottie { + +enum class GradientType: int { + None = 0, + Linear = 1, + Radial = 2 +}; + +/// An item that define a gradient fill +class GradientFill: public ShapeItem { +public: + explicit GradientFill(json11::Json::object const &json) noexcept(false) : + ShapeItem(json), + opacity(KeyframeGroup(Vector1D(100.0))), + startPoint(KeyframeGroup(Vector3D(0.0, 0.0, 0.0))), + endPoint(KeyframeGroup(Vector3D(0.0, 0.0, 0.0))), + gradientType(GradientType::None), + numberOfColors(0), + colors(KeyframeGroup(GradientColorSet())) { + opacity = KeyframeGroup(getObject(json, "o")); + startPoint = KeyframeGroup(getObject(json, "s")); + endPoint = KeyframeGroup(getObject(json, "e")); + + auto gradientTypeRawValue = getInt(json, "t"); + switch (gradientTypeRawValue) { + case 0: + gradientType = GradientType::None; + break; + case 1: + gradientType = GradientType::Linear; + break; + case 2: + gradientType = GradientType::Radial; + break; + default: + throw LottieParsingException(); + } + + if (const auto highlightLengthData = getOptionalObject(json, "h")) { + highlightLength = KeyframeGroup(highlightLengthData.value()); + } + if (const auto highlightAngleData = getOptionalObject(json, "a")) { + highlightAngle = KeyframeGroup(highlightAngleData.value()); + } + + auto colorsContainer = getObject(json, "g"); + numberOfColors = getInt(colorsContainer, "p"); + colors = KeyframeGroup(getObject(colorsContainer, "k")); + + rValue = getOptionalInt(json, "r"); + } + + virtual ~GradientFill() = default; + + virtual void toJson(json11::Json::object &json) const override { + ShapeItem::toJson(json); + + json.insert(std::make_pair("o", opacity.toJson())); + json.insert(std::make_pair("s", startPoint.toJson())); + json.insert(std::make_pair("e", endPoint.toJson())); + json.insert(std::make_pair("t", (int)gradientType)); + + if (highlightLength.has_value()) { + json.insert(std::make_pair("h", highlightLength->toJson())); + } + if (highlightAngle.has_value()) { + json.insert(std::make_pair("a", highlightAngle->toJson())); + } + + json11::Json::object colorsContainer; + colorsContainer.insert(std::make_pair("p", numberOfColors)); + colorsContainer.insert(std::make_pair("k", colors.toJson())); + json.insert(std::make_pair("g", colorsContainer)); + + if (rValue.has_value()) { + json.insert(std::make_pair("r", rValue.value())); + } + } + +public: + /// The opacity of the fill + KeyframeGroup opacity; + + /// The start of the gradient + KeyframeGroup startPoint; + + /// The end of the gradient + KeyframeGroup endPoint; + + /// The type of gradient + GradientType gradientType; + + /// Gradient Highlight Length. Only if type is Radial + std::optional> highlightLength; + + /// Highlight Angle. Only if type is Radial + std::optional> highlightAngle; + + /// The number of color points in the gradient + int numberOfColors; + + /// The Colors of the gradient. + KeyframeGroup colors; + + std::optional rValue; +}; + +} + +#endif /* GradientFill_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/GradientStroke.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/GradientStroke.cpp new file mode 100644 index 0000000000..465b20fde8 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/GradientStroke.cpp @@ -0,0 +1,5 @@ +#include "GradientStroke.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/GradientStroke.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/GradientStroke.hpp new file mode 100644 index 0000000000..77b45b76aa --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/GradientStroke.hpp @@ -0,0 +1,193 @@ +#ifndef GradientStroke_hpp +#define GradientStroke_hpp + +#include "Lottie/Private/Model/ShapeItems/ShapeItem.hpp" +#include "Lottie/Private/Model/ShapeItems/GradientFill.hpp" +#include "Lottie/Private/Model/Keyframes/KeyframeGroup.hpp" +#include "Lottie/Private/Model/Objects/DashElement.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" +#include "Lottie/Public/Primitives/DrawingAttributes.hpp" + +namespace lottie { + +/// An item that define a gradient stroke +class GradientStroke: public ShapeItem { +public: + explicit GradientStroke(json11::Json::object const &json) noexcept(false) : + ShapeItem(json), + opacity(KeyframeGroup(Vector1D(100.0))), + startPoint(KeyframeGroup(Vector3D(0.0, 0.0, 0.0))), + endPoint(KeyframeGroup(Vector3D(0.0, 0.0, 0.0))), + gradientType(GradientType::None), + numberOfColors(0), + colors(KeyframeGroup(GradientColorSet())), + width(KeyframeGroup(Vector1D(0.0))), + lineCap(LineCap::Round), + lineJoin(LineJoin::Round) { + opacity = KeyframeGroup(getObject(json, "o")); + startPoint = KeyframeGroup(getObject(json, "s")); + endPoint = KeyframeGroup(getObject(json, "e")); + + auto gradientTypeRawValue = getInt(json, "t"); + switch (gradientTypeRawValue) { + case 0: + gradientType = GradientType::None; + break; + case 1: + gradientType = GradientType::Linear; + break; + case 2: + gradientType = GradientType::Radial; + break; + default: + throw LottieParsingException(); + } + + if (const auto highlightLengthData = getOptionalObject(json, "h")) { + highlightLength = KeyframeGroup(highlightLengthData.value()); + } + if (const auto highlightAngleData = getOptionalObject(json, "a")) { + highlightAngle = KeyframeGroup(highlightAngleData.value()); + } + + width = KeyframeGroup(getObject(json, "w")); + + if (const auto lineCapRawValue = getOptionalInt(json, "lc")) { + switch (lineCapRawValue.value()) { + case 0: + lineCap = LineCap::None; + break; + case 1: + lineCap = LineCap::Butt; + break; + case 2: + lineCap = LineCap::Round; + break; + case 3: + lineCap = LineCap::Square; + break; + default: + throw LottieParsingException(); + } + } + + if (const auto lineJoinRawValue = getOptionalInt(json, "lj")) { + switch (lineJoinRawValue.value()) { + case 0: + lineJoin = LineJoin::None; + break; + case 1: + lineJoin = LineJoin::Miter; + break; + case 2: + lineJoin = LineJoin::Round; + break; + case 3: + lineJoin = LineJoin::Bevel; + break; + default: + throw LottieParsingException(); + } + } + + if (const auto miterLimitData = getOptionalDouble(json, "ml")) { + miterLimit = miterLimitData.value(); + } + + auto colorsContainer = getObject(json, "g"); + numberOfColors = getInt(colorsContainer, "p"); + auto colorsData = getObject(colorsContainer, "k"); + colors = KeyframeGroup(colorsData); + + if (const auto dashElementsData = getOptionalObjectArray(json, "d")) { + dashPattern = std::vector(); + for (const auto &dashElementData : dashElementsData.value()) { + dashPattern->push_back(DashElement(dashElementData)); + } + } + } + + virtual ~GradientStroke() = default; + + virtual void toJson(json11::Json::object &json) const override { + ShapeItem::toJson(json); + + json.insert(std::make_pair("o", opacity.toJson())); + json.insert(std::make_pair("s", startPoint.toJson())); + json.insert(std::make_pair("e", endPoint.toJson())); + json.insert(std::make_pair("t", (int)gradientType)); + + if (highlightLength.has_value()) { + json.insert(std::make_pair("h", highlightLength->toJson())); + } + if (highlightAngle.has_value()) { + json.insert(std::make_pair("a", highlightAngle->toJson())); + } + + json.insert(std::make_pair("w", width.toJson())); + + json.insert(std::make_pair("lc", (int)lineCap)); + json.insert(std::make_pair("lj", (int)lineJoin)); + + if (miterLimit.has_value()) { + json.insert(std::make_pair("ml", miterLimit.value())); + } + + json11::Json::object colorsContainer; + colorsContainer.insert(std::make_pair("p", numberOfColors)); + colorsContainer.insert(std::make_pair("k", colors.toJson())); + json.insert(std::make_pair("g", colorsContainer)); + + if (dashPattern.has_value()) { + json11::Json::array dashElements; + for (const auto &dashElement : dashPattern.value()) { + dashElements.push_back(dashElement.toJson()); + } + json.insert(std::make_pair("d", dashElements)); + } + } + +public: + /// The opacity of the fill + KeyframeGroup opacity; + + /// The start of the gradient + KeyframeGroup startPoint; + + /// The end of the gradient + KeyframeGroup endPoint; + + /// The type of gradient + GradientType gradientType; + + /// Gradient Highlight Length. Only if type is Radial + std::optional> highlightLength; + + /// Highlight Angle. Only if type is Radial + std::optional> highlightAngle; + + /// The number of color points in the gradient + int numberOfColors; + + /// The Colors of the gradient. + KeyframeGroup colors; + + /// The width of the stroke + KeyframeGroup width; + + /// Line Cap + LineCap lineCap; + + /// Line Join + LineJoin lineJoin; + + /// Miter Limit + std::optional miterLimit; + + /// The dash pattern of the stroke + std::optional> dashPattern; +}; + +} + +#endif /* GradientStroke_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Group.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Group.cpp new file mode 100644 index 0000000000..89ea7daea7 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Group.cpp @@ -0,0 +1,5 @@ +#include "Group.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Group.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Group.hpp new file mode 100644 index 0000000000..10002a2691 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Group.hpp @@ -0,0 +1,53 @@ +#ifndef Group_hpp +#define Group_hpp + +#include "Lottie/Private/Model/ShapeItems/ShapeItem.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +#include +#include + +namespace lottie { + +/// An item that define an ellipse shape +class Group: public ShapeItem { +public: + explicit Group(json11::Json::object const &json) noexcept(false) : + ShapeItem(json) { + auto itemsData = getObjectArray(json, "it"); + for (const auto &itemData : itemsData) { + items.push_back(parseShapeItem(itemData)); + } + + numberOfProperties = getOptionalInt(json, "np"); + } + + virtual ~Group() = default; + + virtual void toJson(json11::Json::object &json) const override { + ShapeItem::toJson(json); + + json11::Json::array itemArray; + for (const auto &item : items) { + json11::Json::object itemJson; + item->toJson(itemJson); + itemArray.push_back(itemJson); + } + + json.insert(std::make_pair("it", itemArray)); + + if (numberOfProperties.has_value()) { + json.insert(std::make_pair("np", numberOfProperties.value())); + } + } + +public: + /// A list of shape items. + std::vector> items; + + std::optional numberOfProperties; +}; + +} + +#endif /* Group_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Merge.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Merge.cpp new file mode 100644 index 0000000000..b22b05d1f6 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Merge.cpp @@ -0,0 +1,5 @@ +#include "Merge.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Merge.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Merge.hpp new file mode 100644 index 0000000000..0492f66664 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Merge.hpp @@ -0,0 +1,64 @@ +#ifndef Merge_hpp +#define Merge_hpp + +#include "Lottie/Private/Model/ShapeItems/ShapeItem.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +namespace lottie { + +enum class MergeMode: int { + None = 0, + Merge = 1, + Add = 2, + Subtract = 3, + Intersect = 4, + Exclude = 5 +}; + +/// An item that define an ellipse shape +class Merge: public ShapeItem { +public: + explicit Merge(json11::Json::object const &json) noexcept(false) : + ShapeItem(json), + mode(MergeMode::None) { + auto modeRawValue = getInt(json, "mm"); + switch (modeRawValue) { + case 0: + mode = MergeMode::None; + break; + case 1: + mode = MergeMode::Merge; + break; + case 2: + mode = MergeMode::Add; + break; + case 3: + mode = MergeMode::Subtract; + break; + case 4: + mode = MergeMode::Intersect; + break; + case 5: + mode = MergeMode::Exclude; + break; + default: + throw LottieParsingException(); + } + } + + virtual ~Merge() = default; + + virtual void toJson(json11::Json::object &json) const override { + ShapeItem::toJson(json); + + json.insert(std::make_pair("mm", (int)mode)); + } + +public: + /// The mode of the merge path + MergeMode mode; +}; + +} + +#endif /* Merge_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Rectangle.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Rectangle.cpp new file mode 100644 index 0000000000..c686130aa9 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Rectangle.cpp @@ -0,0 +1,5 @@ +#include "Rectangle.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Rectangle.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Rectangle.hpp new file mode 100644 index 0000000000..bb4a1d19e9 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Rectangle.hpp @@ -0,0 +1,101 @@ +#ifndef Rectangle_hpp +#define Rectangle_hpp + +#include "Lottie/Private/Model/ShapeItems/ShapeItem.hpp" +#include "Lottie/Private/Model/ShapeItems/Ellipse.hpp" +#include "Lottie/Private/Model/Keyframes/KeyframeGroup.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +namespace lottie { + +/// An item that define an ellipse shape +class Rectangle: public ShapeItem { +public: + explicit Rectangle(json11::Json::object const &json) noexcept(false) : + ShapeItem(json), + position(KeyframeGroup(Vector3D(0.0, 0.0, 0.0))), + size(KeyframeGroup(Vector3D(0.0, 0.0, 0.0))), + cornerRadius(KeyframeGroup(Vector1D(0.0))) { + if (const auto directionRawValue = getOptionalInt(json, "d")) { + switch (directionRawValue.value()) { + case 1: + direction = PathDirection::Clockwise; + break; + case 2: + direction = PathDirection::UserSetClockwise; + break; + case 3: + direction = PathDirection::CounterClockwise; + break; + default: + throw LottieParsingException(); + } + } + + position = KeyframeGroup(getObject(json, "p")); + size = KeyframeGroup(getObject(json, "s")); + cornerRadius = KeyframeGroup(getObject(json, "r")); + } + + virtual ~Rectangle() = default; + + explicit Rectangle( + std::optional name_, + std::optional matchName_, + std::optional expressionIndex_, + std::optional cix_, + std::optional _hidden_, + std::optional index_, + std::optional blendMode_, + std::optional layerClass_, + std::optional direction_, + KeyframeGroup position_, + KeyframeGroup size_, + KeyframeGroup cornerRadius_ + ) : + ShapeItem( + name_, + matchName_, + expressionIndex_, + cix_, + ShapeType::Rectangle, + _hidden_, + index_, + blendMode_, + layerClass_ + ), + direction(direction_), + position(position_), + size(size_), + cornerRadius(cornerRadius_) { + } + + virtual void toJson(json11::Json::object &json) const override { + ShapeItem::toJson(json); + + if (direction.has_value()) { + json.insert(std::make_pair("d", (int)direction.value())); + } + + json.insert(std::make_pair("p", position.toJson())); + json.insert(std::make_pair("s", size.toJson())); + json.insert(std::make_pair("r", cornerRadius.toJson())); + } + +public: + /// The direction of the rect. + std::optional direction; + + /// The position + KeyframeGroup position; + + /// The size + KeyframeGroup size; + + /// The Corner radius of the rectangle + KeyframeGroup cornerRadius; +}; + +} + +#endif /* Rectangle_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Repeater.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Repeater.cpp new file mode 100644 index 0000000000..70477e31af --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Repeater.cpp @@ -0,0 +1,5 @@ +#include "Repeater.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Repeater.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Repeater.hpp new file mode 100644 index 0000000000..9c8b7f86b2 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Repeater.hpp @@ -0,0 +1,100 @@ +#ifndef Repeater_hpp +#define Repeater_hpp + +#include "Lottie/Private/Model/ShapeItems/ShapeItem.hpp" +#include "Lottie/Private/Model/Keyframes/KeyframeGroup.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +namespace lottie { + +/// An item that define a repeater +class Repeater: public ShapeItem { +public: + explicit Repeater(json11::Json::object const &json) noexcept(false) : + ShapeItem(json) { + if (const auto copiesData = getOptionalObject(json, "c")) { + copies = KeyframeGroup(copiesData.value()); + } + if (const auto offsetData = getOptionalObject(json, "o")) { + offset = KeyframeGroup(offsetData.value()); + } + + auto transformContainer = getObject(json, "tr"); + if (const auto startOpacityData = getOptionalObject(transformContainer, "so")) { + startOpacity = KeyframeGroup(startOpacityData.value()); + } + if (const auto endOpacityData = getOptionalObject(transformContainer, "eo")) { + endOpacity = KeyframeGroup(endOpacityData.value()); + } + if (const auto rotationData = getOptionalObject(transformContainer, "r")) { + rotation = KeyframeGroup(rotationData.value()); + } + if (const auto positionData = getOptionalObject(transformContainer, "p")) { + position = KeyframeGroup(positionData.value()); + } + if (const auto scaleData = getOptionalObject(transformContainer, "s")) { + scale = KeyframeGroup(scaleData.value()); + } + } + + virtual ~Repeater() = default; + + virtual void toJson(json11::Json::object &json) const override { + ShapeItem::toJson(json); + + if (copies.has_value()) { + json.insert(std::make_pair("c", copies->toJson())); + } + if (offset.has_value()) { + json.insert(std::make_pair("o", offset->toJson())); + } + + json11::Json::object transformContainer; + if (startOpacity.has_value()) { + json.insert(std::make_pair("so", startOpacity->toJson())); + } + if (endOpacity.has_value()) { + json.insert(std::make_pair("eo", endOpacity->toJson())); + } + if (rotation.has_value()) { + json.insert(std::make_pair("r", rotation->toJson())); + } + if (position.has_value()) { + json.insert(std::make_pair("p", position->toJson())); + } + if (scale.has_value()) { + json.insert(std::make_pair("s", scale->toJson())); + } + + json.insert(std::make_pair("tr", transformContainer)); + } + +public: + /// The number of copies to repeat + std::optional> copies; + + /// The offset of each copy + std::optional> offset; + + /// Start Opacity + std::optional> startOpacity; + + /// End opacity + std::optional> endOpacity; + + /// The rotation + std::optional> rotation; + + /// Anchor Point + std::optional> anchorPoint; + + /// Position + std::optional> position; + + /// Scale + std::optional> scale; +}; + +} + +#endif /* Repeater_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/RoundedRectangle.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/RoundedRectangle.cpp new file mode 100644 index 0000000000..64cba09363 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/RoundedRectangle.cpp @@ -0,0 +1,5 @@ +#include "RoundedRectangle.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/RoundedRectangle.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/RoundedRectangle.hpp new file mode 100644 index 0000000000..f50a21e7c3 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/RoundedRectangle.hpp @@ -0,0 +1,77 @@ +#ifndef RoundedRectangle_hpp +#define RoundedRectangle_hpp + +#include "Lottie/Private/Model/ShapeItems/ShapeItem.hpp" +#include "Lottie/Private/Model/ShapeItems/Ellipse.hpp" +#include "Lottie/Private/Model/Keyframes/KeyframeGroup.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +namespace lottie { + +/// An item that define an ellipse shape +class RoundedRectangle: public ShapeItem { +public: + explicit RoundedRectangle(json11::Json::object const &json) noexcept(false) : + ShapeItem(json), + cornerRadius(KeyframeGroup(Vector1D(0.0))) { + if (const auto directionRawValue = getOptionalInt(json, "d")) { + switch (directionRawValue.value()) { + case 1: + direction = PathDirection::Clockwise; + break; + case 2: + direction = PathDirection::UserSetClockwise; + break; + case 3: + direction = PathDirection::CounterClockwise; + break; + default: + throw LottieParsingException(); + } + } + + if (const auto positionData = getOptionalObject(json, "p")) { + position = KeyframeGroup(positionData.value()); + } + if (const auto sizeData = getOptionalObject(json, "s")) { + size = KeyframeGroup(sizeData.value()); + } + + cornerRadius = KeyframeGroup(getObject(json, "r")); + } + + virtual ~RoundedRectangle() = default; + + virtual void toJson(json11::Json::object &json) const override { + ShapeItem::toJson(json); + + if (direction.has_value()) { + json.insert(std::make_pair("d", (int)direction.value())); + } + if (position.has_value()) { + json.insert(std::make_pair("p", position->toJson())); + } + if (size.has_value()) { + json.insert(std::make_pair("s", size->toJson())); + } + + json.insert(std::make_pair("r", cornerRadius.toJson())); + } + +public: + /// The direction of the rect. + std::optional direction; + + /// The position + std::optional> position; + + /// The size + std::optional> size; + + /// The Corner radius of the rectangle + KeyframeGroup cornerRadius; +}; + +} + +#endif /* RoundedRectangle_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Shape.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Shape.cpp new file mode 100644 index 0000000000..bf5a96876c --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Shape.cpp @@ -0,0 +1,5 @@ +#include "Shape.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Shape.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Shape.hpp new file mode 100644 index 0000000000..b3b52ca370 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Shape.hpp @@ -0,0 +1,55 @@ +#ifndef Shape_hpp +#define Shape_hpp + +#include "Lottie/Private/Model/ShapeItems/ShapeItem.hpp" +#include "Lottie/Private/Model/ShapeItems/Ellipse.hpp" +#include "Lottie/Private/Utility/Primitives/BezierPath.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +#include + +namespace lottie { + +/// An item that defines an custom shape +class Shape: public ShapeItem { +public: + explicit Shape(json11::Json::object const &json) noexcept(false) : + ShapeItem(json), + path(KeyframeGroup(getObject(json, "ks"))) { + if (const auto directionRawValue = getOptionalInt(json, "d")) { + switch (directionRawValue.value()) { + case 1: + direction = PathDirection::Clockwise; + break; + case 2: + direction = PathDirection::UserSetClockwise; + break; + case 3: + direction = PathDirection::CounterClockwise; + break; + default: + throw LottieParsingException(); + } + } + } + + virtual ~Shape() = default; + + virtual void toJson(json11::Json::object &json) const override { + ShapeItem::toJson(json); + + json.insert(std::make_pair("ks", path.toJson())); + + if (direction.has_value()) { + json.insert(std::make_pair("d", (int)direction.value())); + } + } + +public: + KeyframeGroup path; + std::optional direction; +}; + +} + +#endif /* Shape_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/ShapeItem.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/ShapeItem.cpp new file mode 100644 index 0000000000..ca859c56dd --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/ShapeItem.cpp @@ -0,0 +1,55 @@ +#include "ShapeItem.hpp" + +#include "Ellipse.hpp" +#include "Fill.hpp" +#include "GradientFill.hpp" +#include "Group.hpp" +#include "GradientStroke.hpp" +#include "Merge.hpp" +#include "Rectangle.hpp" +#include "RoundedRectangle.hpp" +#include "Repeater.hpp" +#include "Shape.hpp" +#include "Star.hpp" +#include "Stroke.hpp" +#include "Trim.hpp" +#include "ShapeTransform.hpp" + +namespace lottie { + +std::shared_ptr parseShapeItem(json11::Json::object const &json) noexcept(false) { + auto typeRawValue = getString(json, "ty"); + if (typeRawValue == "el") { + return std::make_shared(json); + } else if (typeRawValue == "fl") { + return std::make_shared(json); + } else if (typeRawValue == "gf") { + return std::make_shared(json); + } else if (typeRawValue == "gr") { + return std::make_shared(json); + } else if (typeRawValue == "gs") { + return std::make_shared(json); + } else if (typeRawValue == "mm") { + return std::make_shared(json); + } else if (typeRawValue == "rc") { + return std::make_shared(json); + } else if (typeRawValue == "rp") { + return std::make_shared(json); + } else if (typeRawValue == "sh") { + return std::make_shared(json); + } else if (typeRawValue == "sr") { + return std::make_shared(json); + } else if (typeRawValue == "st") { + return std::make_shared(json); + } else if (typeRawValue == "tm") { + return std::make_shared(json); + } else if (typeRawValue == "tr") { + return std::make_shared(json); + } else if (typeRawValue == "rd") { + return std::make_shared(json); + } else { + throw LottieParsingException(); + } +} + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/ShapeItem.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/ShapeItem.hpp new file mode 100644 index 0000000000..2b05dba93a --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/ShapeItem.hpp @@ -0,0 +1,209 @@ +#ifndef ShapeItem_hpp +#define ShapeItem_hpp + +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +#include + +namespace lottie { + +enum class ShapeType { + Ellipse, + Fill, + GradientFill, + Group, + GradientStroke, + Merge, + Rectangle, + Repeater, + Shape, + Star, + Stroke, + Trim, + Transform, + RoundedRectangle +}; + +/// An item belonging to a Shape Layer +class ShapeItem { +public: + ShapeItem(json11::Json const &jsonAny) noexcept(false) : + type(ShapeType::Ellipse) { + if (!jsonAny.is_object()) { + throw LottieParsingException(); + } + + json11::Json::object const &json = jsonAny.object_items(); + + name = getOptionalString(json, "nm"); + matchName = getOptionalString(json, "mn"); + expressionIndex = getOptionalInt(json, "ix"); + cix = getOptionalInt(json, "cix"); + + index = getOptionalInt(json, "ind"); + blendMode = getOptionalInt(json, "bm"); + + auto typeRawValue = getString(json, "ty"); + if (typeRawValue == "el") { + type = ShapeType::Ellipse; + } else if (typeRawValue == "fl") { + type = ShapeType::Fill; + } else if (typeRawValue == "gf") { + type = ShapeType::GradientFill; + } else if (typeRawValue == "gr") { + type = ShapeType::Group; + } else if (typeRawValue == "gs") { + type = ShapeType::GradientStroke; + } else if (typeRawValue == "mm") { + type = ShapeType::Merge; + } else if (typeRawValue == "rc") { + type = ShapeType::Rectangle; + } else if (typeRawValue == "rp") { + type = ShapeType::Repeater; + } else if (typeRawValue == "sh") { + type = ShapeType::Shape; + } else if (typeRawValue == "sr") { + type = ShapeType::Star; + } else if (typeRawValue == "st") { + type = ShapeType::Stroke; + } else if (typeRawValue == "tm") { + type = ShapeType::Trim; + } else if (typeRawValue == "tr") { + type = ShapeType::Transform; + } else if (typeRawValue == "rd") { + type = ShapeType::RoundedRectangle; + } else { + throw LottieParsingException(); + } + + _hidden = getOptionalBool(json, "hd"); + + layerClass = getOptionalString(json, "cl"); + } + + ShapeItem( + std::optional name_, + std::optional matchName_, + std::optional expressionIndex_, + std::optional cix_, + ShapeType type_, + std::optional _hidden_, + std::optional index_, + std::optional blendMode_, + std::optional layerClass_ + ) : + name(name_), + matchName(matchName_), + expressionIndex(expressionIndex_), + cix(cix_), + type(type_), + _hidden(_hidden_), + index(index_), + blendMode(blendMode_), + layerClass(layerClass_) { + } + + ShapeItem(const ShapeItem&) = delete; + ShapeItem& operator=(ShapeItem&) = delete; + + virtual void toJson(json11::Json::object &json) const { + if (name.has_value()) { + json.insert(std::make_pair("nm", name.value())); + } + if (matchName.has_value()) { + json.insert(std::make_pair("mn", matchName.value())); + } + if (expressionIndex.has_value()) { + json.insert(std::make_pair("ix", expressionIndex.value())); + } + if (cix.has_value()) { + json.insert(std::make_pair("cix", cix.value())); + } + + if (index.has_value()) { + json.insert(std::make_pair("ind", index.value())); + } + if (blendMode.has_value()) { + json.insert(std::make_pair("bm", blendMode.value())); + } + + switch (type) { + case ShapeType::Ellipse: + json.insert(std::make_pair("ty", "el")); + break; + case ShapeType::Fill: + json.insert(std::make_pair("ty", "fl")); + break; + case ShapeType::GradientFill: + json.insert(std::make_pair("ty", "gf")); + break; + case ShapeType::Group: + json.insert(std::make_pair("ty", "gr")); + break; + case ShapeType::GradientStroke: + json.insert(std::make_pair("ty", "gs")); + break; + case ShapeType::Merge: + json.insert(std::make_pair("ty", "mm")); + break; + case ShapeType::Rectangle: + json.insert(std::make_pair("ty", "rc")); + break; + case ShapeType::RoundedRectangle: + json.insert(std::make_pair("ty", "rd")); + break; + case ShapeType::Repeater: + json.insert(std::make_pair("ty", "rp")); + break; + case ShapeType::Shape: + json.insert(std::make_pair("ty", "sh")); + break; + case ShapeType::Star: + json.insert(std::make_pair("ty", "sr")); + break; + case ShapeType::Stroke: + json.insert(std::make_pair("ty", "st")); + break; + case ShapeType::Trim: + json.insert(std::make_pair("ty", "tm")); + break; + case ShapeType::Transform: + json.insert(std::make_pair("ty", "tr")); + break; + } + + if (_hidden.has_value()) { + json.insert(std::make_pair("hd", _hidden.value())); + } + + if (layerClass.has_value()) { + json.insert(std::make_pair("cl", layerClass.value())); + } + } + + bool hidden() const { + if (_hidden.has_value()) { + return _hidden.value(); + } else { + return false; + } + } + +public: + std::optional name; + std::optional matchName; + std::optional expressionIndex; + std::optional cix; + ShapeType type; + std::optional _hidden; + std::optional index; + std::optional blendMode; + + std::optional layerClass; +}; + +std::shared_ptr parseShapeItem(json11::Json::object const &json) noexcept(false); + +} + +#endif /* ShapeItem_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/ShapeTransform.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/ShapeTransform.cpp new file mode 100644 index 0000000000..1df07218bc --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/ShapeTransform.cpp @@ -0,0 +1,5 @@ +#include "ShapeTransform.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/ShapeTransform.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/ShapeTransform.hpp new file mode 100644 index 0000000000..5160cf4a0b --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/ShapeTransform.hpp @@ -0,0 +1,91 @@ +#ifndef ShapeTransform_hpp +#define ShapeTransform_hpp + +#include "Lottie/Private/Model/ShapeItems/ShapeItem.hpp" +#include "Lottie/Private/Model/Keyframes/KeyframeGroup.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +namespace lottie { + +/// An item that define a shape transform +class ShapeTransform: public ShapeItem { +public: + explicit ShapeTransform(json11::Json::object const &json) noexcept(false) : + ShapeItem(json) { + if (const auto anchorData = getOptionalObject(json, "a")) { + anchor = KeyframeGroup(anchorData.value()); + } + if (const auto positionData = getOptionalObject(json, "p")) { + position = KeyframeGroup(positionData.value()); + } + if (const auto scaleData = getOptionalObject(json, "s")) { + scale = KeyframeGroup(scaleData.value()); + } + if (const auto rotationData = getOptionalObject(json, "r")) { + rotation = KeyframeGroup(rotationData.value()); + } + if (const auto opacityData = getOptionalObject(json, "o")) { + opacity = KeyframeGroup(opacityData.value()); + } + if (const auto skewData = getOptionalObject(json, "sk")) { + skew = KeyframeGroup(skewData.value()); + } + if (const auto skewAxisData = getOptionalObject(json, "sa")) { + skewAxis = KeyframeGroup(skewAxisData.value()); + } + } + + virtual ~ShapeTransform() = default; + + virtual void toJson(json11::Json::object &json) const override { + ShapeItem::toJson(json); + + if (anchor.has_value()) { + json.insert(std::make_pair("a", anchor->toJson())); + } + if (position.has_value()) { + json.insert(std::make_pair("p", position->toJson())); + } + if (scale.has_value()) { + json.insert(std::make_pair("s", scale->toJson())); + } + if (rotation.has_value()) { + json.insert(std::make_pair("r", rotation->toJson())); + } + if (opacity.has_value()) { + json.insert(std::make_pair("o", opacity->toJson())); + } + if (skew.has_value()) { + json.insert(std::make_pair("sk", skew->toJson())); + } + if (skewAxis.has_value()) { + json.insert(std::make_pair("sa", skewAxis->toJson())); + } + } + +public: + /// Anchor Point + std::optional> anchor; + + /// Position + std::optional> position; + + /// Scale + std::optional> scale; + + /// Rotation + std::optional> rotation; + + /// opacity + std::optional> opacity; + + /// Skew + std::optional> skew; + + /// Skew Axis + std::optional> skewAxis; +}; + +} + +#endif /* ShapeTransform_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Star.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Star.cpp new file mode 100644 index 0000000000..c5f38f5946 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Star.cpp @@ -0,0 +1,5 @@ +#include "Star.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Star.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Star.hpp new file mode 100644 index 0000000000..7bc5fb16c6 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Star.hpp @@ -0,0 +1,133 @@ +#ifndef Star_hpp +#define Star_hpp + +#include "Lottie/Private/Model/ShapeItems/ShapeItem.hpp" +#include "Lottie/Private/Model/ShapeItems/Ellipse.hpp" +#include "Lottie/Private/Model/Keyframes/KeyframeGroup.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +#include + +namespace lottie { + +enum class StarType: int { + None = 0, + Star = 1, + Polygon = 2 +}; + +/// An item that define a star shape +class Star: public ShapeItem { +public: + explicit Star(json11::Json::object const &json) noexcept(false) : + ShapeItem(json), + position(KeyframeGroup(Vector3D(0.0, 0.0, 0.0))), + outerRadius(KeyframeGroup(Vector1D(0.0))), + outerRoundness(KeyframeGroup(Vector1D(0.0))), + rotation(KeyframeGroup(Vector1D(0.0))), + points(KeyframeGroup(Vector1D(0.0))), + starType(StarType::None) { + if (const auto directionRawValue = getOptionalInt(json, "d")) { + switch (directionRawValue.value()) { + case 1: + direction = PathDirection::Clockwise; + break; + case 2: + direction = PathDirection::UserSetClockwise; + break; + case 3: + direction = PathDirection::CounterClockwise; + break; + default: + throw LottieParsingException(); + } + } + + position = KeyframeGroup(getObject(json, "p")); + outerRadius = KeyframeGroup(getObject(json, "or")); + outerRoundness = KeyframeGroup(getObject(json, "os")); + + if (const auto innerRadiusData = getOptionalObject(json, "ir")) { + innerRadius = KeyframeGroup(innerRadiusData.value()); + } + if (const auto innerRoundnessData = getOptionalObject(json, "is")) { + innerRoundness = KeyframeGroup(innerRoundnessData.value()); + } + + rotation = KeyframeGroup(getObject(json, "r")); + points = KeyframeGroup(getObject(json, "pt")); + + auto starTypeRawValue = getInt(json, "sy"); + switch (starTypeRawValue) { + case 0: + starType = StarType::None; + break; + case 1: + starType = StarType::Star; + break; + case 2: + starType = StarType::Polygon; + break; + default: + throw LottieParsingException(); + } + } + + virtual ~Star() = default; + + virtual void toJson(json11::Json::object &json) const override { + ShapeItem::toJson(json); + + if (direction.has_value()) { + json.insert(std::make_pair("d", (int)direction.value())); + } + + json.insert(std::make_pair("p", position.toJson())); + json.insert(std::make_pair("or", outerRadius.toJson())); + json.insert(std::make_pair("os", outerRoundness.toJson())); + + if (innerRadius.has_value()) { + json.insert(std::make_pair("ir", innerRadius->toJson())); + } + if (innerRoundness.has_value()) { + json.insert(std::make_pair("is", innerRoundness->toJson())); + } + + json.insert(std::make_pair("r", rotation.toJson())); + json.insert(std::make_pair("pt", points.toJson())); + + json.insert(std::make_pair("sy", (int)starType)); + } + +public: + /// The direction of the star. + std::optional direction; + + /// The position of the star + KeyframeGroup position; + + /// The outer radius of the star + KeyframeGroup outerRadius; + + /// The outer roundness of the star + KeyframeGroup outerRoundness; + + /// The outer radius of the star + std::optional> innerRadius; + + /// The outer roundness of the star + std::optional> innerRoundness; + + /// The rotation of the star + KeyframeGroup rotation; + + /// The number of points on the star + KeyframeGroup points; + + /// The type of star + StarType starType; +}; + +} + +#endif /* Star_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Stroke.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Stroke.cpp new file mode 100644 index 0000000000..0c858f2442 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Stroke.cpp @@ -0,0 +1,5 @@ +#include "Stroke.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Stroke.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Stroke.hpp new file mode 100644 index 0000000000..5930dbadd2 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Stroke.hpp @@ -0,0 +1,142 @@ +#ifndef Stroke_hpp +#define Stroke_hpp + +#include "Lottie/Private/Model/ShapeItems/ShapeItem.hpp" +#include "Lottie/Private/Model/ShapeItems/GradientStroke.hpp" +#include "Lottie/Public/Primitives/Color.hpp" +#include "Lottie/Private/Model/Keyframes/KeyframeGroup.hpp" +#include "Lottie/Private/Model/Objects/DashElement.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +#include + +namespace lottie { + +/// An item that define an ellipse shape +class Stroke: public ShapeItem { +public: + explicit Stroke(json11::Json::object const &json) noexcept(false) : + ShapeItem(json), + opacity(KeyframeGroup(Vector1D(100.0))), + color(KeyframeGroup(Color(0.0, 0.0, 0.0, 0.0))), + width(KeyframeGroup(Vector1D(0.0))), + lineCap(LineCap::Round), + lineJoin(LineJoin::Round) { + opacity = KeyframeGroup(getObject(json, "o")); + color = KeyframeGroup(getObject(json, "c")); + width = KeyframeGroup(getObject(json, "w")); + + if (const auto lineCapRawValue = getOptionalInt(json, "lc")) { + switch (lineCapRawValue.value()) { + case 0: + lineCap = LineCap::None; + break; + case 1: + lineCap = LineCap::Butt; + break; + case 2: + lineCap = LineCap::Round; + break; + case 3: + lineCap = LineCap::Square; + break; + default: + throw LottieParsingException(); + } + } + + if (const auto lineJoinRawValue = getOptionalInt(json, "lj")) { + switch (lineJoinRawValue.value()) { + case 0: + lineJoin = LineJoin::None; + break; + case 1: + lineJoin = LineJoin::Miter; + break; + case 2: + lineJoin = LineJoin::Round; + break; + case 3: + lineJoin = LineJoin::Bevel; + break; + default: + throw LottieParsingException(); + } + } + + if (const auto miterLimitData = getOptionalDouble(json, "ml")) { + miterLimit = miterLimitData.value(); + } + + if (const auto dashElementsData = getOptionalObjectArray(json, "d")) { + dashPattern = std::vector(); + for (const auto &dashElementData : dashElementsData.value()) { + dashPattern->push_back(DashElement(dashElementData)); + } + } + + fillEnabled = getOptionalBool(json, "fillEnabled"); + ml2 = getOptionalAny(json, "ml2"); + } + + virtual ~Stroke() = default; + + virtual void toJson(json11::Json::object &json) const override { + ShapeItem::toJson(json); + + json.insert(std::make_pair("o", opacity.toJson())); + json.insert(std::make_pair("c", color.toJson())); + json.insert(std::make_pair("w", width.toJson())); + + json.insert(std::make_pair("lc", (int)lineCap)); + json.insert(std::make_pair("lj", (int)lineJoin)); + + if (miterLimit.has_value()) { + json.insert(std::make_pair("ml", miterLimit.value())); + } + + if (dashPattern.has_value()) { + json11::Json::array dashElements; + for (const auto &dashElement : dashPattern.value()) { + dashElements.push_back(dashElement.toJson()); + } + json.insert(std::make_pair("d", dashElements)); + } + + if (fillEnabled.has_value()) { + json.insert(std::make_pair("fillEnabled", fillEnabled.value())); + } + if (ml2.has_value()) { + json.insert(std::make_pair("ml2", ml2.value())); + } + } + +public: + /// The opacity of the stroke + KeyframeGroup opacity; + + /// The Color of the stroke + KeyframeGroup color; + + /// The width of the stroke + KeyframeGroup width; + + /// Line Cap + LineCap lineCap; + + /// Line Join + LineJoin lineJoin; + + /// Miter Limit + std::optional miterLimit; + + /// The dash pattern of the stroke + std::optional> dashPattern; + + std::optional fillEnabled; + std::optional ml2; +}; + +} + +#endif /* Stroke_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Trim.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Trim.cpp new file mode 100644 index 0000000000..85f95d8b0c --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Trim.cpp @@ -0,0 +1,5 @@ +#include "Trim.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Trim.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Trim.hpp new file mode 100644 index 0000000000..7ae612262b --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Trim.hpp @@ -0,0 +1,62 @@ +#ifndef Trim_hpp +#define Trim_hpp + +#include "Lottie/Private/Model/ShapeItems/ShapeItem.hpp" +#include "Lottie/Private/Model/Keyframes/KeyframeGroup.hpp" +#include "Lottie/Public/Primitives/Vectors.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +namespace lottie { + +enum class TrimType: int { + Simultaneously = 1, + Individually = 2 +}; + +/// An item that defines trim +class Trim: public ShapeItem { +public: + explicit Trim(json11::Json::object const &json) noexcept(false) : + ShapeItem(json), + start(KeyframeGroup(Vector1D(0.0))), + end(KeyframeGroup(Vector1D(0.0))), + offset(KeyframeGroup(Vector1D(0.0))), + trimType(TrimType::Simultaneously) { + start = KeyframeGroup(getObject(json, "s")); + end = KeyframeGroup(getObject(json, "e")); + offset = KeyframeGroup(getObject(json, "o")); + + auto trimTypeRawValue = getInt(json, "m"); + switch (trimTypeRawValue) { + case 1: + trimType = TrimType::Simultaneously; + break; + case 2: + trimType = TrimType::Individually; + break; + default: + throw LottieParsingException(); + } + } + + virtual ~Trim() = default; + + virtual void toJson(json11::Json::object &json) const override { + ShapeItem::toJson(json); + + json.insert(std::make_pair("s", start.toJson())); + json.insert(std::make_pair("e", end.toJson())); + json.insert(std::make_pair("o", offset.toJson())); + json.insert(std::make_pair("m", (int)trimType)); + } + +public: + KeyframeGroup start; + KeyframeGroup end; + KeyframeGroup offset; + TrimType trimType; +}; + +} + +#endif /* Trim_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Text/Font.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Text/Font.cpp new file mode 100644 index 0000000000..7dfd91b3cc --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Text/Font.cpp @@ -0,0 +1,5 @@ +#include "Font.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Text/Font.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Text/Font.hpp new file mode 100644 index 0000000000..8bde83023e --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Text/Font.hpp @@ -0,0 +1,103 @@ +#ifndef Font_hpp +#define Font_hpp + +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +#include +#include + +namespace lottie { + +class Font { +public: + Font( + std::string const &name_, + std::string const &familyName_, + std::string const &style_, + double ascent_ + ) : + name(name_), + familyName(familyName_), + style(style_), + ascent(ascent_) { + } + + explicit Font(json11::Json::object const &json) noexcept(false) { + name = getString(json, "fName"); + familyName = getString(json, "fFamily"); + path = getOptionalString(json, "fPath"); + weight = getOptionalString(json, "fWeight"); + fontClass = getOptionalString(json, "fClass"); + style = getString(json, "fStyle"); + ascent = getDouble(json, "ascent"); + origin = getOptionalInt(json, "origin"); + } + + json11::Json::object toJson() const { + json11::Json::object result; + + result.insert(std::make_pair("fName", name)); + result.insert(std::make_pair("fFamily", familyName)); + if (path.has_value()) { + result.insert(std::make_pair("fPath", path.value())); + } + if (weight.has_value()) { + result.insert(std::make_pair("fWeight", weight.value())); + } + if (fontClass.has_value()) { + result.insert(std::make_pair("fClass", fontClass.value())); + } + result.insert(std::make_pair("fStyle", style)); + result.insert(std::make_pair("ascent", ascent)); + if (origin.has_value()) { + result.insert(std::make_pair("origin", origin.value())); + } + + return result; + } + +public: + std::string name; + std::string familyName; + std::optional path; + std::optional weight; + std::optional fontClass; + std::string style; + double ascent; + std::optional origin; +}; + +/// A list of fonts +class FontList { +public: + FontList(std::vector const &fonts_) : + fonts(fonts_) { + } + + explicit FontList(json11::Json::object const &json) noexcept(false) { + if (const auto fontsData = getOptionalObjectArray(json, "list")) { + for (const auto &fontData : fontsData.value()) { + fonts.emplace_back(fontData); + } + } + } + + json11::Json::object toJson() const { + json11::Json::array fontArray; + + for (const auto &font : fonts) { + fontArray.push_back(font.toJson()); + } + + json11::Json::object result; + result.insert(std::make_pair("list", fontArray)); + return result; + } + +public: + std::vector fonts; +}; + +} + +#endif /* Font_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Text/Glyph.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Text/Glyph.cpp new file mode 100644 index 0000000000..071050db7a --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Text/Glyph.cpp @@ -0,0 +1,5 @@ +#include "Glyph.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Text/Glyph.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Text/Glyph.hpp new file mode 100644 index 0000000000..25d0af5fa9 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Text/Glyph.hpp @@ -0,0 +1,108 @@ +#ifndef Glyph_hpp +#define Glyph_hpp + +#include "Lottie/Private/Model/ShapeItems/ShapeItem.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +#include +#include + +namespace lottie { + +class Glyph { +public: + Glyph( + std::string const &character_, + double fontSize_, + std::string const &fontFamily_, + std::string const &fontStyle_, + double width_, + std::optional>> shapes_ + ) : + character(character_), + fontSize(fontSize_), + fontFamily(fontFamily_), + fontStyle(fontStyle_), + width(width_), + shapes(shapes_) { + } + + explicit Glyph(json11::Json::object const &json) noexcept(false) : + character(""), + fontSize(0.0), + fontFamily(""), + fontStyle(""), + width(0.0) { + character = getString(json, "ch"); + fontSize = getDouble(json, "size"); + fontFamily = getString(json, "fFamily"); + fontStyle = getString(json, "style"); + width = getDouble(json, "w"); + + if (const auto shapeContainer = getOptionalObject(json, "data")) { + internalHasData = true; + + if (const auto shapesData = getOptionalObjectArray(shapeContainer.value(), "shapes")) { + shapes = std::vector>(); + + for (const auto &shapeData : shapesData.value()) { + shapes->push_back(parseShapeItem(shapeData)); + } + } + } + } + + json11::Json::object toJson() const { + json11::Json::object result; + + result.insert(std::make_pair("ch", character)); + result.insert(std::make_pair("size", fontSize)); + result.insert(std::make_pair("fFamily", fontFamily)); + result.insert(std::make_pair("style", fontStyle)); + result.insert(std::make_pair("w", width)); + + if (internalHasData || shapes.has_value()) { + json11::Json::object shapeContainer; + + if (shapes.has_value()) { + json11::Json::array shapeArray; + + for (const auto &shape : shapes.value()) { + json11::Json::object shapeJson; + shape->toJson(shapeJson); + shapeArray.push_back(shapeJson); + } + + shapeContainer.insert(std::make_pair("shapes", shapeArray)); + } + result.insert(std::make_pair("data", shapeContainer)); + } + + return result; + } + +public: + /// The character + std::string character; + + /// The font size of the character + double fontSize; + + /// The font family of the character + std::string fontFamily; + + /// The Style of the character + std::string fontStyle; + + /// The Width of the character + double width; + + /// The Shape Data of the Character + std::optional>> shapes; + + bool internalHasData = false; +}; + +} + +#endif /* Glyph_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Text/TextAnimator.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Text/TextAnimator.cpp new file mode 100644 index 0000000000..7cd9b25715 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Text/TextAnimator.cpp @@ -0,0 +1,5 @@ +#include "TextAnimator.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Text/TextAnimator.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Text/TextAnimator.hpp new file mode 100644 index 0000000000..e302a9051f --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Text/TextAnimator.hpp @@ -0,0 +1,183 @@ +#ifndef TextAnimator_hpp +#define TextAnimator_hpp + +#include "Lottie/Public/Primitives/Vectors.hpp" +#include "Lottie/Public/Primitives/Color.hpp" +#include "Lottie/Private/Model/Keyframes/KeyframeGroup.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +#include +#include + +namespace lottie { + +class TextAnimator { +public: + TextAnimator( + std::optional &name_, + std::optional> anchor_, + std::optional> position_, + std::optional> scale_, + std::optional> skew_, + std::optional> skewAxis_, + std::optional> rotation_, + std::optional> opacity_, + std::optional> strokeColor_, + std::optional> fillColor_, + std::optional> strokeWidth_, + std::optional> tracking_ + ) : + name(name_), + anchor(anchor_), + position(position_), + scale(scale_), + skew(skew_), + skewAxis(skewAxis_), + rotation(rotation_), + opacity(opacity_), + strokeColor(strokeColor_), + fillColor(fillColor_), + strokeWidth(strokeWidth_), + tracking(tracking_) { + } + + explicit TextAnimator(json11::Json const &jsonAny) { + if (!jsonAny.is_object()) { + throw LottieParsingException(); + } + json11::Json::object const &json = jsonAny.object_items(); + + if (const auto nameData = getOptionalString(json, "nm")) { + name = nameData.value(); + } + _extraS = getOptionalAny(json, "s"); + + json11::Json::object const &animatorContainer = getObject(json, "a"); + + if (const auto fillColorData = getOptionalObject(animatorContainer, "fc")) { + fillColor = KeyframeGroup(fillColorData.value()); + } + if (const auto strokeColorData = getOptionalObject(animatorContainer, "sc")) { + strokeColor = KeyframeGroup(strokeColorData.value()); + } + if (const auto strokeWidthData = getOptionalObject(animatorContainer, "sw")) { + strokeWidth = KeyframeGroup(strokeWidthData.value()); + } + if (const auto trackingData = getOptionalObject(animatorContainer, "t")) { + tracking = KeyframeGroup(trackingData.value()); + } + if (const auto anchorData = getOptionalObject(animatorContainer, "a")) { + anchor = KeyframeGroup(anchorData.value()); + } + if (const auto positionData = getOptionalObject(animatorContainer, "p")) { + position = KeyframeGroup(positionData.value()); + } + if (const auto scaleData = getOptionalObject(animatorContainer, "s")) { + scale = KeyframeGroup(scaleData.value()); + } + if (const auto skewData = getOptionalObject(animatorContainer, "sk")) { + skew = KeyframeGroup(skewData.value()); + } + if (const auto skewAxisData = getOptionalObject(animatorContainer, "sa")) { + skewAxis = KeyframeGroup(skewAxisData.value()); + } + if (const auto rotationData = getOptionalObject(animatorContainer, "r")) { + rotation = KeyframeGroup(rotationData.value()); + } + if (const auto opacityData = getOptionalObject(animatorContainer, "o")) { + opacity = KeyframeGroup(opacityData.value()); + } + } + + json11::Json::object toJson() const { + json11::Json::object animatorContainer; + + if (fillColor.has_value()) { + animatorContainer.insert(std::make_pair("fc", fillColor->toJson())); + } + if (strokeColor.has_value()) { + animatorContainer.insert(std::make_pair("sc", strokeColor->toJson())); + } + if (strokeWidth.has_value()) { + animatorContainer.insert(std::make_pair("sw", strokeWidth->toJson())); + } + if (tracking.has_value()) { + animatorContainer.insert(std::make_pair("t", tracking->toJson())); + } + if (anchor.has_value()) { + animatorContainer.insert(std::make_pair("a", anchor->toJson())); + } + if (position.has_value()) { + animatorContainer.insert(std::make_pair("p", position->toJson())); + } + if (scale.has_value()) { + animatorContainer.insert(std::make_pair("s", scale->toJson())); + } + if (skew.has_value()) { + animatorContainer.insert(std::make_pair("sk", skew->toJson())); + } + if (skewAxis.has_value()) { + animatorContainer.insert(std::make_pair("sa", skewAxis->toJson())); + } + if (rotation.has_value()) { + animatorContainer.insert(std::make_pair("r", rotation->toJson())); + } + if (opacity.has_value()) { + animatorContainer.insert(std::make_pair("o", opacity->toJson())); + } + + json11::Json::object result; + result.insert(std::make_pair("a", animatorContainer)); + + if (name.has_value()) { + result.insert(std::make_pair("nm", name.value())); + } + if (_extraS.has_value()) { + result.insert(std::make_pair("s", _extraS.value())); + } + + return result; + } + +public: + std::optional name; + + /// Anchor + std::optional> anchor; + + /// Position + std::optional> position; + + /// Scale + std::optional> scale; + + /// Skew + std::optional> skew; + + /// Skew Axis + std::optional> skewAxis; + + /// Rotation + std::optional> rotation; + + /// Opacity + std::optional> opacity; + + /// Stroke Color + std::optional> strokeColor; + + /// Fill Color + std::optional> fillColor; + + /// Stroke Width + std::optional> strokeWidth; + + /// Tracking + std::optional> tracking; + + std::optional _extraS; +}; + +} + +#endif /* TextAnimator_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Text/TextDocument.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Text/TextDocument.cpp new file mode 100644 index 0000000000..95c3be2b67 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Text/TextDocument.cpp @@ -0,0 +1,5 @@ +#include "TextDocument.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Text/TextDocument.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Text/TextDocument.hpp new file mode 100644 index 0000000000..9eb3b1f154 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Text/TextDocument.hpp @@ -0,0 +1,184 @@ +#ifndef TextDocument_hpp +#define TextDocument_hpp + +#include "Lottie/Public/Primitives/Vectors.hpp" +#include "Lottie/Public/Primitives/Color.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +#include +#include + +namespace lottie { + +enum class TextJustification: int { + Left = 0, + Right = 1, + Center = 2 +}; + +class TextDocument { +public: + TextDocument( + std::string const &text_, + double fontSize_, + std::string const &fontFamily_, + TextJustification justification_, + int tracking_, + double lineHeight_, + std::optional baseline_, + std::optional fillColorData_, + std::optional strokeColorData_, + std::optional strokeWidth_, + std::optional strokeOverFill_, + std::optional textFramePosition_, + std::optional textFrameSize_ + ) : + text(text_), + fontSize(fontSize_), + fontFamily(fontFamily_), + justification(justification_), + tracking(tracking_), + lineHeight(lineHeight_), + baseline(baseline_), + fillColorData(fillColorData_), + strokeColorData(strokeColorData_), + strokeWidth(strokeWidth_), + strokeOverFill(strokeOverFill_), + textFramePosition(textFramePosition_), + textFrameSize(textFrameSize_) { + } + + explicit TextDocument(json11::Json const &jsonAny) noexcept(false) : + text(""), + fontSize(0.0), + fontFamily(""), + justification(TextJustification::Left), + tracking(0), + lineHeight(0.0) { + if (!jsonAny.is_object()) { + throw LottieParsingException(); + } + + json11::Json::object const &json = jsonAny.object_items(); + + text = getString(json, "t"); + fontSize = getDouble(json, "s"); + fontFamily = getString(json, "f"); + + auto justificationRawValue = getInt(json, "j"); + switch (justificationRawValue) { + case 0: + justification = TextJustification::Left; + break; + case 1: + justification = TextJustification::Right; + break; + case 2: + justification = TextJustification::Center; + break; + default: + throw LottieParsingException(); + } + + tracking = getInt(json, "tr"); + lineHeight = getDouble(json, "lh"); + baseline = getOptionalDouble(json, "ls"); + + if (const auto fillColorDataValue = getOptionalAny(json, "fc")) { + fillColorData = Color(fillColorDataValue.value()); + } + + if (const auto strokeColorDataValue = getOptionalAny(json, "sc")) { + strokeColorData = Color(strokeColorDataValue.value()); + } + + strokeWidth = getOptionalDouble(json, "sw"); + strokeOverFill = getOptionalBool(json, "of"); + + if (const auto textFramePositionData = getOptionalAny(json, "ps")) { + textFramePosition = Vector3D(textFramePositionData.value()); + } + if (const auto textFrameSizeData = getOptionalAny(json, "sz")) { + textFrameSize = Vector3D(textFrameSizeData.value()); + } + } + + json11::Json::object toJson() const { + json11::Json::object result; + + result.insert(std::make_pair("t", text)); + result.insert(std::make_pair("s", fontSize)); + result.insert(std::make_pair("f", fontFamily)); + result.insert(std::make_pair("j", (int)justification)); + result.insert(std::make_pair("tr", tracking)); + result.insert(std::make_pair("lh", lineHeight)); + + if (baseline.has_value()) { + result.insert(std::make_pair("ls", baseline.value())); + } + + if (fillColorData.has_value()) { + result.insert(std::make_pair("fc", fillColorData->toJson())); + } + if (strokeColorData.has_value()) { + result.insert(std::make_pair("sc", strokeColorData->toJson())); + } + + if (strokeWidth.has_value()) { + result.insert(std::make_pair("sw", strokeWidth.value())); + } + if (strokeOverFill.has_value()) { + result.insert(std::make_pair("of", strokeOverFill.value())); + } + if (textFramePosition.has_value()) { + result.insert(std::make_pair("ps", textFramePosition->toJson())); + } + if (textFrameSize.has_value()) { + result.insert(std::make_pair("sz", textFrameSize->toJson())); + } + + return result; + } + +public: + /// The Text + std::string text; + + /// The Font size + double fontSize; + + /// The Font Family + std::string fontFamily; + + /// Justification + TextJustification justification; + + /// Tracking + int tracking; + + /// Line Height + double lineHeight; + + /// Baseline + std::optional baseline; + + /// Fill Color data + std::optional fillColorData; + + /// Scroke Color data + std::optional strokeColorData; + + /// Stroke Width + std::optional strokeWidth; + + /// Stroke Over Fill + std::optional strokeOverFill; + + std::optional textFramePosition; + + std::optional textFrameSize; +}; + +} + +#endif /* TextDocument_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Parsing/JsonParsing.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Parsing/JsonParsing.cpp new file mode 100644 index 0000000000..da1dcb51e6 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Parsing/JsonParsing.cpp @@ -0,0 +1,219 @@ +#include "JsonParsing.hpp" + +#include + +namespace lottie { + +thread_local int isExceptionExpectedLevel = 0; + +LottieParsingException::Guard::Guard() { + assert(isExceptionExpectedLevel >= 0); + isExceptionExpectedLevel++; +} + +LottieParsingException::Guard::~Guard() { + assert(isExceptionExpectedLevel - 1 >= 0); + isExceptionExpectedLevel--; +} + +LottieParsingException::LottieParsingException() { + if (isExceptionExpectedLevel == 0) { + assert(true); + } +} + +const char* LottieParsingException::what() const throw() { + return "Lottie parsing exception"; +} + +json11::Json getAny(json11::Json::object const &object, std::string const &key) noexcept(false) { + auto value = object.find(key); + if (value == object.end()) { + throw LottieParsingException(); + } + return value->second; +} + +std::optional getOptionalAny(json11::Json::object const &object, std::string const &key) noexcept(false) { + auto value = object.find(key); + if (value == object.end()) { + return std::nullopt; + } + return value->second; +} + +json11::Json::object getObject(json11::Json::object const &object, std::string const &key) noexcept(false) { + auto value = object.find(key); + if (value == object.end()) { + throw LottieParsingException(); + } + if (!value->second.is_object()) { + throw LottieParsingException(); + } + return value->second.object_items(); +} + +std::optional getOptionalObject(json11::Json::object const &object, std::string const &key) noexcept(false) { + auto value = object.find(key); + if (value == object.end()) { + return std::nullopt; + } + if (!value->second.is_object()) { + throw LottieParsingException(); + } + return value->second.object_items(); +} + +std::vector getObjectArray(json11::Json::object const &object, std::string const &key) noexcept(false) { + auto value = object.find(key); + if (value == object.end()) { + throw LottieParsingException(); + } + if (!value->second.is_array()) { + throw LottieParsingException(); + } + + std::vector result; + for (const auto &item : value->second.array_items()) { + if (!item.is_object()) { + throw LottieParsingException(); + } + result.push_back(item.object_items()); + } + + return result; +} + +std::optional> getOptionalObjectArray(json11::Json::object const &object, std::string const &key) noexcept(false) { + auto value = object.find(key); + if (value == object.end()) { + return std::nullopt; + } + if (!value->second.is_array()) { + throw LottieParsingException(); + } + + std::vector result; + for (const auto &item : value->second.array_items()) { + if (!item.is_object()) { + throw LottieParsingException(); + } + result.push_back(item.object_items()); + } + + return result; +} + +std::vector getAnyArray(json11::Json::object const &object, std::string const &key) noexcept(false) { + auto value = object.find(key); + if (value == object.end()) { + throw LottieParsingException(); + } + if (!value->second.is_array()) { + throw LottieParsingException(); + } + + return value->second.array_items(); +} + +std::optional> getOptionalAnyArray(json11::Json::object const &object, std::string const &key) noexcept(false) { + auto value = object.find(key); + if (value == object.end()) { + throw std::nullopt; + } + if (!value->second.is_array()) { + throw LottieParsingException(); + } + + return value->second.array_items(); +} + +std::string getString(json11::Json::object const &object, std::string const &key) noexcept(false) { + auto value = object.find(key); + if (value == object.end()) { + throw LottieParsingException(); + } + if (!value->second.is_string()) { + throw LottieParsingException(); + } + return value->second.string_value(); +} + +std::optional getOptionalString(json11::Json::object const &object, std::string const &key) noexcept(false) { + auto value = object.find(key); + if (value == object.end()) { + return std::nullopt; + } + if (!value->second.is_string()) { + throw LottieParsingException(); + } + return value->second.string_value(); +} + +int32_t getInt(json11::Json::object const &object, std::string const &key) noexcept(false) { + auto value = object.find(key); + if (value == object.end()) { + throw LottieParsingException(); + } + if (!value->second.is_number()) { + throw LottieParsingException(); + } + return value->second.int_value(); +} + +std::optional getOptionalInt(json11::Json::object const &object, std::string const &key) noexcept(false) { + auto value = object.find(key); + if (value == object.end()) { + return std::nullopt; + } + if (!value->second.is_number()) { + throw LottieParsingException(); + } + return value->second.int_value(); +} + +double getDouble(json11::Json::object const &object, std::string const &key) noexcept(false) { + auto value = object.find(key); + if (value == object.end()) { + throw LottieParsingException(); + } + if (!value->second.is_number()) { + throw LottieParsingException(); + } + return value->second.number_value(); +} + +std::optional getOptionalDouble(json11::Json::object const &object, std::string const &key) noexcept(false) { + auto value = object.find(key); + if (value == object.end()) { + return std::nullopt; + } + if (!value->second.is_number()) { + throw LottieParsingException(); + } + return value->second.number_value(); +} + +bool getBool(json11::Json::object const &object, std::string const &key) noexcept(false) { + auto value = object.find(key); + if (value == object.end()) { + throw LottieParsingException(); + } + if (!value->second.is_bool()) { + throw LottieParsingException(); + } + return value->second.bool_value(); +} + +std::optional getOptionalBool(json11::Json::object const &object, std::string const &key) noexcept(false) { + auto value = object.find(key); + if (value == object.end()) { + return std::nullopt; + } + if (!value->second.is_bool()) { + throw LottieParsingException(); + } + return value->second.bool_value(); +} + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Parsing/JsonParsing.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Parsing/JsonParsing.hpp new file mode 100644 index 0000000000..37b666d38a --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Parsing/JsonParsing.hpp @@ -0,0 +1,52 @@ +#ifndef JsonParsing_hpp +#define JsonParsing_hpp + +#include "lottiejson11/lottiejson11.hpp" + +#include +#include +#include + +namespace lottie { + +class LottieParsingException: public std::exception { +public: + class Guard { + public: + Guard(); + ~Guard(); + }; + +public: + LottieParsingException(); + + virtual const char* what() const throw(); +}; + +json11::Json getAny(json11::Json::object const &object, std::string const &key) noexcept(false); +std::optional getOptionalAny(json11::Json::object const &object, std::string const &key) noexcept(false); + +json11::Json::object getObject(json11::Json::object const &object, std::string const &key) noexcept(false); +std::optional getOptionalObject(json11::Json::object const &object, std::string const &key) noexcept(false); + +std::vector getObjectArray(json11::Json::object const &object, std::string const &key) noexcept(false); +std::optional> getOptionalObjectArray(json11::Json::object const &object, std::string const &key) noexcept(false); + +std::vector getAnyArray(json11::Json::object const &object, std::string const &key) noexcept(false); +std::optional> getOptionalAnyArray(json11::Json::object const &object, std::string const &key) noexcept(false); + +std::string getString(json11::Json::object const &object, std::string const &key) noexcept(false); +std::optional getOptionalString(json11::Json::object const &object, std::string const &key) noexcept(false); + +int32_t getInt(json11::Json::object const &object, std::string const &key) noexcept(false); +std::optional getOptionalInt(json11::Json::object const &object, std::string const &key) noexcept(false); + +double getDouble(json11::Json::object const &object, std::string const &key) noexcept(false); +std::optional getOptionalDouble(json11::Json::object const &object, std::string const &key) noexcept(false); + +bool getBool(json11::Json::object const &object, std::string const &key) noexcept(false); +std::optional getOptionalBool(json11::Json::object const &object, std::string const &key) noexcept(false); + +} + +#endif /* JsonParsing_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/BezierPath.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/BezierPath.cpp new file mode 100644 index 0000000000..95b6e1fc68 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/BezierPath.cpp @@ -0,0 +1,129 @@ +#include "BezierPath.hpp" + +#include +#include + +namespace lottie { + +static CGRect calculateBoundingRectOpt(float const *pointsX, float const *pointsY, int count) { + float minX = 0.0; + float maxX = 0.0; + vDSP_minv(pointsX, 1, &minX, count); + vDSP_maxv(pointsX, 1, &maxX, count); + + float minY = 0.0; + float maxY = 0.0; + vDSP_minv(pointsY, 1, &minY, count); + vDSP_maxv(pointsY, 1, &maxY, count); + + return CGRect(minX, minY, maxX - minX, maxY - minY); +} + +CGRect bezierPathsBoundingBoxParallel(BezierPathsBoundingBoxContext &context, std::vector const &paths) { + int pointCount = 0; + + float *pointsX = context.pointsX; + float *pointsY = context.pointsY; + int pointsSize = context.pointsSize; + + for (const auto &path : paths) { + PathElement const *pathElements = path.elements().data(); + int pathElementCount = (int)path.elements().size(); + + for (int i = 0; i < pathElementCount; i++) { + const auto &element = pathElements[i]; + + if (pointsSize < pointCount + 1) { + pointsSize = (pointCount + 1) * 2; + pointsX = (float *)realloc(pointsX, pointsSize * 4); + pointsY = (float *)realloc(pointsY, pointsSize * 4); + } + pointsX[pointCount] = (float)element.vertex.point.x; + pointsY[pointCount] = (float)element.vertex.point.y; + pointCount++; + + if (i != 0) { + const auto &previousElement = pathElements[i - 1]; + if (previousElement.vertex.outTangentRelative().isZero() && element.vertex.inTangentRelative().isZero()) { + } else { + if (pointsSize < pointCount + 1) { + pointsSize = (pointCount + 2) * 2; + pointsX = (float *)realloc(pointsX, pointsSize * 4); + pointsY = (float *)realloc(pointsY, pointsSize * 4); + } + pointsX[pointCount] = (float)previousElement.vertex.outTangent.x; + pointsY[pointCount] = (float)previousElement.vertex.outTangent.y; + pointCount++; + pointsX[pointCount] = (float)element.vertex.inTangent.x; + pointsY[pointCount] = (float)element.vertex.inTangent.y; + pointCount++; + } + } + } + } + + context.pointsX = pointsX; + context.pointsY = pointsY; + context.pointsSize = pointsSize; + + if (pointCount == 0) { + return CGRect(0.0, 0.0, 0.0, 0.0); + } + + return calculateBoundingRectOpt(pointsX, pointsY, pointCount); +} + +CGRect bezierPathsBoundingBox(std::vector const &paths) { + int pointCount = 0; + + float *pointsX = (float *)malloc(128 * 4); + float *pointsY = (float *)malloc(128 * 4); + int pointsSize = 128; + + for (const auto &path : paths) { + PathElement const *pathElements = path.elements().data(); + int pathElementCount = (int)path.elements().size(); + + for (int i = 0; i < pathElementCount; i++) { + const auto &element = pathElements[i]; + + if (pointsSize < pointCount + 1) { + pointsSize = (pointCount + 1) * 2; + pointsX = (float *)realloc(pointsX, pointsSize * 4); + pointsY = (float *)realloc(pointsY, pointsSize * 4); + } + pointsX[pointCount] = (float)element.vertex.point.x; + pointsY[pointCount] = (float)element.vertex.point.y; + pointCount++; + + if (i != 0) { + const auto &previousElement = pathElements[i - 1]; + if (previousElement.vertex.outTangentRelative().isZero() && element.vertex.inTangentRelative().isZero()) { + } else { + if (pointsSize < pointCount + 1) { + pointsSize = (pointCount + 2) * 2; + pointsX = (float *)realloc(pointsX, pointsSize * 4); + pointsY = (float *)realloc(pointsY, pointsSize * 4); + } + pointsX[pointCount] = (float)previousElement.vertex.outTangent.x; + pointsY[pointCount] = (float)previousElement.vertex.outTangent.y; + pointCount++; + pointsX[pointCount] = (float)element.vertex.inTangent.x; + pointsY[pointCount] = (float)element.vertex.inTangent.y; + pointCount++; + } + } + } + } + + free(pointsX); + free(pointsY); + + if (pointCount == 0) { + return CGRect(0.0, 0.0, 0.0, 0.0); + } + + return calculateBoundingRectOpt(pointsX, pointsY, pointCount); +} + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/BezierPath.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/BezierPath.hpp new file mode 100644 index 0000000000..881a5917ef --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/BezierPath.hpp @@ -0,0 +1,593 @@ +#ifndef BezierPath_hpp +#define BezierPath_hpp + +#include "Lottie/Private/Utility/Primitives/CurveVertex.hpp" +#include "Lottie/Private/Utility/Primitives/PathElement.hpp" +#include "Lottie/Private/Parsing/JsonParsing.hpp" +#include "Lottie/Public/Primitives/CGPath.hpp" + +#include + +namespace lottie { + +struct BezierTrimPathPosition { + double start; + double end; + + explicit BezierTrimPathPosition(double start_, double end_) : + start(start_), + end(end_) { + } +}; + +class BezierPathContents: public std::enable_shared_from_this { +public: + /// Initializes a new Bezier Path. + explicit BezierPathContents(CurveVertex const &startPoint) : + elements({ PathElement(startPoint) }) { + } + + BezierPathContents() : + elements({}), + closed(false) { + } + + explicit BezierPathContents(json11::Json const &jsonAny) noexcept(false) : + elements({}) { + json11::Json::object const *json = nullptr; + if (jsonAny.is_object()) { + json = &jsonAny.object_items(); + } else if (jsonAny.is_array()) { + if (jsonAny.array_items().empty()) { + throw LottieParsingException(); + } + if (!jsonAny.array_items()[0].is_object()) { + throw LottieParsingException(); + } + json = &jsonAny.array_items()[0].object_items(); + } + + if (const auto closedData = getOptionalBool(*json, "c")) { + closed = closedData.value(); + } + + auto vertexContainer = getAnyArray(*json, "v"); + auto inPointsContainer = getAnyArray(*json, "i"); + auto outPointsContainer = getAnyArray(*json, "o"); + + if (vertexContainer.size() != inPointsContainer.size() || inPointsContainer.size() != outPointsContainer.size()) { + throw LottieParsingException(); + } + if (vertexContainer.empty()) { + return; + } + + /// Create first point + Vector2D firstPoint(vertexContainer[0]); + Vector2D firstInPoint(inPointsContainer[0]); + Vector2D firstOutPoint(outPointsContainer[0]); + CurveVertex firstVertex = CurveVertex::relative( + firstPoint, + firstInPoint, + firstOutPoint + ); + PathElement previousElement(firstVertex); + elements.push_back(previousElement); + + for (size_t i = 1; i < vertexContainer.size(); i++) { + Vector2D point(vertexContainer[i]); + Vector2D inPoint(inPointsContainer[i]); + Vector2D outPoint(outPointsContainer[i]); + CurveVertex vertex = CurveVertex::relative( + point, + inPoint, + outPoint + ); + auto pathElement = previousElement.pathElementTo(vertex); + elements.push_back(pathElement); + previousElement = pathElement; + } + + if (closed.value_or(false)) { + auto closeElement = previousElement.pathElementTo(firstVertex); + elements.push_back(closeElement); + } + } + + BezierPathContents(const BezierPathContents&) = delete; + BezierPathContents& operator=(BezierPathContents&) = delete; + + json11::Json toJson() const { + json11::Json::object result; + + json11::Json::array vertices; + json11::Json::array inPoints; + json11::Json::array outPoints; + + for (const auto &element : elements) { + vertices.push_back(element.vertex.point.toJson()); + inPoints.push_back(element.vertex.inTangentRelative().toJson()); + outPoints.push_back(element.vertex.outTangentRelative().toJson()); + } + + result.insert(std::make_pair("v", vertices)); + result.insert(std::make_pair("i", inPoints)); + result.insert(std::make_pair("o", outPoints)); + + if (closed.has_value()) { + result.insert(std::make_pair("c", closed.value())); + } + + return json11::Json(result); + } + + std::shared_ptr cgPath() const { + auto cgPath = CGPath::makePath(); + + std::optional previousElement; + for (const auto &element : elements) { + if (previousElement.has_value()) { + if (previousElement->vertex.outTangentRelative().isZero() && element.vertex.inTangentRelative().isZero()) { + cgPath->addLineTo(element.vertex.point); + } else { + cgPath->addCurveTo(element.vertex.point, previousElement->vertex.outTangent, element.vertex.inTangent); + } + } else { + cgPath->moveTo(element.vertex.point); + } + previousElement = element; + } + if (closed.value_or(true)) { + cgPath->closeSubpath(); + } + return cgPath; + } + +public: + std::vector elements; + std::optional closed; + + double length() { + if (_length.has_value()) { + return _length.value(); + } else { + double result = 0.0; + for (size_t i = 1; i < elements.size(); i++) { + result += elements[i].length(elements[i - 1]); + } + _length = result; + return result; + } + } + +private: + std::optional _length; + +public: + void moveToStartPoint(CurveVertex const &vertex) { + elements = { PathElement(vertex) }; + _length = std::nullopt; + } + + void addVertex(CurveVertex const &vertex) { + addElement(PathElement(vertex)); + } + + void reserveCapacity(size_t capacity) { + elements.reserve(capacity); + } + + void setElementCount(size_t count) { + elements.resize(count, PathElement(CurveVertex::absolute(Vector2D(0.0, 0.0), Vector2D(0.0, 0.0), Vector2D(0.0, 0.0)))); + } + + void invalidateLength() { + _length.reset(); + } + + void addCurve(Vector2D const &toPoint, Vector2D const &outTangent, Vector2D const &inTangent) { + if (elements.empty()) { + return; + } + auto previous = elements[elements.size() - 1]; + auto newVertex = CurveVertex::absolute(toPoint, inTangent, toPoint); + updateVertex( + CurveVertex::absolute(previous.vertex.point, previous.vertex.inTangent, outTangent), + (int)elements.size() - 1, + false + ); + addVertex(newVertex); + } + + void addLine(Vector2D const &toPoint) { + if (elements.empty()) { + return; + } + auto previous = elements[elements.size() - 1]; + auto newVertex = CurveVertex::relative(toPoint, Vector2D::Zero(), Vector2D::Zero()); + updateVertex( + CurveVertex::absolute(previous.vertex.point, previous.vertex.inTangent, previous.vertex.point), + (int)elements.size() - 1, + false + ); + addVertex(newVertex); + } + + void close() { + closed = true; + } + + void addElement(PathElement const &pathElement) { + elements.push_back(pathElement); + } + + void updateVertex(CurveVertex const &vertex, int atIndex, bool remeasure) { + if (remeasure) { + PathElement newElement(CurveVertex::absolute(Vector2D::Zero(), Vector2D::Zero(), Vector2D::Zero())); + if (atIndex > 0) { + auto previousElement = elements[atIndex - 1]; + newElement = previousElement.pathElementTo(vertex); + } else { + newElement = PathElement(vertex); + } + elements[atIndex] = newElement; + + if (atIndex + 1 < elements.size()) { + auto nextElement = elements[atIndex + 1]; + elements[atIndex + 1] = newElement.pathElementTo(nextElement.vertex); + } + + } else { + auto oldElement = elements[atIndex]; + elements[atIndex] = oldElement.updateVertex(vertex); + } + } + + /// Trims a path fromLength toLength with an offset. + /// + /// Length and offset are defined in the length coordinate space. + /// If any argument is outside the range of this path, then it will be looped over the path from finish to start. + /// + /// Cutting the curve when fromLength is less than toLength + /// x x x x + /// ~~~~~~~~~~~~~~~ooooooooooooooooooooooooooooooooooooooooooooooooo------------------- + /// |Offset |fromLength toLength| | + /// + /// Cutting the curve when from Length is greater than toLength + /// x x x x x + /// oooooooooooooooooo--------------------~~~~~~~~~~~~~~~~ooooooooooooooooooooooooooooo + /// | toLength| |Offset |fromLength | + /// + std::vector> trim(double fromLength, double toLength, double offsetLength) { + if (elements.size() <= 1) { + return {}; + } + + if (fromLength == toLength) { + return {}; + } + + double lengthValue = length(); + + /// Normalize lengths to the curve length. + auto start = fmod(fromLength + offsetLength, lengthValue); + auto end = fmod(toLength + offsetLength, lengthValue); + + if (start < 0.0) { + start = lengthValue + start; + } + + if (end < 0.0) { + end = lengthValue + end; + } + + if (start == lengthValue) { + start = 0.0; + } + if (end == 0.0) { + end = lengthValue; + } + + if ( + (start == 0.0 && end == lengthValue) || + start == end || + (start == lengthValue && end == 0.0) + ) { + /// The trim encompasses the entire path. Return. + return { shared_from_this() }; + } + + if (start > end) { + // Start is greater than end. Two paths are returned. + return trimPathAtLengths({ + BezierTrimPathPosition(0.0, end), + BezierTrimPathPosition(start, lengthValue) + }); + } + + return trimPathAtLengths({ BezierTrimPathPosition(start, end) }); + } + + // MARK: Private + + std::vector> trimPathAtLengths(std::vector const &positions) { + if (positions.empty()) { + return {}; + } + auto remainingPositions = positions; + + auto trim = remainingPositions[0]; + remainingPositions.erase(remainingPositions.begin()); + + std::vector> paths; + + double runningLength = 0.0; + bool finishedTrimming = false; + auto pathElements = elements; + + auto currentPath = std::make_shared(); + int i = 0; + + while (!finishedTrimming) { + if (pathElements.size() <= i) { + /// Do this for rounding errors + paths.push_back(currentPath); + finishedTrimming = true; + continue; + } + /// Loop through and add elements within start->end range. + /// Get current element + auto element = pathElements[i]; + double elementLength = 0.0; + if (i != 0) { + elementLength = element.length(pathElements[i - 1]); + } + + /// Calculate new running length. + auto newLength = runningLength + elementLength; + + if (newLength < trim.start) { + /// Element is not included in the trim, continue. + runningLength = newLength; + i = i + 1; + /// Increment index, we are done with this element. + continue; + } + + if (newLength == trim.start) { + /// Current element IS the start element. + /// For start we want to add a zero length element. + currentPath->moveToStartPoint(element.vertex); + runningLength = newLength; + i = i + 1; + /// Increment index, we are done with this element. + continue; + } + + if (runningLength < trim.start && trim.start < newLength && currentPath->elements.size() == 0) { + /// The start of the trim is between this element and the previous, trim. + /// Get previous element. + auto previousElement = pathElements[i - 1]; + /// Trim it + auto trimLength = trim.start - runningLength; + auto trimResults = element.splitElementAtPosition(previousElement, trimLength); + /// Add the right span start. + currentPath->moveToStartPoint(trimResults.rightSpan.start.vertex); + + pathElements[i] = trimResults.rightSpan.end; + pathElements[i - 1] = trimResults.rightSpan.start; + runningLength = runningLength + trimResults.leftSpan.end.length(trimResults.leftSpan.start); + /// Dont increment index or the current length, the end of this path can be within this span. + continue; + } + + if (trim.start < newLength && newLength < trim.end) { + /// Element lies within the trim span. + currentPath->addElement(element); + runningLength = newLength; + i = i + 1; + continue; + } + + if (newLength == trim.end) { + /// Element is the end element. + /// The element could have a new length if it's added right after the start node. + currentPath->addElement(element); + /// We are done with this span. + runningLength = newLength; + i = i + 1; + /// Allow the path to be finalized. + /// Fall through to finalize path and move to next position + } + + if (runningLength < trim.end && trim.end < newLength) { + /// New element must be cut for end. + /// Get previous element. + auto previousElement = pathElements[i - 1]; + /// Trim it + auto trimLength = trim.end - runningLength; + auto trimResults = element.splitElementAtPosition(previousElement, trimLength); + /// Add the left span end. + + currentPath->updateVertex(trimResults.leftSpan.start.vertex, (int)currentPath->elements.size() - 1, false); + currentPath->addElement(trimResults.leftSpan.end); + + pathElements[i] = trimResults.rightSpan.end; + pathElements[i - 1] = trimResults.rightSpan.start; + runningLength = runningLength + trimResults.leftSpan.end.length(trimResults.leftSpan.start); + /// Dont increment index or the current length, the start of the next path can be within this span. + /// We are done with this span. + /// Allow the path to be finalized. + /// Fall through to finalize path and move to next position + } + + paths.push_back(currentPath); + currentPath = std::make_shared(); + if (remainingPositions.size() > 0) { + trim = remainingPositions[0]; + remainingPositions.erase(remainingPositions.begin()); + } else { + finishedTrimming = true; + } + } + return paths; + } +}; + +class BezierPath { +public: + /// Initializes a new Bezier Path. + explicit BezierPath(CurveVertex const &startPoint) : + _contents(std::make_shared(startPoint)) { + } + + BezierPath() : + _contents(std::make_shared()) { + } + + explicit BezierPath(json11::Json const &jsonAny) noexcept(false) : + _contents(std::make_shared(jsonAny)) { + } + + json11::Json toJson() const { + return _contents->toJson(); + } + + double length() { + return _contents->length(); + } + + void moveToStartPoint(CurveVertex const &vertex) { + _contents->moveToStartPoint(vertex); + } + + void addVertex(CurveVertex const &vertex) { + _contents->addVertex(vertex); + } + + void reserveCapacity(size_t capacity) { + _contents->reserveCapacity(capacity); + } + + void setElementCount(size_t count) { + _contents->setElementCount(count); + } + + void invalidateLength() { + _contents->invalidateLength(); + } + + void addCurve(Vector2D const &toPoint, Vector2D const &outTangent, Vector2D const &inTangent) { + _contents->addCurve(toPoint, outTangent, inTangent); + } + + void addLine(Vector2D const &toPoint) { + _contents->addLine(toPoint); + } + + void close() { + _contents->close(); + } + + void addElement(PathElement const &pathElement) { + _contents->addElement(pathElement); + } + + void updateVertex(CurveVertex const &vertex, int atIndex, bool remeasure) { + _contents->updateVertex(vertex, atIndex, remeasure); + } + + /// Trims a path fromLength toLength with an offset. + /// + /// Length and offset are defined in the length coordinate space. + /// If any argument is outside the range of this path, then it will be looped over the path from finish to start. + /// + /// Cutting the curve when fromLength is less than toLength + /// x x x x + /// ~~~~~~~~~~~~~~~ooooooooooooooooooooooooooooooooooooooooooooooooo------------------- + /// |Offset |fromLength toLength| | + /// + /// Cutting the curve when from Length is greater than toLength + /// x x x x x + /// oooooooooooooooooo--------------------~~~~~~~~~~~~~~~~ooooooooooooooooooooooooooooo + /// | toLength| |Offset |fromLength | + /// + std::vector trim(double fromLength, double toLength, double offsetLength) { + std::vector result; + + auto resultContents = _contents->trim(fromLength, toLength, offsetLength); + for (const auto &resultContent : resultContents) { + result.emplace_back(resultContent); + } + + return result; + } + + // MARK: Private + + std::vector const &elements() const { + return _contents->elements; + } + + std::vector &mutableElements() { + return _contents->elements; + } + + std::optional const &closed() const { + return _contents->closed; + } + void setClosed(std::optional const &closed) { + _contents->closed = closed; + } + + std::shared_ptr cgPath() const { + return _contents->cgPath(); + } + + BezierPath copyUsingTransform(CATransform3D const &transform) const { + if (transform == CATransform3D::identity()) { + return (*this); + } + BezierPath result; + result._contents->closed = _contents->closed; + result.reserveCapacity(_contents->elements.size()); + for (const auto &element : _contents->elements) { + result._contents->elements.emplace_back(element.vertex.transformed(transform)); + } + return result; + } + +public: + BezierPath(std::shared_ptr contents) : + _contents(contents) { + } + +private: + std::shared_ptr _contents; +}; + +class BezierPathsBoundingBoxContext { +public: + BezierPathsBoundingBoxContext() : + pointsX((float *)malloc(1024 * 4)), + pointsY((float *)malloc(1024 * 4)), + pointsSize(1024) { + } + + ~BezierPathsBoundingBoxContext() { + free(pointsX); + free(pointsY); + } + +public: + float *pointsX = nullptr; + float *pointsY = nullptr; + int pointsSize = 0; +}; + +CGRect bezierPathsBoundingBox(std::vector const &paths); +CGRect bezierPathsBoundingBoxParallel(BezierPathsBoundingBoxContext &context, std::vector const &paths); + +} + +#endif /* BezierPath_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/CompoundBezierPath.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/CompoundBezierPath.cpp new file mode 100644 index 0000000000..31df703956 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/CompoundBezierPath.cpp @@ -0,0 +1,5 @@ +#include "CompoundBezierPath.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/CompoundBezierPath.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/CompoundBezierPath.hpp new file mode 100644 index 0000000000..c9766c93ae --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/CompoundBezierPath.hpp @@ -0,0 +1,176 @@ +#ifndef CompoundBezierPath_hpp +#define CompoundBezierPath_hpp + +#include "Lottie/Private/Utility/Primitives/BezierPath.hpp" + +namespace lottie { + +/// A collection of BezierPath objects that can be trimmed and added. +/// +class CompoundBezierPath: public std::enable_shared_from_this { +public: + CompoundBezierPath() : + paths({}) { + } + + CompoundBezierPath(BezierPath const &path) : + paths({ path }) { + } + + CompoundBezierPath(std::vector paths_, std::optional length_) : + paths(paths_), _length(length_) { + } + + CompoundBezierPath(std::vector paths_) : + paths(paths_) { + } + +public: + std::vector paths; + + double length() { + if (_length.has_value()) { + return _length.value(); + } else { + double l = 0.0; + for (auto &path : paths) { + l += path.length(); + } + _length = l; + return l; + } + } + +private: + std::optional _length; + +public: + std::shared_ptr addingPath(BezierPath const &path) const { + auto newPaths = paths; + newPaths.push_back(path); + return std::make_shared(newPaths); + } + + void appendPath(BezierPath const &path) { + paths.push_back(path); + _length.reset(); + } + + std::shared_ptr combine(std::shared_ptr compoundBezier) { + auto newPaths = paths; + for (const auto &path : compoundBezier->paths) { + newPaths.push_back(path); + } + return std::make_shared(newPaths); + } + + std::shared_ptr trim(double fromPosition, double toPosition, double offset) { + if (fromPosition == toPosition) { + return std::make_shared(); + } + + /*bool trimSimultaneously = false; + if (trimSimultaneously) { + /// Trim each path individually. + std::vector newPaths; + for (auto &path : paths) { + auto trimmedPaths = path.trim(fromPosition * path.length(), toPosition * path.length(), offset * path.length()); + for (const auto &trimmedPath : trimmedPaths) { + newPaths.push_back(trimmedPath); + } + } + return std::make_shared(newPaths); + }*/ + + double lengthValue = length(); + + /// Normalize lengths to the curve length. + double startPosition = fmod(fromPosition + offset, 1.0); + double endPosition = fmod(toPosition + 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; + } + + if ((startPosition == 0.0 && endPosition == 1.0) || + startPosition == endPosition || + (startPosition == 1.0 && endPosition == 0.0)) { + /// The trim encompasses the entire path. Return. + return shared_from_this(); + } + + std::vector positions; + if (endPosition < startPosition) { + positions = { + BezierTrimPathPosition(0.0, endPosition * lengthValue), + BezierTrimPathPosition(startPosition * lengthValue, lengthValue) + }; + } else { + positions = { BezierTrimPathPosition(startPosition * lengthValue, endPosition * lengthValue) }; + } + + auto compoundPath = std::make_shared(); + auto trim = positions[0]; + positions.erase(positions.begin()); + double pathStartPosition = 0.0; + + bool finishedTrimming = false; + int i = 0; + + while (!finishedTrimming) { + if (paths.size() <= i) { + /// Rounding errors + finishedTrimming = true; + continue; + } + auto path = paths[i]; + + auto pathEndPosition = pathStartPosition + path.length(); + + if (pathEndPosition < trim.start) { + /// Path is not included in the trim, continue. + pathStartPosition = pathEndPosition; + i = i + 1; + continue; + } else if (trim.start <= pathStartPosition && pathEndPosition <= trim.end) { + /// Full Path is inside of trim. Add full path. + compoundPath = compoundPath->addingPath(path); + } else { + auto trimPaths = path.trim(trim.start > pathStartPosition ? (trim.start - pathStartPosition) : 0, trim.end < pathEndPosition ? (trim.end - pathStartPosition) : path.length(), 0.0); + if (!trimPaths.empty()) { + compoundPath = compoundPath->addingPath(trimPaths[0]); + } + } + + if (trim.end <= pathEndPosition) { + /// We are done with the current trim. + /// Advance trim but remain on the same path in case the next trim overlaps it. + if (positions.size() > 0) { + trim = positions[0]; + positions.erase(positions.begin()); + } else { + finishedTrimming = true; + } + } else { + pathStartPosition = pathEndPosition; + i = i + 1; + } + } + return compoundPath; + } +}; + +} + +#endif /* CompoundBezierPath_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/CoordinateSpace.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/CoordinateSpace.cpp new file mode 100644 index 0000000000..695b8dc4e9 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/CoordinateSpace.cpp @@ -0,0 +1,5 @@ +#include "CoordinateSpace.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/CoordinateSpace.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/CoordinateSpace.hpp new file mode 100644 index 0000000000..37a7b7054c --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/CoordinateSpace.hpp @@ -0,0 +1,13 @@ +#ifndef CoordinateSpace_hpp +#define CoordinateSpace_hpp + +namespace lottie { + +enum class CoordinateSpace { + Type2d, + Type3d +}; + +} + +#endif /* CoordinateSpace_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/CurveVertex.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/CurveVertex.cpp new file mode 100644 index 0000000000..5c1d4a0cb0 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/CurveVertex.cpp @@ -0,0 +1,5 @@ +#include "CurveVertex.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/CurveVertex.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/CurveVertex.hpp new file mode 100644 index 0000000000..ef67e0765c --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/CurveVertex.hpp @@ -0,0 +1,197 @@ +#ifndef CurveVertex_hpp +#define CurveVertex_hpp + +#include "Lottie/Public/Primitives/Vectors.hpp" +#include "Lottie/Public/Primitives/CGPath.hpp" + +#include + +namespace lottie { + +template +struct CurveVertexSplitResult { + T start; + T trimPoint; + T end; + + explicit CurveVertexSplitResult( + T const &start_, + T const &trimPoint_, + T const &end_ + ) : + start(start_), + trimPoint(trimPoint_), + end(end_) { + } +}; + +/// A single vertex with an in and out tangent +struct CurveVertex { +private: + /// Initializes a curve point with absolute or relative values + explicit CurveVertex(Vector2D const &point_, Vector2D const &inTangent_, Vector2D const &outTangent_, bool isRelative_) : + point(point_), + inTangent(isRelative_ ? (point_ + inTangent_) : inTangent_), + outTangent(isRelative_ ? (point_ + outTangent_) : outTangent_) { + } + +public: + static CurveVertex absolute(Vector2D const &point_, Vector2D const &inTangent_, Vector2D const &outTangent_) { + return CurveVertex(point_, inTangent_, outTangent_, false); + } + + static CurveVertex relative(Vector2D const &point_, Vector2D const &inTangent_, Vector2D const &outTangent_) { + return CurveVertex(point_, inTangent_, outTangent_, true); + } + + Vector2D inTangentRelative() const { + Vector2D result = inTangent - point; + return result; + } + + Vector2D outTangentRelative() const { + Vector2D result = outTangent - point; + return result; + } + + CurveVertex reversed() const { + return CurveVertex(point, outTangent, inTangent, false); + } + + CurveVertex translated(Vector2D const &translation) const { + return CurveVertex(point + translation, inTangent + translation, outTangent + translation, false); + } + + CurveVertex transformed(CATransform3D const &transform) const { + return CurveVertex(transformVector(point, transform), transformVector(inTangent, transform), transformVector(outTangent, transform), false); + } + +public: + Vector2D point = Vector2D::Zero(); + + Vector2D inTangent = Vector2D::Zero(); + Vector2D outTangent = Vector2D::Zero(); + + /// Trims a path defined by two Vertices at a specific position, from 0 to 1 + /// + /// The path can be visualized below. + /// + /// F is fromVertex. + /// V is the vertex of the receiver. + /// P is the position from 0-1. + /// O is the outTangent of fromVertex. + /// F====O=========P=======I====V + /// + /// After trimming the curve can be visualized below. + /// + /// S is the returned Start vertex. + /// E is the returned End vertex. + /// T is the trim point. + /// TI and TO are the new tangents for the trimPoint + /// NO and NI are the new tangents for the startPoint and endPoints + /// S==NO=========TI==T==TO=======NI==E + CurveVertexSplitResult splitCurve(CurveVertex const &toVertex, double position) const { + /// If position is less than or equal to 0, trim at start. + if (position <= 0.0) { + return CurveVertexSplitResult( + CurveVertex(point, inTangentRelative(), Vector2D::Zero(), true), + CurveVertex(point, Vector2D::Zero(), outTangentRelative(), true), + toVertex + ); + } + + /// If position is greater than or equal to 1, trim at end. + if (position >= 1.0) { + return CurveVertexSplitResult( + *this, + CurveVertex(toVertex.point, toVertex.inTangentRelative(), Vector2D::Zero(), true), + CurveVertex(toVertex.point, Vector2D::Zero(), toVertex.outTangentRelative(), true) + ); + } + + if (outTangentRelative().isZero() && toVertex.inTangentRelative().isZero()) { + /// If both tangents are zero, then span to be trimmed is a straight line. + Vector2D trimPoint = interpolate(point, toVertex.point, position); + return CurveVertexSplitResult( + *this, + CurveVertex(trimPoint, Vector2D::Zero(), Vector2D::Zero(), true), + toVertex + ); + } + /// Cutting by amount gives incorrect length.... + /// One option is to cut by a stride until it gets close then edge it down. + /// Measuring a percentage of the spans does not equal the same as measuring a percentage of length. + /// This is where the historical trim path bugs come from. + Vector2D a = interpolate(point, outTangent, position); + Vector2D b = interpolate(outTangent, toVertex.inTangent, position); + Vector2D c = interpolate(toVertex.inTangent, toVertex.point, position); + Vector2D d = interpolate(a, b, position); + Vector2D e = interpolate(b, c, position); + Vector2D f = interpolate(d, e, position); + return CurveVertexSplitResult( + CurveVertex::absolute(point, inTangent, a), + CurveVertex::absolute(f, d, e), + CurveVertex::absolute(toVertex.point, c, toVertex.outTangent) + ); + } + + /// Trims a curve of a known length to a specific length and returns the points. + /// + /// There is not a performant yet accurate way to cut a curve to a specific length. + /// This calls splitCurve(toVertex: position:) to split the curve and then measures + /// the length of the new curve. The function then iterates through the samples, + /// adjusting the position of the cut for a more precise cut. + /// Usually a single iteration is enough to get within 0.5 points of the desired + /// length. + /// + /// This function should probably live in PathElement, since it deals with curve + /// lengths. + CurveVertexSplitResult trimCurve(CurveVertex const &toVertex, double atLength, double curveLength, int maxSamples, double accuracy = 1.0) const { + double currentPosition = atLength / curveLength; + auto results = splitCurve(toVertex, currentPosition); + + if (maxSamples == 0) { + return results; + } + + for (int i = 1; i <= maxSamples; i++) { + auto length = results.start.distanceTo(results.trimPoint); + auto lengthDiff = atLength - length; + /// Check if length is correct. + if (lengthDiff < accuracy) { + return results; + } + auto diffPosition = std::max(std::min((currentPosition / length) * lengthDiff, currentPosition * 0.5), currentPosition * (-0.5)); + currentPosition = diffPosition + currentPosition; + results = splitCurve(toVertex, currentPosition); + } + return results; + } + + /// The distance from the receiver to the provided vertex. + /// + /// For lines (zeroed tangents) the distance between the two points is measured. + /// For curves the curve is iterated over by sample count and the points are measured. + /// This is ~99% accurate at a sample count of 30 + double distanceTo(CurveVertex const &toVertex, int sampleCount = 25) const { + if (outTangentRelative().isZero() && toVertex.inTangentRelative().isZero()) { + /// Return a linear distance. + return point.distanceTo(toVertex.point); + } + + double distance = 0.0; + + auto previousPoint = point; + for (int i = 0; i < sampleCount; i++) { + auto pointOnCurve = splitCurve(toVertex, ((double)(i)) / ((double)(sampleCount))).trimPoint; + distance = distance + previousPoint.distanceTo(pointOnCurve.point); + previousPoint = pointOnCurve.point; + } + distance = distance + previousPoint.distanceTo(toVertex.point); + return distance; + } +}; + +} + +#endif /* CurveVertex_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/PathElement.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/PathElement.cpp new file mode 100644 index 0000000000..24b73221a0 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/PathElement.cpp @@ -0,0 +1,5 @@ +#include "PathElement.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/PathElement.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/PathElement.hpp new file mode 100644 index 0000000000..16af3c82ee --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/PathElement.hpp @@ -0,0 +1,90 @@ +#ifndef PathElement_hpp +#define PathElement_hpp + +#include "Lottie/Private/Utility/Primitives/CurveVertex.hpp" + +namespace lottie { + +template +struct PathSplitResultSpan { + T start; + T end; + + explicit PathSplitResultSpan(T const &start_, T const &end_) : + start(start_), end(end_) { + } +}; + +template +struct PathSplitResult { + PathSplitResultSpan leftSpan; + PathSplitResultSpan rightSpan; + + explicit PathSplitResult(PathSplitResultSpan const &leftSpan_, PathSplitResultSpan const &rightSpan_) : + leftSpan(leftSpan_), rightSpan(rightSpan_) { + } +}; + +/// A path section, containing one point and its length to the previous point. +/// +/// The relationship between this path element and the previous is implicit. +/// Ideally a path section would be defined by two vertices and a length. +/// We don't do this however, as it would effectively double the memory footprint +/// of path data. +/// +struct PathElement { + /// Initializes a new path with length of 0 + explicit PathElement(CurveVertex const &vertex_) : + vertex(vertex_) { + } + + /// Initializes a new path with length + explicit PathElement(std::optional length_, CurveVertex const &vertex_) : + vertex(vertex_) { + } + + /// The vertex of the element + CurveVertex vertex; + + /// Returns a new path element define the span from the receiver to the new vertex. + PathElement pathElementTo(CurveVertex const &toVertex) const { + return PathElement(std::nullopt, toVertex); + } + + PathElement updateVertex(CurveVertex const &newVertex) const { + return PathElement(newVertex); + } + + /// Splits an element span defined by the receiver and fromElement to a position 0-1 + PathSplitResult splitElementAtPosition(PathElement const &fromElement, double atLength) { + /// Trim the span. Start and trim go into the first, trim and end go into second. + auto trimResults = fromElement.vertex.trimCurve(vertex, atLength, length(fromElement), 3); + + /// Create the elements for the break + auto spanAStart = PathElement( + std::nullopt, + CurveVertex::absolute( + fromElement.vertex.point, + fromElement.vertex.inTangent, + trimResults.start.outTangent + )); + /// Recalculating the length here is a waste as the trimCurve function also accurately calculates this length. + auto spanAEnd = spanAStart.pathElementTo(trimResults.trimPoint); + + auto spanBStart = PathElement(trimResults.trimPoint); + auto spanBEnd = spanBStart.pathElementTo(trimResults.end); + return PathSplitResult( + PathSplitResultSpan(spanAStart, spanAEnd), + PathSplitResultSpan(spanBStart, spanBEnd) + ); + } + + double length(PathElement const &previous) { + double result = previous.vertex.distanceTo(vertex); + return result; + } +}; + +} + +#endif /* PathElement_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/DynamicProperties/AnimationKeypath.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/DynamicProperties/AnimationKeypath.cpp new file mode 100644 index 0000000000..8f90bfc95e --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/DynamicProperties/AnimationKeypath.cpp @@ -0,0 +1,5 @@ +#include "AnimationKeypath.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/DynamicProperties/AnimationKeypath.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/DynamicProperties/AnimationKeypath.hpp new file mode 100644 index 0000000000..8679106d12 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/DynamicProperties/AnimationKeypath.hpp @@ -0,0 +1,51 @@ +#ifndef AnimationKeypath_hpp +#define AnimationKeypath_hpp + +#include +#include + +namespace lottie { + +/// `AnimationKeypath` is an object that describes a keypath search for nodes in the +/// animation JSON. `AnimationKeypath` matches views and properties inside of `AnimationView` +/// to their backing `Animation` model by name. +/// +/// A keypath can be used to set properties on an existing animation, or can be validated +/// with an existing `Animation`. +/// +/// `AnimationKeypath` can describe a specific object, or can use wildcards for fuzzy matching +/// of objects. Acceptable wildcards are either "*" (star) or "**" (double star). +/// Single star will search a single depth for the next object. +/// Double star will search any depth. +/// +/// Read More at https://airbnb.io/lottie/#/ios?id=dynamic-animation-properties +/// +/// EG: +/// @"Layer.Shape Group.Stroke 1.Color" +/// Represents a specific color node on a specific stroke. +/// +/// @"**.Stroke 1.Color" +/// Represents the color node for every Stroke named "Stroke 1" in the animation. +class AnimationKeypath { +public: + /// Creates a keypath from a dot-separated string. The string is separated by "." + /*public init(keypath: String) { + keys = keypath.components(separatedBy: ".") + }*/ + + /// Creates a keypath from a list of strings. + AnimationKeypath(std::vector const &keys) : + _keys(keys) { + } + + std::vector const &keys() const { + return _keys; + } + +private: + std::vector _keys; +}; + +} + +#endif /* AnimationKeypath_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/DynamicProperties/AnyValueProvider.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/DynamicProperties/AnyValueProvider.cpp new file mode 100644 index 0000000000..f56124c957 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/DynamicProperties/AnyValueProvider.cpp @@ -0,0 +1,5 @@ +#include "AnyValueProvider.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/DynamicProperties/AnyValueProvider.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/DynamicProperties/AnyValueProvider.hpp new file mode 100644 index 0000000000..a9b708afbd --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/DynamicProperties/AnyValueProvider.hpp @@ -0,0 +1,38 @@ +#ifndef AnyValueProvider_hpp +#define AnyValueProvider_hpp + +#include "Lottie/Public/Primitives/AnimationTime.hpp" +#include "Lottie/Private/Model/Keyframes/KeyframeGroup.hpp" +#include "Lottie/Public/Primitives/AnyValue.hpp" + +#include +#include + +namespace lottie { + +/// `AnyValueProvider` is a protocol that return animation data for a property at a +/// given time. Every frame an `AnimationView` queries all of its properties and asks +/// if their ValueProvider has an update. If it does the AnimationView will read the +/// property and update that portion of the animation. +/// +/// Value Providers can be used to dynamically set animation properties at run time. +class AnyValueProvider { +public: + /// The Type of the value provider + virtual AnyValue::Type valueType() const = 0; + + /// Asks the provider if it has an update for the given frame. + virtual bool hasUpdate(AnimationFrameTime frame) const = 0; +}; + +/// A base protocol for strongly-typed Value Providers +template +class ValueProvider: public AnyValueProvider { +public: + /// Asks the provider to update the container with its value for the frame. + virtual T value(AnimationFrameTime frame) = 0; +}; + +} + +#endif /* AnyValueProvider_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/FontProvider/AnimationFontProvider.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/FontProvider/AnimationFontProvider.cpp new file mode 100644 index 0000000000..a6a4774df7 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/FontProvider/AnimationFontProvider.cpp @@ -0,0 +1,5 @@ +#include "AnimationFontProvider.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/FontProvider/AnimationFontProvider.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/FontProvider/AnimationFontProvider.hpp new file mode 100644 index 0000000000..f03a137db4 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/FontProvider/AnimationFontProvider.hpp @@ -0,0 +1,33 @@ +#ifndef AnimationFontProvider_hpp +#define AnimationFontProvider_hpp + +#include "Lottie/Public/Primitives/CTFont.hpp" + +#include + +namespace lottie { + +/// Font provider is a protocol that is used to supply fonts to `AnimationView`. +/// +class AnimationFontProvider { +public: + virtual std::shared_ptr fontFor(std::string const &family, double size) = 0; +}; + +/// Default Font provider. +class DefaultFontProvider: public AnimationFontProvider { +public: + DefaultFontProvider() { + } + + virtual ~DefaultFontProvider() = default; + + virtual std::shared_ptr fontFor(std::string const &family, double size) override { + //CTFontCreateWithName(family as CFString, size, nil) + return nullptr; + } +}; + +} + +#endif /* AnimationFontProvider_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/ImageProvider/AnimationImageProvider.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/ImageProvider/AnimationImageProvider.hpp new file mode 100644 index 0000000000..3c55ffd944 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/ImageProvider/AnimationImageProvider.hpp @@ -0,0 +1,16 @@ +#ifndef AnimationImageProvider_hpp +#define AnimationImageProvider_hpp + +#include "Lottie/Public/Primitives/CALayer.hpp" +#include "Lottie/Private/Model/Assets/ImageAsset.hpp" + +namespace lottie { + +class AnimationImageProvider { +public: + virtual std::shared_ptr imageForAsset(ImageAsset const &imageAsset) = 0; +}; + +} + +#endif /* AnimationImageProvider_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Keyframes/Interpolatable.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Keyframes/Interpolatable.cpp new file mode 100644 index 0000000000..8165d87d71 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Keyframes/Interpolatable.cpp @@ -0,0 +1,15 @@ +#include "Interpolatable.hpp" + +namespace lottie { + +double remapDouble(double value, double fromLow, double fromHigh, double toLow, double toHigh) { + return toLow + (value - fromLow) * (toHigh - toLow) / (fromHigh - fromLow); +} + +double clampDouble(double value, double a, double b) { + double minValue = a <= b ? a : b; + double maxValue = a <= b ? b : a; + return std::max(std::min(value, maxValue), minValue); +} + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Keyframes/Interpolatable.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Keyframes/Interpolatable.hpp new file mode 100644 index 0000000000..2bfabdca07 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Keyframes/Interpolatable.hpp @@ -0,0 +1,14 @@ +#ifndef Interpolatable_hpp +#define Interpolatable_hpp + +#include + +namespace lottie { + +double remapDouble(double value, double fromLow, double fromHigh, double toLow, double toHigh); + +double clampDouble(double value, double a, double b); + +} + +#endif /* Interpolatable_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Keyframes/Keyframe.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Keyframes/Keyframe.cpp new file mode 100644 index 0000000000..e699e6daf8 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Keyframes/Keyframe.cpp @@ -0,0 +1,5 @@ +#include "Keyframe.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Keyframes/Keyframe.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Keyframes/Keyframe.hpp new file mode 100644 index 0000000000..51f9ae4f13 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Keyframes/Keyframe.hpp @@ -0,0 +1,256 @@ +#ifndef Keyframe_hpp +#define Keyframe_hpp + +#include "Lottie/Public/Primitives/AnimationTime.hpp" +#include "Lottie/Public/Primitives/Vectors.hpp" +#include "Lottie/Public/Keyframes/Interpolatable.hpp" +#include "Lottie/Public/Keyframes/ValueInterpolators.hpp" + +#include + +namespace lottie { + +/// A keyframe with a single value, and timing information +/// about when the value should be displayed and how it +/// should be interpolated. +template +class Keyframe { +public: + /// Initialize a value-only keyframe with no time data. + Keyframe( + T const &value_, + std::optional spatialInTangent_, + std::optional spatialOutTangent_ + ) : + value(value_), + time(0), + isHold(true), + inTangent(std::nullopt), + outTangent(std::nullopt), + spatialInTangent(spatialInTangent_), + spatialOutTangent(spatialOutTangent_) { + } + + /// Initialize a keyframe + Keyframe( + T value_, + AnimationFrameTime time_, + bool isHold_, + std::optional inTangent_, + std::optional outTangent_, + std::optional spatialInTangent_, + std::optional spatialOutTangent_ + ) : + value(value_), + time(time_), + isHold(isHold_), + inTangent(inTangent_), + outTangent(outTangent_), + spatialInTangent(spatialInTangent_), + spatialOutTangent(spatialOutTangent_) { + } + + bool operator==(Keyframe const &rhs) { + return value == rhs.value + && time == rhs.time + && isHold == rhs.isHold + && inTangent == rhs.inTangent + && outTangent == rhs.outTangent + && spatialInTangent == rhs.spatialInTangent + && spatialOutTangent == rhs.spatialOutTangent; + } + + bool operator!=(Keyframe const &rhs) { + return !(*this == rhs); + } + +public: + T interpolate(Keyframe const &to, double progress) { + std::optional spatialOutTangent2d; + if (spatialOutTangent) { + spatialOutTangent2d = Vector2D(spatialOutTangent->x, spatialOutTangent->y); + } + std::optional spatialInTangent2d; + if (to.spatialInTangent) { + spatialInTangent2d = Vector2D(to.spatialInTangent->x, to.spatialInTangent->y); + } + return ValueInterpolator::interpolate(value, to.value, progress, spatialOutTangent2d, spatialInTangent2d); + } + + /// Interpolates the keyTime into a value from 0-1 + double interpolatedProgress(Keyframe const &to, double keyTime) { + double startTime = time; + double endTime = to.time; + if (keyTime <= startTime) { + return 0.0; + } + if (endTime <= keyTime) { + return 1.0; + } + + if (isHold) { + return 0.0; + } + + Vector2D outTanPoint = Vector2D::Zero(); + if (outTangent.has_value()) { + outTanPoint = outTangent.value(); + } + Vector2D inTanPoint = Vector2D(1.0, 1.0); + if (to.inTangent.has_value()) { + inTanPoint = to.inTangent.value(); + } + double progress = remapDouble(keyTime, startTime, endTime, 0.0, 1.0); + if (!outTanPoint.isZero() || inTanPoint != Vector2D(1.0, 1.0)) { + /// Cubic interpolation + progress = cubicBezierInterpolate(progress, Vector2D::Zero(), outTanPoint, inTanPoint, Vector2D(1.0, 1.0)); + } + return progress; + } + +public: + /// The value of the keyframe + T value; + /// The time in frames of the keyframe. + AnimationFrameTime time; + /// A hold keyframe freezes interpolation until the next keyframe that is not a hold. + bool isHold; + /// The in tangent for the time interpolation curve. + std::optional inTangent; + /// The out tangent for the time interpolation curve. + std::optional outTangent; + + /// The spatial in tangent of the vector. + std::optional spatialInTangent; + /// The spatial out tangent of the vector. + std::optional spatialOutTangent; +}; + +template +class KeyframeData { +public: + KeyframeData( + std::optional startValue_, + std::optional endValue_, + std::optional time_, + std::optional hold_, + std::optional inTangent_, + std::optional outTangent_, + std::optional spatialInTangent_, + std::optional spatialOutTangent_ + ) : + startValue(startValue_), + endValue(endValue_), + time(time_), + hold(hold_), + inTangent(inTangent_), + outTangent(outTangent_), + spatialInTangent(spatialInTangent_), + spatialOutTangent(spatialOutTangent_) { + } + + explicit KeyframeData(json11::Json const &json) noexcept(false) { + if (!json.is_object()) { + throw LottieParsingException(); + } + + if (const auto startValueData = getOptionalAny(json.object_items(), "s")) { + startValue = T(startValueData.value()); + } + + if (const auto endValueData = getOptionalAny(json.object_items(), "e")) { + endValue = T(endValueData.value()); + } + + time = getOptionalDouble(json.object_items(), "t"); + hold = getOptionalInt(json.object_items(), "h"); + + if (const auto inTangentData = getOptionalObject(json.object_items(), "i")) { + inTangent = Vector2D(inTangentData.value()); + } + + if (const auto outTangentData = getOptionalObject(json.object_items(), "o")) { + outTangent = Vector2D(outTangentData.value()); + } + + if (const auto spatialInTangentData = getOptionalAny(json.object_items(), "ti")) { + spatialInTangent = Vector3D(spatialInTangentData.value()); + } + + if (const auto spatialOutTangentData = getOptionalAny(json.object_items(), "to")) { + spatialOutTangent = Vector3D(spatialOutTangentData.value()); + } + + if (const auto nDataValue = getOptionalAny(json.object_items(), "n")) { + nData = nDataValue.value(); + } + } + + json11::Json::object toJson() const { + json11::Json::object result; + + if (startValue.has_value()) { + result.insert(std::make_pair("s", startValue->toJson())); + } + if (endValue.has_value()) { + result.insert(std::make_pair("e", endValue->toJson())); + } + if (time.has_value()) { + result.insert(std::make_pair("t", time.value())); + } + if (hold.has_value()) { + result.insert(std::make_pair("h", hold.value())); + } + if (inTangent.has_value()) { + result.insert(std::make_pair("i", inTangent->toJson())); + } + if (outTangent.has_value()) { + result.insert(std::make_pair("o", outTangent->toJson())); + } + if (spatialInTangent.has_value()) { + result.insert(std::make_pair("ti", spatialInTangent->toJson())); + } + if (spatialOutTangent.has_value()) { + result.insert(std::make_pair("to", spatialOutTangent->toJson())); + } + if (nData.has_value()) { + result.insert(std::make_pair("n", nData.value())); + } + + return result; + } + +public: + /// The start value of the keyframe + std::optional startValue; + /// The End value of the keyframe. Note: Newer versions animation json do not have this field. + std::optional endValue; + /// The time in frames of the keyframe. + std::optional time; + /// A hold keyframe freezes interpolation until the next keyframe that is not a hold. + std::optional hold; + + /// The in tangent for the time interpolation curve. + std::optional inTangent; + /// The out tangent for the time interpolation curve. + std::optional outTangent; + + /// The spacial in tangent of the vector. + std::optional spatialInTangent; + /// The spacial out tangent of the vector. + std::optional spatialOutTangent; + + std::optional nData; + + bool isHold() const { + if (hold.has_value()) { + return hold.value() > 0; + } else { + return false; + } + } +}; + +} + +#endif /* Keyframe_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Keyframes/ValueInterpolators.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Keyframes/ValueInterpolators.cpp new file mode 100644 index 0000000000..55e4fac688 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Keyframes/ValueInterpolators.cpp @@ -0,0 +1,5 @@ +#include "ValueInterpolators.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Keyframes/ValueInterpolators.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Keyframes/ValueInterpolators.hpp new file mode 100644 index 0000000000..b2c063dccc --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Keyframes/ValueInterpolators.hpp @@ -0,0 +1,231 @@ +#ifndef ValueInterpolators_hpp +#define ValueInterpolators_hpp + +#include "Lottie/Public/Primitives/Vectors.hpp" +#include "Lottie/Public/Primitives/Color.hpp" +#include "Lottie/Private/Utility/Primitives/BezierPath.hpp" +#include "Lottie/Private/Model/Text/TextDocument.hpp" +#include "Lottie/Public/Primitives/GradientColorSet.hpp" +#include "Lottie/Public/Primitives/DashPattern.hpp" + +#include +#include + +namespace lottie { + +template +struct ValueInterpolator { +}; + +template<> +struct ValueInterpolator { +public: + static double interpolate(double value, double to, double amount, std::optional spatialOutTangent, std::optional spatialInTangent) { + return value + ((to - value) * amount); + } +}; + +template<> +struct ValueInterpolator { +public: + static Vector1D interpolate(Vector1D const &value, Vector1D const &to, double amount, std::optional spatialOutTangent, std::optional spatialInTangent) { + return Vector1D(ValueInterpolator::interpolate(value.value, to.value, amount, spatialOutTangent, spatialInTangent)); + } +}; + +template<> +struct ValueInterpolator { +public: + static Vector2D interpolate(Vector2D const &value, Vector2D const &to, double amount, Vector2D spatialOutTangent, Vector2D spatialInTangent) { + auto cp1 = value + spatialOutTangent; + auto cp2 = to + spatialInTangent; + + return value.interpolate(to, cp1, cp2, amount); + } + + static Vector2D interpolate(Vector2D const &value, Vector2D const &to, double amount) { + return value.interpolate(to, amount); + } +}; + +template<> +struct ValueInterpolator { +public: + static Vector3D interpolate(Vector3D const &value, Vector3D const &to, double amount, std::optional spatialOutTangent, std::optional spatialInTangent) { + if (spatialOutTangent && spatialInTangent) { + Vector2D from2d(value.x, value.y); + Vector2D to2d(to.x, to.y); + + auto cp1 = from2d + spatialOutTangent.value(); + auto cp2 = to2d + spatialInTangent.value(); + + Vector2D result2d = from2d.interpolate(to2d, cp1, cp2, amount); + + return Vector3D( + result2d.x, + result2d.y, + ValueInterpolator::interpolate(value.z, to.z, amount, spatialOutTangent, spatialInTangent) + ); + } + + return Vector3D( + ValueInterpolator::interpolate(value.x, to.x, amount, spatialOutTangent, spatialInTangent), + ValueInterpolator::interpolate(value.y, to.y, amount, spatialOutTangent, spatialInTangent), + ValueInterpolator::interpolate(value.z, to.z, amount, spatialOutTangent, spatialInTangent) + ); + } +}; + +template<> +struct ValueInterpolator { +public: + static Color interpolate(Color const &value, Color const &to, double amount, std::optional spatialOutTangent, std::optional spatialInTangent) { + return Color( + ValueInterpolator::interpolate(value.r, to.r, amount, spatialOutTangent, spatialInTangent), + ValueInterpolator::interpolate(value.g, to.g, amount, spatialOutTangent, spatialInTangent), + ValueInterpolator::interpolate(value.b, to.b, amount, spatialOutTangent, spatialInTangent), + ValueInterpolator::interpolate(value.a, to.a, amount, spatialOutTangent, spatialInTangent) + ); + } +}; + +template<> +struct ValueInterpolator { +public: + static CurveVertex interpolate(CurveVertex const &value, CurveVertex const &to, double amount, Vector2D spatialOutTangent, Vector2D spatialInTangent) { + return CurveVertex::absolute( + ValueInterpolator::interpolate(value.point, to.point, amount, spatialOutTangent, spatialInTangent), + ValueInterpolator::interpolate(value.inTangent, to.inTangent, amount, spatialOutTangent, spatialInTangent), + ValueInterpolator::interpolate(value.outTangent, to.outTangent, amount, spatialOutTangent, spatialInTangent) + ); + } + + static CurveVertex interpolate(CurveVertex const &value, CurveVertex const &to, double amount) { + return CurveVertex::absolute( + ValueInterpolator::interpolate(value.point, to.point, amount), + ValueInterpolator::interpolate(value.inTangent, to.inTangent, amount), + ValueInterpolator::interpolate(value.outTangent, to.outTangent, amount) + ); + } +}; + +template<> +struct ValueInterpolator { +public: + static BezierPath interpolate(BezierPath const &value, BezierPath const &to, double amount, std::optional spatialOutTangent, std::optional spatialInTangent) { + BezierPath newPath; + newPath.reserveCapacity(std::max(value.elements().size(), to.elements().size())); + //TODO:probably a bug in the upstream code, uncomment + //newPath.setClosed(value.closed()); + size_t elementCount = std::min(value.elements().size(), to.elements().size()); + + if (spatialInTangent && spatialOutTangent) { + Vector2D spatialInTangentValue = spatialInTangent.value(); + Vector2D spatialOutTangentValue = spatialOutTangent.value(); + + for (size_t i = 0; i < elementCount; i++) { + const auto &fromVertex = value.elements()[i].vertex; + const auto &toVertex = to.elements()[i].vertex; + + newPath.addVertex(ValueInterpolator::interpolate(fromVertex, toVertex, amount, spatialOutTangentValue, spatialInTangentValue)); + } + } else { + for (size_t i = 0; i < elementCount; i++) { + const auto &fromVertex = value.elements()[i].vertex; + const auto &toVertex = to.elements()[i].vertex; + + newPath.addVertex(ValueInterpolator::interpolate(fromVertex, toVertex, amount)); + } + } + return newPath; + } + + static void setInplace(BezierPath const &value, BezierPath &resultPath) { + resultPath.reserveCapacity(value.elements().size()); + resultPath.setElementCount(value.elements().size()); + resultPath.invalidateLength(); + + memcpy(resultPath.mutableElements().data(), value.elements().data(), value.elements().size() * sizeof(PathElement)); + } + + static void interpolateInplace(BezierPath const &value, BezierPath const &to, double amount, std::optional spatialOutTangent, std::optional spatialInTangent, BezierPath &resultPath) { + /*if (value.elements().size() != to.elements().size()) { + return to; + }*/ + + //TODO:probably a bug in the upstream code, uncomment + //newPath.setClosed(value.closed()); + int elementCount = (int)std::min(value.elements().size(), to.elements().size()); + + resultPath.reserveCapacity(std::max(value.elements().size(), to.elements().size())); + resultPath.setElementCount(elementCount); + resultPath.invalidateLength(); + + if (spatialInTangent && spatialOutTangent) { + Vector2D spatialInTangentValue = spatialInTangent.value(); + Vector2D spatialOutTangentValue = spatialOutTangent.value(); + + for (int i = 0; i < elementCount; i++) { + const auto &fromVertex = value.elements()[i].vertex; + const auto &toVertex = to.elements()[i].vertex; + + auto vertex = ValueInterpolator::interpolate(fromVertex, toVertex, amount, spatialOutTangentValue, spatialInTangentValue); + + resultPath.updateVertex(vertex, i, false); + } + } else { + for (int i = 0; i < elementCount; i++) { + const auto &fromVertex = value.elements()[i].vertex; + const auto &toVertex = to.elements()[i].vertex; + + auto vertex = ValueInterpolator::interpolate(fromVertex, toVertex, amount); + + resultPath.updateVertex(vertex, i, false); + } + } + } +}; + +template<> +struct ValueInterpolator { +public: + static TextDocument interpolate(TextDocument const &value, TextDocument const &to, double amount, std::optional spatialOutTangent, std::optional spatialInTangent) { + if (amount == 1.0) { + return to; + } else { + return value; + } + } +}; + +template<> +struct ValueInterpolator { +public: + static GradientColorSet interpolate(GradientColorSet const &value, GradientColorSet const &to, double amount, std::optional spatialOutTangent, std::optional spatialInTangent) { + assert(value.colors.size() == to.colors.size()); + std::vector colors; + size_t colorCount = std::min(value.colors.size(), to.colors.size()); + for (size_t i = 0; i < colorCount; i++) { + colors.push_back(ValueInterpolator::interpolate(value.colors[i], to.colors[i], amount, spatialOutTangent, spatialInTangent)); + } + return GradientColorSet(colors); + } +}; + +template<> +struct ValueInterpolator { +public: + static DashPattern interpolate(DashPattern const &value, DashPattern const &to, double amount, std::optional spatialOutTangent, std::optional spatialInTangent) { + assert(value.values.size() == to.values.size()); + std::vector values; + size_t colorCount = std::min(value.values.size(), to.values.size()); + for (size_t i = 0; i < colorCount; i++) { + values.push_back(ValueInterpolator::interpolate(value.values[i], to.values[i], amount, spatialOutTangent, spatialInTangent)); + } + return DashPattern(std::move(values)); + } +}; + +} + +#endif /* ValueInterpolators_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/AnimationTime.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/AnimationTime.cpp new file mode 100644 index 0000000000..c804b75b10 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/AnimationTime.cpp @@ -0,0 +1,5 @@ +#include "AnimationTime.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/AnimationTime.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/AnimationTime.hpp new file mode 100644 index 0000000000..02a3e558d3 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/AnimationTime.hpp @@ -0,0 +1,14 @@ +#ifndef AnimationTime_hpp +#define AnimationTime_hpp + +namespace lottie { + +/// Defines animation time in Frames (Seconds * Framerate). +typedef double AnimationFrameTime; + +/// Defines animation time by a progress from 0 (beginning of the animation) to 1 (end of the animation) +typedef double AnimationProgressTime; + +} + +#endif /* AnimationTime_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/AnyValue.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/AnyValue.cpp new file mode 100644 index 0000000000..1af508b3de --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/AnyValue.cpp @@ -0,0 +1,5 @@ +#include "AnyValue.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/AnyValue.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/AnyValue.hpp new file mode 100644 index 0000000000..516b2e7d10 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/AnyValue.hpp @@ -0,0 +1,245 @@ +#ifndef AnyValue_hpp +#define AnyValue_hpp + +#include "Lottie/Public/Primitives/Vectors.hpp" +#include "Lottie/Public/Primitives/Color.hpp" +#include "Lottie/Private/Utility/Primitives/BezierPath.hpp" +#include "Lottie/Private/Model/Text/TextDocument.hpp" +#include "Lottie/Private/Model/ShapeItems/GradientFill.hpp" +#include "Lottie/Private/Model/Objects/DashElement.hpp" + +#include +#include + +namespace lottie { + +class AnyValue { +public: + enum class Type { + Double, + Vector1D, + Vector2D, + Vector3D, + Color, + BezierPath, + TextDocument, + GradientColorSet, + DashPattern + }; + +public: + AnyValue(double value) : + _type(Type::Double), + _doubleValue(value) { + } + + AnyValue(Vector1D const &value) : + _type(Type::Vector1D), + _vector1DValue(value) { + } + + AnyValue(Vector2D const &value) : + _type(Type::Vector2D), + _vector2DValue(value) { + } + + AnyValue(Vector3D const & value) : + _type(Type::Vector3D), + _vector3DValue(value) { + } + + AnyValue(Color const &value) : + _type(Type::Color), + _colorValue(value) { + } + + AnyValue(BezierPath const &value) : + _type(Type::BezierPath), + _bezierPathValue(value) { + } + + AnyValue(TextDocument const &value) : + _type(Type::TextDocument), + _textDocumentValue(value) { + } + + AnyValue(GradientColorSet const &value) : + _type(Type::GradientColorSet), + _gradientColorSetValue(value) { + } + + AnyValue(DashPattern const &value) : + _type(Type::DashPattern), + _dashPatternValue(value) { + } + + template::value>> + double get() { + return asDouble(); + } + + template::value>> + Vector1D get() { + return asVector1D(); + } + + template::value>> + Vector2D get() { + return asVector2D(); + } + + template::value>> + Vector3D get() { + return asVector3D(); + } + + template::value>> + Color get() { + return asColor(); + } + + template::value>> + BezierPath get() { + return asBezierPath(); + } + + template::value>> + TextDocument get() { + return asTextDocument(); + } + + template::value>> + GradientColorSet get() { + return asGradientColorSet(); + } + + template::value>> + DashPattern get() { + return asDashPattern(); + } + +public: + Type type() { + return _type; + } + + double asDouble() { + return _doubleValue.value(); + } + + Vector1D asVector1D() { + return _vector1DValue.value(); + } + + Vector2D asVector2D() { + return _vector2DValue.value(); + } + + Vector3D asVector3D() { + return _vector3DValue.value(); + } + + Color asColor() { + return _colorValue.value(); + } + + BezierPath asBezierPath() { + return _bezierPathValue.value(); + } + + TextDocument asTextDocument() { + return _textDocumentValue.value(); + } + + GradientColorSet asGradientColorSet() { + return _gradientColorSetValue.value(); + } + + DashPattern asDashPattern() { + return _dashPatternValue.value(); + } + +private: + Type _type; + + std::optional _doubleValue; + std::optional _vector1DValue; + std::optional _vector2DValue; + std::optional _vector3DValue; + std::optional _colorValue; + std::optional _bezierPathValue; + std::optional _textDocumentValue; + std::optional _gradientColorSetValue; + std::optional _dashPatternValue; +}; + +template +struct AnyValueType { +}; + +template<> +struct AnyValueType { + static AnyValue::Type type() { + return AnyValue::Type::Double; + } +}; + +template<> +struct AnyValueType { + static AnyValue::Type type() { + return AnyValue::Type::Vector1D; + } +}; + +template<> +struct AnyValueType { + static AnyValue::Type type() { + return AnyValue::Type::Vector2D; + } +}; + +template<> +struct AnyValueType { + static AnyValue::Type type() { + return AnyValue::Type::Vector3D; + } +}; + +template<> +struct AnyValueType { + static AnyValue::Type type() { + return AnyValue::Type::Color; + } +}; + +template<> +struct AnyValueType { + static AnyValue::Type type() { + return AnyValue::Type::BezierPath; + } +}; + +template<> +struct AnyValueType { + static AnyValue::Type type() { + return AnyValue::Type::TextDocument; + } +}; + +template<> +struct AnyValueType { + static AnyValue::Type type() { + return AnyValue::Type::GradientColorSet; + } +}; + +template<> +struct AnyValueType { + static AnyValue::Type type() { + return AnyValue::Type::DashPattern; + } +}; + +} + +#endif /* AnyValue_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/CALayer.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/CALayer.hpp new file mode 100644 index 0000000000..eb1b3837c9 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/CALayer.hpp @@ -0,0 +1,764 @@ +#ifndef CALayer_hpp +#define CALayer_hpp + +#include "Lottie/Public/Primitives/Color.hpp" +#include "Lottie/Public/Primitives/Vectors.hpp" +#include "Lottie/Public/Primitives/CGPath.hpp" +#include "Lottie/Private/Model/ShapeItems/Fill.hpp" +#include "Lottie/Private/Model/Layers/LayerModel.hpp" +#include "Lottie/Public/Primitives/DrawingAttributes.hpp" +#include "Lottie/Private/Model/ShapeItems/GradientFill.hpp" + +#include +#include +#include + +namespace lottie { + +enum class CGBlendMode { + Normal, + DestinationIn, + DestinationOut +}; + +class CGImage { +public: + virtual ~CGImage() = default; +}; + +class CGGradient { +public: + CGGradient(std::vector const &colors, std::vector const &locations) : + _colors(colors), + _locations(locations) { + assert(_colors.size() == _locations.size()); + } + + std::vector const &colors() const { + return _colors; + } + + std::vector const &locations() const { + return _locations; + } + +private: + std::vector _colors; + std::vector _locations; +}; + +class CGContext { +public: + virtual ~CGContext() = default; + + virtual int width() const = 0; + virtual int height() const = 0; + + virtual std::shared_ptr makeLayer(int width, int height) = 0; + + virtual void saveState() = 0; + virtual void restoreState() = 0; + + virtual void fillPath(std::shared_ptr const &path, FillRule fillRule, Color const &color) = 0; + virtual void linearGradientFillPath(std::shared_ptr const &path, FillRule fillRule, CGGradient const &gradient, Vector2D const &start, Vector2D const &end) = 0; + virtual void radialGradientFillPath(std::shared_ptr const &path, FillRule fillRule, CGGradient const &gradient, Vector2D const &startCenter, double startRadius, Vector2D const &endCenter, double endRadius) = 0; + + virtual void strokePath(std::shared_ptr const &path, double lineWidth, LineJoin lineJoin, LineCap lineCap, double dashPhase, std::vector const &dashPattern, Color const &color) = 0; + virtual void linearGradientStrokePath(std::shared_ptr const &path, double lineWidth, LineJoin lineJoin, LineCap lineCap, double dashPhase, std::vector const &dashPattern, CGGradient const &gradient, Vector2D const &start, Vector2D const &end) = 0; + virtual void radialGradientStrokePath(std::shared_ptr const &path, double lineWidth, LineJoin lineJoin, LineCap lineCap, double dashPhase, std::vector const &dashPattern, CGGradient const &gradient, Vector2D const &startCenter, double startRadius, Vector2D const &endCenter, double endRadius) = 0; + + virtual void fill(CGRect const &rect, Color const &fillColor) = 0; + virtual void setBlendMode(CGBlendMode blendMode) = 0; + + virtual void setAlpha(double alpha) = 0; + + virtual void concatenate(CATransform3D const &transform) = 0; + + virtual void draw(std::shared_ptr const &other, CGRect const &rect) = 0; +}; + +class RenderableItem { +public: + enum class Type { + Shape, + GradientFill + }; + +public: + RenderableItem() { + } + + virtual ~RenderableItem() = default; + + virtual Type type() const = 0; + virtual CGRect boundingRect() const = 0; + + virtual bool isEqual(std::shared_ptr rhs) const = 0; +}; + +class ShapeRenderableItem: public RenderableItem { +public: + struct Fill { + Color color; + FillRule rule; + + Fill(Color color_, FillRule rule_) : + color(color_), rule(rule_) { + } + + bool operator==(Fill const &rhs) const { + if (color != rhs.color) { + return false; + } + if (rule != rhs.rule) { + return false; + } + return true; + } + + bool operator!=(Fill const &rhs) const { + return !(*this == rhs); + } + }; + + struct Stroke { + Color color; + double lineWidth = 0.0; + LineJoin lineJoin = LineJoin::Round; + LineCap lineCap = LineCap::Square; + double dashPhase = 0.0; + std::vector dashPattern; + + Stroke( + Color color_, + double lineWidth_, + LineJoin lineJoin_, + LineCap lineCap_, + double dashPhase_, + std::vector dashPattern_ + ) : + color(color_), + lineWidth(lineWidth_), + lineJoin(lineJoin_), + lineCap(lineCap_), + dashPhase(dashPhase_), + dashPattern(dashPattern_) { + } + + bool operator==(Stroke const &rhs) const { + if (color != rhs.color) { + return false; + } + if (lineWidth != rhs.lineWidth) { + return false; + } + if (lineJoin != rhs.lineJoin) { + return false; + } + if (lineCap != rhs.lineCap) { + return false; + } + if (dashPhase != rhs.dashPhase) { + return false; + } + if (dashPattern != rhs.dashPattern) { + return false; + } + return true; + } + + bool operator!=(Stroke const &rhs) const { + return !(*this == rhs); + } + }; + +public: + ShapeRenderableItem( + std::shared_ptr path_, + std::optional const &fill_, + std::optional const &stroke_ + ) : + path(path_), + fill(fill_), + stroke(stroke_) { + } + + virtual Type type() const override { + return Type::Shape; + } + + virtual CGRect boundingRect() const override { + if (path) { + CGRect shapeBounds = path->boundingBox(); + if (stroke) { + shapeBounds = shapeBounds.insetBy(-stroke->lineWidth / 2.0, -stroke->lineWidth / 2.0); + } + return shapeBounds; + } else { + return CGRect(0.0, 0.0, 0.0, 0.0); + } + } + + virtual bool isEqual(std::shared_ptr rhs) const override { + if (rhs->type() != type()) { + return false; + } + ShapeRenderableItem *other = (ShapeRenderableItem *)rhs.get(); + if ((path == nullptr) != (other->path == nullptr)) { + return false; + } else if (path) { + if (!path->isEqual(other->path.get())) { + return false; + } + } + if (fill != other->fill) { + return false; + } + if (stroke != other->stroke) { + return false; + } + return false; + } + +public: + std::shared_ptr path; + std::optional fill; + std::optional stroke; +}; + +class GradientFillRenderableItem: public RenderableItem { +public: + GradientFillRenderableItem( + std::shared_ptr path_, + FillRule pathFillRule_, + GradientType gradientType_, + std::vector const &colors_, + std::vector const &locations_, + Vector2D const &start_, + Vector2D const &end_, + CGRect bounds_ + ) : + path(path_), + pathFillRule(pathFillRule_), + gradientType(gradientType_), + colors(colors_), + locations(locations_), + start(start_), + end(end_), + bounds(bounds_) { + } + + virtual Type type() const override { + return Type::GradientFill; + } + + virtual CGRect boundingRect() const override { + return bounds; + } + + virtual bool isEqual(std::shared_ptr rhs) const override { + if (rhs->type() != type()) { + return false; + } + GradientFillRenderableItem *other = (GradientFillRenderableItem *)rhs.get(); + + if (gradientType != other->gradientType) { + return false; + } + if (colors != other->colors) { + return false; + } + if (locations != other->locations) { + return false; + } + if (start != other->start) { + return false; + } + if (end != other->end) { + return false; + } + if (bounds != other->bounds) { + return false; + } + + return true; + } + +public: + std::shared_ptr path; + FillRule pathFillRule; + GradientType gradientType; + std::vector colors; + std::vector locations; + Vector2D start; + Vector2D end; + CGRect bounds; +}; + +class RenderTreeNodeContent { +public: + enum class ShadingType { + Solid, + Gradient + }; + + class Shading { + public: + Shading() { + } + + virtual ~Shading() = default; + + virtual ShadingType type() const = 0; + }; + + class SolidShading: public Shading { + public: + SolidShading(Color const &color_, double opacity_) : + color(color_), + opacity(opacity_) { + } + + virtual ShadingType type() const override { + return ShadingType::Solid; + } + + public: + Color color; + double opacity = 0.0; + }; + + class GradientShading: public Shading { + public: + GradientShading( + double opacity_, + GradientType gradientType_, + std::vector const &colors_, + std::vector const &locations_, + Vector2D const &start_, + Vector2D const &end_ + ) : + opacity(opacity_), + gradientType(gradientType_), + colors(colors_), + locations(locations_), + start(start_), + end(end_) { + } + + virtual ShadingType type() const override { + return ShadingType::Gradient; + } + + public: + double opacity = 0.0; + GradientType gradientType; + std::vector colors; + std::vector locations; + Vector2D start; + Vector2D end; + }; + + struct Stroke { + std::shared_ptr shading; + double lineWidth = 0.0; + LineJoin lineJoin = LineJoin::Round; + LineCap lineCap = LineCap::Square; + double miterLimit = 4.0; + double dashPhase = 0.0; + std::vector dashPattern; + + Stroke( + std::shared_ptr shading_, + double lineWidth_, + LineJoin lineJoin_, + LineCap lineCap_, + double miterLimit_, + double dashPhase_, + std::vector dashPattern_ + ) : + shading(shading_), + lineWidth(lineWidth_), + lineJoin(lineJoin_), + lineCap(lineCap_), + miterLimit(miterLimit_), + dashPhase(dashPhase_), + dashPattern(dashPattern_) { + } + }; + + struct Fill { + std::shared_ptr shading; + FillRule rule; + + Fill( + std::shared_ptr shading_, + FillRule rule_ + ) : + shading(shading_), + rule(rule_) { + } + }; + +public: + RenderTreeNodeContent( + std::vector paths_, + std::shared_ptr stroke_, + std::shared_ptr fill_ + ) : + paths(paths_), + stroke(stroke_), + fill(fill_) { + } + +public: + std::vector paths; + std::shared_ptr stroke; + std::shared_ptr fill; +}; + +class RenderTreeNode { +public: + RenderTreeNode( + CGRect bounds_, + Vector2D position_, + CATransform3D transform_, + double alpha_, + bool masksToBounds_, + bool isHidden_, + std::shared_ptr content_, + std::vector> subnodes_, + std::shared_ptr mask_, + bool invertMask_ + ) : + _bounds(bounds_), + _position(position_), + _transform(transform_), + _alpha(alpha_), + _masksToBounds(masksToBounds_), + _isHidden(isHidden_), + _content(content_), + _subnodes(subnodes_), + _mask(mask_), + _invertMask(invertMask_) { + } + + ~RenderTreeNode() { + } + +public: + CGRect const &bounds() const { + return _bounds; + } + + Vector2D const &position() const { + return _position; + } + + CATransform3D const &transform() const { + return _transform; + } + + double alpha() const { + return _alpha; + } + + bool masksToBounds() const { + return _masksToBounds; + } + + bool isHidden() const { + return _isHidden; + } + + std::shared_ptr const &content() const { + return _content; + } + + std::vector> const &subnodes() const { + return _subnodes; + } + + std::shared_ptr const &mask() const { + return _mask; + } + + bool invertMask() const { + return _invertMask; + } + +public: + CGRect _bounds; + Vector2D _position; + CATransform3D _transform = CATransform3D::identity(); + double _alpha = 1.0; + bool _masksToBounds = false; + bool _isHidden = false; + std::shared_ptr _content; + std::vector> _subnodes; + std::shared_ptr _mask; + bool _invertMask = false; +}; + +class CALayer: public std::enable_shared_from_this { +public: + CALayer() { + } + + virtual ~CALayer() = default; + + void addSublayer(std::shared_ptr layer) { + if (layer->_superlayer) { + layer->_superlayer->removeSublayer(layer.get()); + } + layer->_superlayer = this; + _sublayers.push_back(layer); + } + + void insertSublayer(std::shared_ptr layer, int index) { + if (layer->_superlayer) { + layer->_superlayer->removeSublayer(layer.get()); + } + layer->_superlayer = this; + _sublayers.insert(_sublayers.begin() + index, layer); + } + + void removeFromSuperlayer() { + if (_superlayer) { + _superlayer->removeSublayer(this); + } + } + + bool needsDisplay() const { + return _needsDisplay; + } + void setNeedsDisplay(bool needsDisplay) { + _needsDisplay = true; + } + + virtual bool implementsDraw() const { + return false; + } + + virtual bool isInvertedMatte() const { + return false; + } + + virtual void draw(std::shared_ptr const &context) { + } + + virtual std::shared_ptr renderableItem() { + return nullptr; + } + + bool isHidden() const { + return _isHidden; + } + void setIsHidden(bool isHidden) { + _isHidden = isHidden; + } + + float opacity() const { + return _opacity; + } + void setOpacity(float opacity) { + _opacity = opacity; + } + + Vector2D const &position() const { + return _position; + } + void setPosition(Vector2D const &position) { + _position = position; + } + + CGRect const &bounds() const { + return _bounds; + } + void setBounds(CGRect const &bounds) { + _bounds = bounds; + } + + virtual CGRect effectiveBounds() const { + return bounds(); + } + + CATransform3D const &transform() const { + return _transform; + } + void setTransform(CATransform3D const &transform) { + _transform = transform; + } + + std::shared_ptr const &mask() const { + return _mask; + } + void setMask(std::shared_ptr mask) { + _mask = mask; + } + + bool masksToBounds() const { + return _masksToBounds; + } + void setMasksToBounds(bool masksToBounds) { + _masksToBounds = masksToBounds; + } + + std::vector> const &sublayers() const { + return _sublayers; + } + + std::optional const &compositingFilter() const { + return _compositingFilter; + } + void setCompositingFilter(std::optional const &compositingFilter) { + _compositingFilter = compositingFilter; + } + + std::shared_ptr const &contents() const { + return _contents; + } + void setContents(std::shared_ptr contents) { + _contents = contents; + } + +protected: + template + std::shared_ptr shared_from_base() { + return std::static_pointer_cast(shared_from_this()); + } + +private: + void removeSublayer(CALayer *layer) { + for (auto it = _sublayers.begin(); it != _sublayers.end(); it++) { + if (it->get() == layer) { + layer->_superlayer = nullptr; + _sublayers.erase(it); + break; + } + } + } + +private: + CALayer *_superlayer = nullptr; + std::vector> _sublayers; + bool _needsDisplay = false; + bool _isHidden = false; + float _opacity = 1.0; + Vector2D _position = Vector2D(0.0, 0.0); + CGRect _bounds = CGRect(0.0, 0.0, 0.0, 0.0); + CATransform3D _transform = CATransform3D::identity(); + std::shared_ptr _mask; + bool _masksToBounds = false; + std::optional _compositingFilter; + std::shared_ptr _contents; +}; + +class CAShapeLayer: public CALayer { +public: + CAShapeLayer() { + } + + virtual ~CAShapeLayer() = default; + + std::optional const &strokeColor() { + return _strokeColor; + } + void setStrokeColor(std::optional const &strokeColor) { + _strokeColor = strokeColor; + } + + std::optional const &fillColor() { + return _fillColor; + } + void setFillColor(std::optional const &fillColor) { + _fillColor = fillColor; + } + + FillRule fillRule() { + return _fillRule; + } + void setFillRule(FillRule fillRule) { + _fillRule = fillRule; + } + + std::shared_ptr const &path() const { + return _path; + } + void setPath(std::shared_ptr const &path) { + _path = path; + } + + double lineWidth() const { + return _lineWidth; + } + void setLineWidth(double lineWidth) { + _lineWidth = lineWidth; + } + + LineJoin lineJoin() const { + return _lineJoin; + } + void setLineJoin(LineJoin lineJoin) { + _lineJoin = lineJoin; + } + + LineCap lineCap() const { + return _lineCap; + } + void setLineCap(LineCap lineCap) { + _lineCap = lineCap; + } + + double lineDashPhase() const { + return _lineDashPhase; + } + void setLineDashPhase(double lineDashPhase) { + _lineDashPhase = lineDashPhase; + } + + std::vector const &dashPattern() const { + return _dashPattern; + } + void setDashPattern(std::vector const &dashPattern) { + _dashPattern = dashPattern; + } + + virtual CGRect effectiveBounds() const override { + if (_path) { + CGRect boundingBox = _path->boundingBox(); + if (_strokeColor) { + boundingBox.x -= _lineWidth / 2.0; + boundingBox.y -= _lineWidth / 2.0; + boundingBox.width += _lineWidth; + boundingBox.height += _lineWidth; + } + return boundingBox; + } else { + return CGRect(0.0, 0.0, 0.0, 0.0); + } + } + + /*virtual bool implementsDraw() const override { + return true; + } + + virtual void draw(std::shared_ptr const &context) override;*/ + + std::shared_ptr renderableItem() override; + +private: + std::optional _strokeColor; + std::optional _fillColor = Color(0.0, 0.0, 0.0, 1.0); + FillRule _fillRule = FillRule::NonZeroWinding; + std::shared_ptr _path; + double _lineWidth = 1.0; + LineJoin _lineJoin = LineJoin::Miter; + LineCap _lineCap = LineCap::Butt; + double _lineDashPhase = 0.0; + std::vector _dashPattern; +}; + +} + +#endif /* CALayer_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/CALayer.mm b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/CALayer.mm new file mode 100644 index 0000000000..1b7d55db3d --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/CALayer.mm @@ -0,0 +1,497 @@ +#include "CALayer.hpp" +#include "Lottie/Public/Primitives/CALayerCocoa.h" + +#include "Lottie/Public/Primitives/VectorsCocoa.h" +#include "Lottie/Public/Primitives/CGPathCocoa.h" + +#import + +namespace lottie { + +namespace { + +int alignUp(int size, int align) { + assert(((align - 1) & align) == 0); + + int alignmentMask = align - 1; + return (size + alignmentMask) & ~alignmentMask; +} + +} + +CGImageImpl::CGImageImpl(::CGImageRef image) { + _image = CGImageRetain(image); +} + +CGImageImpl::~CGImageImpl() { + CFRelease(_image); +} + +::CGImageRef CGImageImpl::nativeImage() const { + return _image; +} + + +CGContextImpl::CGContextImpl(int width, int height) { + _width = width; + _height = height; + _bytesPerRow = alignUp(width * 4, 16); + _backingData.resize(_bytesPerRow * _height); + memset(_backingData.data(), 0, _backingData.size()); + + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); + + CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedFirst; + _context = CGBitmapContextCreate(_backingData.data(), _width, _height, 8, _bytesPerRow, colorSpace, bitmapInfo); + + CGContextClearRect(_context, CGRectMake(0.0, 0.0, _width, _height)); + + //CGContextSetInterpolationQuality(_context, kCGInterpolationLow); + //CGContextSetAllowsAntialiasing(_context, true); + //CGContextSetShouldAntialias(_context, true); + + CFRelease(colorSpace); + + _topContext = CGContextRetain(_context); +} + +CGContextImpl::CGContextImpl(CGContextRef context, int width, int height) { + _topContext = CGContextRetain(context); + _layer = CGLayerCreateWithContext(context, CGSizeMake(width, height), nil); + _context = CGContextRetain(CGLayerGetContext(_layer)); + _width = width; + _height = height; +} + +CGContextImpl::~CGContextImpl() { + CFRelease(_context); + if (_topContext) { + CFRelease(_topContext); + } + if (_layer) { + CFRelease(_layer); + } +} + +int CGContextImpl::width() const { + return _width; +} + +int CGContextImpl::height() const { + return _height; +} + +std::shared_ptr CGContextImpl::makeLayer(int width, int height) { + return std::make_shared(_topContext, width, height); +} + +void CGContextImpl::saveState() { + CGContextSaveGState(_context); +} + +void CGContextImpl::restoreState() { + CGContextRestoreGState(_context); +} + +void CGContextImpl::fillPath(std::shared_ptr const &path, FillRule fillRule, Color const &color) { + CGContextBeginPath(_context); + CGPathCocoaImpl::withNativePath(path, [context = _context](CGPathRef nativePath) { + CGContextAddPath(context, nativePath); + }); + + CGFloat components[4] = { color.r, color.g, color.b, color.a }; + CGColorRef nativeColor = CGColorCreate(CGBitmapContextGetColorSpace(_topContext), components); + CGContextSetFillColorWithColor(_context, nativeColor); + CFRelease(nativeColor); + + switch (fillRule) { + case FillRule::EvenOdd: { + CGContextEOFillPath(_context); + break; + } + default: { + CGContextFillPath(_context); + break; + } + } +} + +void CGContextImpl::linearGradientFillPath(std::shared_ptr const &path, FillRule fillRule, CGGradient const &gradient, Vector2D const &start, Vector2D const &end) { + CGContextSaveGState(_context); + CGContextBeginPath(_context); + CGPathCocoaImpl::withNativePath(path, [context = _context](CGPathRef nativePath) { + CGContextAddPath(context, nativePath); + }); + + switch (fillRule) { + case FillRule::EvenOdd: { + CGContextEOClip(_context); + break; + } + default: { + CGContextClip(_context); + break; + } + } + + std::vector components; + components.reserve(gradient.colors().size() + 4); + + for (const auto &color : gradient.colors()) { + components.push_back(color.r); + components.push_back(color.g); + components.push_back(color.b); + components.push_back(color.a); + } + + assert(gradient.colors().size() == gradient.locations().size()); + + CGGradientRef nativeGradient = CGGradientCreateWithColorComponents(CGBitmapContextGetColorSpace(_topContext), components.data(), gradient.locations().data(), gradient.locations().size()); + if (nativeGradient) { + CGContextDrawLinearGradient(_context, nativeGradient, CGPointMake(start.x, start.y), CGPointMake(end.x, end.y), kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation); + CFRelease(nativeGradient); + } + + CGContextResetClip(_context); + CGContextRestoreGState(_context); +} + +void CGContextImpl::radialGradientFillPath(std::shared_ptr const &path, FillRule fillRule, CGGradient const &gradient, Vector2D const &startCenter, double startRadius, Vector2D const &endCenter, double endRadius) { + CGContextSaveGState(_context); + CGContextBeginPath(_context); + CGPathCocoaImpl::withNativePath(path, [context = _context](CGPathRef nativePath) { + CGContextAddPath(context, nativePath); + }); + + switch (fillRule) { + case FillRule::EvenOdd: { + CGContextEOClip(_context); + break; + } + default: { + CGContextClip(_context); + break; + } + } + + std::vector components; + components.reserve(gradient.colors().size() + 4); + + for (const auto &color : gradient.colors()) { + components.push_back(color.r); + components.push_back(color.g); + components.push_back(color.b); + components.push_back(color.a); + } + + assert(gradient.colors().size() == gradient.locations().size()); + + CGGradientRef nativeGradient = CGGradientCreateWithColorComponents(CGBitmapContextGetColorSpace(_topContext), components.data(), gradient.locations().data(), gradient.locations().size()); + if (nativeGradient) { + CGContextDrawRadialGradient(_context, nativeGradient, CGPointMake(startCenter.x, startCenter.y), startRadius, CGPointMake(endCenter.x, endCenter.y), endRadius, kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation); + CFRelease(nativeGradient); + } + + CGContextResetClip(_context); + CGContextRestoreGState(_context); +} + +void CGContextImpl::strokePath(std::shared_ptr const &path, double lineWidth, LineJoin lineJoin, LineCap lineCap, double dashPhase, std::vector const &dashPattern, Color const &color) { + CGContextBeginPath(_context); + CGPathCocoaImpl::withNativePath(path, [context = _context](CGPathRef nativePath) { + CGContextAddPath(context, nativePath); + }); + + CGFloat components[4] = { color.r, color.g, color.b, color.a }; + CGColorRef nativeColor = CGColorCreate(CGBitmapContextGetColorSpace(_topContext), components); + CGContextSetStrokeColorWithColor(_context, nativeColor); + CFRelease(nativeColor); + + CGContextSetLineWidth(_context, lineWidth); + + switch (lineJoin) { + case LineJoin::Miter: { + CGContextSetLineJoin(_context, kCGLineJoinMiter); + break; + } + case LineJoin::Round: { + CGContextSetLineJoin(_context, kCGLineJoinRound); + break; + } + case LineJoin::Bevel: { + CGContextSetLineJoin(_context, kCGLineJoinBevel); + break; + } + default: { + CGContextSetLineJoin(_context, kCGLineJoinBevel); + break; + } + } + + switch (lineCap) { + case LineCap::Butt: { + CGContextSetLineCap(_context, kCGLineCapButt); + break; + } + case LineCap::Round: { + CGContextSetLineCap(_context, kCGLineCapRound); + break; + } + case LineCap::Square: { + CGContextSetLineCap(_context, kCGLineCapSquare); + break; + } + default: { + CGContextSetLineCap(_context, kCGLineCapSquare); + break; + } + } + + if (!dashPattern.empty()) { + CGContextSetLineDash(_context, dashPhase, dashPattern.data(), dashPattern.size()); + } + CGContextStrokePath(_context); +} + +void CGContextImpl::linearGradientStrokePath(std::shared_ptr const &path, double lineWidth, LineJoin lineJoin, LineCap lineCap, double dashPhase, std::vector const &dashPattern, CGGradient const &gradient, Vector2D const &start, Vector2D const &end) { + CGContextSaveGState(_context); + CGContextBeginPath(_context); + CGPathCocoaImpl::withNativePath(path, [context = _context](CGPathRef nativePath) { + CGContextAddPath(context, nativePath); + }); + + CGContextSetLineWidth(_context, lineWidth); + + switch (lineJoin) { + case LineJoin::Miter: { + CGContextSetLineJoin(_context, kCGLineJoinMiter); + break; + } + case LineJoin::Round: { + CGContextSetLineJoin(_context, kCGLineJoinRound); + break; + } + case LineJoin::Bevel: { + CGContextSetLineJoin(_context, kCGLineJoinBevel); + break; + } + default: { + CGContextSetLineJoin(_context, kCGLineJoinBevel); + break; + } + } + + switch (lineCap) { + case LineCap::Butt: { + CGContextSetLineCap(_context, kCGLineCapButt); + break; + } + case LineCap::Round: { + CGContextSetLineCap(_context, kCGLineCapRound); + break; + } + case LineCap::Square: { + CGContextSetLineCap(_context, kCGLineCapSquare); + break; + } + default: { + CGContextSetLineCap(_context, kCGLineCapSquare); + break; + } + } + + if (!dashPattern.empty()) { + CGContextSetLineDash(_context, dashPhase, dashPattern.data(), dashPattern.size()); + } + + CGContextReplacePathWithStrokedPath(_context); + CGContextClip(_context); + + std::vector components; + components.reserve(gradient.colors().size() + 4); + + for (const auto &color : gradient.colors()) { + components.push_back(color.r); + components.push_back(color.g); + components.push_back(color.b); + components.push_back(color.a); + } + + assert(gradient.colors().size() == gradient.locations().size()); + + CGGradientRef nativeGradient = CGGradientCreateWithColorComponents(CGBitmapContextGetColorSpace(_topContext), components.data(), gradient.locations().data(), gradient.locations().size()); + if (nativeGradient) { + CGContextDrawLinearGradient(_context, nativeGradient, CGPointMake(start.x, start.y), CGPointMake(end.x, end.y), kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation); + CFRelease(nativeGradient); + } + + CGContextResetClip(_context); + CGContextRestoreGState(_context); +} + +void CGContextImpl::radialGradientStrokePath(std::shared_ptr const &path, double lineWidth, LineJoin lineJoin, LineCap lineCap, double dashPhase, std::vector const &dashPattern, CGGradient const &gradient, Vector2D const &startCenter, double startRadius, Vector2D const &endCenter, double endRadius) { + CGContextSaveGState(_context); + CGContextBeginPath(_context); + CGPathCocoaImpl::withNativePath(path, [context = _context](CGPathRef nativePath) { + CGContextAddPath(context, nativePath); + }); + + CGContextSetLineWidth(_context, lineWidth); + + switch (lineJoin) { + case LineJoin::Miter: { + CGContextSetLineJoin(_context, kCGLineJoinMiter); + break; + } + case LineJoin::Round: { + CGContextSetLineJoin(_context, kCGLineJoinRound); + break; + } + case LineJoin::Bevel: { + CGContextSetLineJoin(_context, kCGLineJoinBevel); + break; + } + default: { + CGContextSetLineJoin(_context, kCGLineJoinBevel); + break; + } + } + + switch (lineCap) { + case LineCap::Butt: { + CGContextSetLineCap(_context, kCGLineCapButt); + break; + } + case LineCap::Round: { + CGContextSetLineCap(_context, kCGLineCapRound); + break; + } + case LineCap::Square: { + CGContextSetLineCap(_context, kCGLineCapSquare); + break; + } + default: { + CGContextSetLineCap(_context, kCGLineCapSquare); + break; + } + } + + if (!dashPattern.empty()) { + CGContextSetLineDash(_context, dashPhase, dashPattern.data(), dashPattern.size()); + } + + CGContextReplacePathWithStrokedPath(_context); + CGContextClip(_context); + + std::vector components; + components.reserve(gradient.colors().size() + 4); + + for (const auto &color : gradient.colors()) { + components.push_back(color.r); + components.push_back(color.g); + components.push_back(color.b); + components.push_back(color.a); + } + + assert(gradient.colors().size() == gradient.locations().size()); + + CGGradientRef nativeGradient = CGGradientCreateWithColorComponents(CGBitmapContextGetColorSpace(_topContext), components.data(), gradient.locations().data(), gradient.locations().size()); + if (nativeGradient) { + CGContextDrawRadialGradient(_context, nativeGradient, CGPointMake(startCenter.x, startCenter.y), startRadius, CGPointMake(endCenter.x, endCenter.y), endRadius, kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation); + CFRelease(nativeGradient); + } + + CGContextResetClip(_context); + CGContextRestoreGState(_context); +} + +void CGContextImpl::fill(CGRect const &rect, Color const &fillColor) { + CGFloat components[4] = { fillColor.r, fillColor.g, fillColor.b, fillColor.a }; + CGColorRef nativeColor = CGColorCreate(CGBitmapContextGetColorSpace(_topContext), components); + CGContextSetFillColorWithColor(_context, nativeColor); + CFRelease(nativeColor); + + CGContextFillRect(_context, CGRectMake(rect.x, rect.y, rect.width, rect.height)); +} + +void CGContextImpl::setBlendMode(CGBlendMode blendMode) { + ::CGBlendMode nativeMode = kCGBlendModeNormal; + switch (blendMode) { + case CGBlendMode::Normal: { + nativeMode = kCGBlendModeNormal; + break; + } + case CGBlendMode::DestinationIn: { + nativeMode = kCGBlendModeDestinationIn; + break; + } + case CGBlendMode::DestinationOut: { + nativeMode = kCGBlendModeDestinationOut; + break; + } + } + CGContextSetBlendMode(_context, nativeMode); +} + +void CGContextImpl::setAlpha(double alpha) { + CGContextSetAlpha(_context, alpha); +} + +void CGContextImpl::concatenate(CATransform3D const &transform) { + CGContextConcatCTM(_context, CATransform3DGetAffineTransform(nativeTransform(transform))); +} + +std::shared_ptr CGContextImpl::makeImage() const { + ::CGImageRef nativeImage = CGBitmapContextCreateImage(_context); + if (nativeImage) { + auto image = std::make_shared(nativeImage); + CFRelease(nativeImage); + return image; + } else { + return nil; + } +} + +void CGContextImpl::draw(std::shared_ptr const &other, CGRect const &rect) { + CGContextImpl *impl = (CGContextImpl *)other.get(); + if (impl->_layer) { + CGContextDrawLayerInRect(_context, CGRectMake(rect.x, rect.y, rect.width, rect.height), impl->_layer); + } else { + auto image = impl->makeImage(); + CGContextDrawImage(_context, CGRectMake(rect.x, rect.y, rect.width, rect.height), ((CGImageImpl *)image.get())->nativeImage()); + } +} + +std::shared_ptr CAShapeLayer::renderableItem() { + if (!_path) { + return nullptr; + } + + std::optional fill; + if (_fillColor) { + fill = ShapeRenderableItem::Fill( + _fillColor.value(), + _fillRule + ); + } + + std::optional stroke; + if (_strokeColor) { + stroke = ShapeRenderableItem::Stroke( + _strokeColor.value(), + _lineWidth, + _lineJoin, + _lineCap, + _lineDashPhase, + _dashPattern + ); + } + + return std::make_shared( + _path, + fill, + stroke + ); +} + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/CALayerCocoa.h b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/CALayerCocoa.h new file mode 100644 index 0000000000..c04bbfcffe --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/CALayerCocoa.h @@ -0,0 +1,74 @@ +#ifndef CALayerCocoa_h +#define CALayerCocoa_h + +#import + +#include "Lottie/Public/Primitives/CALayer.hpp" + +namespace lottie { + +class CGImageImpl: public CGImage { +public: + CGImageImpl(::CGImageRef image); + virtual ~CGImageImpl(); + ::CGImageRef nativeImage() const; + +private: + CGImageRef _image = nil; +}; + +class CGContextImpl: public CGContext { +public: + CGContextImpl(int width, int height); + CGContextImpl(CGContextRef context, int width, int height); + virtual ~CGContextImpl(); + + virtual int width() const override; + virtual int height() const override; + + std::shared_ptr makeLayer(int width, int height) override; + + virtual void saveState() override; + virtual void restoreState() override; + + virtual void fillPath(std::shared_ptr const &path, FillRule fillRule, Color const &color) override; + virtual void linearGradientFillPath(std::shared_ptr const &path, FillRule fillRule, CGGradient const &gradient, Vector2D const &start, Vector2D const &end) override; + virtual void radialGradientFillPath(std::shared_ptr const &path, FillRule fillRule, CGGradient const &gradient, Vector2D const &startCenter, double startRadius, Vector2D const &endCenter, double endRadius) override; + + virtual void strokePath(std::shared_ptr const &path, double lineWidth, LineJoin lineJoin, LineCap lineCap, double dashPhase, std::vector const &dashPattern, Color const &color) override; + virtual void linearGradientStrokePath(std::shared_ptr const &path, double lineWidth, LineJoin lineJoin, LineCap lineCap, double dashPhase, std::vector const &dashPattern, CGGradient const &gradient, Vector2D const &start, Vector2D const &end) override; + virtual void radialGradientStrokePath(std::shared_ptr const &path, double lineWidth, LineJoin lineJoin, LineCap lineCap, double dashPhase, std::vector const &dashPattern, CGGradient const &gradient, Vector2D const &startCenter, double startRadius, Vector2D const &endCenter, double endRadius) override; + + virtual void fill(CGRect const &rect, Color const &fillColor) override; + virtual void setBlendMode(CGBlendMode blendMode) override; + virtual void setAlpha(double alpha) override; + virtual void concatenate(CATransform3D const &transform) override; + + virtual std::shared_ptr makeImage() const; + virtual void draw(std::shared_ptr const &other, CGRect const &rect) override; + + CGContextRef nativeContext() const { + return _context; + } + + std::vector &backingData() { + return _backingData; + } + + int bytesPerRow() { + return _bytesPerRow; + } + +private: + int _width = 0; + int _height = 0; + int _bytesPerRow = 0; + std::vector _backingData; + CGContextRef _context = nil; + CGContextRef _topContext = nil; + CGLayerRef _layer = nil; +}; + +} + +#endif /* CALayerCocoa_h */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/CGPath.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/CGPath.cpp new file mode 100644 index 0000000000..7b352cf13b --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/CGPath.cpp @@ -0,0 +1,194 @@ +#include "CGPath.hpp" + +#include + +namespace lottie { + +namespace { + +void addPointToBoundingRect(bool *isFirst, CGRect *rect, Vector2D const *point) { + if (*isFirst) { + *isFirst = false; + + rect->x = point->x; + rect->y = point->y; + rect->width = 0.0; + rect->height = 0.0; + + return; + } + if (point->x > rect->x + rect->width) { + rect->width = point->x - rect->x; + } + if (point->y > rect->y + rect->height) { + rect->height = point->y - rect->y; + } + if (point->x < rect->x) { + rect->width += rect->x - point->x; + rect->x = point->x; + } + if (point->y < rect->y) { + rect->height += rect->y - point->y; + rect->y = point->y; + } +} + +} + +Vector2D transformVector(Vector2D const &v, CATransform3D const &m) { + return Vector2D( + m.m11 * v.x + m.m21 * v.y + m.m41 * 1.0, + m.m12 * v.x + m.m22 * v.y + m.m42 * 1.0 + ); +} + +class CGPathImpl: public CGPath { +public: + CGPathImpl(); + virtual ~CGPathImpl(); + + virtual CGRect boundingBox() const override; + + virtual bool empty() const override; + + virtual std::shared_ptr copyUsingTransform(CATransform3D const &transform) const override; + + virtual void addLineTo(Vector2D const &point) override; + virtual void addCurveTo(Vector2D const &point, Vector2D const &control1, Vector2D const &control2) override; + virtual void moveTo(Vector2D const &point) override; + virtual void closeSubpath() override; + virtual void addRect(CGRect const &rect) override; + virtual void addPath(std::shared_ptr const &path) override; + virtual bool isEqual(CGPath *other) const override; + virtual void enumerate(std::function) override; + +private: + std::vector _items; +}; + +CGPathImpl::CGPathImpl() { +} + +CGPathImpl::~CGPathImpl() { +} + +CGRect CGPathImpl::boundingBox() const { + bool isFirst = true; + CGRect result(0.0, 0.0, 0.0, 0.0); + + for (const auto &item : _items) { + switch (item.type) { + case CGPathItem::Type::MoveTo: { + addPointToBoundingRect(&isFirst, &result, &item.points[0]); + break; + } + case CGPathItem::Type::LineTo: { + addPointToBoundingRect(&isFirst, &result, &item.points[0]); + break; + } + case CGPathItem::Type::CurveTo: { + addPointToBoundingRect(&isFirst, &result, &item.points[0]); + addPointToBoundingRect(&isFirst, &result, &item.points[1]); + addPointToBoundingRect(&isFirst, &result, &item.points[2]); + break; + } + case CGPathItem::Type::Close: { + break; + } + default: { + break; + } + } + } + + return result; +} + +bool CGPathImpl::empty() const { + return _items.empty(); +} + +std::shared_ptr CGPathImpl::copyUsingTransform(CATransform3D const &transform) const { + auto result = std::make_shared(); + + if (transform == CATransform3D::identity()) { + result->_items = _items; + return result; + } + + result->_items.reserve(_items.capacity()); + for (auto &sourceItem : _items) { + CGPathItem &item = result->_items.emplace_back(sourceItem.type); + item.points[0] = transformVector(sourceItem.points[0], transform); + item.points[1] = transformVector(sourceItem.points[1], transform); + item.points[2] = transformVector(sourceItem.points[2], transform); + } + + return result; +} + +void CGPathImpl::addLineTo(Vector2D const &point) { + CGPathItem &item = _items.emplace_back(CGPathItem::Type::LineTo); + item.points[0] = point; +} + +void CGPathImpl::addCurveTo(Vector2D const &point, Vector2D const &control1, Vector2D const &control2) { + CGPathItem &item = _items.emplace_back(CGPathItem::Type::CurveTo); + item.points[0] = control1; + item.points[1] = control2; + item.points[2] = point; +} + +void CGPathImpl::moveTo(Vector2D const &point) { + CGPathItem &item = _items.emplace_back(CGPathItem::Type::MoveTo); + item.points[0] = point; +} + +void CGPathImpl::closeSubpath() { + _items.emplace_back(CGPathItem::Type::Close); +} + +void CGPathImpl::addRect(CGRect const &rect) { + assert(false); + //CGPathAddRect(_path, nil, ::CGRectMake(rect.x, rect.y, rect.width, rect.height)); +} + +void CGPathImpl::addPath(std::shared_ptr const &path) { + if (_items.size() == 0) { + _items = std::static_pointer_cast(path)->_items; + } else { + size_t totalItemCount = _items.size() + std::static_pointer_cast(path)->_items.size(); + if (_items.capacity() < totalItemCount) { + _items.reserve(totalItemCount); + } + for (const auto &item : std::static_pointer_cast(path)->_items) { + _items.push_back(item); + } + } +} + +bool CGPathImpl::isEqual(CGPath *other) const { + if (_items.size() != ((CGPathImpl *)other)->_items.size()) { + return false; + } + + for (size_t i = 0; i < _items.size(); i++) { + if (_items[i] != ((CGPathImpl *)other)->_items[i]) { + return false; + } + } + + return true; +} + +void CGPathImpl::enumerate(std::function f) { + for (const auto &item : _items) { + f(item); + } +} + +std::shared_ptr CGPath::makePath() { + return std::static_pointer_cast(std::make_shared()); +} + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/CGPath.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/CGPath.hpp new file mode 100644 index 0000000000..76462832a1 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/CGPath.hpp @@ -0,0 +1,75 @@ +#ifndef CGPath_hpp +#define CGPath_hpp + +#include "Lottie/Public/Primitives/Vectors.hpp" + +#include + +namespace lottie { + +struct CGPathItem { + enum class Type { + MoveTo, + LineTo, + CurveTo, + Close + }; + + Type type; + Vector2D points[3] = { Vector2D(0.0, 0.0), Vector2D(0.0, 0.0), Vector2D(0.0, 0.0) }; + + explicit CGPathItem(Type type_) : + type(type_) { + } + + bool operator==(const CGPathItem &rhs) const { + if (type != rhs.type) { + return false; + } + if (points[0] != rhs.points[0]) { + return false; + } + if (points[1] != rhs.points[1]) { + return false; + } + if (points[2] != rhs.points[2]) { + return false; + } + + return true; + } + + bool operator!=(const CGPathItem &rhs) const { + return !(*this == rhs); + } +}; + +class CGPath { +public: + static std::shared_ptr makePath(); + + virtual ~CGPath() = default; + + virtual CGRect boundingBox() const = 0; + + virtual bool empty() const = 0; + + virtual std::shared_ptr copyUsingTransform(CATransform3D const &transform) const = 0; + + virtual void addLineTo(Vector2D const &point) = 0; + virtual void addCurveTo(Vector2D const &point, Vector2D const &control1, Vector2D const &control2) = 0; + virtual void moveTo(Vector2D const &point) = 0; + virtual void closeSubpath() = 0; + virtual void addRect(CGRect const &rect) = 0; + virtual void addPath(std::shared_ptr const &path) = 0; + + virtual void enumerate(std::function) = 0; + + virtual bool isEqual(CGPath *other) const = 0; +}; + +Vector2D transformVector(Vector2D const &v, CATransform3D const &m); + +} + +#endif /* CGPath_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/CGPath.mm b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/CGPath.mm new file mode 100644 index 0000000000..a7798daa9a --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/CGPath.mm @@ -0,0 +1,240 @@ +#include "CGPath.hpp" +#include "Lottie/Public/Primitives/CGPathCocoa.h" + +#import + +namespace { + +void addPointToBoundingRect(bool *isFirst, CGRect *rect, CGPoint *point) { + if (*isFirst) { + *isFirst = false; + + rect->origin.x = point->x; + rect->origin.y = point->y; + rect->size.width = 0.0; + rect->size.height = 0.0; + + return; + } + if (point->x > rect->origin.x + rect->size.width) { + rect->size.width = point->x - rect->origin.x; + } + if (point->y > rect->origin.y + rect->size.height) { + rect->size.height = point->y - rect->origin.y; + } + if (point->x < rect->origin.x) { + rect->size.width += rect->origin.x - point->x; + rect->origin.x = point->x; + } + if (point->y < rect->origin.y) { + rect->size.height += rect->origin.y - point->y; + rect->origin.y = point->y; + } +} + +} + +CGRect calculatePathBoundingBox(CGPathRef path) { + __block CGRect result = CGRectMake(0.0, 0.0, 0.0, 0.0); + __block bool isFirst = true; + + CGPathApplyWithBlock(path, ^(const CGPathElement * _Nonnull element) { + switch (element->type) { + case kCGPathElementMoveToPoint: { + addPointToBoundingRect(&isFirst, &result, &element->points[0]); + break; + } + case kCGPathElementAddLineToPoint: { + addPointToBoundingRect(&isFirst, &result, &element->points[0]); + break; + } + case kCGPathElementAddCurveToPoint: { + addPointToBoundingRect(&isFirst, &result, &element->points[0]); + addPointToBoundingRect(&isFirst, &result, &element->points[1]); + addPointToBoundingRect(&isFirst, &result, &element->points[2]); + break; + } + case kCGPathElementAddQuadCurveToPoint: { + addPointToBoundingRect(&isFirst, &result, &element->points[0]); + addPointToBoundingRect(&isFirst, &result, &element->points[1]); + break; + } + case kCGPathElementCloseSubpath: { + break; + } + } + }); + + return result; +} + +namespace lottie { + +CGPathCocoaImpl::CGPathCocoaImpl() { + _path = CGPathCreateMutable(); +} + +CGPathCocoaImpl::CGPathCocoaImpl(CGMutablePathRef path) { + CFRetain(path); + _path = path; +} + +CGPathCocoaImpl::~CGPathCocoaImpl() { + CGPathRelease(_path); +} + +CGRect CGPathCocoaImpl::boundingBox() const { + auto rect = calculatePathBoundingBox(_path); + return CGRect(rect.origin.x, rect.origin.y, rect.size.width, rect.size.height); +} + +bool CGPathCocoaImpl::empty() const { + return CGPathIsEmpty(_path); +} + +std::shared_ptr CGPathCocoaImpl::copyUsingTransform(CATransform3D const &transform) const { + ::CATransform3D nativeTransform; + nativeTransform.m11 = transform.m11; + nativeTransform.m12 = transform.m12; + nativeTransform.m13 = transform.m13; + nativeTransform.m14 = transform.m14; + + nativeTransform.m21 = transform.m21; + nativeTransform.m22 = transform.m22; + nativeTransform.m23 = transform.m23; + nativeTransform.m24 = transform.m24; + + nativeTransform.m31 = transform.m31; + nativeTransform.m32 = transform.m32; + nativeTransform.m33 = transform.m33; + nativeTransform.m34 = transform.m34; + + nativeTransform.m41 = transform.m41; + nativeTransform.m42 = transform.m42; + nativeTransform.m43 = transform.m43; + nativeTransform.m44 = transform.m44; + + auto affineTransform = CATransform3DGetAffineTransform(nativeTransform); + + CGPathRef resultPath = CGPathCreateCopyByTransformingPath(_path, &affineTransform); + if (resultPath == nil) { + return nullptr; + } + + CGMutablePathRef resultMutablePath = CGPathCreateMutableCopy(resultPath); + CGPathRelease(resultPath); + auto result = std::make_shared(resultMutablePath); + CGPathRelease(resultMutablePath); + + return result; +} + +void CGPathCocoaImpl::addLineTo(Vector2D const &point) { + CGPathAddLineToPoint(_path, nil, point.x, point.y); +} + +void CGPathCocoaImpl::addCurveTo(Vector2D const &point, Vector2D const &control1, Vector2D const &control2) { + CGPathAddCurveToPoint(_path, nil, control1.x, control1.y, control2.x, control2.y, point.x, point.y); +} + +void CGPathCocoaImpl::moveTo(Vector2D const &point) { + CGPathMoveToPoint(_path, nil, point.x, point.y); +} + +void CGPathCocoaImpl::closeSubpath() { + CGPathCloseSubpath(_path); +} + +void CGPathCocoaImpl::addRect(CGRect const &rect) { + CGPathAddRect(_path, nil, ::CGRectMake(rect.x, rect.y, rect.width, rect.height)); +} + +void CGPathCocoaImpl::addPath(std::shared_ptr const &path) { + if (CGPathIsEmpty(_path)) { + _path = CGPathCreateMutableCopy(std::static_pointer_cast(path)->_path); + } else { + CGPathAddPath(_path, nil, std::static_pointer_cast(path)->_path); + } +} + +CGPathRef CGPathCocoaImpl::nativePath() const { + return _path; +} + +bool CGPathCocoaImpl::isEqual(CGPath *other) const { + CGPathCocoaImpl *otherImpl = (CGPathCocoaImpl *)other; + return CGPathEqualToPath(_path, otherImpl->_path); +} + +void CGPathCocoaImpl::enumerate(std::function f) { + CGPathApplyWithBlock(_path, ^(const CGPathElement * _Nonnull element) { + CGPathItem item(CGPathItem::Type::MoveTo); + + switch (element->type) { + case kCGPathElementMoveToPoint: { + item.type = CGPathItem::Type::MoveTo; + item.points[0] = Vector2D(element->points[0].x, element->points[0].y); + f(item); + break; + } + case kCGPathElementAddLineToPoint: { + item.type = CGPathItem::Type::LineTo; + item.points[0] = Vector2D(element->points[0].x, element->points[0].y); + f(item); + break; + } + case kCGPathElementAddCurveToPoint: { + item.type = CGPathItem::Type::CurveTo; + item.points[0] = Vector2D(element->points[0].x, element->points[0].y); + item.points[1] = Vector2D(element->points[1].x, element->points[1].y); + item.points[2] = Vector2D(element->points[2].x, element->points[2].y); + f(item); + break; + } + case kCGPathElementAddQuadCurveToPoint: { + break; + } + case kCGPathElementCloseSubpath: { + item.type = CGPathItem::Type::Close; + f(item); + break; + } + } + }); +} + +void CGPathCocoaImpl::withNativePath(std::shared_ptr const &path, std::function f) { + CGMutablePathRef result = CGPathCreateMutable(); + + path->enumerate([result](CGPathItem const &element) { + switch (element.type) { + case CGPathItem::Type::MoveTo: { + CGPathMoveToPoint(result, nullptr, element.points[0].x, element.points[0].y); + break; + } + case CGPathItem::Type::LineTo: { + CGPathAddLineToPoint(result, nullptr, element.points[0].x, element.points[0].y); + break; + } + case CGPathItem::Type::CurveTo: { + CGPathAddCurveToPoint(result, nullptr, element.points[0].x, element.points[0].y, element.points[1].x, element.points[1].y, element.points[2].x, element.points[2].y); + break; + } + case CGPathItem::Type::Close: { + CGPathCloseSubpath(result); + break; + } + default: + break; + } + }); + + f(result); + CFRelease(result); +} + +/*std::shared_ptr CGPath::makePath() { + return std::static_pointer_cast(std::make_shared()); +}*/ + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/CGPathCocoa.h b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/CGPathCocoa.h new file mode 100644 index 0000000000..744ef5f293 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/CGPathCocoa.h @@ -0,0 +1,42 @@ +#ifndef CGPathCocoa_h +#define CGPathCocoa_h + +#include "Lottie/Public/Primitives/CGPath.hpp" + +#include + +CGRect calculatePathBoundingBox(CGPathRef path); + +namespace lottie { + +class CGPathCocoaImpl: public CGPath { +public: + CGPathCocoaImpl(); + explicit CGPathCocoaImpl(CGMutablePathRef path); + virtual ~CGPathCocoaImpl(); + + virtual CGRect boundingBox() const override; + + virtual bool empty() const override; + + virtual std::shared_ptr copyUsingTransform(CATransform3D const &transform) const override; + + virtual void addLineTo(Vector2D const &point) override; + virtual void addCurveTo(Vector2D const &point, Vector2D const &control1, Vector2D const &control2) override; + virtual void moveTo(Vector2D const &point) override; + virtual void closeSubpath() override; + virtual void addRect(CGRect const &rect) override; + virtual void addPath(std::shared_ptr const &path) override; + virtual CGPathRef nativePath() const; + virtual bool isEqual(CGPath *other) const override; + virtual void enumerate(std::function) override; + + static void withNativePath(std::shared_ptr const &path, std::function f); + +private: + ::CGMutablePathRef _path = nil; +}; + +} + +#endif /* CGPathCocoa_h */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/CTFont.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/CTFont.cpp new file mode 100644 index 0000000000..24022559bf --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/CTFont.cpp @@ -0,0 +1,5 @@ +#include "CTFont.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/CTFont.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/CTFont.hpp new file mode 100644 index 0000000000..351d4bfc22 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/CTFont.hpp @@ -0,0 +1,12 @@ +#ifndef CTFont_hpp +#define CTFont_hpp + +namespace lottie { + +class CTFont { + +}; + +} + +#endif /* CTFont_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/Color.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/Color.cpp new file mode 100644 index 0000000000..c1ecb86f45 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/Color.cpp @@ -0,0 +1,29 @@ +#include "Color.hpp" + +#include + +namespace lottie { + +Color Color::fromString(std::string const &string) { + if (string.empty()) { + return Color(0.0, 0.0, 0.0, 0.0); + } + + std::string workString = string; + if (workString[0] == '#') { + workString.erase(workString.begin()); + } + + std::istringstream converter(workString); + uint32_t rgbValue; + converter >> std::hex >> rgbValue; + + return Color( + ((double)((rgbValue & 0xFF0000) >> 16)) / 255.0, + ((double)((rgbValue & 0x00FF00) >> 8)) / 255.0, + ((double)(rgbValue & 0x0000FF)) / 255.0, + 1.0 + ); +} + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/Color.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/Color.hpp new file mode 100644 index 0000000000..8fdbdea8ae --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/Color.hpp @@ -0,0 +1,144 @@ +#ifndef Color_hpp +#define Color_hpp + +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +#include + +namespace lottie { + +enum class ColorFormatDenominator { + One, + OneHundred, + TwoFiftyFive +}; + +struct Color { + double r; + double g; + double b; + double a; + + bool operator==(Color const &rhs) const { + if (r != rhs.r) { + return false; + } + if (g != rhs.g) { + return false; + } + if (b != rhs.b) { + return false; + } + if (a != rhs.a) { + return false; + } + return true; + } + + bool operator!=(Color const &rhs) const { + return !(*this == rhs); + } + + explicit Color(double r_, double g_, double b_, double a_, ColorFormatDenominator denominator = ColorFormatDenominator::One) { + double denominatorValue = 1.0; + switch (denominator) { + case ColorFormatDenominator::One: { + denominatorValue = 1.0; + break; + } + case ColorFormatDenominator::OneHundred: { + denominatorValue = 100.0; + break; + } + case ColorFormatDenominator::TwoFiftyFive: { + denominatorValue = 255.0; + break; + } + } + + r = r_ / denominatorValue; + g = g_ / denominatorValue; + b = b_ / denominatorValue; + a = a_ / denominatorValue; + } + + explicit Color(json11::Json const &jsonAny) noexcept(false) : + r(0.0), g(0.0), b(0.0), a(0.0) { + if (!jsonAny.is_array()) { + throw LottieParsingException(); + } + + for (const auto &item : jsonAny.array_items()) { + if (!item.is_number()) { + throw LottieParsingException(); + } + } + + size_t index = 0; + + double r1 = 0.0; + if (index < jsonAny.array_items().size()) { + if (!jsonAny.array_items()[index].is_number()) { + throw LottieParsingException(); + } + r1 = jsonAny.array_items()[index].number_value(); + index++; + } + + double g1 = 0.0; + if (index < jsonAny.array_items().size()) { + if (!jsonAny.array_items()[index].is_number()) { + throw LottieParsingException(); + } + g1 = jsonAny.array_items()[index].number_value(); + index++; + } + + double b1 = 0.0; + if (index < jsonAny.array_items().size()) { + if (!jsonAny.array_items()[index].is_number()) { + throw LottieParsingException(); + } + b1 = jsonAny.array_items()[index].number_value(); + index++; + } + + double a1 = 0.0; + if (index < jsonAny.array_items().size()) { + if (!jsonAny.array_items()[index].is_number()) { + throw LottieParsingException(); + } + a1 = jsonAny.array_items()[index].number_value(); + index++; + } + + if (r1 > 1.0 && r1 > 1.0 && b1 > 1.0 && a1 > 1.0) { + r1 = r1 / 255.0; + g1 = g1 / 255.0; + b1 = b1 / 255.0; + a1 = a1 / 255.0; + } + + r = r1; + g = g1; + b = b1; + a = a1; + } + + json11::Json toJson() const { + json11::Json::array result; + + result.push_back(json11::Json(r)); + result.push_back(json11::Json(g)); + result.push_back(json11::Json(b)); + result.push_back(json11::Json(a)); + + return result; + } + + static Color fromString(std::string const &string); +}; + +} + +#endif /* Color_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/DashPattern.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/DashPattern.cpp new file mode 100644 index 0000000000..33426de338 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/DashPattern.cpp @@ -0,0 +1,5 @@ +#include "DashPattern.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/DashPattern.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/DashPattern.hpp new file mode 100644 index 0000000000..d22ee1e4d8 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/DashPattern.hpp @@ -0,0 +1,18 @@ +#ifndef DashPattern_hpp +#define DashPattern_hpp + +#include + +namespace lottie { + +struct DashPattern { + DashPattern(std::vector &&values_) : + values(std::move(values_)) { + } + + std::vector values; +}; + +} + +#endif /* DashPattern_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/DrawingAttributes.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/DrawingAttributes.hpp new file mode 100644 index 0000000000..7b43581e65 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/DrawingAttributes.hpp @@ -0,0 +1,22 @@ +#ifndef DrawingAttributes_hpp +#define DrawingAttributes_hpp + +namespace lottie { + +enum class LineCap: int { + None = 0, + Butt = 1, + Round = 2, + Square = 3 +}; + +enum class LineJoin: int { + None = 0, + Miter = 1, + Round = 2, + Bevel = 3 +}; + +} + +#endif /* DrawingAttributes_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/GradientColorSet.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/GradientColorSet.cpp new file mode 100644 index 0000000000..3a92ce0d41 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/GradientColorSet.cpp @@ -0,0 +1,5 @@ +#include "GradientColorSet.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/GradientColorSet.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/GradientColorSet.hpp new file mode 100644 index 0000000000..e1db01a15f --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/GradientColorSet.hpp @@ -0,0 +1,42 @@ +#ifndef GradientColorSet_hpp +#define GradientColorSet_hpp + +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +#include + +namespace lottie { + +struct GradientColorSet { + GradientColorSet() { + } + + explicit GradientColorSet(json11::Json const &jsonAny) noexcept(false) { + if (!jsonAny.is_array()) { + throw LottieParsingException(); + } + + for (const auto &item : jsonAny.array_items()) { + if (!item.is_number()) { + throw LottieParsingException(); + } + colors.push_back(item.number_value()); + } + } + + json11::Json toJson() const { + json11::Json::array result; + + for (auto value : colors) { + result.push_back(value); + } + + return result; + } + + std::vector colors; +}; + +} + +#endif /* GradientColorSet_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/RenderTree.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/RenderTree.cpp new file mode 100644 index 0000000000..eec19c1596 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/RenderTree.cpp @@ -0,0 +1,140 @@ +#include "RenderTree.hpp" + +namespace lottie { + +BoundingBoxNode::BoundingBoxNode( + LayerParams const &layer_, + CGRect const &globalRect_, + CGRect const &localRect_, + CATransform3D const &globalTransform_, + bool drawsContent_, + std::shared_ptr renderableItem_, + bool isInvertedMatte_, + std::vector> const &subnodes_, + std::shared_ptr const &mask_ +) : +layer(layer_), +globalRect(globalRect_), +localRect(localRect_), +globalTransform(globalTransform_), +drawsContent(drawsContent_), +renderableItem(renderableItem_), +isInvertedMatte(isInvertedMatte_), +subnodes(subnodes_), +mask(mask_) { +} + +std::shared_ptr boundingBoxTree(std::shared_ptr const &layer, Vector2D const &globalSize, CATransform3D const &parentTransform) { + if (layer->isHidden() || layer->opacity() == 0.0f) { + return nullptr; + } + + if (layer->masksToBounds()) { + if (layer->bounds().empty()) { + return nullptr; + } + } + + auto currentTransform = parentTransform; + + currentTransform = currentTransform.translated(Vector2D(layer->position().x, layer->position().y)); + currentTransform = currentTransform.translated(Vector2D(-layer->bounds().x, -layer->bounds().y)); + currentTransform = layer->transform() * currentTransform; + + if (!currentTransform.isInvertible()) { + return nullptr; + } + + std::optional effectiveLocalBounds; + + auto renderableItem = layer->renderableItem(); + if (renderableItem) { + effectiveLocalBounds = renderableItem->boundingRect(); + } else if (layer->implementsDraw()) { + effectiveLocalBounds = layer->bounds(); + } + + bool isInvertedMatte = layer->isInvertedMatte(); + if (isInvertedMatte) { + effectiveLocalBounds = layer->bounds(); + } + + if (effectiveLocalBounds && effectiveLocalBounds->empty()) { + effectiveLocalBounds = std::nullopt; + } + + std::vector> subnodes; + std::optional subnodesGlobalRect; + + for (const auto &sublayer : layer->sublayers()) { + if (const auto subnode = boundingBoxTree(sublayer, globalSize, currentTransform)) { + subnodes.push_back(subnode); + + if (subnodesGlobalRect) { + subnodesGlobalRect = subnodesGlobalRect->unionWith(subnode->globalRect); + } else { + subnodesGlobalRect = subnode->globalRect; + } + } + } + + std::optional fuzzyGlobalRect; + + if (effectiveLocalBounds) { + CGRect effectiveGlobalBounds = effectiveLocalBounds->applyingTransform(currentTransform); + if (fuzzyGlobalRect) { + fuzzyGlobalRect = fuzzyGlobalRect->unionWith(effectiveGlobalBounds); + } else { + fuzzyGlobalRect = effectiveGlobalBounds; + } + } + + if (subnodesGlobalRect) { + if (fuzzyGlobalRect) { + fuzzyGlobalRect = fuzzyGlobalRect->unionWith(subnodesGlobalRect.value()); + } else { + fuzzyGlobalRect = subnodesGlobalRect; + } + } + + if (!fuzzyGlobalRect) { + return nullptr; + } + + CGRect globalRect( + std::floor(fuzzyGlobalRect->x), + std::floor(fuzzyGlobalRect->y), + std::ceil(fuzzyGlobalRect->width + fuzzyGlobalRect->x - floor(fuzzyGlobalRect->x)), + std::ceil(fuzzyGlobalRect->height + fuzzyGlobalRect->y - floor(fuzzyGlobalRect->y)) + ); + + if (!CGRect(0.0, 0.0, globalSize.x, globalSize.y).intersects(globalRect)) { + return nullptr; + } + + std::shared_ptr maskNode; + if (layer->mask()) { + if (const auto maskNodeValue = boundingBoxTree(layer->mask(), globalSize, currentTransform)) { + if (!maskNodeValue->globalRect.intersects(globalRect)) { + return nullptr; + } + maskNode = maskNodeValue; + } else { + return nullptr; + } + } + + return std::make_shared( + layer, + globalRect, + CGRect(0.0, 0.0, 0.0, 0.0), + currentTransform, + effectiveLocalBounds.has_value(), + renderableItem, + isInvertedMatte, + subnodes, + maskNode + ); +} + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/RenderTree.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/RenderTree.hpp new file mode 100644 index 0000000000..1ab374e226 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/RenderTree.hpp @@ -0,0 +1,172 @@ +#ifndef RenderTree_hpp +#define RenderTree_hpp + +#include + +#include "Lottie/Public/Primitives/Vectors.hpp" +#include "Lottie/Public/Primitives/CALayer.hpp" + +namespace lottie { + +struct BoundingBoxNode { + struct LayerParams { + CGRect _bounds; + Vector2D _position; + CATransform3D _transform; + double _opacity; + bool _masksToBounds; + bool _isHidden; + + LayerParams( + CGRect bounds_, + Vector2D position_, + CATransform3D transform_, + double opacity_, + bool masksToBounds_, + bool isHidden_ + ) : + _bounds(bounds_), + _position(position_), + _transform(transform_), + _opacity(opacity_), + _masksToBounds(masksToBounds_), + _isHidden(isHidden_) { + } + + LayerParams(std::shared_ptr const &layer) : + _bounds(layer->bounds()), + _position(layer->position()), + _transform(layer->transform()), + _opacity(layer->opacity()), + _masksToBounds(layer->masksToBounds()), + _isHidden(layer->isHidden()) { + } + + bool operator==(LayerParams const &rhs) const { + if (_bounds != rhs._bounds) { + return false; + } + if (_position != rhs._position) { + return false; + } + if (_transform != rhs._transform) { + return false; + } + if (_opacity != rhs._opacity) { + return false; + } + if (_masksToBounds != rhs._masksToBounds) { + return false; + } + if (_isHidden != rhs._isHidden) { + return false; + } + return true; + } + + bool operator!=(LayerParams const &rhs) const { + return !(*this == rhs); + } + + CGRect bounds() const { + return _bounds; + } + + Vector2D position() const { + return _position; + } + + CATransform3D transform() const { + return _transform; + } + + double opacity() const { + return _opacity; + } + + bool masksToBounds() const { + return _masksToBounds; + } + + bool isHidden() const { + return _isHidden; + } + }; + + LayerParams layer; + CGRect globalRect; + CGRect localRect; + CATransform3D globalTransform; + bool drawsContent; + std::shared_ptr renderableItem; + bool isInvertedMatte; + std::vector> subnodes; + std::shared_ptr mask; + + explicit BoundingBoxNode( + LayerParams const &layer_, + CGRect const &globalRect_, + CGRect const &localRect_, + CATransform3D const &globalTransform_, + bool drawsContent_, + std::shared_ptr renderableItem_, + bool isInvertedMatte_, + std::vector> const &subnodes_, + std::shared_ptr const &mask_ + ); + + bool operator==(BoundingBoxNode const &rhs) const { + if (layer != rhs.layer) { + return false; + } + if (globalRect != rhs.globalRect) { + return false; + } + if (localRect != rhs.localRect) { + return false; + } + if (globalTransform != rhs.globalTransform) { + return false; + } + if (drawsContent != rhs.drawsContent) { + return false; + } + if ((renderableItem == nullptr) != (rhs.renderableItem == nullptr)) { + return false; + } else if (renderableItem) { + if (!renderableItem->isEqual(rhs.renderableItem)) { + return false; + } + } + if (isInvertedMatte != rhs.isInvertedMatte) { + return false; + } + if (subnodes.size() != rhs.subnodes.size()) { + return false; + } else { + for (size_t i = 0; i < subnodes.size(); i++) { + if ((*subnodes[i].get()) != (*rhs.subnodes[i].get())) { + return false; + } + } + } + if ((mask == nullptr) != (rhs.mask == nullptr)) { + return false; + } else if (mask) { + if ((*mask.get()) != *(rhs.mask.get())) { + return false; + } + } + return true; + } + + bool operator!=(BoundingBoxNode const &rhs) const { + return !(*this == rhs); + } +}; + +std::shared_ptr boundingBoxTree(std::shared_ptr const &layer, Vector2D const &globalSize, CATransform3D const &parentTransform); + +} + +#endif /* RenderTree_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/Vectors.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/Vectors.hpp new file mode 100644 index 0000000000..9cdff038d1 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/Vectors.hpp @@ -0,0 +1,494 @@ +#ifndef Vectors_hpp +#define Vectors_hpp + +#include "Lottie/Private/Parsing/JsonParsing.hpp" + +#include +#include + +namespace lottie { + +struct Vector1D { + enum class InternalRepresentationType { + SingleNumber, + Array + }; + + explicit Vector1D(double value_) : + value(value_) { + } + + explicit Vector1D(json11::Json const &json) noexcept(false) { + if (json.is_number()) { + value = json.number_value(); + } else if (json.is_array()) { + if (json.array_items().empty()) { + throw LottieParsingException(); + } + if (!json.array_items()[0].is_number()) { + throw LottieParsingException(); + } + value = json.array_items()[0].number_value(); + } else { + throw LottieParsingException(); + } + } + + json11::Json toJson() const { + return json11::Json(value); + } + + double value; + + double distanceTo(Vector1D const &to) const { + return abs(to.value - value); + } +}; + +double interpolate(double value, double to, double amount); + +Vector1D interpolate( + Vector1D const &from, + Vector1D const &to, + double amount +); + +struct Vector2D { + static Vector2D Zero() { + return Vector2D(0.0, 0.0); + } + + Vector2D() : + x(0.0), + y(0.0) { + } + + explicit Vector2D(double x_, double y_) : + x(x_), + y(y_) { + } + + explicit Vector2D(json11::Json const &json) noexcept(false) { + x = 0.0; + y = 0.0; + + if (json.is_array()) { + int index = 0; + + if (json.array_items().size() > index) { + if (!json.array_items()[index].is_number()) { + throw LottieParsingException(); + } + x = json.array_items()[index].number_value(); + index++; + } + + if (json.array_items().size() > index) { + if (!json.array_items()[index].is_number()) { + throw LottieParsingException(); + } + y = json.array_items()[index].number_value(); + index++; + } + } else if (json.is_object()) { + auto xAny = getAny(json.object_items(), "x"); + if (xAny.is_number()) { + x = xAny.number_value(); + } else if (xAny.is_array()) { + if (xAny.array_items().empty()) { + throw LottieParsingException(); + } + if (!xAny.array_items()[0].is_number()) { + throw LottieParsingException(); + } + x = xAny.array_items()[0].number_value(); + } + + auto yAny = getAny(json.object_items(), "y"); + if (yAny.is_number()) { + y = yAny.number_value(); + } else if (yAny.is_array()) { + if (yAny.array_items().empty()) { + throw LottieParsingException(); + } + if (!yAny.array_items()[0].is_number()) { + throw LottieParsingException(); + } + y = yAny.array_items()[0].number_value(); + } + } else { + throw LottieParsingException(); + } + } + + json11::Json toJson() const { + json11::Json::object result; + + result.insert(std::make_pair("x", x)); + result.insert(std::make_pair("y", y)); + + return json11::Json(result); + } + + double x; + double y; + + Vector2D operator+(Vector2D const &rhs) const { + return Vector2D(x + rhs.x, y + rhs.y); + } + + Vector2D operator-(Vector2D const &rhs) const { + return Vector2D(x - rhs.x, y - rhs.y); + } + + Vector2D operator*(double scalar) const { + return Vector2D(x * scalar, y * scalar); + } + + bool operator==(Vector2D const &rhs) const { + return x == rhs.x && y == rhs.y; + } + + bool operator!=(Vector2D const &rhs) const { + return !(*this == rhs); + } + + bool isZero() const { + return x == 0.0 && y == 0.0; + } + + double distanceTo(Vector2D const &to) const { + auto deltaX = to.x - x; + auto deltaY = to.y - y; + return sqrt(deltaX * deltaX + deltaY * deltaY); + } + + bool colinear(Vector2D const &a, Vector2D const &b) const { + double area = x * (a.y - b.y) + a.x * (b.y - y) + b.x * (y - a.y); + double accuracy = 0.05; + if (area < accuracy && area > -accuracy) { + return true; + } + return false; + } + + Vector2D pointOnPath(Vector2D const &to, Vector2D const &outTangent, Vector2D const &inTangent, double amount) const; + + Vector2D interpolate(Vector2D const &to, double amount) const; + + Vector2D interpolate( + Vector2D const &to, + Vector2D const &outTangent, + Vector2D const &inTangent, + double amount, + int maxIterations = 3, + int samples = 20, + double accuracy = 1.0 + ) const; +}; + +Vector2D interpolate( + Vector2D const &from, + Vector2D const &to, + double amount +); + +struct Vector3D { + explicit Vector3D(double x_, double y_, double z_) : + x(x_), + y(y_), + z(z_) { + } + + explicit Vector3D(json11::Json const &json) noexcept(false) { + if (!json.is_array()) { + throw LottieParsingException(); + } + + int index = 0; + + x = 0.0; + y = 0.0; + z = 0.0; + + if (json.array_items().size() > index) { + if (!json.array_items()[index].is_number()) { + throw LottieParsingException(); + } + x = json.array_items()[index].number_value(); + index++; + } + + if (json.array_items().size() > index) { + if (!json.array_items()[index].is_number()) { + throw LottieParsingException(); + } + y = json.array_items()[index].number_value(); + index++; + } + + if (json.array_items().size() > index) { + if (!json.array_items()[index].is_number()) { + throw LottieParsingException(); + } + z = json.array_items()[index].number_value(); + index++; + } + } + + json11::Json toJson() const { + json11::Json::array result; + + result.push_back(json11::Json(x)); + result.push_back(json11::Json(y)); + result.push_back(json11::Json(z)); + + return json11::Json(result); + } + + double x = 0.0; + double y = 0.0; + double z = 0.0; +}; + +Vector3D interpolate( + Vector3D const &from, + Vector3D const &to, + double amount +); + +inline double degreesToRadians(double value) { + return value * M_PI / 180.0; +} + +inline double radiansToDegrees(double value) { + return value * 180.0 / M_PI; +} + +struct CATransform3D { + double m11, m12, m13, m14; + double m21, m22, m23, m24; + double m31, m32, m33, m34; + double m41, m42, m43, m44; + + CATransform3D( + double m11_, double m12_, double m13_, double m14_, + double m21_, double m22_, double m23_, double m24_, + double m31_, double m32_, double m33_, double m34_, + double m41_, double m42_, double m43_, double m44_ + ) : + m11(m11_), m12(m12_), m13(m13_), m14(m14_), + m21(m21_), m22(m22_), m23(m23_), m24(m24_), + m31(m31_), m32(m32_), m33(m33_), m34(m34_), + m41(m41_), m42(m42_), m43(m43_), m44(m44_) { + } + + bool operator==(CATransform3D const &rhs) const { + return m11 == rhs.m11 && m12 == rhs.m12 && m13 == rhs.m13 && m14 == rhs.m14 && + m21 == rhs.m21 && m22 == rhs.m22 && m23 == rhs.m23 && m24 == rhs.m24 && + m31 == rhs.m31 && m32 == rhs.m32 && m33 == rhs.m33 && m34 == rhs.m34 && + m41 == rhs.m41 && m42 == rhs.m42 && m43 == rhs.m43 && m44 == rhs.m44; + } + + bool operator!=(CATransform3D const &rhs) const { + return !(*this == rhs); + } + + inline bool isIdentity() const { + return m11 == 1.0 && m12 == 0.0 && m13 == 0.0 && m14 == 0.0 && + m21 == 0.0 && m22 == 1.0 && m23 == 0.0 && m24 == 0.0 && + m31 == 0.0 && m32 == 0.0 && m33 == 1.0 && m34 == 0.0 && + m41 == 0.0 && m42 == 0.0 && m43 == 0.0 && m44 == 1.0; + } + + static CATransform3D makeTranslation(double tx, double ty, double tz) { + return CATransform3D( + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + tx, ty, tz, 1 + ); + } + + static CATransform3D makeScale(double sx, double sy, double sz) { + return CATransform3D( + sx, 0, 0, 0, + 0, sy, 0, 0, + 0, 0, sz, 0, + 0, 0, 0, 1 + ); + } + + static CATransform3D makeRotation(double radians, double x, double y, double z); + + static CATransform3D makeSkew(double skew, double skewAxis) { + double mCos = cos(degreesToRadians(skewAxis)); + double mSin = sin(degreesToRadians(skewAxis)); + double aTan = tan(degreesToRadians(skew)); + + CATransform3D transform1( + mCos, + mSin, + 0.0, + 0.0, + -mSin, + mCos, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0 + ); + + CATransform3D transform2( + 1.0, + 0.0, + 0.0, + 0.0, + aTan, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0 + ); + + CATransform3D transform3( + mCos, + -mSin, + 0.0, + 0.0, + mSin, + mCos, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0 + ); + + return transform3 * transform2 * transform1; + } + + static CATransform3D makeTransform( + Vector2D const &anchor, + Vector2D const &position, + Vector2D const &scale, + double rotation, + std::optional skew, + std::optional skewAxis + ) { + CATransform3D result = CATransform3D::identity(); + if (skew.has_value() && skewAxis.has_value()) { + result = CATransform3D::identity().translated(position).rotated(rotation).skewed(-skew.value(), skewAxis.value()).scaled(Vector2D(scale.x * 0.01, scale.y * 0.01)).translated(Vector2D(-anchor.x, -anchor.y)); + } else { + result = CATransform3D::identity().translated(position).rotated(rotation).scaled(Vector2D(scale.x * 0.01, scale.y * 0.01)).translated(Vector2D(-anchor.x, -anchor.y)); + } + + return result; + } + + CATransform3D rotated(double degrees) const; + + CATransform3D translated(Vector2D const &translation) const; + + CATransform3D scaled(Vector2D const &scale) const; + + CATransform3D skewed(double skew, double skewAxis) const { + return CATransform3D::makeSkew(skew, skewAxis) * (*this); + } + + static CATransform3D const &identity() { + return _identity; + } + + CATransform3D operator*(CATransform3D const &b) const; + + bool isInvertible() const; + + CATransform3D inverted() const; + +private: + static CATransform3D _identity; +}; + +struct CGRect { + explicit CGRect(double x_, double y_, double width_, double height_) : + x(x_), y(y_), width(width_), height(height_) { + } + + double x = 0.0; + double y = 0.0; + double width = 0.0; + double height = 0.0; + + static CGRect veryLarge() { + return CGRect( + -100000000.0, + -100000000.0, + 200000000.0, + 200000000.0 + ); + } + + bool operator==(CGRect const &rhs) const { + return x == rhs.x && y == rhs.y && width == rhs.width && height == rhs.height; + } + + bool operator!=(CGRect const &rhs) const { + return !(*this == rhs); + } + + bool empty() const { + return width <= 0.0 || height <= 0.0; + } + + CGRect insetBy(double dx, double dy) const { + CGRect result = *this; + + result.x += dx; + result.y += dy; + result.width -= dx * 2.0; + result.height -= dy * 2.0; + + return result; + } + + bool intersects(CGRect const &other) const; + bool contains(CGRect const &other) const; + + CGRect intersection(CGRect const &other) const; + CGRect unionWith(CGRect const &other) const; + + CGRect applyingTransform(CATransform3D const &transform) const; +}; + +inline bool isInRangeOrEqual(double value, double from, double to) { + return from <= value && value <= to; +} + +inline bool isInRange(double value, double from, double to) { + return from < value && value < to; +} + +double cubicBezierInterpolate(double value, Vector2D const &P0, Vector2D const &P1, Vector2D const &P2, Vector2D const &P3); + +} + +#endif /* Vectors_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/Vectors.mm b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/Vectors.mm new file mode 100644 index 0000000000..e37b668c02 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/Vectors.mm @@ -0,0 +1,518 @@ +#include "Vectors.hpp" + +#include "VectorsCocoa.h" + +#include "Lottie/Public/Keyframes/Interpolatable.hpp" + +#include + +#import + +#import + +namespace lottie { + +CATransform3D CATransform3D::_identity = CATransform3D( + 1.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, + 0.0, 0.0, 0.0, 1.0 +); + +double interpolate(double value, double to, double amount) { + return value + ((to - value) * amount); +} + +Vector1D interpolate( + Vector1D const &from, + Vector1D const &to, + double amount +) { + return Vector1D(interpolate(from.value, to.value, amount)); +} + +Vector2D interpolate( + Vector2D const &from, + Vector2D const &to, + double amount +) { + return Vector2D(interpolate(from.x, to.x, amount), interpolate(from.y, to.y, amount)); +} + + +Vector3D interpolate( + Vector3D const &from, + Vector3D const &to, + double amount +) { + return Vector3D(interpolate(from.x, to.x, amount), interpolate(from.y, to.y, amount), interpolate(from.z, to.z, amount)); +} + +static double cubicRoot(double value) { + return pow(value, 1.0 / 3.0); +} + +static double SolveQuadratic(double a, double b, double c) { + double result = (-b + sqrt((b * b) - 4 * a * c)) / (2 * a); + if (isInRangeOrEqual(result, 0.0, 1.0)) { + return result; + } + + result = (-b - sqrt((b * b) - 4 * a * c)) / (2 * a); + if (isInRangeOrEqual(result, 0.0, 1.0)) { + return result; + } + + return -1.0; +} + +static double SolveCubic(double a, double b, double c, double d) { + if (a == 0.0) { + return SolveQuadratic(b, c, d); + } + if (d == 0.0) { + return 0.0; + } + b /= a; + c /= a; + d /= a; + double q = (3.0 * c - (b * b)) / 9.0; + double r = (-27.0 * d + b * (9.0 * c - 2.0 * (b * b))) / 54.0; + double disc = (q * q * q) + (r * r); + double term1 = b / 3.0; + + if (disc > 0.0) { + double s = r + sqrt(disc); + s = (s < 0) ? -cubicRoot(-s) : cubicRoot(s); + double t = r - sqrt(disc); + t = (t < 0) ? -cubicRoot(-t) : cubicRoot(t); + + double result = -term1 + s + t; + if (isInRangeOrEqual(result, 0.0, 1.0)) { + return result; + } + } else if (disc == 0) { + double r13 = (r < 0) ? -cubicRoot(-r) : cubicRoot(r); + + double result = -term1 + 2.0 * r13; + if (isInRangeOrEqual(result, 0.0, 1.0)) { + return result; + } + + result = -(r13 + term1); + if (isInRangeOrEqual(result, 0.0, 1.0)) { + return result; + } + } else { + q = -q; + double dum1 = q * q * q; + dum1 = acos(r / sqrt(dum1)); + double r13 = 2.0 * sqrt(q); + + double result = -term1 + r13 * cos(dum1 / 3.0); + if (isInRangeOrEqual(result, 0.0, 1.0)) { + return result; + } + result = -term1 + r13 * cos((dum1 + 2.0 * M_PI) / 3.0); + if (isInRangeOrEqual(result, 0.0, 1.0)) { + return result; + } + result = -term1 + r13 * cos((dum1 + 4.0 * M_PI) / 3.0); + if (isInRangeOrEqual(result, 0.0, 1.0)) { + return result; + } + } + + return -1; +} + +double cubicBezierInterpolate(double value, Vector2D const &P0, Vector2D const &P1, Vector2D const &P2, Vector2D const &P3) { + double t = 0.0; + if (value == P0.x) { + // Handle corner cases explicitly to prevent rounding errors + t = 0.0; + } else if (value == P3.x) { + t = 1.0; + } else { + // Calculate t + double a = -P0.x + 3 * P1.x - 3 * P2.x + P3.x; + double b = 3 * P0.x - 6 * P1.x + 3 * P2.x; + double c = -3 * P0.x + 3 * P1.x; + double d = P0.x - value; + double tTemp = SolveCubic(a, b, c, d); + if (tTemp == -1.0) { + return -1.0; + } + t = tTemp; + } + + // Calculate y from t + double oneMinusT = 1.0 - t; + return (oneMinusT * oneMinusT * oneMinusT) * P0.y + 3 * t * (oneMinusT * oneMinusT) * P1.y + 3 * (t * t) * (1 - t) * P2.y + (t * t * t) * P3.y; +} + +struct InterpolationPoint2D { + InterpolationPoint2D(Vector2D const point_, double distance_) : + point(point_), distance(distance_) { + } + + Vector2D point; + double distance; +}; + +namespace { + double interpolateDouble(double value, double to, double amount) { + return value + ((to - value) * amount); + } +} + +Vector2D Vector2D::pointOnPath(Vector2D const &to, Vector2D const &outTangent, Vector2D const &inTangent, double amount) const { + auto a = interpolate(outTangent, amount); + auto b = outTangent.interpolate(inTangent, amount); + auto c = inTangent.interpolate(to, amount); + auto d = a.interpolate(b, amount); + auto e = b.interpolate(c, amount); + auto f = d.interpolate(e, amount); + return f; +} + +Vector2D Vector2D::interpolate(Vector2D const &to, double amount) const { + return Vector2D( + interpolateDouble(x, to.x, amount), + interpolateDouble(y, to.y, amount) + ); +} + +Vector2D Vector2D::interpolate( + Vector2D const &to, + Vector2D const &outTangent, + Vector2D const &inTangent, + double amount, + int maxIterations, + int samples, + double accuracy +) const { + if (amount == 0.0) { + return *this; + } + if (amount == 1.0) { + return to; + } + + if (colinear(outTangent, inTangent) && outTangent.colinear(inTangent, to)) { + return interpolate(to, amount); + } + + double step = 1.0 / (double)samples; + + std::vector points; + points.push_back(InterpolationPoint2D(*this, 0.0)); + double totalLength = 0.0; + + Vector2D previousPoint = *this; + double previousAmount = 0.0; + + int closestPoint = 0; + + while (previousAmount < 1.0) { + previousAmount = previousAmount + step; + + if (previousAmount < amount) { + closestPoint = closestPoint + 1; + } + + auto newPoint = pointOnPath(to, outTangent, inTangent, previousAmount); + auto distance = previousPoint.distanceTo(newPoint); + totalLength = totalLength + distance; + points.push_back(InterpolationPoint2D(newPoint, totalLength)); + previousPoint = newPoint; + } + + double accurateDistance = amount * totalLength; + auto point = points[closestPoint]; + + bool foundPoint = false; + + double pointAmount = ((double)closestPoint) * step; + double nextPointAmount = pointAmount + step; + + int refineIterations = 0; + while (!foundPoint) { + refineIterations = refineIterations + 1; + /// First see if the next point is still less than the projected length. + auto nextPoint = points[closestPoint + 1]; + if (nextPoint.distance < accurateDistance) { + point = nextPoint; + closestPoint = closestPoint + 1; + pointAmount = ((double)closestPoint) * step; + nextPointAmount = pointAmount + step; + if (closestPoint == (int)points.size()) { + foundPoint = true; + } + continue; + } + if (accurateDistance < point.distance) { + closestPoint = closestPoint - 1; + if (closestPoint < 0) { + foundPoint = true; + continue; + } + point = points[closestPoint]; + pointAmount = ((double)closestPoint) * step; + nextPointAmount = pointAmount + step; + continue; + } + + /// Now we are certain the point is the closest point under the distance + auto pointDiff = nextPoint.distance - point.distance; + auto proposedPointAmount = remapDouble((accurateDistance - point.distance) / pointDiff, 0.0, 1.0, pointAmount, nextPointAmount); + + auto newPoint = pointOnPath(to, outTangent, inTangent, proposedPointAmount); + auto newDistance = point.distance + point.point.distanceTo(newPoint); + pointAmount = proposedPointAmount; + point = InterpolationPoint2D(newPoint, newDistance); + if (accurateDistance - newDistance <= accuracy || + newDistance - accurateDistance <= accuracy) { + foundPoint = true; + } + + if (refineIterations == maxIterations) { + foundPoint = true; + } + } + return point.point; +} + +::CATransform3D nativeTransform(CATransform3D const &value) { + ::CATransform3D result; + + result.m11 = value.m11; + result.m12 = value.m12; + result.m13 = value.m13; + result.m14 = value.m14; + + result.m21 = value.m21; + result.m22 = value.m22; + result.m23 = value.m23; + result.m24 = value.m24; + + result.m31 = value.m31; + result.m32 = value.m32; + result.m33 = value.m33; + result.m34 = value.m34; + + result.m41 = value.m41; + result.m42 = value.m42; + result.m43 = value.m43; + result.m44 = value.m44; + + return result; +} + +CATransform3D fromNativeTransform(::CATransform3D const &value) { + CATransform3D result = CATransform3D::identity(); + + result.m11 = value.m11; + result.m12 = value.m12; + result.m13 = value.m13; + result.m14 = value.m14; + + result.m21 = value.m21; + result.m22 = value.m22; + result.m23 = value.m23; + result.m24 = value.m24; + + result.m31 = value.m31; + result.m32 = value.m32; + result.m33 = value.m33; + result.m34 = value.m34; + + result.m41 = value.m41; + result.m42 = value.m42; + result.m43 = value.m43; + result.m44 = value.m44; + + return result; +} + +CATransform3D CATransform3D::makeRotation(double radians, double x, double y, double z) { + return fromNativeTransform(CATransform3DMakeRotation(radians, x, y, z)); + + /*if (x == 0.0 && y == 0.0 && z == 0.0) { + return CATransform3D::identity(); + } + + float s = sin(radians); + float c = cos(radians); + + float len = sqrt(x*x + y*y + z*z); + x /= len; y /= len; z /= len; + + CATransform3D returnValue = CATransform3D::identity(); + + returnValue.m11 = c + (1-c) * x*x; + returnValue.m12 = (1-c) * x*y + s*z; + returnValue.m13 = (1-c) * x*z - s*y; + returnValue.m14 = 0; + + returnValue.m21 = (1-c) * y*x - s*z; + returnValue.m22 = c + (1-c) * y*y; + returnValue.m23 = (1-c) * y*z + s*x; + returnValue.m24 = 0; + + returnValue.m31 = (1-c) * z*x + s*y; + returnValue.m32 = (1-c) * y*z - s*x; + returnValue.m33 = c + (1-c) * z*z; + returnValue.m34 = 0; + + returnValue.m41 = 0; + returnValue.m42 = 0; + returnValue.m43 = 0; + returnValue.m44 = 1; + + return returnValue;*/ +} + +CATransform3D CATransform3D::rotated(double degrees) const { + return fromNativeTransform(CATransform3DRotate(nativeTransform(*this), degreesToRadians(degrees), 0.0, 0.0, 1.0)); + //return CATransform3D::makeRotation(degreesToRadians(degrees), 0.0, 0.0, 1.0) * (*this); +} + +CATransform3D CATransform3D::translated(Vector2D const &translation) const { + return fromNativeTransform(CATransform3DTranslate(nativeTransform(*this), translation.x, translation.y, 0.0)); +} + +CATransform3D CATransform3D::scaled(Vector2D const &scale) const { + return fromNativeTransform(CATransform3DScale(nativeTransform(*this), scale.x, scale.y, 1.0)); + //return CATransform3D::makeScale(scale.x, scale.y, 1.0) * (*this); +} + +CATransform3D CATransform3D::operator*(CATransform3D const &b) const { + if (isIdentity()) { + return b; + } + if (b.isIdentity()) { + return *this; + } + + const CATransform3D lhs = b; + const CATransform3D &rhs = *this; + CATransform3D result = CATransform3D::identity(); + + result.m11 = (lhs.m11*rhs.m11)+(lhs.m21*rhs.m12)+(lhs.m31*rhs.m13)+(lhs.m41*rhs.m14); + result.m12 = (lhs.m12*rhs.m11)+(lhs.m22*rhs.m12)+(lhs.m32*rhs.m13)+(lhs.m42*rhs.m14); + result.m13 = (lhs.m13*rhs.m11)+(lhs.m23*rhs.m12)+(lhs.m33*rhs.m13)+(lhs.m43*rhs.m14); + result.m14 = (lhs.m14*rhs.m11)+(lhs.m24*rhs.m12)+(lhs.m34*rhs.m13)+(lhs.m44*rhs.m14); + + result.m21 = (lhs.m11*rhs.m21)+(lhs.m21*rhs.m22)+(lhs.m31*rhs.m23)+(lhs.m41*rhs.m24); + result.m22 = (lhs.m12*rhs.m21)+(lhs.m22*rhs.m22)+(lhs.m32*rhs.m23)+(lhs.m42*rhs.m24); + result.m23 = (lhs.m13*rhs.m21)+(lhs.m23*rhs.m22)+(lhs.m33*rhs.m23)+(lhs.m43*rhs.m24); + result.m24 = (lhs.m14*rhs.m21)+(lhs.m24*rhs.m22)+(lhs.m34*rhs.m23)+(lhs.m44*rhs.m24); + + result.m31 = (lhs.m11*rhs.m31)+(lhs.m21*rhs.m32)+(lhs.m31*rhs.m33)+(lhs.m41*rhs.m34); + result.m32 = (lhs.m12*rhs.m31)+(lhs.m22*rhs.m32)+(lhs.m32*rhs.m33)+(lhs.m42*rhs.m34); + result.m33 = (lhs.m13*rhs.m31)+(lhs.m23*rhs.m32)+(lhs.m33*rhs.m33)+(lhs.m43*rhs.m34); + result.m34 = (lhs.m14*rhs.m31)+(lhs.m24*rhs.m32)+(lhs.m34*rhs.m33)+(lhs.m44*rhs.m34); + + result.m41 = (lhs.m11*rhs.m41)+(lhs.m21*rhs.m42)+(lhs.m31*rhs.m43)+(lhs.m41*rhs.m44); + result.m42 = (lhs.m12*rhs.m41)+(lhs.m22*rhs.m42)+(lhs.m32*rhs.m43)+(lhs.m42*rhs.m44); + result.m43 = (lhs.m13*rhs.m41)+(lhs.m23*rhs.m42)+(lhs.m33*rhs.m43)+(lhs.m43*rhs.m44); + result.m44 = (lhs.m14*rhs.m41)+(lhs.m24*rhs.m42)+(lhs.m34*rhs.m43)+(lhs.m44*rhs.m44); + + return result; +} + +bool CATransform3D::isInvertible() const { + return std::abs(m11 * m22 - m12 * m21) >= 0.00000001; +} + +CATransform3D CATransform3D::inverted() const { + return fromNativeTransform(CATransform3DMakeAffineTransform(CGAffineTransformInvert(CATransform3DGetAffineTransform(nativeTransform(*this))))); +} + +bool CGRect::intersects(CGRect const &other) const { + return CGRectIntersectsRect(CGRectMake(x, y, width, height), CGRectMake(other.x, other.y, other.width, other.height)); +} + +bool CGRect::contains(CGRect const &other) const { + return CGRectContainsRect(CGRectMake(x, y, width, height), CGRectMake(other.x, other.y, other.width, other.height)); +} + +CGRect CGRect::intersection(CGRect const &other) const { + auto result = CGRectIntersection(CGRectMake(x, y, width, height), CGRectMake(other.x, other.y, other.width, other.height)); + return CGRect(result.origin.x, result.origin.y, result.size.width, result.size.height); +} + +CGRect CGRect::unionWith(CGRect const &other) const { + auto result = CGRectUnion(CGRectMake(x, y, width, height), CGRectMake(other.x, other.y, other.width, other.height)); + return CGRect(result.origin.x, result.origin.y, result.size.width, result.size.height); +} + +static inline Vector2D applyingTransformToPoint(CATransform3D const &transform, Vector2D const &point) { + double newX = point.x * transform.m11 + point.y * transform.m21 + transform.m41; + double newY = point.x * transform.m12 + point.y * transform.m22 + transform.m42; + double newW = point.x * transform.m14 + point.y * transform.m24 + transform.m44; + + return Vector2D(newX / newW, newY / newW); +} + +CGRect CGRect::applyingTransform(CATransform3D const &transform) const { + if (transform.isIdentity()) { + return *this; + } + + Vector2D topLeft = applyingTransformToPoint(transform, Vector2D(x, y)); + Vector2D topRight = applyingTransformToPoint(transform, Vector2D(x + width, y)); + Vector2D bottomLeft = applyingTransformToPoint(transform, Vector2D(x, y + height)); + Vector2D bottomRight = applyingTransformToPoint(transform, Vector2D(x + width, y + height)); + + double minX = topLeft.x; + if (topRight.x < minX) { + minX = topRight.x; + } + if (bottomLeft.x < minX) { + minX = bottomLeft.x; + } + if (bottomRight.x < minX) { + minX = bottomRight.x; + } + + double minY = topLeft.y; + if (topRight.y < minY) { + minY = topRight.y; + } + if (bottomLeft.y < minY) { + minY = bottomLeft.y; + } + if (bottomRight.y < minY) { + minY = bottomRight.y; + } + + double maxX = topLeft.x; + if (topRight.x > maxX) { + maxX = topRight.x; + } + if (bottomLeft.x > maxX) { + maxX = bottomLeft.x; + } + if (bottomRight.x > maxX) { + maxX = bottomRight.x; + } + + double maxY = topLeft.y; + if (topRight.y > maxY) { + maxY = topRight.y; + } + if (bottomLeft.y > maxY) { + maxY = bottomLeft.y; + } + if (bottomRight.y > maxY) { + maxY = bottomRight.y; + } + + CGRect result(minX, minY, maxX - minX, maxY - minY); + + return result; +} + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/VectorsCocoa.h b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/VectorsCocoa.h new file mode 100644 index 0000000000..9804d2f2f1 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/VectorsCocoa.h @@ -0,0 +1,13 @@ +#ifndef VectorsCocoa_h +#define VectorsCocoa_h + +#import + +namespace lottie { + +::CATransform3D nativeTransform(CATransform3D const &value); +CATransform3D fromNativeTransform(::CATransform3D const &value); + +} + +#endif /* VectorsCocoa_h */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/TextProvider/AnimationTextProvider.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/TextProvider/AnimationTextProvider.cpp new file mode 100644 index 0000000000..6ba1e020c8 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/TextProvider/AnimationTextProvider.cpp @@ -0,0 +1,5 @@ +#include "AnimationTextProvider.hpp" + +namespace lottie { + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/TextProvider/AnimationTextProvider.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/TextProvider/AnimationTextProvider.hpp new file mode 100644 index 0000000000..dc5d324628 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/TextProvider/AnimationTextProvider.hpp @@ -0,0 +1,50 @@ +#ifndef AnimationTextProvider_hpp +#define AnimationTextProvider_hpp + +#include +#include + +namespace lottie { + +/// Text provider is a protocol that is used to supply text to `AnimationView`. +class AnimationTextProvider { +public: + virtual std::string textFor(std::string const &keypathName, std::string const &sourceText) = 0; +}; + +/// Text provider that simply map values from dictionary +class DictionaryTextProvider: public AnimationTextProvider { +public: + DictionaryTextProvider(std::map const &values) : + _values(values) { + } + + virtual std::string textFor(std::string const &keypathName, std::string const &sourceText) override { + const auto it = _values.find(keypathName); + if (it != _values.end()) { + return it->second; + } else { + return sourceText; + } + } + +private: + std::map _values; +}; + +/// Default text provider. Uses text in the animation file +class DefaultTextProvider: public AnimationTextProvider { +public: + DefaultTextProvider() { + } + + virtual ~DefaultTextProvider() = default; + + virtual std::string textFor(std::string const &keypathName, std::string const &sourceText) override { + return sourceText; + } +}; + +} + +#endif /* AnimationTextProvider_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/LottieAnimation.mm b/submodules/TelegramUI/Components/LottieCpp/Sources/LottieAnimation.mm new file mode 100644 index 0000000000..2d25dd7e0f --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/LottieAnimation.mm @@ -0,0 +1,56 @@ +#include + +#include "Lottie/Private/Model/Animation.hpp" + +#include + +@interface LottieAnimation () { +@public + std::shared_ptr _animation; +} + +@end + +@implementation LottieAnimation + +- (instancetype _Nullable)initWithData:(NSData * _Nonnull)data { + self = [super init]; + if (self != nil) { + std::string errorText; + auto json = json11::Json::parse(std::string((uint8_t const *)data.bytes, ((uint8_t const *)data.bytes) + data.length), errorText); + if (!json.is_object()) { + return nil; + } + + try { + _animation = lottie::Animation::fromJson(json.object_items()); + } catch(...) { + return nil; + } + } + return self; +} + +- (NSInteger)frameCount { + return (NSInteger)(_animation->endFrame - _animation->startFrame); +} + +- (CGSize)size { + return CGSizeMake(_animation->width, _animation->height); +} + +- (NSData * _Nonnull)toJson { + json11::Json::object json = _animation->toJson(); + std::string jsonString = json11::Json(json).dump(); + return [[NSData alloc] initWithBytes:jsonString.data() length:jsonString.size()]; +} + +@end + +@implementation LottieAnimation (Internal) + +- (std::shared_ptr)animationImpl { + return _animation; +} + +@end diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/LottieAnimationContainer.mm b/submodules/TelegramUI/Components/LottieCpp/Sources/LottieAnimationContainer.mm new file mode 100644 index 0000000000..9fe84fc5f3 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/LottieAnimationContainer.mm @@ -0,0 +1,595 @@ +#include + +#include "Lottie/Private/MainThread/LayerContainers/MainThreadAnimationLayer.hpp" +#include "LottieAnimationInternal.h" +#include "RenderNode.hpp" +#include "LottieRenderTreeInternal.h" + +namespace lottie { + +struct RenderNodeDesc { + struct LayerParams { + CGRect _bounds; + Vector2D _position; + CATransform3D _transform; + double _opacity; + bool _masksToBounds; + bool _isHidden; + + LayerParams( + CGRect bounds_, + Vector2D position_, + CATransform3D transform_, + double opacity_, + bool masksToBounds_, + bool isHidden_ + ) : + _bounds(bounds_), + _position(position_), + _transform(transform_), + _opacity(opacity_), + _masksToBounds(masksToBounds_), + _isHidden(isHidden_) { + } + + LayerParams(std::shared_ptr const &layer) : + _bounds(layer->bounds()), + _position(layer->position()), + _transform(layer->transform()), + _opacity(layer->opacity()), + _masksToBounds(layer->masksToBounds()), + _isHidden(layer->isHidden()) { + } + + CGRect bounds() const { + return _bounds; + } + + Vector2D position() const { + return _position; + } + + CATransform3D transform() const { + return _transform; + } + + double opacity() const { + return _opacity; + } + + bool masksToBounds() const { + return _masksToBounds; + } + + bool isHidden() const { + return _isHidden; + } + }; + + LayerParams layer; + CGRect globalRect; + CGRect localRect; + CATransform3D globalTransform; + bool drawsContent; + bool renderContent; + int drawContentDescendants; + bool isInvertedMatte; + std::shared_ptr mask; + + explicit RenderNodeDesc( + LayerParams const &layer_, + CGRect const &globalRect_, + CGRect const &localRect_, + CATransform3D const &globalTransform_, + bool drawsContent_, + bool renderContent_, + int drawContentDescendants_, + bool isInvertedMatte_ + ) : + layer(layer_), + globalRect(globalRect_), + localRect(localRect_), + globalTransform(globalTransform_), + drawsContent(drawsContent_), + renderContent(renderContent_), + drawContentDescendants(drawContentDescendants_), + isInvertedMatte(isInvertedMatte_) { + } +}; + +/*static std::optional processRenderTree(std::shared_ptr const &node, Vector2D const &globalSize, CATransform3D const &parentTransform, bool isInvertedMask, BezierPathsBoundingBoxContext &bezierPathsBoundingBoxContext) { + if (node->isHidden() || node->alpha() == 0.0f) { + return std::nullopt; + } + + if (node->masksToBounds()) { + if (node->bounds().empty()) { + return std::nullopt; + } + } + + auto currentTransform = parentTransform; + + Vector2D localTranslation(node->position().x + -node->bounds().x, node->position().y + -node->bounds().y); + CATransform3D localTransform = node->transform(); + if (localTransform.isIdentity()) { + currentTransform.m41 += localTranslation.x; + currentTransform.m42 += localTranslation.y; + + localTransform.m41 += localTranslation.x; + localTransform.m42 += localTranslation.y; + } else { + localTransform.m41 += localTranslation.x; + localTransform.m42 += localTranslation.y; + + currentTransform = localTransform * currentTransform; + } + + if (!currentTransform.isInvertible()) { + return std::nullopt; + } + + std::optional effectiveLocalBounds; + + double alpha = node->alpha(); + + if (node->content()) { + RenderTreeNodeContent *shapeContent = node->content().get(); + + CGRect shapeBounds = bezierPathsBoundingBoxParallel(bezierPathsBoundingBoxContext, shapeContent->paths); + + if (shapeContent->stroke) { + shapeBounds = shapeBounds.insetBy(-shapeContent->stroke->lineWidth / 2.0, -shapeContent->stroke->lineWidth / 2.0); + effectiveLocalBounds = shapeBounds; + + switch (shapeContent->stroke->shading->type()) { + case RenderTreeNodeContent::ShadingType::Solid: { + RenderTreeNodeContent::SolidShading *solidShading = (RenderTreeNodeContent::SolidShading *)shapeContent->stroke->shading.get(); + + alpha *= solidShading->opacity; + + break; + } + case RenderTreeNodeContent::ShadingType::Gradient: { + + break; + } + default: + break; + } + } else if (shapeContent->fill) { + effectiveLocalBounds = shapeBounds; + + switch (shapeContent->fill->shading->type()) { + case RenderTreeNodeContent::ShadingType::Solid: { + RenderTreeNodeContent::SolidShading *solidShading = (RenderTreeNodeContent::SolidShading *)shapeContent->fill->shading.get(); + + alpha *= solidShading->opacity; + + break; + } + case RenderTreeNodeContent::ShadingType::Gradient: { + RenderTreeNodeContent::GradientShading *gradientShading = (RenderTreeNodeContent::GradientShading *)shapeContent->fill->shading.get(); + + alpha *= gradientShading->opacity; + + break; + } + default: + break; + } + } + } + + bool isInvertedMatte = isInvertedMask; + if (isInvertedMatte) { + effectiveLocalBounds = node->bounds(); + } + + if (effectiveLocalBounds && effectiveLocalBounds->empty()) { + effectiveLocalBounds = std::nullopt; + } + + std::optional effectiveLocalRect; + if (effectiveLocalBounds.has_value()) { + effectiveLocalRect = effectiveLocalBounds; + } + + std::vector> subnodes; + std::optional subnodesGlobalRect; + bool masksToBounds = node->masksToBounds(); + + int drawContentDescendants = 0; + + for (const auto &item : node->subnodes()) { + if (const auto subnode = processRenderTree(item, globalSize, currentTransform, false, bezierPathsBoundingBoxContext)) { + drawContentDescendants += subnode->drawContentDescendants; + + if (subnode->renderContent) { + drawContentDescendants += 1; + } + + if (!subnode->localRect.empty()) { + if (effectiveLocalRect.has_value()) { + effectiveLocalRect = effectiveLocalRect->unionWith(subnode->localRect); + } else { + effectiveLocalRect = subnode->localRect; + } + } + + if (subnodesGlobalRect) { + subnodesGlobalRect = subnodesGlobalRect->unionWith(subnode->globalRect); + } else { + subnodesGlobalRect = subnode->globalRect; + } + } + } + + if (masksToBounds && effectiveLocalRect.has_value()) { + if (node->bounds().contains(effectiveLocalRect.value())) { + masksToBounds = false; + } + } + + std::optional fuzzyGlobalRect; + + if (effectiveLocalBounds) { + CGRect effectiveGlobalBounds = effectiveLocalBounds->applyingTransform(currentTransform); + if (fuzzyGlobalRect) { + fuzzyGlobalRect = fuzzyGlobalRect->unionWith(effectiveGlobalBounds); + } else { + fuzzyGlobalRect = effectiveGlobalBounds; + } + } + + if (subnodesGlobalRect) { + if (fuzzyGlobalRect) { + fuzzyGlobalRect = fuzzyGlobalRect->unionWith(subnodesGlobalRect.value()); + } else { + fuzzyGlobalRect = subnodesGlobalRect; + } + } + + if (!fuzzyGlobalRect) { + return std::nullopt; + } + + CGRect globalRect( + std::floor(fuzzyGlobalRect->x), + std::floor(fuzzyGlobalRect->y), + std::ceil(fuzzyGlobalRect->width + fuzzyGlobalRect->x - floor(fuzzyGlobalRect->x)), + std::ceil(fuzzyGlobalRect->height + fuzzyGlobalRect->y - floor(fuzzyGlobalRect->y)) + ); + + if (!CGRect(0.0, 0.0, globalSize.x, globalSize.y).intersects(globalRect)) { + return std::nullopt; + } + + if (masksToBounds && effectiveLocalBounds) { + CGRect effectiveGlobalBounds = effectiveLocalBounds->applyingTransform(currentTransform); + if (effectiveGlobalBounds.contains(CGRect(0.0, 0.0, globalSize.x, globalSize.y))) { + masksToBounds = false; + } + } + + std::optional maskNode; + if (node->mask()) { + if (const auto maskNodeValue = processRenderTree(node->mask(), globalSize, currentTransform, node->invertMask(), bezierPathsBoundingBoxContext)) { + if (!maskNodeValue->globalRect.intersects(globalRect)) { + return std::nullopt; + } + maskNode = maskNodeValue; + } else { + return std::nullopt; + } + } + + CGRect localRect = effectiveLocalRect.value_or(CGRect(0.0, 0.0, 0.0, 0.0)).applyingTransform(localTransform); + + return RenderNodeDesc( + RenderNodeDesc::LayerParams( + node->bounds(), + node->position(), + node->transform(), + alpha, + masksToBounds, + node->isHidden() + ), + globalRect, + localRect, + currentTransform, + effectiveLocalBounds.has_value(), + node->content() != nullptr, + drawContentDescendants, + isInvertedMatte + ); +}*/ + +static std::shared_ptr convertRenderTree(std::shared_ptr const &node, Vector2D const &globalSize, CATransform3D const &parentTransform, bool isInvertedMask, BezierPathsBoundingBoxContext &bezierPathsBoundingBoxContext) { + if (node->isHidden() || node->alpha() == 0.0f) { + return nullptr; + } + + if (node->masksToBounds()) { + if (node->bounds().empty()) { + return nullptr; + } + } + + auto currentTransform = parentTransform; + + Vector2D localTranslation(node->position().x + -node->bounds().x, node->position().y + -node->bounds().y); + CATransform3D localTransform = node->transform(); + localTransform = localTransform.translated(localTranslation); + //if (localTransform.isIdentity()) { + // localTransform.m41 += localTranslation.x; + // localTransform.m42 += localTranslation.y; + //} else { + // localTransform.m41 += localTranslation.x; + // localTransform.m42 += localTranslation.y; + //} + + currentTransform = localTransform * currentTransform; + + if (!currentTransform.isInvertible()) { + return nullptr; + } + + std::optional effectiveLocalBounds; + + double alpha = node->alpha(); + + if (node->content()) { + RenderTreeNodeContent *shapeContent = node->content().get(); + + CGRect shapeBounds = bezierPathsBoundingBoxParallel(bezierPathsBoundingBoxContext, shapeContent->paths); + + if (shapeContent->stroke) { + shapeBounds = shapeBounds.insetBy(-shapeContent->stroke->lineWidth / 2.0, -shapeContent->stroke->lineWidth / 2.0); + effectiveLocalBounds = shapeBounds; + + switch (shapeContent->stroke->shading->type()) { + case RenderTreeNodeContent::ShadingType::Solid: { + RenderTreeNodeContent::SolidShading *solidShading = (RenderTreeNodeContent::SolidShading *)shapeContent->stroke->shading.get(); + + alpha *= solidShading->opacity; + + break; + } + case RenderTreeNodeContent::ShadingType::Gradient: { + + break; + } + default: + break; + } + } else if (shapeContent->fill) { + effectiveLocalBounds = shapeBounds; + + switch (shapeContent->fill->shading->type()) { + case RenderTreeNodeContent::ShadingType::Solid: { + RenderTreeNodeContent::SolidShading *solidShading = (RenderTreeNodeContent::SolidShading *)shapeContent->fill->shading.get(); + + alpha *= solidShading->opacity; + + break; + } + case RenderTreeNodeContent::ShadingType::Gradient: { + RenderTreeNodeContent::GradientShading *gradientShading = (RenderTreeNodeContent::GradientShading *)shapeContent->fill->shading.get(); + + alpha *= gradientShading->opacity; + + break; + } + default: + break; + } + } + } + + bool isInvertedMatte = isInvertedMask; + if (isInvertedMatte) { + effectiveLocalBounds = node->bounds(); + } + + if (effectiveLocalBounds && effectiveLocalBounds->empty()) { + effectiveLocalBounds = std::nullopt; + } + + std::optional effectiveLocalRect; + if (effectiveLocalBounds.has_value()) { + effectiveLocalRect = effectiveLocalBounds; + } + + std::vector> subnodes; + std::optional subnodesGlobalRect; + bool masksToBounds = node->masksToBounds(); + + int drawContentDescendants = 0; + + for (const auto &item : node->subnodes()) { + if (const auto subnode = convertRenderTree(item, globalSize, currentTransform, false, bezierPathsBoundingBoxContext)) { + subnodes.push_back(subnode); + + drawContentDescendants += subnode->drawContentDescendants; + + if (subnode->renderContent) { + drawContentDescendants += 1; + } + + if (!subnode->localRect.empty()) { + if (effectiveLocalRect.has_value()) { + effectiveLocalRect = effectiveLocalRect->unionWith(subnode->localRect); + } else { + effectiveLocalRect = subnode->localRect; + } + } + + if (subnodesGlobalRect) { + subnodesGlobalRect = subnodesGlobalRect->unionWith(subnode->globalRect); + } else { + subnodesGlobalRect = subnode->globalRect; + } + } + } + + if (masksToBounds && effectiveLocalRect.has_value()) { + if (node->bounds().contains(effectiveLocalRect.value())) { + masksToBounds = false; + } + } + + std::optional fuzzyGlobalRect; + + if (effectiveLocalBounds) { + CGRect effectiveGlobalBounds = effectiveLocalBounds->applyingTransform(currentTransform); + if (fuzzyGlobalRect) { + fuzzyGlobalRect = fuzzyGlobalRect->unionWith(effectiveGlobalBounds); + } else { + fuzzyGlobalRect = effectiveGlobalBounds; + } + } + + if (subnodesGlobalRect) { + if (fuzzyGlobalRect) { + fuzzyGlobalRect = fuzzyGlobalRect->unionWith(subnodesGlobalRect.value()); + } else { + fuzzyGlobalRect = subnodesGlobalRect; + } + } + + if (!fuzzyGlobalRect) { + return nullptr; + } + + CGRect globalRect( + std::floor(fuzzyGlobalRect->x), + std::floor(fuzzyGlobalRect->y), + std::ceil(fuzzyGlobalRect->width + fuzzyGlobalRect->x - floor(fuzzyGlobalRect->x)), + std::ceil(fuzzyGlobalRect->height + fuzzyGlobalRect->y - floor(fuzzyGlobalRect->y)) + ); + + if (!CGRect(0.0, 0.0, globalSize.x, globalSize.y).intersects(globalRect)) { + return nullptr; + } + + if (masksToBounds && effectiveLocalBounds) { + CGRect effectiveGlobalBounds = effectiveLocalBounds->applyingTransform(currentTransform); + if (effectiveGlobalBounds.contains(CGRect(0.0, 0.0, globalSize.x, globalSize.y))) { + masksToBounds = false; + } + } + + std::shared_ptr maskNode; + if (node->mask()) { + if (const auto maskNodeValue = convertRenderTree(node->mask(), globalSize, currentTransform, node->invertMask(), bezierPathsBoundingBoxContext)) { + if (!maskNodeValue->globalRect.intersects(globalRect)) { + return nullptr; + } + maskNode = maskNodeValue; + } else { + return nullptr; + } + } + + CGRect localRect = effectiveLocalRect.value_or(CGRect(0.0, 0.0, 0.0, 0.0)).applyingTransform(localTransform); + + return std::make_shared( + OutputRenderNode::LayerParams( + node->bounds(), + node->position(), + node->transform(), + alpha, + masksToBounds, + node->isHidden() + ), + globalRect, + localRect, + currentTransform, + effectiveLocalBounds.has_value(), + node->content(), + drawContentDescendants, + isInvertedMatte, + subnodes, + maskNode + ); +} + +} + +@interface LottieAnimationContainer () { +@public + std::shared_ptr _layer; + std::shared_ptr _bezierPathsBoundingBoxContext; +} + +@end + +@implementation LottieAnimationContainer + +- (instancetype _Nonnull)initWithAnimation:(LottieAnimation * _Nonnull)animation { + self = [super init]; + if (self != nil) { + _bezierPathsBoundingBoxContext = std::make_shared(); + + _animation = animation; + + _layer = std::make_shared( + *[animation animationImpl].get(), + std::make_shared(), + std::make_shared(), + std::make_shared() + ); + } + return self; +} + +- (void)update:(NSInteger)frame { + _layer->setCurrentFrame(frame); +} + +- (LottieRenderNode * _Nonnull)getCurrentRenderTreeForSize:(CGSize)size { + auto renderNode = _layer->renderTreeNode(); + if (!renderNode) { + return nil; + } + + //processRenderTree(renderNode, lottie::Vector2D((int)size.width, (int)size.height), lottie::CATransform3D::identity().scaled(lottie::Vector2D(size.width / (double)_animation.size.width, size.height / (double)_animation.size.height)), false, *_bezierPathsBoundingBoxContext.get()); + + auto node = convertRenderTree(renderNode, lottie::Vector2D((int)size.width, (int)size.height), lottie::CATransform3D::identity().scaled(lottie::Vector2D(size.width / (double)_animation.size.width, size.height / (double)_animation.size.height)), false, *_bezierPathsBoundingBoxContext.get()); + + if (node) { + return [[LottieRenderNode alloc] initWithRenderNode:node]; + } else { + node = std::make_shared( + lottie::OutputRenderNode::LayerParams( + lottie::CGRect(0.0, 0.0, size.width, size.height), + lottie::Vector2D(0.0, 0.0), + lottie::CATransform3D::identity(), + 1.0, + false, + false + ), + lottie::CGRect(0.0, 0.0, size.width, size.height), + lottie::CGRect(0.0, 0.0, size.width, size.height), + lottie::CATransform3D::identity(), + false, + nullptr, + true, + false, + std::vector>(), + nullptr + ); + return [[LottieRenderNode alloc] initWithRenderNode:node]; + } +} + +@end + +@implementation LottieAnimationContainer (Internal) + +- (std::shared_ptr)layer { + return _layer; +} + +@end diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/LottieAnimationContainerInternal.h b/submodules/TelegramUI/Components/LottieCpp/Sources/LottieAnimationContainerInternal.h new file mode 100644 index 0000000000..c5f3a105cc --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/LottieAnimationContainerInternal.h @@ -0,0 +1,13 @@ +#ifndef LottieAnimationContainerInternal_h +#define LottieAnimationContainerInternal_h + +#include "Lottie/Private/MainThread/LayerContainers/MainThreadAnimationLayer.hpp" +#include + +@interface LottieAnimationContainer (Internal) + +@property (nonatomic, readonly) std::shared_ptr layer; + +@end + +#endif /* LottieAnimationContainerInternal_h */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/LottieAnimationInternal.h b/submodules/TelegramUI/Components/LottieCpp/Sources/LottieAnimationInternal.h new file mode 100644 index 0000000000..d314f023d5 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/LottieAnimationInternal.h @@ -0,0 +1,15 @@ +#ifndef LottieAnimationInternal_h +#define LottieAnimationInternal_h + +#include +#include "Lottie/Private/Model/Animation.hpp" + +#include + +@interface LottieAnimation (Internal) + +- (std::shared_ptr)animationImpl; + +@end + +#endif /* LottieAnimationInternal_h */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/LottieRenderTree.mm b/submodules/TelegramUI/Components/LottieCpp/Sources/LottieRenderTree.mm new file mode 100644 index 0000000000..19bfcb3f57 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/LottieRenderTree.mm @@ -0,0 +1,370 @@ +#include +#include "LottieRenderTreeInternal.h" + +#include "Lottie/Public/Primitives/CGPath.hpp" +#include "Lottie/Public/Primitives/CGPathCocoa.h" +#include "Lottie/Public/Primitives/Color.hpp" +#include "Lottie/Public/Primitives/CALayer.hpp" +#include "Lottie/Public/Primitives/VectorsCocoa.h" + +#include "RenderNode.hpp" + +namespace { + +} + +@interface LottiePath () { + std::vector _paths; +} + +@end + +@implementation LottiePath + +- (instancetype)initWithPaths:(std::vector)paths __attribute__((objc_direct)) { + self = [super init]; + if (self != nil) { + _paths = paths; + } + return self; +} + +/*- (instancetype _Nonnull)initWithCGPath:(CGPathRef _Nonnull)cgPath { + self = [super init]; + if (self != nil) { + CGMutablePathRef mutableCopy = CGPathCreateMutableCopy(cgPath); + _path = std::make_shared(mutableCopy); + CFRelease(mutableCopy); + } + return self; +}*/ + +- (CGRect)boundingBox { + lottie::CGRect result = bezierPathsBoundingBox(_paths); + return CGRectMake(result.x, result.y, result.width, result.height); +} + +- (void)enumerateItems:(void (^ _Nonnull)(LottiePathItem * _Nonnull))iterate { + LottiePathItem item; + + for (const auto &path : _paths) { + std::optional previousElement; + for (const auto &element : path.elements()) { + if (previousElement.has_value()) { + if (previousElement->vertex.outTangentRelative().isZero() && element.vertex.inTangentRelative().isZero()) { + item.type = LottiePathItemTypeLineTo; + item.points[0] = CGPointMake(element.vertex.point.x, element.vertex.point.y); + iterate(&item); + } else { + item.type = LottiePathItemTypeCurveTo; + item.points[2] = CGPointMake(element.vertex.point.x, element.vertex.point.y); + item.points[1] = CGPointMake(element.vertex.inTangent.x, element.vertex.inTangent.y); + item.points[0] = CGPointMake(previousElement->vertex.outTangent.x, previousElement->vertex.outTangent.y); + iterate(&item); + } + } else { + item.type = LottiePathItemTypeMoveTo; + item.points[0] = CGPointMake(element.vertex.point.x, element.vertex.point.y); + iterate(&item); + } + previousElement = element; + } + if (path.closed().value_or(true)) { + item.type = LottiePathItemTypeClose; + iterate(&item); + } + } + + /*_path->enumerate([iterate](lottie::CGPathItem const &element) { + LottiePathItem item; + + switch (element.type) { + case lottie::CGPathItem::Type::MoveTo: { + item.type = LottiePathItemTypeMoveTo; + item.points[0] = CGPointMake(element.points[0].x, element.points[0].y); + iterate(&item); + break; + } + case lottie::CGPathItem::Type::LineTo: { + item.type = LottiePathItemTypeLineTo; + item.points[0] = CGPointMake(element.points[0].x, element.points[0].y); + iterate(&item); + break; + } + case lottie::CGPathItem::Type::CurveTo: { + item.type = LottiePathItemTypeCurveTo; + item.points[0] = CGPointMake(element.points[0].x, element.points[0].y); + item.points[1] = CGPointMake(element.points[1].x, element.points[1].y); + item.points[2] = CGPointMake(element.points[2].x, element.points[2].y); + iterate(&item); + break; + } + case lottie::CGPathItem::Type::Close: { + item.type = LottiePathItemTypeClose; + iterate(&item); + break; + } + } + });*/ +} + +@end + +@implementation LottieColorStop : NSObject + +- (instancetype _Nonnull)initWithColor:(LottieColor)color location:(CGFloat)location __attribute__((objc_direct)) { + self = [super init]; + if (self != nil) { + _color = color; + _location = location; + } + return self; +} + +@end + +@implementation LottieRenderContentShading + +- (instancetype _Nonnull)init { + self = [super init]; + if (self != nil) { + } + return self; +} + +@end + +static LottieColor lottieColorFromColor(lottie::Color color) { + LottieColor result; + result.r = color.r; + result.g = color.g; + result.b = color.b; + result.a = color.a; + + return result; +} + +@implementation LottieRenderContentSolidShading + +- (instancetype _Nonnull)initWithSolidShading:(lottie::RenderTreeNodeContent::SolidShading *)solidShading __attribute__((objc_direct)) { + self = [super init]; + if (self != nil) { + _color = lottieColorFromColor(solidShading->color); + _opacity = solidShading->opacity; + } + return self; +} + +@end + +@implementation LottieRenderContentGradientShading + +- (instancetype _Nonnull)initWithGradientShading:(lottie::RenderTreeNodeContent::GradientShading *)gradientShading __attribute__((objc_direct)) { + self = [super init]; + if (self != nil) { + _opacity = gradientShading->opacity; + + switch (gradientShading->gradientType) { + case lottie::GradientType::Radial: { + _gradientType = LottieGradientTypeRadial; + break; + } + default: { + _gradientType = LottieGradientTypeLinear; + break; + } + } + + NSMutableArray *colorStops = [[NSMutableArray alloc] initWithCapacity:gradientShading->colors.size()]; + for (size_t i = 0; i < gradientShading->colors.size(); i++) { + [colorStops addObject:[[LottieColorStop alloc] initWithColor:lottieColorFromColor(gradientShading->colors[i]) location:gradientShading->locations[i]]]; + } + _colorStops = colorStops; + + _start = CGPointMake(gradientShading->start.x, gradientShading->start.y); + _end = CGPointMake(gradientShading->end.x, gradientShading->end.y); + } + return self; +} + +@end + +@implementation LottieRenderContentFill + +- (instancetype _Nonnull)initWithFill:(std::shared_ptr const &)fill __attribute__((objc_direct)) { + self = [super init]; + if (self != nil) { + switch (fill->shading->type()) { + case lottie::RenderTreeNodeContent::ShadingType::Solid: { + _shading = [[LottieRenderContentSolidShading alloc] initWithSolidShading:(lottie::RenderTreeNodeContent::SolidShading *)fill->shading.get()]; + break; + } + case lottie::RenderTreeNodeContent::ShadingType::Gradient: { + _shading = [[LottieRenderContentGradientShading alloc] initWithGradientShading:(lottie::RenderTreeNodeContent::GradientShading *)fill->shading.get()]; + break; + } + default: { + abort(); + } + } + + switch (fill->rule) { + case lottie::FillRule::EvenOdd: { + _fillRule = LottieFillRuleEvenOdd; + break; + } + default: { + _fillRule = LottieFillRuleWinding; + break; + } + } + } + return self; +} + +@end + +@implementation LottieRenderContentStroke + +- (instancetype _Nonnull)initWithStroke:(std::shared_ptr const &)stroke __attribute__((objc_direct)) { + self = [super init]; + if (self != nil) { + switch (stroke->shading->type()) { + case lottie::RenderTreeNodeContent::ShadingType::Solid: { + _shading = [[LottieRenderContentSolidShading alloc] initWithSolidShading:(lottie::RenderTreeNodeContent::SolidShading *)stroke->shading.get()]; + break; + } + case lottie::RenderTreeNodeContent::ShadingType::Gradient: { + _shading = [[LottieRenderContentGradientShading alloc] initWithGradientShading:(lottie::RenderTreeNodeContent::GradientShading *)stroke->shading.get()]; + break; + } + default: { + abort(); + } + } + + _lineWidth = stroke->lineWidth; + + switch (stroke->lineJoin) { + case lottie::LineJoin::Miter: { + _lineJoin = kCGLineJoinMiter; + break; + } + case lottie::LineJoin::Round: { + _lineJoin = kCGLineJoinRound; + break; + } + case lottie::LineJoin::Bevel: { + _lineJoin = kCGLineJoinBevel; + break; + } + default: { + _lineJoin = kCGLineJoinBevel; + break; + } + } + + switch (stroke->lineCap) { + case lottie::LineCap::Butt: { + _lineCap = kCGLineCapButt; + break; + } + case lottie::LineCap::Round: { + _lineCap = kCGLineCapRound; + break; + } + case lottie::LineCap::Square: { + _lineCap = kCGLineCapSquare; + break; + } + default: { + _lineCap = kCGLineCapSquare; + break; + } + } + + _miterLimit = stroke->miterLimit; + + _dashPhase = stroke->dashPhase; + + if (!stroke->dashPattern.empty()) { + NSMutableArray *dashPattern = [[NSMutableArray alloc] initWithCapacity:stroke->dashPattern.size()]; + for (auto value : stroke->dashPattern) { + [dashPattern addObject:@(value)]; + } + _dashPattern = dashPattern; + } + } + return self; +} + +@end + +@implementation LottieRenderContent + +- (instancetype _Nonnull)initWithRenderContent:(std::shared_ptr const &)content __attribute__((objc_direct)) { + self = [super init]; + if (self != nil) { + _path = [[LottiePath alloc] initWithPaths:content->paths]; + if (content->stroke) { + _stroke = [[LottieRenderContentStroke alloc] initWithStroke:content->stroke]; + } + if (content->fill) { + _fill = [[LottieRenderContentFill alloc] initWithFill:content->fill]; + } + } + return self; +} + +@end + +@implementation LottieRenderNode + +@end + +@implementation LottieRenderNode (Internal) + +- (instancetype _Nonnull)initWithRenderNode:(std::shared_ptr const &)renderNode __attribute__((objc_direct)) { + self = [super init]; + if (self != nil) { + auto position = renderNode->layer.position(); + _position = CGPointMake(position.x, position.y); + + auto bounds = renderNode->layer.bounds(); + _bounds = CGRectMake(bounds.x, bounds.y, bounds.width, bounds.height); + + _transform = lottie::nativeTransform(renderNode->layer.transform()); + _opacity = renderNode->layer.opacity(); + _masksToBounds = renderNode->layer.masksToBounds(); + _isHidden = renderNode->layer.isHidden(); + + auto globalRect = renderNode->globalRect; + _globalRect = CGRectMake(globalRect.x, globalRect.y, globalRect.width, globalRect.height); + + _globalTransform = lottie::nativeTransform(renderNode->globalTransform); + + if (renderNode->renderContent) { + _renderContent = [[LottieRenderContent alloc] initWithRenderContent:renderNode->renderContent]; + } + + _hasSimpleContents = renderNode->drawContentDescendants <= 1; + _isInvertedMatte = renderNode->isInvertedMatte; + + if (!renderNode->subnodes.empty()) { + NSMutableArray *subnodes = [[NSMutableArray alloc] init]; + for (const auto &subnode : renderNode->subnodes) { + [subnodes addObject:[[LottieRenderNode alloc] initWithRenderNode:subnode]]; + } + _subnodes = subnodes; + } else { + _subnodes = [[NSArray alloc] init]; + } + + if (renderNode->mask) { + _mask = [[LottieRenderNode alloc] initWithRenderNode:renderNode->mask]; + } + } + return self; +} + +@end diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/LottieRenderTreeInternal.h b/submodules/TelegramUI/Components/LottieCpp/Sources/LottieRenderTreeInternal.h new file mode 100644 index 0000000000..a8b93f2746 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/LottieRenderTreeInternal.h @@ -0,0 +1,15 @@ +#ifndef LottieRenderTreeInternal_h +#define LottieRenderTreeInternal_h + +#include +#import "RenderNode.hpp" + +#include + +@interface LottieRenderNode (Internal) + +- (instancetype _Nonnull)initWithRenderNode:(std::shared_ptr const &)renderNode __attribute__((objc_direct)); + +@end + +#endif /* LottieRenderTreeInternal_h */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/RenderNode.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/RenderNode.cpp new file mode 100644 index 0000000000..81dd5562e4 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/RenderNode.cpp @@ -0,0 +1,29 @@ +#include "RenderNode.hpp" + +namespace lottie { + +OutputRenderNode::OutputRenderNode( + LayerParams const &layer_, + CGRect const &globalRect_, + CGRect const &localRect_, + CATransform3D const &globalTransform_, + bool drawsContent_, + std::shared_ptr renderContent_, + int drawContentDescendants_, + bool isInvertedMatte_, + std::vector> const &subnodes_, + std::shared_ptr const &mask_ +) : +layer(layer_), +globalRect(globalRect_), +localRect(localRect_), +globalTransform(globalTransform_), +drawsContent(drawsContent_), +renderContent(renderContent_), +drawContentDescendants(drawContentDescendants_), +isInvertedMatte(isInvertedMatte_), +subnodes(subnodes_), +mask(mask_) { +} + +} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/RenderNode.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/RenderNode.hpp new file mode 100644 index 0000000000..5888390c48 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/RenderNode.hpp @@ -0,0 +1,94 @@ +#ifndef RenderNode_hpp +#define RenderNode_hpp + +#include "Lottie/Public/Primitives/CALayer.hpp" + +namespace lottie { + +struct OutputRenderNode { + struct LayerParams { + CGRect _bounds; + Vector2D _position; + CATransform3D _transform; + double _opacity; + bool _masksToBounds; + bool _isHidden; + + LayerParams( + CGRect bounds_, + Vector2D position_, + CATransform3D transform_, + double opacity_, + bool masksToBounds_, + bool isHidden_ + ) : + _bounds(bounds_), + _position(position_), + _transform(transform_), + _opacity(opacity_), + _masksToBounds(masksToBounds_), + _isHidden(isHidden_) { + } + + LayerParams(std::shared_ptr const &layer) : + _bounds(layer->bounds()), + _position(layer->position()), + _transform(layer->transform()), + _opacity(layer->opacity()), + _masksToBounds(layer->masksToBounds()), + _isHidden(layer->isHidden()) { + } + + CGRect bounds() const { + return _bounds; + } + + Vector2D position() const { + return _position; + } + + CATransform3D transform() const { + return _transform; + } + + double opacity() const { + return _opacity; + } + + bool masksToBounds() const { + return _masksToBounds; + } + + bool isHidden() const { + return _isHidden; + } + }; + + LayerParams layer; + CGRect globalRect; + CGRect localRect; + CATransform3D globalTransform; + bool drawsContent; + std::shared_ptr renderContent; + int drawContentDescendants; + bool isInvertedMatte; + std::vector> subnodes; + std::shared_ptr mask; + + explicit OutputRenderNode( + LayerParams const &layer_, + CGRect const &globalRect_, + CGRect const &localRect_, + CATransform3D const &globalTransform_, + bool drawsContent_, + std::shared_ptr renderContent_, + int drawContentDescendants_, + bool isInvertedMatte_, + std::vector> const &subnodes_, + std::shared_ptr const &mask_ + ); +}; + +} + +#endif /* RenderNode_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/lottiejson11/lottiejson11.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/lottiejson11/lottiejson11.cpp new file mode 100644 index 0000000000..c826a925d0 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/lottiejson11/lottiejson11.cpp @@ -0,0 +1,790 @@ +/* Copyright (c) 2013 Dropbox, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "lottiejson11.hpp" +#include +#include +#include +#include +#include + +namespace lottiejson11 { + +static const int max_depth = 200; + +using std::string; +using std::vector; +using std::map; +using std::make_shared; +using std::initializer_list; +using std::move; + +/* Helper for representing null - just a do-nothing struct, plus comparison + * operators so the helpers in JsonValue work. We can't use nullptr_t because + * it may not be orderable. + */ +struct NullStruct { + bool operator==(NullStruct) const { return true; } + bool operator<(NullStruct) const { return false; } +}; + +/* * * * * * * * * * * * * * * * * * * * + * Serialization + */ + +static void dump(NullStruct, string &out) { + out += "null"; +} + +static void dump(double value, string &out) { + if (std::isfinite(value)) { + char buf[32]; + snprintf(buf, sizeof buf, "%.17g", value); + out += buf; + } else { + out += "null"; + } +} + +static void dump(int value, string &out) { + char buf[32]; + snprintf(buf, sizeof buf, "%d", value); + out += buf; +} + +static void dump(bool value, string &out) { + out += value ? "true" : "false"; +} + +static void dump(const string &value, string &out) { + out += '"'; + for (size_t i = 0; i < value.length(); i++) { + const char ch = value[i]; + if (ch == '\\') { + out += "\\\\"; + } else if (ch == '"') { + out += "\\\""; + } else if (ch == '\b') { + out += "\\b"; + } else if (ch == '\f') { + out += "\\f"; + } else if (ch == '\n') { + out += "\\n"; + } else if (ch == '\r') { + out += "\\r"; + } else if (ch == '\t') { + out += "\\t"; + } else if (static_cast(ch) <= 0x1f) { + char buf[8]; + snprintf(buf, sizeof buf, "\\u%04x", ch); + out += buf; + } else if (static_cast(ch) == 0xe2 && static_cast(value[i+1]) == 0x80 + && static_cast(value[i+2]) == 0xa8) { + out += "\\u2028"; + i += 2; + } else if (static_cast(ch) == 0xe2 && static_cast(value[i+1]) == 0x80 + && static_cast(value[i+2]) == 0xa9) { + out += "\\u2029"; + i += 2; + } else { + out += ch; + } + } + out += '"'; +} + +static void dump(const Json::array &values, string &out) { + bool first = true; + out += "["; + for (const auto &value : values) { + if (!first) + out += ", "; + value.dump(out); + first = false; + } + out += "]"; +} + +static void dump(const Json::object &values, string &out) { + bool first = true; + out += "{"; + for (const auto &kv : values) { + if (!first) + out += ", "; + dump(kv.first, out); + out += ": "; + kv.second.dump(out); + first = false; + } + out += "}"; +} + +void Json::dump(string &out) const { + m_ptr->dump(out); +} + +/* * * * * * * * * * * * * * * * * * * * + * Value wrappers + */ + +template +class Value : public JsonValue { +protected: + + // Constructors + explicit Value(const T &value) : m_value(value) {} + explicit Value(T &&value) : m_value(std::move(value)) {} + + // Get type tag + Json::Type type() const override { + return tag; + } + + // Comparisons + bool equals(const JsonValue * other) const override { + return m_value == static_cast *>(other)->m_value; + } + bool less(const JsonValue * other) const override { + return m_value < static_cast *>(other)->m_value; + } + + const T m_value; + void dump(string &out) const override { lottiejson11::dump(m_value, out); } +}; + +class JsonDouble final : public Value { + double number_value() const override { return m_value; } + int int_value() const override { return static_cast(m_value); } + bool equals(const JsonValue * other) const override { return m_value == other->number_value(); } + bool less(const JsonValue * other) const override { return m_value < other->number_value(); } +public: + explicit JsonDouble(double value) : Value(value) {} +}; + +class JsonInt final : public Value { + double number_value() const override { return m_value; } + int int_value() const override { return m_value; } + bool equals(const JsonValue * other) const override { return m_value == other->number_value(); } + bool less(const JsonValue * other) const override { return m_value < other->number_value(); } +public: + explicit JsonInt(int value) : Value(value) {} +}; + +class JsonBoolean final : public Value { + bool bool_value() const override { return m_value; } +public: + explicit JsonBoolean(bool value) : Value(value) {} +}; + +class JsonString final : public Value { + const string &string_value() const override { return m_value; } +public: + explicit JsonString(const string &value) : Value(value) {} + explicit JsonString(string &&value) : Value(std::move(value)) {} +}; + +class JsonArray final : public Value { + const Json::array &array_items() const override { return m_value; } + const Json & operator[](size_t i) const override; +public: + explicit JsonArray(const Json::array &value) : Value(value) {} + explicit JsonArray(Json::array &&value) : Value(std::move(value)) {} +}; + +class JsonObject final : public Value { + const Json::object &object_items() const override { return m_value; } + const Json & operator[](const string &key) const override; +public: + explicit JsonObject(const Json::object &value) : Value(value) {} + explicit JsonObject(Json::object &&value) : Value(std::move(value)) {} +}; + +class JsonNull final : public Value { +public: + JsonNull() : Value({}) {} +}; + +/* * * * * * * * * * * * * * * * * * * * + * Static globals - static-init-safe + */ +struct Statics { + const std::shared_ptr null = make_shared(); + const std::shared_ptr t = make_shared(true); + const std::shared_ptr f = make_shared(false); + const string empty_string; + const vector empty_vector; + const map empty_map; + Statics() {} +}; + +static const Statics & statics() { + static const Statics s {}; + return s; +} + +static const Json & static_null() { + // This has to be separate, not in Statics, because Json() accesses statics().null. + static const Json json_null; + return json_null; +} + +/* * * * * * * * * * * * * * * * * * * * + * Constructors + */ + +Json::Json() noexcept : m_ptr(statics().null) {} +Json::Json(std::nullptr_t) noexcept : m_ptr(statics().null) {} +Json::Json(double value) : m_ptr(make_shared(value)) {} +Json::Json(int value) : m_ptr(make_shared(value)) {} +Json::Json(bool value) : m_ptr(value ? statics().t : statics().f) {} +Json::Json(const string &value) : m_ptr(make_shared(value)) {} +Json::Json(string &&value) : m_ptr(make_shared(std::move(value))) {} +Json::Json(const char * value) : m_ptr(make_shared(value)) {} +Json::Json(const Json::array &values) : m_ptr(make_shared(values)) {} +Json::Json(Json::array &&values) : m_ptr(make_shared(std::move(values))) {} +Json::Json(const Json::object &values) : m_ptr(make_shared(values)) {} +Json::Json(Json::object &&values) : m_ptr(make_shared(std::move(values))) {} + +/* * * * * * * * * * * * * * * * * * * * + * Accessors + */ + +Json::Type Json::type() const { return m_ptr->type(); } +double Json::number_value() const { return m_ptr->number_value(); } +int Json::int_value() const { return m_ptr->int_value(); } +bool Json::bool_value() const { return m_ptr->bool_value(); } +const string & Json::string_value() const { return m_ptr->string_value(); } +const vector & Json::array_items() const { return m_ptr->array_items(); } +const map & Json::object_items() const { return m_ptr->object_items(); } +const Json & Json::operator[] (size_t i) const { return (*m_ptr)[i]; } +const Json & Json::operator[] (const string &key) const { return (*m_ptr)[key]; } + +double JsonValue::number_value() const { return 0; } +int JsonValue::int_value() const { return 0; } +bool JsonValue::bool_value() const { return false; } +const string & JsonValue::string_value() const { return statics().empty_string; } +const vector & JsonValue::array_items() const { return statics().empty_vector; } +const map & JsonValue::object_items() const { return statics().empty_map; } +const Json & JsonValue::operator[] (size_t) const { return static_null(); } +const Json & JsonValue::operator[] (const string &) const { return static_null(); } + +const Json & JsonObject::operator[] (const string &key) const { + auto iter = m_value.find(key); + return (iter == m_value.end()) ? static_null() : iter->second; +} +const Json & JsonArray::operator[] (size_t i) const { + if (i >= m_value.size()) return static_null(); + else return m_value[i]; +} + +/* * * * * * * * * * * * * * * * * * * * + * Comparison + */ + +bool Json::operator== (const Json &other) const { + if (m_ptr == other.m_ptr) + return true; + if (m_ptr->type() != other.m_ptr->type()) + return false; + + return m_ptr->equals(other.m_ptr.get()); +} + +bool Json::operator< (const Json &other) const { + if (m_ptr == other.m_ptr) + return false; + if (m_ptr->type() != other.m_ptr->type()) + return m_ptr->type() < other.m_ptr->type(); + + return m_ptr->less(other.m_ptr.get()); +} + +/* * * * * * * * * * * * * * * * * * * * + * Parsing + */ + +/* esc(c) + * + * Format char c suitable for printing in an error message. + */ +static inline string esc(char c) { + char buf[12]; + if (static_cast(c) >= 0x20 && static_cast(c) <= 0x7f) { + snprintf(buf, sizeof buf, "'%c' (%d)", c, c); + } else { + snprintf(buf, sizeof buf, "(%d)", c); + } + return string(buf); +} + +static inline bool in_range(long x, long lower, long upper) { + return (x >= lower && x <= upper); +} + +namespace { +/* JsonParser + * + * Object that tracks all state of an in-progress parse. + */ +struct JsonParser final { + + /* State + */ + const string &str; + size_t i; + string &err; + bool failed; + const JsonParse strategy; + + /* fail(msg, err_ret = Json()) + * + * Mark this parse as failed. + */ + Json fail(string &&msg) { + return fail(std::move(msg), Json()); + } + + template + T fail(string &&msg, const T err_ret) { + if (!failed) + err = std::move(msg); + failed = true; + return err_ret; + } + + /* consume_whitespace() + * + * Advance until the current character is non-whitespace. + */ + void consume_whitespace() { + while (str[i] == ' ' || str[i] == '\r' || str[i] == '\n' || str[i] == '\t') + i++; + } + + /* consume_comment() + * + * Advance comments (c-style inline and multiline). + */ + bool consume_comment() { + bool comment_found = false; + if (str[i] == '/') { + i++; + if (i == str.size()) + return fail("unexpected end of input after start of comment", false); + if (str[i] == '/') { // inline comment + i++; + // advance until next line, or end of input + while (i < str.size() && str[i] != '\n') { + i++; + } + comment_found = true; + } + else if (str[i] == '*') { // multiline comment + i++; + if (i > str.size()-2) + return fail("unexpected end of input inside multi-line comment", false); + // advance until closing tokens + while (!(str[i] == '*' && str[i+1] == '/')) { + i++; + if (i > str.size()-2) + return fail( + "unexpected end of input inside multi-line comment", false); + } + i += 2; + comment_found = true; + } + else + return fail("malformed comment", false); + } + return comment_found; + } + + /* consume_garbage() + * + * Advance until the current character is non-whitespace and non-comment. + */ + void consume_garbage() { + consume_whitespace(); + if(strategy == JsonParse::COMMENTS) { + bool comment_found = false; + do { + comment_found = consume_comment(); + if (failed) return; + consume_whitespace(); + } + while(comment_found); + } + } + + /* get_next_token() + * + * Return the next non-whitespace character. If the end of the input is reached, + * flag an error and return 0. + */ + char get_next_token() { + consume_garbage(); + if (failed) return static_cast(0); + if (i == str.size()) + return fail("unexpected end of input", static_cast(0)); + + return str[i++]; + } + + /* encode_utf8(pt, out) + * + * Encode pt as UTF-8 and add it to out. + */ + void encode_utf8(long pt, string & out) { + if (pt < 0) + return; + + if (pt < 0x80) { + out += static_cast(pt); + } else if (pt < 0x800) { + out += static_cast((pt >> 6) | 0xC0); + out += static_cast((pt & 0x3F) | 0x80); + } else if (pt < 0x10000) { + out += static_cast((pt >> 12) | 0xE0); + out += static_cast(((pt >> 6) & 0x3F) | 0x80); + out += static_cast((pt & 0x3F) | 0x80); + } else { + out += static_cast((pt >> 18) | 0xF0); + out += static_cast(((pt >> 12) & 0x3F) | 0x80); + out += static_cast(((pt >> 6) & 0x3F) | 0x80); + out += static_cast((pt & 0x3F) | 0x80); + } + } + + /* parse_string() + * + * Parse a string, starting at the current position. + */ + string parse_string() { + string out; + long last_escaped_codepoint = -1; + while (true) { + if (i == str.size()) + return fail("unexpected end of input in string", ""); + + char ch = str[i++]; + + if (ch == '"') { + encode_utf8(last_escaped_codepoint, out); + return out; + } + + if (in_range(ch, 0, 0x1f)) + return fail("unescaped " + esc(ch) + " in string", ""); + + // The usual case: non-escaped characters + if (ch != '\\') { + encode_utf8(last_escaped_codepoint, out); + last_escaped_codepoint = -1; + out += ch; + continue; + } + + // Handle escapes + if (i == str.size()) + return fail("unexpected end of input in string", ""); + + ch = str[i++]; + + if (ch == 'u') { + // Extract 4-byte escape sequence + string esc = str.substr(i, 4); + // Explicitly check length of the substring. The following loop + // relies on std::string returning the terminating NUL when + // accessing str[length]. Checking here reduces brittleness. + if (esc.length() < 4) { + return fail("bad \\u escape: " + esc, ""); + } + for (size_t j = 0; j < 4; j++) { + if (!in_range(esc[j], 'a', 'f') && !in_range(esc[j], 'A', 'F') + && !in_range(esc[j], '0', '9')) + return fail("bad \\u escape: " + esc, ""); + } + + long codepoint = strtol(esc.data(), nullptr, 16); + + // JSON specifies that characters outside the BMP shall be encoded as a pair + // of 4-hex-digit \u escapes encoding their surrogate pair components. Check + // whether we're in the middle of such a beast: the previous codepoint was an + // escaped lead (high) surrogate, and this is a trail (low) surrogate. + if (in_range(last_escaped_codepoint, 0xD800, 0xDBFF) + && in_range(codepoint, 0xDC00, 0xDFFF)) { + // Reassemble the two surrogate pairs into one astral-plane character, per + // the UTF-16 algorithm. + encode_utf8((((last_escaped_codepoint - 0xD800) << 10) + | (codepoint - 0xDC00)) + 0x10000, out); + last_escaped_codepoint = -1; + } else { + encode_utf8(last_escaped_codepoint, out); + last_escaped_codepoint = codepoint; + } + + i += 4; + continue; + } + + encode_utf8(last_escaped_codepoint, out); + last_escaped_codepoint = -1; + + if (ch == 'b') { + out += '\b'; + } else if (ch == 'f') { + out += '\f'; + } else if (ch == 'n') { + out += '\n'; + } else if (ch == 'r') { + out += '\r'; + } else if (ch == 't') { + out += '\t'; + } else if (ch == '"' || ch == '\\' || ch == '/') { + out += ch; + } else { + return fail("invalid escape character " + esc(ch), ""); + } + } + } + + /* parse_number() + * + * Parse a double. + */ + Json parse_number() { + size_t start_pos = i; + + if (str[i] == '-') + i++; + + // Integer part + if (str[i] == '0') { + i++; + if (in_range(str[i], '0', '9')) + return fail("leading 0s not permitted in numbers"); + } else if (in_range(str[i], '1', '9')) { + i++; + while (in_range(str[i], '0', '9')) + i++; + } else { + return fail("invalid " + esc(str[i]) + " in number"); + } + + if (str[i] != '.' && str[i] != 'e' && str[i] != 'E' + && (i - start_pos) <= static_cast(std::numeric_limits::digits10)) { + return std::atoi(str.c_str() + start_pos); + } + + // Decimal part + if (str[i] == '.') { + i++; + if (!in_range(str[i], '0', '9')) + return fail("at least one digit required in fractional part"); + + while (in_range(str[i], '0', '9')) + i++; + } + + // Exponent part + if (str[i] == 'e' || str[i] == 'E') { + i++; + + if (str[i] == '+' || str[i] == '-') + i++; + + if (!in_range(str[i], '0', '9')) + return fail("at least one digit required in exponent"); + + while (in_range(str[i], '0', '9')) + i++; + } + + return std::strtod(str.c_str() + start_pos, nullptr); + } + + /* expect(str, res) + * + * Expect that 'str' starts at the character that was just read. If it does, advance + * the input and return res. If not, flag an error. + */ + Json expect(const string &expected, Json res) { + assert(i != 0); + i--; + if (str.compare(i, expected.length(), expected) == 0) { + i += expected.length(); + return res; + } else { + return fail("parse error: expected " + expected + ", got " + str.substr(i, expected.length())); + } + } + + /* parse_json() + * + * Parse a JSON object. + */ + Json parse_json(int depth) { + if (depth > max_depth) { + return fail("exceeded maximum nesting depth"); + } + + char ch = get_next_token(); + if (failed) + return Json(); + + if (ch == '-' || (ch >= '0' && ch <= '9')) { + i--; + return parse_number(); + } + + if (ch == 't') + return expect("true", true); + + if (ch == 'f') + return expect("false", false); + + if (ch == 'n') + return expect("null", Json()); + + if (ch == '"') + return parse_string(); + + if (ch == '{') { + map data; + ch = get_next_token(); + if (ch == '}') + return data; + + while (1) { + if (ch != '"') + return fail("expected '\"' in object, got " + esc(ch)); + + string key = parse_string(); + if (failed) + return Json(); + + ch = get_next_token(); + if (ch != ':') + return fail("expected ':' in object, got " + esc(ch)); + + data[std::move(key)] = parse_json(depth + 1); + if (failed) + return Json(); + + ch = get_next_token(); + if (ch == '}') + break; + if (ch != ',') + return fail("expected ',' in object, got " + esc(ch)); + + ch = get_next_token(); + } + return data; + } + + if (ch == '[') { + vector data; + ch = get_next_token(); + if (ch == ']') + return data; + + while (1) { + i--; + data.push_back(parse_json(depth + 1)); + if (failed) + return Json(); + + ch = get_next_token(); + if (ch == ']') + break; + if (ch != ',') + return fail("expected ',' in list, got " + esc(ch)); + + ch = get_next_token(); + (void)ch; + } + return data; + } + + return fail("expected value, got " + esc(ch)); + } +}; +}//namespace { + +Json Json::parse(const string &in, string &err, JsonParse strategy) { + JsonParser parser { in, 0, err, false, strategy }; + Json result = parser.parse_json(0); + + // Check for any trailing garbage + parser.consume_garbage(); + if (parser.failed) + return Json(); + if (parser.i != in.size()) + return parser.fail("unexpected trailing " + esc(in[parser.i])); + + return result; +} + +// Documented in lottiejson11.hpp +vector Json::parse_multi(const string &in, + std::string::size_type &parser_stop_pos, + string &err, + JsonParse strategy) { + JsonParser parser { in, 0, err, false, strategy }; + parser_stop_pos = 0; + vector json_vec; + while (parser.i != in.size() && !parser.failed) { + json_vec.push_back(parser.parse_json(0)); + if (parser.failed) + break; + + // Check for another object + parser.consume_garbage(); + if (parser.failed) + break; + parser_stop_pos = parser.i; + } + return json_vec; +} + +/* * * * * * * * * * * * * * * * * * * * + * Shape-checking + */ + +bool Json::has_shape(const shape & types, string & err) const { + if (!is_object()) { + err = "expected JSON object, got " + dump(); + return false; + } + + const auto& obj_items = object_items(); + for (auto & item : types) { + const auto it = obj_items.find(item.first); + if (it == obj_items.cend() || it->second.type() != item.second) { + err = "bad type for " + item.first + " in " + dump(); + return false; + } + } + + return true; +} + +} // namespace lottiejson11 diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/lottiejson11/lottiejson11.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/lottiejson11/lottiejson11.hpp new file mode 100644 index 0000000000..7761b93d49 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/lottiejson11/lottiejson11.hpp @@ -0,0 +1,232 @@ +/* json11 + * + * json11 is a tiny JSON library for C++11, providing JSON parsing and serialization. + * + * The core object provided by the library is json11::Json. A Json object represents any JSON + * value: null, bool, number (int or double), string (std::string), array (std::vector), or + * object (std::map). + * + * Json objects act like values: they can be assigned, copied, moved, compared for equality or + * order, etc. There are also helper methods Json::dump, to serialize a Json to a string, and + * Json::parse (static) to parse a std::string as a Json object. + * + * Internally, the various types of Json object are represented by the JsonValue class + * hierarchy. + * + * A note on numbers - JSON specifies the syntax of number formatting but not its semantics, + * so some JSON implementations distinguish between integers and floating-point numbers, while + * some don't. In json11, we choose the latter. Because some JSON implementations (namely + * Javascript itself) treat all numbers as the same type, distinguishing the two leads + * to JSON that will be *silently* changed by a round-trip through those implementations. + * Dangerous! To avoid that risk, json11 stores all numbers as double internally, but also + * provides integer helpers. + * + * Fortunately, double-precision IEEE754 ('double') can precisely store any integer in the + * range +/-2^53, which includes every 'int' on most systems. (Timestamps often use int64 + * or long long to avoid the Y2038K problem; a double storing microseconds since some epoch + * will be exact for +/- 275 years.) + */ + +/* Copyright (c) 2013 Dropbox, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#pragma once + +#include +#include +#include +#include +#include + +#ifdef _MSC_VER + #if _MSC_VER <= 1800 // VS 2013 + #ifndef noexcept + #define noexcept throw() + #endif + + #ifndef snprintf + #define snprintf _snprintf_s + #endif + #endif +#endif + +namespace lottiejson11 { + +enum JsonParse { + STANDARD, COMMENTS +}; + +class JsonValue; + +class Json final { +public: + // Types + enum Type { + NUL, NUMBER, BOOL, STRING, ARRAY, OBJECT + }; + + // Array and object typedefs + typedef std::vector array; + typedef std::map object; + + // Constructors for the various types of JSON value. + Json() noexcept; // NUL + Json(std::nullptr_t) noexcept; // NUL + Json(double value); // NUMBER + Json(int value); // NUMBER + Json(bool value); // BOOL + Json(const std::string &value); // STRING + Json(std::string &&value); // STRING + Json(const char * value); // STRING + Json(const array &values); // ARRAY + Json(array &&values); // ARRAY + Json(const object &values); // OBJECT + Json(object &&values); // OBJECT + + // Implicit constructor: anything with a to_json() function. + template + Json(const T & t) : Json(t.to_json()) {} + + // Implicit constructor: map-like objects (std::map, std::unordered_map, etc) + template ().begin()->first)>::value + && std::is_constructible().begin()->second)>::value, + int>::type = 0> + Json(const M & m) : Json(object(m.begin(), m.end())) {} + + // Implicit constructor: vector-like objects (std::list, std::vector, std::set, etc) + template ().begin())>::value, + int>::type = 0> + Json(const V & v) : Json(array(v.begin(), v.end())) {} + + // This prevents Json(some_pointer) from accidentally producing a bool. Use + // Json(bool(some_pointer)) if that behavior is desired. + Json(void *) = delete; + + // Accessors + Type type() const; + + bool is_null() const { return type() == NUL; } + bool is_number() const { return type() == NUMBER; } + bool is_bool() const { return type() == BOOL; } + bool is_string() const { return type() == STRING; } + bool is_array() const { return type() == ARRAY; } + bool is_object() const { return type() == OBJECT; } + + // Return the enclosed value if this is a number, 0 otherwise. Note that json11 does not + // distinguish between integer and non-integer numbers - number_value() and int_value() + // can both be applied to a NUMBER-typed object. + double number_value() const; + int int_value() const; + + // Return the enclosed value if this is a boolean, false otherwise. + bool bool_value() const; + // Return the enclosed string if this is a string, "" otherwise. + const std::string &string_value() const; + // Return the enclosed std::vector if this is an array, or an empty vector otherwise. + const array &array_items() const; + // Return the enclosed std::map if this is an object, or an empty map otherwise. + const object &object_items() const; + + // Return a reference to arr[i] if this is an array, Json() otherwise. + const Json & operator[](size_t i) const; + // Return a reference to obj[key] if this is an object, Json() otherwise. + const Json & operator[](const std::string &key) const; + + // Serialize. + void dump(std::string &out) const; + std::string dump() const { + std::string out; + dump(out); + return out; + } + + // Parse. If parse fails, return Json() and assign an error message to err. + static Json parse(const std::string & in, + std::string & err, + JsonParse strategy = JsonParse::STANDARD); + static Json parse(const char * in, + std::string & err, + JsonParse strategy = JsonParse::STANDARD) { + if (in) { + return parse(std::string(in), err, strategy); + } else { + err = "null input"; + return nullptr; + } + } + // Parse multiple objects, concatenated or separated by whitespace + static std::vector parse_multi( + const std::string & in, + std::string::size_type & parser_stop_pos, + std::string & err, + JsonParse strategy = JsonParse::STANDARD); + + static inline std::vector parse_multi( + const std::string & in, + std::string & err, + JsonParse strategy = JsonParse::STANDARD) { + std::string::size_type parser_stop_pos; + return parse_multi(in, parser_stop_pos, err, strategy); + } + + bool operator== (const Json &rhs) const; + bool operator< (const Json &rhs) const; + bool operator!= (const Json &rhs) const { return !(*this == rhs); } + bool operator<= (const Json &rhs) const { return !(rhs < *this); } + bool operator> (const Json &rhs) const { return (rhs < *this); } + bool operator>= (const Json &rhs) const { return !(*this < rhs); } + + /* has_shape(types, err) + * + * Return true if this is a JSON object and, for each item in types, has a field of + * the given type. If not, return false and set err to a descriptive message. + */ + typedef std::initializer_list> shape; + bool has_shape(const shape & types, std::string & err) const; + +private: + std::shared_ptr m_ptr; +}; + +// Internal class hierarchy - JsonValue objects are not exposed to users of this API. +class JsonValue { +protected: + friend class Json; + friend class JsonInt; + friend class JsonDouble; + virtual Json::Type type() const = 0; + virtual bool equals(const JsonValue * other) const = 0; + virtual bool less(const JsonValue * other) const = 0; + virtual void dump(std::string &out) const = 0; + virtual double number_value() const; + virtual int int_value() const; + virtual bool bool_value() const; + virtual const std::string &string_value() const; + virtual const Json::array &array_items() const; + virtual const Json &operator[](size_t i) const; + virtual const Json::object &object_items() const; + virtual const Json &operator[](const std::string &key) const; + virtual ~JsonValue() {} +}; + +} // namespace lottiejson11 diff --git a/submodules/TelegramUI/Components/LottieMetal/BUILD b/submodules/TelegramUI/Components/LottieMetal/BUILD new file mode 100644 index 0000000000..9c92883589 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieMetal/BUILD @@ -0,0 +1,24 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "LottieMetal", + module_name = "LottieMetal", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/AsyncDisplayKit", + "//submodules/Display", + "//submodules/AnimatedStickerNode", + "//submodules/GZip", + "//submodules/MetalEngine", + "//submodules/TelegramUI/Components/LottieCpp", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/LottieMetal/Sources/LottieMetalAnimatedStickerNode.swift b/submodules/TelegramUI/Components/LottieMetal/Sources/LottieMetalAnimatedStickerNode.swift new file mode 100644 index 0000000000..bc27655510 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieMetal/Sources/LottieMetalAnimatedStickerNode.swift @@ -0,0 +1,212 @@ +import Foundation +import UIKit +import Display +import AsyncDisplayKit +import SwiftSignalKit +import AnimatedStickerNode +import MetalEngine +import LottieCpp +import GZip + +public final class LottieMetalAnimatedStickerNode: ASDisplayNode, AnimatedStickerNode { + private final class LoadFrameTask { + var isCancelled: Bool = false + } + + public var automaticallyLoadFirstFrame: Bool = false + public var automaticallyLoadLastFrame: Bool = false + public var playToCompletionOnStop: Bool = false + + private var lottieInstance: LottieAnimationContainer? + + private var didStart: Bool = false + public var started: () -> Void = {} + + public var completed: (Bool) -> Void = { _ in } + private var didComplete: Bool = false + + public var frameUpdated: (Int, Int) -> Void = { _, _ in } + public var currentFrameIndex: Int { + get { + return self.frameIndex + } set(value) { + } + } + public var currentFrameCount: Int { + get { + if let lottieInstance = self.lottieInstance { + return Int(lottieInstance.animation.frameCount) + } else { + return 0 + } + } set(value) { + } + } + public var currentFrameImage: UIImage? { + return nil + } + + public private(set) var isPlaying: Bool = false + public var stopAtNearestLoop: Bool = false + + private let statusPromise = Promise() + public var status: Signal { + return self.statusPromise.get() + } + + public var autoplay: Bool = true + + public var visibility: Bool = false { + didSet { + self.updatePlayback() + } + } + + public var overrideVisibility: Bool = false + + public var isPlayingChanged: (Bool) -> Void = { _ in } + + private var sourceDisposable: Disposable? + private var playbackSize: CGSize? + + private var frameIndex: Int = 0 + private var playbackMode: AnimatedStickerPlaybackMode = .loop + + override public init() { + super.init() + + self.backgroundColor = .blue + } + + deinit { + } + + public func cloneCurrentFrame(from otherNode: AnimatedStickerNode?) { + } + + public func setup(source: AnimatedStickerNodeSource, width: Int, height: Int, playbackMode: AnimatedStickerPlaybackMode, mode: AnimatedStickerMode) { + self.didStart = false + self.didComplete = false + + self.sourceDisposable?.dispose() + + self.playbackSize = CGSize(width: CGFloat(width), height: CGFloat(height)) + self.playbackMode = playbackMode + + self.sourceDisposable = (source.directDataPath(attemptSynchronously: false) + |> filter { $0 != nil } + |> take(1) + |> deliverOnMainQueue).startStrict(next: { [weak self] path in + guard let self, let path = path else { + return + } + + if source.isVideo { + } else { + guard let data = try? Data(contentsOf: URL(fileURLWithPath: path)) else { + return + } + + let decompressedData = TGGUnzipData(data, 8 * 1024 * 1024) ?? data + guard let lottieAnimation = LottieAnimation(data: decompressedData) else { + print("Could not load sticker data") + return + } + let lottieInstance = LottieAnimationContainer(animation: lottieAnimation) + self.setupPlayback(lottieInstance: lottieInstance) + } + }).strict() + } + + private func updatePlayback() { + let isPlaying = self.visibility && self.lottieInstance != nil + if self.isPlaying != isPlaying { + self.isPlaying = isPlaying + self.isPlayingChanged(self.isPlaying) + } + } + + private func advanceFrameIfPossible() { + /*var frameCount: Int? + if let lottieInstance = self.lottieInstance { + frameCount = Int(lottieInstance.frameCount) + } else if let videoSource = self.videoSource { + frameCount = Int(videoSource.frameCount) + } + guard let frameCount = frameCount else { + return + } + + if self.frameIndex == frameCount - 1 { + switch self.playbackMode { + case .loop: + self.completed(false) + case let .count(count): + if count <= 1 { + if !self.didComplete { + self.didComplete = true + self.completed(true) + } + return + } else { + self.playbackMode = .count(count - 1) + self.completed(false) + } + case .once: + if !self.didComplete { + self.didComplete = true + self.completed(true) + } + return + case .still: + break + } + } + + let nextFrameIndex = (self.frameIndex + 1) % frameCount + self.frameIndex = nextFrameIndex + + self.updateFrameImageIfNeeded() + self.updateLoadFrameTasks()*/ + } + + private func setupPlayback(lottieInstance: LottieAnimationContainer) { + self.lottieInstance = lottieInstance + + self.updatePlayback() + } + + public func reset() { + } + + public func playOnce() { + } + + public func playLoop() { + } + + public func play(firstFrame: Bool, fromIndex: Int?) { + if let fromIndex = fromIndex { + self.frameIndex = fromIndex + } + } + + public func pause() { + } + + public func stop() { + } + + public func seekTo(_ position: AnimatedStickerPlaybackPosition) { + } + + public func playIfNeeded() -> Bool { + return false + } + + public func updateLayout(size: CGSize) { + } + + public func setOverlayColor(_ color: UIColor?, replace: Bool, animated: Bool) { + } +}