mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-11-06 17:00:13 +00:00
Add lottiecpp
This commit is contained in:
parent
0f12dceca7
commit
8605198d72
2
.bazelrc
2
.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/.*\.cc$","@-std=c++17"
|
||||||
build --per_file_copt="third-party/webrtc/.*\.mm$","@-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/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
|
build --swiftcopt=-whole-module-optimization
|
||||||
|
|
||||||
|
|||||||
@ -84,6 +84,7 @@ swift_library(
|
|||||||
"//submodules/TelegramUI/Components/Chat/ChatMessageTransitionNode",
|
"//submodules/TelegramUI/Components/Chat/ChatMessageTransitionNode",
|
||||||
"//submodules/AnimatedStickerNode",
|
"//submodules/AnimatedStickerNode",
|
||||||
"//submodules/TelegramAnimatedStickerNode",
|
"//submodules/TelegramAnimatedStickerNode",
|
||||||
|
"//submodules/TelegramUI/Components/LottieMetal",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
|||||||
@ -74,6 +74,7 @@ import UIKitRuntimeUtils
|
|||||||
import ChatMessageTransitionNode
|
import ChatMessageTransitionNode
|
||||||
import AnimatedStickerNode
|
import AnimatedStickerNode
|
||||||
import TelegramAnimatedStickerNode
|
import TelegramAnimatedStickerNode
|
||||||
|
import LottieMetal
|
||||||
|
|
||||||
private struct BubbleItemAttributes {
|
private struct BubbleItemAttributes {
|
||||||
var isAttachment: Bool
|
var isAttachment: Bool
|
||||||
@ -5822,7 +5823,8 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
|||||||
|
|
||||||
do {
|
do {
|
||||||
let pathPrefix = item.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(resource.id)
|
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))
|
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
|
var animationFrame: CGRect
|
||||||
if isStickerEffect {
|
if isStickerEffect {
|
||||||
|
|||||||
33
submodules/TelegramUI/Components/LottieCpp/BUILD
Normal file
33
submodules/TelegramUI/Components/LottieCpp/BUILD
Normal file
@ -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",
|
||||||
|
],
|
||||||
|
)
|
||||||
@ -0,0 +1,27 @@
|
|||||||
|
#ifndef LottieAnimation_h
|
||||||
|
#define LottieAnimation_h
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
#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 */
|
||||||
@ -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 */
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
#ifndef LottieCpp_h
|
||||||
|
#define LottieCpp_h
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* DctHuffman_h */
|
||||||
@ -0,0 +1,140 @@
|
|||||||
|
#ifndef LottieRenderTree_h
|
||||||
|
#define LottieRenderTree_h
|
||||||
|
|
||||||
|
#import <QuartzCore/QuartzCore.h>
|
||||||
|
|
||||||
|
#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<LottieColorStop *> * _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<NSNumber *> * _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<LottieRenderNode *> * _Nonnull subnodes;
|
||||||
|
@property (nonatomic, readonly, direct) LottieRenderNode * _Nullable mask;
|
||||||
|
|
||||||
|
- (instancetype _Nonnull)init NS_UNAVAILABLE;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* LottieRenderTree_h */
|
||||||
@ -0,0 +1,29 @@
|
|||||||
|
#include "CompositionLayer.hpp"
|
||||||
|
|
||||||
|
#include "Lottie/Public/Primitives/RenderTree.hpp"
|
||||||
|
|
||||||
|
namespace lottie {
|
||||||
|
|
||||||
|
InvertedMatteLayer::InvertedMatteLayer(std::shared_ptr<CompositionLayer> inputMatte) :
|
||||||
|
_inputMatte(inputMatte) {
|
||||||
|
setBounds(inputMatte->bounds());
|
||||||
|
setNeedsDisplay(true);
|
||||||
|
|
||||||
|
addSublayer(_inputMatte);
|
||||||
|
}
|
||||||
|
|
||||||
|
void InvertedMatteLayer::setup() {
|
||||||
|
_inputMatte->setLayerDelegate(shared_from_base<InvertedMatteLayer>());
|
||||||
|
}
|
||||||
|
|
||||||
|
void InvertedMatteLayer::frameUpdated(double frame) {
|
||||||
|
setNeedsDisplay(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<InvertedMatteLayer> makeInvertedMatteLayer(std::shared_ptr<CompositionLayer> compositionLayer) {
|
||||||
|
auto result = std::make_shared<InvertedMatteLayer>(compositionLayer);
|
||||||
|
result->setup();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -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 <memory>
|
||||||
|
|
||||||
|
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<CompositionLayer> inputMatte);
|
||||||
|
|
||||||
|
void setup();
|
||||||
|
|
||||||
|
std::shared_ptr<CompositionLayer> _inputMatte;
|
||||||
|
//let wrapperLayer = CALayer()
|
||||||
|
|
||||||
|
virtual void frameUpdated(double frame) override;
|
||||||
|
/*virtual bool implementsDraw() const override;
|
||||||
|
virtual void draw(std::shared_ptr<CGContext> const &context) override;*/
|
||||||
|
//virtual std::shared_ptr<RenderableItem> renderableItem() override;
|
||||||
|
|
||||||
|
virtual bool isInvertedMatte() const override {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::shared_ptr<InvertedMatteLayer> makeInvertedMatteLayer(std::shared_ptr<CompositionLayer> compositionLayer);
|
||||||
|
|
||||||
|
/// The base class for a child layer of CompositionContainer
|
||||||
|
class CompositionLayer: public CALayer, public KeypathSearchable {
|
||||||
|
public:
|
||||||
|
CompositionLayer(std::shared_ptr<LayerModel> const &layer, Vector2D size) {
|
||||||
|
_contentsLayer = std::make_shared<CALayer>();
|
||||||
|
|
||||||
|
_transformNode = std::make_shared<LayerTransformNode>(layer->transform);
|
||||||
|
|
||||||
|
if (layer->masks.has_value()) {
|
||||||
|
_maskLayer = std::make_shared<MaskContainerLayer>(layer->masks.value());
|
||||||
|
} else {
|
||||||
|
_maskLayer = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
_matteType = layer->matte;
|
||||||
|
|
||||||
|
_inFrame = layer->inFrame;
|
||||||
|
_outFrame = layer->outFrame;
|
||||||
|
_timeStretch = layer->timeStretch();
|
||||||
|
_startFrame = layer->startTime;
|
||||||
|
if (layer->name.has_value()) {
|
||||||
|
_keypathName = layer->name.value();
|
||||||
|
} else {
|
||||||
|
_keypathName = "Layer";
|
||||||
|
}
|
||||||
|
|
||||||
|
_childKeypaths.push_back(_transformNode->transformProperties());
|
||||||
|
|
||||||
|
_contentsLayer->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<std::string, std::shared_ptr<AnyNodeProperty>> keypathProperties() const override {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual std::shared_ptr<CALayer> 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<std::shared_ptr<KeypathSearchable>> const &childKeypaths() const override {
|
||||||
|
return _childKeypaths;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<CompositionLayer> _matteLayer;
|
||||||
|
void setMatteLayer(std::shared_ptr<CompositionLayer> matteLayer) {
|
||||||
|
_matteLayer = matteLayer;
|
||||||
|
if (matteLayer) {
|
||||||
|
if (_matteType.has_value() && _matteType.value() == MatteType::Invert) {
|
||||||
|
setMask(makeInvertedMatteLayer(matteLayer));
|
||||||
|
} else {
|
||||||
|
setMask(matteLayer);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setMask(nullptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::weak_ptr<CompositionLayerDelegate> const &layerDelegate() const {
|
||||||
|
return _layerDelegate;
|
||||||
|
}
|
||||||
|
void setLayerDelegate(std::weak_ptr<CompositionLayerDelegate> const &layerDelegate) {
|
||||||
|
_layerDelegate = layerDelegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<CALayer> const &contentsLayer() const {
|
||||||
|
return _contentsLayer;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<MaskContainerLayer> const &maskLayer() const {
|
||||||
|
return _maskLayer;
|
||||||
|
}
|
||||||
|
void setMaskLayer(std::shared_ptr<MaskContainerLayer> const &maskLayer) {
|
||||||
|
_maskLayer = maskLayer;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<MatteType> const &matteType() const {
|
||||||
|
return _matteType;
|
||||||
|
}
|
||||||
|
|
||||||
|
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> renderTreeNode() {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
std::shared_ptr<LayerTransformNode> const transformNode() const {
|
||||||
|
return _transformNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::shared_ptr<CALayer> _contentsLayer;
|
||||||
|
std::optional<MatteType> _matteType;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::weak_ptr<CompositionLayerDelegate> _layerDelegate;
|
||||||
|
|
||||||
|
std::shared_ptr<LayerTransformNode> _transformNode;
|
||||||
|
|
||||||
|
std::shared_ptr<MaskContainerLayer> _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<RenderTreeNode> _renderTree;
|
||||||
|
|
||||||
|
public:
|
||||||
|
virtual bool isImageCompositionLayer() const {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual bool isTextCompositionLayer() const {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::vector<std::shared_ptr<KeypathSearchable>> _childKeypaths;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* CompositionLayer_hpp */
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
#ifndef CompositionLayerDelegate_hpp
|
||||||
|
#define CompositionLayerDelegate_hpp
|
||||||
|
|
||||||
|
namespace lottie {
|
||||||
|
|
||||||
|
class CompositionLayerDelegate {
|
||||||
|
public:
|
||||||
|
virtual void frameUpdated(double frame) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* CompositionLayerDelegate_hpp */
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
#include "ImageCompositionLayer.hpp"
|
||||||
|
|
||||||
|
namespace lottie {
|
||||||
|
|
||||||
|
}
|
||||||
@ -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<ImageLayerModel> const &imageLayer, Vector2D const &size) :
|
||||||
|
CompositionLayer(imageLayer, size) {
|
||||||
|
_imageReferenceID = imageLayer->referenceID;
|
||||||
|
|
||||||
|
contentsLayer()->setMasksToBounds(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<CGImage> image() {
|
||||||
|
return _image;
|
||||||
|
}
|
||||||
|
void setImage(std::shared_ptr<CGImage> 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<CGImage> _image;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* ImageCompositionLayer_hpp */
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
#include "MaskContainerLayer.hpp"
|
||||||
|
|
||||||
|
namespace lottie {
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,178 @@
|
|||||||
|
#ifndef MaskContainerLayer_hpp
|
||||||
|
#define MaskContainerLayer_hpp
|
||||||
|
|
||||||
|
#include "Lottie/Private/Model/Objects/Mask.hpp"
|
||||||
|
#include "Lottie/Public/Primitives/CALayer.hpp"
|
||||||
|
#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/NodePropertyMap.hpp"
|
||||||
|
#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/NodeProperty.hpp"
|
||||||
|
#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/KeyframeInterpolator.hpp"
|
||||||
|
|
||||||
|
namespace lottie {
|
||||||
|
|
||||||
|
inline MaskMode usableMaskMode(MaskMode mode) {
|
||||||
|
switch (mode) {
|
||||||
|
case MaskMode::Add:
|
||||||
|
return MaskMode::Add;
|
||||||
|
case MaskMode::Subtract:
|
||||||
|
return MaskMode::Subtract;
|
||||||
|
case MaskMode::Intersect:
|
||||||
|
return MaskMode::Intersect;
|
||||||
|
case MaskMode::Lighten:
|
||||||
|
return MaskMode::Add;
|
||||||
|
case MaskMode::Darken:
|
||||||
|
return MaskMode::Darken;
|
||||||
|
case MaskMode::Difference:
|
||||||
|
return MaskMode::Intersect;
|
||||||
|
case MaskMode::None:
|
||||||
|
return MaskMode::None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MaskNodeProperties: public NodePropertyMap {
|
||||||
|
public:
|
||||||
|
MaskNodeProperties(std::shared_ptr<Mask> const &mask) :
|
||||||
|
_mode(mask->mode()),
|
||||||
|
_inverted(mask->inverted) {
|
||||||
|
_opacity = std::make_shared<NodeProperty<Vector1D>>(std::make_shared<KeyframeInterpolator<Vector1D>>(mask->opacity->keyframes));
|
||||||
|
_shape = std::make_shared<NodeProperty<BezierPath>>(std::make_shared<KeyframeInterpolator<BezierPath>>(mask->shape.keyframes));
|
||||||
|
_expansion = std::make_shared<NodeProperty<Vector1D>>(std::make_shared<KeyframeInterpolator<Vector1D>>(mask->expansion->keyframes));
|
||||||
|
|
||||||
|
_propertyMap.insert(std::make_pair("Opacity", _opacity));
|
||||||
|
_propertyMap.insert(std::make_pair("Shape", _shape));
|
||||||
|
_propertyMap.insert(std::make_pair("Expansion", _expansion));
|
||||||
|
|
||||||
|
for (const auto &it : _propertyMap) {
|
||||||
|
_properties.push_back(it.second);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual std::vector<std::shared_ptr<AnyNodeProperty>> &properties() override {
|
||||||
|
return _properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual std::vector<std::shared_ptr<KeypathSearchable>> const &childKeypaths() const override {
|
||||||
|
return _childKeypaths;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<NodeProperty<Vector1D>> const &opacity() const {
|
||||||
|
return _opacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<NodeProperty<BezierPath>> const &shape() const {
|
||||||
|
return _shape;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<NodeProperty<Vector1D>> const &expansion() const {
|
||||||
|
return _expansion;
|
||||||
|
}
|
||||||
|
|
||||||
|
MaskMode mode() const {
|
||||||
|
return _mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool inverted() const {
|
||||||
|
return _inverted;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::map<std::string, std::shared_ptr<AnyNodeProperty>> _propertyMap;
|
||||||
|
std::vector<std::shared_ptr<KeypathSearchable>> _childKeypaths;
|
||||||
|
|
||||||
|
std::vector<std::shared_ptr<AnyNodeProperty>> _properties;
|
||||||
|
|
||||||
|
MaskMode _mode = MaskMode::Add;
|
||||||
|
bool _inverted = false;
|
||||||
|
|
||||||
|
std::shared_ptr<NodeProperty<Vector1D>> _opacity;
|
||||||
|
std::shared_ptr<NodeProperty<BezierPath>> _shape;
|
||||||
|
std::shared_ptr<NodeProperty<Vector1D>> _expansion;
|
||||||
|
};
|
||||||
|
|
||||||
|
class MaskLayer: public CALayer {
|
||||||
|
public:
|
||||||
|
MaskLayer(std::shared_ptr<Mask> const &mask) :
|
||||||
|
_properties(mask) {
|
||||||
|
_maskLayer = std::make_shared<CAShapeLayer>();
|
||||||
|
|
||||||
|
addSublayer(_maskLayer);
|
||||||
|
|
||||||
|
if (mask->mode() == MaskMode::Add) {
|
||||||
|
_maskLayer->setFillColor(Color(1.0, 0.0, 0.0, 1.0));
|
||||||
|
} else {
|
||||||
|
_maskLayer->setFillColor(Color(0.0, 1.0, 0.0, 1.0));
|
||||||
|
}
|
||||||
|
_maskLayer->setFillRule(FillRule::EvenOdd);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual ~MaskLayer() = default;
|
||||||
|
|
||||||
|
void updateWithFrame(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<CGPath>(newPath);
|
||||||
|
}
|
||||||
|
_maskLayer->setPath(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
MaskNodeProperties _properties;
|
||||||
|
|
||||||
|
std::shared_ptr<CAShapeLayer> _maskLayer;
|
||||||
|
};
|
||||||
|
|
||||||
|
class MaskContainerLayer: public CALayer {
|
||||||
|
public:
|
||||||
|
MaskContainerLayer(std::vector<std::shared_ptr<Mask>> const &masks) {
|
||||||
|
auto containerLayer = std::make_shared<CALayer>();
|
||||||
|
bool firstObject = true;
|
||||||
|
for (const auto &mask : masks) {
|
||||||
|
auto maskLayer = std::make_shared<MaskLayer>(mask);
|
||||||
|
_maskLayers.push_back(maskLayer);
|
||||||
|
|
||||||
|
auto usableMode = usableMaskMode(mask->mode());
|
||||||
|
if (usableMode == MaskMode::None) {
|
||||||
|
continue;
|
||||||
|
} else if (usableMode == MaskMode::Add || firstObject) {
|
||||||
|
firstObject = false;
|
||||||
|
containerLayer->addSublayer(maskLayer);
|
||||||
|
} else {
|
||||||
|
containerLayer->setMask(maskLayer);
|
||||||
|
auto newContainer = std::make_shared<CALayer>();
|
||||||
|
newContainer->addSublayer(containerLayer);
|
||||||
|
containerLayer = newContainer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addSublayer(containerLayer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Internal
|
||||||
|
|
||||||
|
void updateWithFrame(double frame, bool forceUpdates) {
|
||||||
|
for (const auto &maskLayer : _maskLayers) {
|
||||||
|
maskLayer->updateWithFrame(frame, forceUpdates);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<std::shared_ptr<MaskLayer>> _maskLayers;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* MaskContainerLayer_hpp */
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
#include "NullCompositionLayer.hpp"
|
||||||
|
|
||||||
|
namespace lottie {
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
#ifndef NullCompositionLayer_hpp
|
||||||
|
#define NullCompositionLayer_hpp
|
||||||
|
|
||||||
|
#include "Lottie/Private/MainThread/LayerContainers/CompLayers/CompositionLayer.hpp"
|
||||||
|
|
||||||
|
namespace lottie {
|
||||||
|
|
||||||
|
class NullCompositionLayer: public CompositionLayer {
|
||||||
|
public:
|
||||||
|
NullCompositionLayer(std::shared_ptr<LayerModel> const &layer) :
|
||||||
|
CompositionLayer(layer, Vector2D::Zero()) {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* NullCompositionLayer_hpp */
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
#include "PreCompositionLayer.hpp"
|
||||||
|
|
||||||
|
namespace lottie {
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,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<PreCompLayerModel> const &precomp,
|
||||||
|
PrecompAsset const &asset,
|
||||||
|
std::shared_ptr<LayerImageProvider> const &layerImageProvider,
|
||||||
|
std::shared_ptr<AnimationTextProvider> const &textProvider,
|
||||||
|
std::shared_ptr<AnimationFontProvider> const &fontProvider,
|
||||||
|
std::shared_ptr<AssetLibrary> const &assetLibrary,
|
||||||
|
double frameRate
|
||||||
|
) : CompositionLayer(precomp, Vector2D(precomp->width, precomp->height)) {
|
||||||
|
if (precomp->timeRemapping) {
|
||||||
|
_remappingNode = std::make_shared<NodeProperty<Vector1D>>(std::make_shared<KeyframeInterpolator<Vector1D>>(precomp->timeRemapping->keyframes));
|
||||||
|
}
|
||||||
|
_frameRate = frameRate;
|
||||||
|
|
||||||
|
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<std::shared_ptr<ImageCompositionLayer>> imageLayers;
|
||||||
|
|
||||||
|
std::shared_ptr<CompositionLayer> mattedLayer;
|
||||||
|
|
||||||
|
for (auto layerIt = layers.rbegin(); layerIt != layers.rend(); layerIt++) {
|
||||||
|
std::shared_ptr<CompositionLayer> layer = *layerIt;
|
||||||
|
layer->setBounds(bounds());
|
||||||
|
_animationLayers.push_back(layer);
|
||||||
|
|
||||||
|
if (layer->isImageCompositionLayer()) {
|
||||||
|
imageLayers.push_back(std::static_pointer_cast<ImageCompositionLayer>(layer));
|
||||||
|
}
|
||||||
|
if (mattedLayer) {
|
||||||
|
/// The previous layer requires this layer to be its matte
|
||||||
|
mattedLayer->setMatteLayer(layer);
|
||||||
|
mattedLayer = nullptr;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (layer->matteType().has_value() && (layer->matteType().value() == MatteType::Add || layer->matteType().value() == MatteType::Invert)) {
|
||||||
|
/// We have a layer that requires a matte.
|
||||||
|
mattedLayer = layer;
|
||||||
|
}
|
||||||
|
contentsLayer()->addSublayer(layer);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto &layer : layers) {
|
||||||
|
_childKeypaths.push_back(layer);
|
||||||
|
}
|
||||||
|
|
||||||
|
layerImageProvider->addImageLayers(imageLayers);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual std::map<std::string, std::shared_ptr<AnyNodeProperty>> keypathProperties() const override {
|
||||||
|
if (!_remappingNode) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::map<std::string, std::shared_ptr<AnyNodeProperty>> result;
|
||||||
|
result.insert(std::make_pair("Time Remap", _remappingNode));
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void displayContentsWithFrame(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> renderTreeNode() override {
|
||||||
|
if (_contentsLayer->isHidden()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<RenderTreeNode> maskNode;
|
||||||
|
bool invertMask = false;
|
||||||
|
if (_matteLayer) {
|
||||||
|
maskNode = _matteLayer->renderTreeNode();
|
||||||
|
if (maskNode && _matteType.has_value() && _matteType.value() == MatteType::Invert) {
|
||||||
|
invertMask = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::shared_ptr<RenderTreeNode>> renderTreeValue;
|
||||||
|
auto renderTreeContentItem = renderTree();
|
||||||
|
if (renderTreeContentItem) {
|
||||||
|
renderTreeValue.push_back(renderTreeContentItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::shared_ptr<RenderTreeNode>> subnodes;
|
||||||
|
subnodes.push_back(std::make_shared<RenderTreeNode>(
|
||||||
|
_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<RenderTreeNode>(
|
||||||
|
bounds(),
|
||||||
|
position(),
|
||||||
|
transform(),
|
||||||
|
opacity(),
|
||||||
|
masksToBounds(),
|
||||||
|
isHidden(),
|
||||||
|
nullptr,
|
||||||
|
subnodes,
|
||||||
|
maskNode,
|
||||||
|
invertMask
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<RenderTreeNode> renderTree() {
|
||||||
|
std::vector<std::shared_ptr<RenderTreeNode>> 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<std::shared_ptr<RenderTreeNode>> subnodes;
|
||||||
|
return std::make_shared<RenderTreeNode>(
|
||||||
|
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<NodeProperty<Vector1D>> _remappingNode;
|
||||||
|
|
||||||
|
std::vector<std::shared_ptr<CompositionLayer>> _animationLayers;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* PreCompositionLayer_hpp */
|
||||||
File diff suppressed because it is too large
Load Diff
@ -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<ShapeLayerModel> const &shapeLayer);
|
||||||
|
ShapeCompositionLayer(std::shared_ptr<SolidLayerModel> const &solidLayer);
|
||||||
|
|
||||||
|
virtual void displayContentsWithFrame(double frame, bool forceUpdates) override;
|
||||||
|
virtual std::shared_ptr<RenderTreeNode> renderTreeNode() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::shared_ptr<ShapeLayerPresentationTree> _contentTree;
|
||||||
|
|
||||||
|
AnimationFrameTime _frameTime = 0.0;
|
||||||
|
bool _frameTimeInitialized = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* ShapeCompositionLayer_hpp */
|
||||||
@ -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<CurveVertex> points;
|
||||||
|
|
||||||
|
if (radius <= 0.0) {
|
||||||
|
/// No Corners
|
||||||
|
points = {
|
||||||
|
/// Lead In
|
||||||
|
CurveVertex::relative(
|
||||||
|
Vector2D(size.x, -size.y),
|
||||||
|
Vector2D::Zero(),
|
||||||
|
Vector2D::Zero())
|
||||||
|
.translated(position),
|
||||||
|
/// Corner 1
|
||||||
|
CurveVertex::relative(
|
||||||
|
Vector2D(size.x, size.y),
|
||||||
|
Vector2D::Zero(),
|
||||||
|
Vector2D::Zero())
|
||||||
|
.translated(position),
|
||||||
|
/// Corner 2
|
||||||
|
CurveVertex::relative(
|
||||||
|
Vector2D(-size.x, size.y),
|
||||||
|
Vector2D::Zero(),
|
||||||
|
Vector2D::Zero())
|
||||||
|
.translated(position),
|
||||||
|
/// Corner 3
|
||||||
|
CurveVertex::relative(
|
||||||
|
Vector2D(-size.x, -size.y),
|
||||||
|
Vector2D::Zero(),
|
||||||
|
Vector2D::Zero())
|
||||||
|
.translated(position),
|
||||||
|
/// Corner 4
|
||||||
|
CurveVertex::relative(
|
||||||
|
Vector2D(size.x, -size.y),
|
||||||
|
Vector2D::Zero(),
|
||||||
|
Vector2D::Zero())
|
||||||
|
.translated(position)
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
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<CurveVertex> vertices;
|
||||||
|
vertices.push_back(CurveVertex::relative(point + position, Vector2D::Zero(), Vector2D::Zero()));
|
||||||
|
|
||||||
|
Vector2D previousPoint = point;
|
||||||
|
bool longSegment = false;
|
||||||
|
int numPoints = (int)(ceil(numberOfPoints) * 2.0);
|
||||||
|
for (int i = 0; i < numPoints; i++) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -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 */
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
#include "TextCompositionLayer.hpp"
|
||||||
|
|
||||||
|
namespace lottie {
|
||||||
|
|
||||||
|
}
|
||||||
@ -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<TextLayerModel> const &textLayer, std::shared_ptr<AnimationTextProvider> textProvider, std::shared_ptr<AnimationFontProvider> fontProvider) :
|
||||||
|
CompositionLayer(textLayer, Vector2D::Zero()) {
|
||||||
|
std::shared_ptr<TextAnimatorNode> rootNode;
|
||||||
|
for (const auto &animator : textLayer->animators) {
|
||||||
|
rootNode = std::make_shared<TextAnimatorNode>(rootNode, animator);
|
||||||
|
}
|
||||||
|
_rootNode = rootNode;
|
||||||
|
_textDocument = std::make_shared<KeyframeInterpolator<TextDocument>>(textLayer->text.keyframes);
|
||||||
|
|
||||||
|
_textProvider = textProvider;
|
||||||
|
_fontProvider = fontProvider;
|
||||||
|
|
||||||
|
//_contentsLayer->addSublayer(_textLayer);
|
||||||
|
|
||||||
|
assert(false);
|
||||||
|
//self.textLayer.masksToBounds = false
|
||||||
|
//self.textLayer.isGeometryFlipped = true
|
||||||
|
|
||||||
|
if (_rootNode) {
|
||||||
|
_childKeypaths.push_back(rootNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<AnimationTextProvider> const &textProvider() const {
|
||||||
|
return _textProvider;
|
||||||
|
}
|
||||||
|
void setTextProvider(std::shared_ptr<AnimationTextProvider> const &textProvider) {
|
||||||
|
_textProvider = textProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<AnimationFontProvider> const &fontProvider() const {
|
||||||
|
return _fontProvider;
|
||||||
|
}
|
||||||
|
void setFontProvider(std::shared_ptr<AnimationFontProvider> const &fontProvider) {
|
||||||
|
_fontProvider = fontProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void displayContentsWithFrame(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<TextAnimatorNode> _rootNode;
|
||||||
|
std::shared_ptr<KeyframeInterpolator<TextDocument>> _textDocument;
|
||||||
|
|
||||||
|
//std::shared_ptr<CoreTextRenderLayer> _textLayer;
|
||||||
|
std::shared_ptr<AnimationTextProvider> _textProvider;
|
||||||
|
std::shared_ptr<AnimationFontProvider> _fontProvider;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* TextCompositionLayer_hpp */
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
#include "MainThreadAnimationLayer.hpp"
|
||||||
|
|
||||||
|
namespace lottie {
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,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<CGImage> imageForAsset(ImageAsset const &asset) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class MainThreadAnimationLayer: public CALayer {
|
||||||
|
public:
|
||||||
|
MainThreadAnimationLayer(
|
||||||
|
Animation const &animation,
|
||||||
|
std::shared_ptr<AnimationImageProvider> const &imageProvider,
|
||||||
|
std::shared_ptr<AnimationTextProvider> const &textProvider,
|
||||||
|
std::shared_ptr<AnimationFontProvider> const &fontProvider
|
||||||
|
) {
|
||||||
|
if (animation.assetLibrary) {
|
||||||
|
_layerImageProvider = std::make_shared<LayerImageProvider>(imageProvider, animation.assetLibrary->imageAssets);
|
||||||
|
} else {
|
||||||
|
std::map<std::string, std::shared_ptr<ImageAsset>> imageAssets;
|
||||||
|
_layerImageProvider = std::make_shared<LayerImageProvider>(imageProvider, imageAssets);
|
||||||
|
}
|
||||||
|
|
||||||
|
_layerTextProvider = std::make_shared<LayerTextProvider>(textProvider);
|
||||||
|
_layerFontProvider = std::make_shared<LayerFontProvider>(fontProvider);
|
||||||
|
|
||||||
|
setBounds(CGRect(0.0, 0.0, animation.width, animation.height));
|
||||||
|
|
||||||
|
auto layers = initializeCompositionLayers(
|
||||||
|
animation.layers,
|
||||||
|
animation.assetLibrary,
|
||||||
|
_layerImageProvider,
|
||||||
|
textProvider,
|
||||||
|
fontProvider,
|
||||||
|
animation.framerate
|
||||||
|
);
|
||||||
|
|
||||||
|
std::vector<std::shared_ptr<ImageCompositionLayer>> imageLayers;
|
||||||
|
std::vector<std::shared_ptr<TextCompositionLayer>> textLayers;
|
||||||
|
|
||||||
|
std::shared_ptr<CompositionLayer> mattedLayer;
|
||||||
|
|
||||||
|
for (auto layerIt = layers.rbegin(); layerIt != layers.rend(); layerIt++) {
|
||||||
|
std::shared_ptr<CompositionLayer> const &layer = *layerIt;
|
||||||
|
layer->setBounds(bounds());
|
||||||
|
_animationLayers.push_back(layer);
|
||||||
|
|
||||||
|
if (layer->isImageCompositionLayer()) {
|
||||||
|
imageLayers.push_back(std::static_pointer_cast<ImageCompositionLayer>(layer));
|
||||||
|
}
|
||||||
|
if (layer->isTextCompositionLayer()) {
|
||||||
|
textLayers.push_back(std::static_pointer_cast<TextCompositionLayer>(layer));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mattedLayer) {
|
||||||
|
/// The previous layer requires this layer to be its matte
|
||||||
|
mattedLayer->setMatteLayer(layer);
|
||||||
|
mattedLayer = nullptr;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (layer->matteType().has_value() && (layer->matteType() == MatteType::Add || layer->matteType() == MatteType::Invert)) {
|
||||||
|
/// We have a layer that requires a matte.
|
||||||
|
mattedLayer = layer;
|
||||||
|
}
|
||||||
|
addSublayer(layer);
|
||||||
|
}
|
||||||
|
|
||||||
|
_layerImageProvider->addImageLayers(imageLayers);
|
||||||
|
_layerImageProvider->reloadImages();
|
||||||
|
_layerTextProvider->addTextLayers(textLayers);
|
||||||
|
_layerTextProvider->reloadTexts();
|
||||||
|
_layerFontProvider->addTextLayers(textLayers);
|
||||||
|
_layerFontProvider->reloadTexts();
|
||||||
|
|
||||||
|
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<std::shared_ptr<CompositionLayer>> const &animationLayers() const {
|
||||||
|
return _animationLayers;
|
||||||
|
}
|
||||||
|
|
||||||
|
void reloadImages() {
|
||||||
|
_layerImageProvider->reloadImages();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Forces the view to update its drawing.
|
||||||
|
void forceDisplayUpdate() {
|
||||||
|
for (const auto &layer : _animationLayers) {
|
||||||
|
layer->displayWithFrame(currentFrame(), true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void logHierarchyKeypaths() {
|
||||||
|
printf("Lottie: Logging Animation Keypaths\n");
|
||||||
|
assert(false);
|
||||||
|
//animationLayers.forEach({ $0.logKeypaths(for: nil) })
|
||||||
|
}
|
||||||
|
|
||||||
|
void setValueProvider(std::shared_ptr<AnyValueProvider> const &valueProvider, AnimationKeypath const &keypath) {
|
||||||
|
/*for (const auto &layer : _animationLayers) {
|
||||||
|
assert(false);
|
||||||
|
if let foundProperties = layer.nodeProperties(for: keypath) {
|
||||||
|
for property in foundProperties {
|
||||||
|
property.setProvider(provider: valueProvider)
|
||||||
|
}
|
||||||
|
layer.displayWithFrame(frame: presentation()?.currentFrame ?? currentFrame, forceUpdates: true)
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<AnyValue> getValue(AnimationKeypath const &keypath, std::optional<double> atFrame) {
|
||||||
|
/*for (const auto &layer : _animationLayers) {
|
||||||
|
assert(false);
|
||||||
|
if
|
||||||
|
let foundProperties = layer.nodeProperties(for: keypath),
|
||||||
|
let first = foundProperties.first
|
||||||
|
{
|
||||||
|
return first.valueProvider.value(frame: atFrame ?? currentFrame)
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<AnyValue> getOriginalValue(AnimationKeypath const &keypath, std::optional<double> 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<CALayer> layerForKeypath(AnimationKeypath const &keyPath) {
|
||||||
|
assert(false);
|
||||||
|
/*for layer in animationLayers {
|
||||||
|
if let foundLayer = layer.layer(for: keypath) {
|
||||||
|
return foundLayer
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::shared_ptr<AnimatorNode>> animatorNodesForKeypath(AnimationKeypath const &keypath) {
|
||||||
|
std::vector<std::shared_ptr<AnimatorNode>> results;
|
||||||
|
/*for (const auto &layer : _animationLayers) {
|
||||||
|
if let nodes = layer.animatorNodes(for: keypath) {
|
||||||
|
results.append(contentsOf: nodes)
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
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<AnimationImageProvider> imageProvider() const {
|
||||||
|
return _layerImageProvider->imageProvider();
|
||||||
|
}
|
||||||
|
void setImageProvider(std::shared_ptr<AnimationImageProvider> const &imageProvider) {
|
||||||
|
_layerImageProvider->setImageProvider(imageProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<AnimationTextProvider> textProvider() const {
|
||||||
|
return _layerTextProvider->textProvider();
|
||||||
|
}
|
||||||
|
void setTextProvider(std::shared_ptr<AnimationTextProvider> const &textProvider) {
|
||||||
|
_layerTextProvider->setTextProvider(textProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<AnimationFontProvider> fontProvider() const {
|
||||||
|
return _layerFontProvider->fontProvider();
|
||||||
|
}
|
||||||
|
void setFontProvider(std::shared_ptr<AnimationFontProvider> const &fontProvider) {
|
||||||
|
_layerFontProvider->setFontProvider(fontProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual std::shared_ptr<RenderTreeNode> renderTreeNode() {
|
||||||
|
std::vector<std::shared_ptr<RenderTreeNode>> subnodes;
|
||||||
|
for (const auto &animationLayer : _animationLayers) {
|
||||||
|
bool found = false;
|
||||||
|
for (const auto &sublayer : sublayers()) {
|
||||||
|
if (animationLayer == sublayer) {
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (found) {
|
||||||
|
auto node = animationLayer->renderTreeNode();
|
||||||
|
if (node) {
|
||||||
|
subnodes.push_back(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::make_shared<RenderTreeNode>(
|
||||||
|
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<AnimationImageProvider> _imageProvider;
|
||||||
|
std::shared_ptr<AnimationTextProvider> _textProvider;
|
||||||
|
std::shared_ptr<AnimationFontProvider> _fontProvider;
|
||||||
|
|
||||||
|
bool _respectAnimationFrameRate = true;
|
||||||
|
|
||||||
|
std::vector<std::shared_ptr<CompositionLayer>> _animationLayers;
|
||||||
|
|
||||||
|
std::shared_ptr<LayerImageProvider> _layerImageProvider;
|
||||||
|
std::shared_ptr<LayerTextProvider> _layerTextProvider;
|
||||||
|
std::shared_ptr<LayerFontProvider> _layerFontProvider;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* MainThreadAnimationLayer_hpp */
|
||||||
@ -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<std::shared_ptr<CompositionLayer>> initializeCompositionLayers(
|
||||||
|
std::vector<std::shared_ptr<LayerModel>> const &layers,
|
||||||
|
std::shared_ptr<AssetLibrary> const &assetLibrary,
|
||||||
|
std::shared_ptr<LayerImageProvider> const &layerImageProvider,
|
||||||
|
std::shared_ptr<AnimationTextProvider> const &textProvider,
|
||||||
|
std::shared_ptr<AnimationFontProvider> const &fontProvider,
|
||||||
|
double frameRate
|
||||||
|
) {
|
||||||
|
std::vector<std::shared_ptr<CompositionLayer>> compositionLayers;
|
||||||
|
std::map<int, std::shared_ptr<CompositionLayer>> layerMap;
|
||||||
|
|
||||||
|
/// Organize the assets into a dictionary of [ID : ImageAsset]
|
||||||
|
std::vector<std::shared_ptr<LayerModel>> childLayers;
|
||||||
|
|
||||||
|
for (const auto &layer : layers) {
|
||||||
|
if (layer->hidden) {
|
||||||
|
auto genericLayer = std::make_shared<NullCompositionLayer>(layer);
|
||||||
|
compositionLayers.push_back(genericLayer);
|
||||||
|
if (layer->index) {
|
||||||
|
layerMap.insert(std::make_pair(layer->index.value(), genericLayer));
|
||||||
|
}
|
||||||
|
} else if (layer->type == LayerType::Shape) {
|
||||||
|
auto shapeContainer = std::make_shared<ShapeCompositionLayer>(std::static_pointer_cast<ShapeLayerModel>(layer));
|
||||||
|
compositionLayers.push_back(shapeContainer);
|
||||||
|
if (layer->index) {
|
||||||
|
layerMap.insert(std::make_pair(layer->index.value(), shapeContainer));
|
||||||
|
}
|
||||||
|
} else if (layer->type == LayerType::Solid) {
|
||||||
|
auto shapeContainer = std::make_shared<ShapeCompositionLayer>(std::static_pointer_cast<SolidLayerModel>(layer));
|
||||||
|
compositionLayers.push_back(shapeContainer);
|
||||||
|
if (layer->index) {
|
||||||
|
layerMap.insert(std::make_pair(layer->index.value(), shapeContainer));
|
||||||
|
}
|
||||||
|
} else if (layer->type == LayerType::Precomp && assetLibrary) {
|
||||||
|
auto precompLayer = std::static_pointer_cast<PreCompLayerModel>(layer);
|
||||||
|
auto precompAssetIt = assetLibrary->precompAssets.find(precompLayer->referenceID);
|
||||||
|
if (precompAssetIt != assetLibrary->precompAssets.end()) {
|
||||||
|
auto precompContainer = std::make_shared<PreCompositionLayer>(
|
||||||
|
precompLayer,
|
||||||
|
*(precompAssetIt->second),
|
||||||
|
layerImageProvider,
|
||||||
|
textProvider,
|
||||||
|
fontProvider,
|
||||||
|
assetLibrary,
|
||||||
|
frameRate
|
||||||
|
);
|
||||||
|
compositionLayers.push_back(precompContainer);
|
||||||
|
if (layer->index) {
|
||||||
|
layerMap.insert(std::make_pair(layer->index.value(), precompContainer));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (layer->type == LayerType::Image && assetLibrary) {
|
||||||
|
auto imageLayer = std::static_pointer_cast<ImageLayerModel>(layer);
|
||||||
|
auto imageAssetIt = assetLibrary->imageAssets.find(imageLayer->referenceID);
|
||||||
|
if (imageAssetIt != assetLibrary->imageAssets.end()) {
|
||||||
|
auto imageContainer = std::make_shared<ImageCompositionLayer>(
|
||||||
|
imageLayer,
|
||||||
|
Vector2D((*imageAssetIt->second).width, (*imageAssetIt->second).height)
|
||||||
|
);
|
||||||
|
compositionLayers.push_back(imageContainer);
|
||||||
|
if (layer->index) {
|
||||||
|
layerMap.insert(std::make_pair(layer->index.value(), imageContainer));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (layer->type == LayerType::Text) {
|
||||||
|
auto textContainer = std::make_shared<TextCompositionLayer>(std::static_pointer_cast<TextLayerModel>(layer), textProvider, fontProvider);
|
||||||
|
compositionLayers.push_back(textContainer);
|
||||||
|
if (layer->index) {
|
||||||
|
layerMap.insert(std::make_pair(layer->index.value(), textContainer));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
auto genericLayer = std::make_shared<NullCompositionLayer>(layer);
|
||||||
|
compositionLayers.push_back(genericLayer);
|
||||||
|
if (layer->index) {
|
||||||
|
layerMap.insert(std::make_pair(layer->index.value(), genericLayer));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (layer->parent) {
|
||||||
|
childLayers.push_back(layer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Now link children with their parents
|
||||||
|
for (const auto &layerModel : childLayers) {
|
||||||
|
if (!layerModel->index.has_value()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (const auto parentID = layerModel->parent) {
|
||||||
|
auto childLayerIt = layerMap.find(layerModel->index.value());
|
||||||
|
if (childLayerIt != layerMap.end()) {
|
||||||
|
auto parentLayerIt = layerMap.find(parentID.value());
|
||||||
|
if (parentLayerIt != layerMap.end()) {
|
||||||
|
childLayerIt->second->transformNode()->setParentNode(parentLayerIt->second->transformNode());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return compositionLayers;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
#ifndef CompositionLayersInitializer_hpp
|
||||||
|
#define CompositionLayersInitializer_hpp
|
||||||
|
|
||||||
|
#include "Lottie/Private/MainThread/LayerContainers/CompLayers/CompositionLayer.hpp"
|
||||||
|
#include "Lottie/Private/Model/Assets/AssetLibrary.hpp"
|
||||||
|
#include "Lottie/Private/MainThread/LayerContainers/Utility/LayerImageProvider.hpp"
|
||||||
|
#include "Lottie/Public/TextProvider/AnimationTextProvider.hpp"
|
||||||
|
#include "Lottie/Public/FontProvider/AnimationFontProvider.hpp"
|
||||||
|
|
||||||
|
namespace lottie {
|
||||||
|
|
||||||
|
std::vector<std::shared_ptr<CompositionLayer>> initializeCompositionLayers(
|
||||||
|
std::vector<std::shared_ptr<LayerModel>> const &layers,
|
||||||
|
std::shared_ptr<AssetLibrary> const &assetLibrary,
|
||||||
|
std::shared_ptr<LayerImageProvider> const &layerImageProvider,
|
||||||
|
std::shared_ptr<AnimationTextProvider> const &textProvider,
|
||||||
|
std::shared_ptr<AnimationFontProvider> const &fontProvider,
|
||||||
|
double frameRate
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* CompositionLayersInitializer_hpp */
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
#include "LayerFontProvider.hpp"
|
||||||
|
|
||||||
|
namespace lottie {
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,45 @@
|
|||||||
|
#ifndef LayerFontProvider_hpp
|
||||||
|
#define LayerFontProvider_hpp
|
||||||
|
|
||||||
|
#include "Lottie/Public/FontProvider/AnimationFontProvider.hpp"
|
||||||
|
#include "Lottie/Private/MainThread/LayerContainers/CompLayers/TextCompositionLayer.hpp"
|
||||||
|
|
||||||
|
namespace lottie {
|
||||||
|
|
||||||
|
/// Connects a LottieFontProvider to a group of text layers
|
||||||
|
class LayerFontProvider {
|
||||||
|
public:
|
||||||
|
LayerFontProvider(std::shared_ptr<AnimationFontProvider> const &fontProvider) {
|
||||||
|
_fontProvider = fontProvider;
|
||||||
|
reloadTexts();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<AnimationFontProvider> const &fontProvider() const {
|
||||||
|
return _fontProvider;
|
||||||
|
}
|
||||||
|
void setFontProvider(std::shared_ptr<AnimationFontProvider> const &fontProvider) {
|
||||||
|
_fontProvider = fontProvider;
|
||||||
|
reloadTexts();
|
||||||
|
}
|
||||||
|
|
||||||
|
void addTextLayers(std::vector<std::shared_ptr<TextCompositionLayer>> const &layers) {
|
||||||
|
for (const auto &layer : layers) {
|
||||||
|
_textLayers.push_back(layer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void reloadTexts() {
|
||||||
|
for (const auto &layer : _textLayers) {
|
||||||
|
layer->setFontProvider(_fontProvider);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<std::shared_ptr<TextCompositionLayer>> _textLayers;
|
||||||
|
|
||||||
|
std::shared_ptr<AnimationFontProvider> _fontProvider;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* LayerFontProvider_hpp */
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
#include "LayerImageProvider.hpp"
|
||||||
|
|
||||||
|
namespace lottie {
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,58 @@
|
|||||||
|
#ifndef LayerImageProvider_hpp
|
||||||
|
#define LayerImageProvider_hpp
|
||||||
|
|
||||||
|
#include "Lottie/Public/ImageProvider/AnimationImageProvider.hpp"
|
||||||
|
#include "Lottie/Private/Model/Assets/ImageAsset.hpp"
|
||||||
|
#include "Lottie/Private/MainThread/LayerContainers/CompLayers/ImageCompositionLayer.hpp"
|
||||||
|
|
||||||
|
namespace lottie {
|
||||||
|
|
||||||
|
/// Connects a LottieImageProvider to a group of image layers
|
||||||
|
class LayerImageProvider {
|
||||||
|
public:
|
||||||
|
LayerImageProvider(std::shared_ptr<AnimationImageProvider> const &imageProvider, std::map<std::string, std::shared_ptr<ImageAsset>> const &assets) :
|
||||||
|
_imageProvider(imageProvider),
|
||||||
|
_imageAssets(assets) {
|
||||||
|
reloadImages();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<AnimationImageProvider> imageProvider() const {
|
||||||
|
return _imageProvider;
|
||||||
|
}
|
||||||
|
void setImageProvider(std::shared_ptr<AnimationImageProvider> const &imageProvider) {
|
||||||
|
_imageProvider = imageProvider;
|
||||||
|
reloadImages();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::shared_ptr<ImageCompositionLayer>> const &imageLayers() const {
|
||||||
|
return _imageLayers;
|
||||||
|
}
|
||||||
|
|
||||||
|
void addImageLayers(std::vector<std::shared_ptr<ImageCompositionLayer>> const &layers) {
|
||||||
|
for (const auto &layer : layers) {
|
||||||
|
auto it = _imageAssets.find(layer->imageReferenceID());
|
||||||
|
if (it != _imageAssets.end()) {
|
||||||
|
_imageLayers.push_back(layer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void reloadImages() {
|
||||||
|
for (const auto &imageLayer : imageLayers()) {
|
||||||
|
auto it = _imageAssets.find(imageLayer->imageReferenceID());
|
||||||
|
if (it != _imageAssets.end()) {
|
||||||
|
imageLayer->setImage(_imageProvider->imageForAsset(*it->second));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::shared_ptr<AnimationImageProvider> _imageProvider;
|
||||||
|
std::vector<std::shared_ptr<ImageCompositionLayer>> _imageLayers;
|
||||||
|
|
||||||
|
std::map<std::string, std::shared_ptr<ImageAsset>> _imageAssets;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* LayerImageProvider_hpp */
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
#include "LayerTextProvider.hpp"
|
||||||
|
|
||||||
|
namespace lottie {
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,45 @@
|
|||||||
|
#ifndef LayerTextProvider_hpp
|
||||||
|
#define LayerTextProvider_hpp
|
||||||
|
|
||||||
|
#include "Lottie/Public/TextProvider/AnimationTextProvider.hpp"
|
||||||
|
#include "Lottie/Private/MainThread/LayerContainers/CompLayers/TextCompositionLayer.hpp"
|
||||||
|
|
||||||
|
namespace lottie {
|
||||||
|
|
||||||
|
/// Connects a LottieTextProvider to a group of text layers
|
||||||
|
class LayerTextProvider {
|
||||||
|
public:
|
||||||
|
LayerTextProvider(std::shared_ptr<AnimationTextProvider> const &textProvider) {
|
||||||
|
_textProvider = textProvider;
|
||||||
|
reloadTexts();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<AnimationTextProvider> const &textProvider() const {
|
||||||
|
return _textProvider;
|
||||||
|
}
|
||||||
|
void setTextProvider(std::shared_ptr<AnimationTextProvider> const &textProvider) {
|
||||||
|
_textProvider = textProvider;
|
||||||
|
reloadTexts();
|
||||||
|
}
|
||||||
|
|
||||||
|
void addTextLayers(std::vector<std::shared_ptr<TextCompositionLayer>> const &layers) {
|
||||||
|
for (const auto &layer : layers) {
|
||||||
|
_textLayers.push_back(layer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void reloadTexts() {
|
||||||
|
for (const auto &layer : _textLayers) {
|
||||||
|
layer->setTextProvider(_textProvider);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<std::shared_ptr<TextCompositionLayer>> _textLayers;
|
||||||
|
|
||||||
|
std::shared_ptr<AnimationTextProvider> _textProvider;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* LayerTextProvider_hpp */
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
#include "LayerTransformNode.hpp"
|
||||||
|
|
||||||
|
namespace lottie {
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,205 @@
|
|||||||
|
#ifndef LayerTransformNode_hpp
|
||||||
|
#define LayerTransformNode_hpp
|
||||||
|
|
||||||
|
#include "Lottie/Private/Model/Objects/Transform.hpp"
|
||||||
|
#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/NodePropertyMap.hpp"
|
||||||
|
#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/KeypathSearchable.hpp"
|
||||||
|
#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/NodeProperty.hpp"
|
||||||
|
#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/KeyframeInterpolator.hpp"
|
||||||
|
#include "Lottie/Private/MainThread/NodeRenderSystem/Protocols/AnimatorNode.hpp"
|
||||||
|
#include "Lottie/Private/MainThread/NodeRenderSystem/Protocols/NodeOutput.hpp"
|
||||||
|
#include "Lottie/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/PassThroughOutputNode.hpp"
|
||||||
|
|
||||||
|
namespace lottie {
|
||||||
|
|
||||||
|
class LayerTransformProperties: public KeypathSearchableNodePropertyMap {
|
||||||
|
public:
|
||||||
|
LayerTransformProperties(std::shared_ptr<Transform> transform) {
|
||||||
|
_anchor = std::make_shared<NodeProperty<Vector3D>>(std::make_shared<KeyframeInterpolator<Vector3D>>(transform->anchorPoint().keyframes));
|
||||||
|
_scale = std::make_shared<NodeProperty<Vector3D>>(std::make_shared<KeyframeInterpolator<Vector3D>>(transform->scale().keyframes));
|
||||||
|
_rotation = std::make_shared<NodeProperty<Vector1D>>(std::make_shared<KeyframeInterpolator<Vector1D>>(transform->rotation().keyframes));
|
||||||
|
_opacity = std::make_shared<NodeProperty<Vector1D>>(std::make_shared<KeyframeInterpolator<Vector1D>>(transform->opacity().keyframes));
|
||||||
|
|
||||||
|
std::map<std::string, std::shared_ptr<AnyNodeProperty>> propertyMap;
|
||||||
|
_keypathProperties.insert(std::make_pair("Anchor Point", _anchor));
|
||||||
|
_keypathProperties.insert(std::make_pair("Scale", _scale));
|
||||||
|
_keypathProperties.insert(std::make_pair("Rotation", _rotation));
|
||||||
|
_keypathProperties.insert(std::make_pair("Opacity", _opacity));
|
||||||
|
|
||||||
|
if (transform->positionX().has_value() && transform->positionY().has_value()) {
|
||||||
|
auto xPosition = std::make_shared<NodeProperty<Vector1D>>(std::make_shared<KeyframeInterpolator<Vector1D>>(transform->positionX()->keyframes));
|
||||||
|
auto yPosition = std::make_shared<NodeProperty<Vector1D>>(std::make_shared<KeyframeInterpolator<Vector1D>>(transform->positionY()->keyframes));
|
||||||
|
_keypathProperties.insert(std::make_pair("X Position", xPosition));
|
||||||
|
_keypathProperties.insert(std::make_pair("Y Position", yPosition));
|
||||||
|
|
||||||
|
_positionX = xPosition;
|
||||||
|
_positionY = yPosition;
|
||||||
|
_position = nullptr;
|
||||||
|
} else if (transform->position().has_value()) {
|
||||||
|
auto position = std::make_shared<NodeProperty<Vector3D>>(std::make_shared<KeyframeInterpolator<Vector3D>>(transform->position()->keyframes));
|
||||||
|
_keypathProperties.insert(std::make_pair("Position", position));
|
||||||
|
|
||||||
|
_position = position;
|
||||||
|
_positionX = nullptr;
|
||||||
|
_positionY = nullptr;
|
||||||
|
} else {
|
||||||
|
_position = nullptr;
|
||||||
|
_positionX = nullptr;
|
||||||
|
_positionY = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto &it : _keypathProperties) {
|
||||||
|
_properties.push_back(it.second);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual ~LayerTransformProperties() = default;
|
||||||
|
|
||||||
|
virtual std::vector<std::shared_ptr<AnyNodeProperty>> &properties() override {
|
||||||
|
return _properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual std::vector<std::shared_ptr<KeypathSearchable>> const &childKeypaths() const override {
|
||||||
|
return _childKeypaths;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual std::string keypathName() const override {
|
||||||
|
return "Transform";
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual std::map<std::string, std::shared_ptr<AnyNodeProperty>> keypathProperties() const override {
|
||||||
|
return _keypathProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual std::shared_ptr<CALayer> keypathLayer() const override {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<NodeProperty<Vector3D>> const &anchor() {
|
||||||
|
return _anchor;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<NodeProperty<Vector3D>> const &scale() {
|
||||||
|
return _scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<NodeProperty<Vector1D>> const &rotation() {
|
||||||
|
return _rotation;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<NodeProperty<Vector3D>> const &position() {
|
||||||
|
return _position;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<NodeProperty<Vector1D>> const &positionX() {
|
||||||
|
return _positionX;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<NodeProperty<Vector1D>> const &positionY() {
|
||||||
|
return _positionY;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<NodeProperty<Vector1D>> const &opacity() {
|
||||||
|
return _opacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::map<std::string, std::shared_ptr<AnyNodeProperty>> _keypathProperties;
|
||||||
|
std::vector<std::shared_ptr<KeypathSearchable>> _childKeypaths;
|
||||||
|
|
||||||
|
std::vector<std::shared_ptr<AnyNodeProperty>> _properties;
|
||||||
|
|
||||||
|
std::shared_ptr<NodeProperty<Vector3D>> _anchor;
|
||||||
|
std::shared_ptr<NodeProperty<Vector3D>> _scale;
|
||||||
|
std::shared_ptr<NodeProperty<Vector1D>> _rotation;
|
||||||
|
std::shared_ptr<NodeProperty<Vector3D>> _position;
|
||||||
|
std::shared_ptr<NodeProperty<Vector1D>> _positionX;
|
||||||
|
std::shared_ptr<NodeProperty<Vector1D>> _positionY;
|
||||||
|
std::shared_ptr<NodeProperty<Vector1D>> _opacity;
|
||||||
|
};
|
||||||
|
|
||||||
|
class LayerTransformNode: public AnimatorNode {
|
||||||
|
public:
|
||||||
|
LayerTransformNode(std::shared_ptr<Transform> transform) :
|
||||||
|
AnimatorNode(nullptr),
|
||||||
|
_transformProperties(std::make_shared<LayerTransformProperties>(transform)) {
|
||||||
|
_outputNode = std::make_shared<PassThroughOutputNode>(nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual ~LayerTransformNode() = default;
|
||||||
|
|
||||||
|
virtual std::shared_ptr<NodeOutput> outputNode() override {
|
||||||
|
return _outputNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual std::shared_ptr<KeypathSearchableNodePropertyMap> propertyMap() const override {
|
||||||
|
return _transformProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual bool shouldRebuildOutputs(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<LayerTransformProperties> const &transformProperties() {
|
||||||
|
return _transformProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
float opacity() {
|
||||||
|
return _opacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
CATransform3D const &globalTransform() {
|
||||||
|
return _globalTransform;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::shared_ptr<NodeOutput> _outputNode;
|
||||||
|
|
||||||
|
std::shared_ptr<LayerTransformProperties> _transformProperties;
|
||||||
|
|
||||||
|
float _opacity = 1.0;
|
||||||
|
CATransform3D _localTransform = CATransform3D::identity();
|
||||||
|
CATransform3D _globalTransform = CATransform3D::identity();
|
||||||
|
|
||||||
|
public:
|
||||||
|
virtual LayerTransformNode *asLayerTransformNode() override {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* LayerTransformNode_hpp */
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
#include "NodeProperty.hpp"
|
||||||
|
|
||||||
|
namespace lottie {
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,54 @@
|
|||||||
|
#ifndef NodeProperty_hpp
|
||||||
|
#define NodeProperty_hpp
|
||||||
|
|
||||||
|
#include "Lottie/Public/Primitives/AnyValue.hpp"
|
||||||
|
#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/AnyNodeProperty.hpp"
|
||||||
|
#include "Lottie/Public/DynamicProperties/AnyValueProvider.hpp"
|
||||||
|
#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueContainer.hpp"
|
||||||
|
|
||||||
|
namespace lottie {
|
||||||
|
|
||||||
|
/// A node property that holds a reference to a T ValueProvider and a T ValueContainer.
|
||||||
|
template<typename T>
|
||||||
|
class NodeProperty: public AnyNodeProperty {
|
||||||
|
public:
|
||||||
|
NodeProperty(std::shared_ptr<ValueProvider<T>> provider) :
|
||||||
|
_typedContainer(provider->value(0.0)),
|
||||||
|
_valueProvider(provider) {
|
||||||
|
_typedContainer.setNeedsUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
virtual AnyValue::Type valueType() const override {
|
||||||
|
return AnyValueType<T>::type();
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual T value() {
|
||||||
|
return _typedContainer.outputValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual bool needsUpdate(double frame) const override {
|
||||||
|
return _typedContainer.needsUpdate() || _valueProvider->hasUpdate(frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void setProvider(std::shared_ptr<AnyValueProvider> provider) override {
|
||||||
|
/*if (provider->valueType() != valueType()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_valueProvider = provider;
|
||||||
|
_typedContainer.setNeedsUpdate();*/
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void update(double frame) override {
|
||||||
|
_typedContainer.setValue(_valueProvider->value(frame), frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
ValueContainer<T> _typedContainer;
|
||||||
|
std::shared_ptr<ValueProvider<T>> _valueProvider;
|
||||||
|
//std::shared_ptr<AnyValueProvider> _originalValueProvider;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* NodeProperty_hpp */
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
#include "AnyNodeProperty.hpp"
|
||||||
|
|
||||||
|
namespace lottie {
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,33 @@
|
|||||||
|
#ifndef AnyNodeProperty_hpp
|
||||||
|
#define AnyNodeProperty_hpp
|
||||||
|
|
||||||
|
#include "Lottie/Public/Primitives/AnyValue.hpp"
|
||||||
|
#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/AnyValueContainer.hpp"
|
||||||
|
#include "Lottie/Public/DynamicProperties/AnyValueProvider.hpp"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace lottie {
|
||||||
|
|
||||||
|
/// A property of a node. The node property holds a provider and a container
|
||||||
|
class AnyNodeProperty {
|
||||||
|
public:
|
||||||
|
virtual ~AnyNodeProperty() = default;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/// Returns true if the property needs to recompute its stored value
|
||||||
|
virtual bool needsUpdate(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<AnyValueProvider> provider) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* AnyNodeProperty_hpp */
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
#include "AnyValueContainer.hpp"
|
||||||
|
|
||||||
|
namespace lottie {
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
#ifndef AnyValueContainer_hpp
|
||||||
|
#define AnyValueContainer_hpp
|
||||||
|
|
||||||
|
#include "Lottie/Public/Primitives/AnyValue.hpp"
|
||||||
|
|
||||||
|
namespace lottie {
|
||||||
|
|
||||||
|
class AnyValueContainer {
|
||||||
|
public:
|
||||||
|
/// The stored value of the container
|
||||||
|
virtual AnyValue value() const = 0;
|
||||||
|
|
||||||
|
/// Notifies the provider that it should update its container
|
||||||
|
virtual void setNeedsUpdate() = 0;
|
||||||
|
|
||||||
|
/// When true the container needs to have its value updated by its provider
|
||||||
|
virtual bool needsUpdate() const = 0;
|
||||||
|
|
||||||
|
/// The frame time of the last provided update
|
||||||
|
virtual double lastUpdateFrame() const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* AnyValueContainer_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 */
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
#ifndef HasUpdate_hpp
|
||||||
|
#define HasUpdate_hpp
|
||||||
|
|
||||||
|
namespace lottie {
|
||||||
|
|
||||||
|
class HasUpdate {
|
||||||
|
public:
|
||||||
|
/// The last frame in which this node was updated.
|
||||||
|
virtual bool hasUpdate() = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* HasUpdate_hpp */
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
#include "KeypathSearchable.hpp"
|
||||||
|
|
||||||
|
namespace lottie {
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,36 @@
|
|||||||
|
#ifndef KeypathSearchable_hpp
|
||||||
|
#define KeypathSearchable_hpp
|
||||||
|
|
||||||
|
#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/AnyNodeProperty.hpp"
|
||||||
|
#include "Lottie/Public/Primitives/CALayer.hpp"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <map>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace lottie {
|
||||||
|
|
||||||
|
class KeypathSearchable;
|
||||||
|
|
||||||
|
class HasChildKeypaths {
|
||||||
|
public:
|
||||||
|
/// Children Keypaths
|
||||||
|
virtual std::vector<std::shared_ptr<KeypathSearchable>> const &childKeypaths() const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Protocol that provides keypath search functionality. Returns all node properties associated with a keypath.
|
||||||
|
class KeypathSearchable: virtual public HasChildKeypaths {
|
||||||
|
public:
|
||||||
|
/// The name of the Keypath
|
||||||
|
virtual std::string keypathName() const = 0;
|
||||||
|
|
||||||
|
/// A list of properties belonging to the keypath.
|
||||||
|
virtual std::map<std::string, std::shared_ptr<AnyNodeProperty>> keypathProperties() const = 0;
|
||||||
|
|
||||||
|
virtual std::shared_ptr<CALayer> keypathLayer() const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* KeypathSearchable_hpp */
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
#include "NodePropertyMap.hpp"
|
||||||
|
|
||||||
|
namespace lottie {
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,41 @@
|
|||||||
|
#ifndef NodePropertyMap_hpp
|
||||||
|
#define NodePropertyMap_hpp
|
||||||
|
|
||||||
|
#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/AnyNodeProperty.hpp"
|
||||||
|
#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/KeypathSearchable.hpp"
|
||||||
|
#include "Lottie/Public/Primitives/CALayer.hpp"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace lottie {
|
||||||
|
|
||||||
|
class NodePropertyMap: virtual public HasChildKeypaths {
|
||||||
|
public:
|
||||||
|
virtual std::vector<std::shared_ptr<AnyNodeProperty>> &properties() = 0;
|
||||||
|
|
||||||
|
bool needsLocalUpdate(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<CALayer> keypathLayer() const override {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* NodePropertyMap_hpp */
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
#include "ValueContainer.hpp"
|
||||||
|
|
||||||
|
namespace lottie {
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,58 @@
|
|||||||
|
#ifndef ValueContainer_hpp
|
||||||
|
#define ValueContainer_hpp
|
||||||
|
|
||||||
|
#include "Lottie/Public/Primitives/AnyValue.hpp"
|
||||||
|
#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/AnyValueContainer.hpp"
|
||||||
|
|
||||||
|
namespace lottie {
|
||||||
|
|
||||||
|
/// A container for a node value that is Typed to T.
|
||||||
|
template<typename T>
|
||||||
|
class ValueContainer: public AnyValueContainer {
|
||||||
|
public:
|
||||||
|
ValueContainer(T value) :
|
||||||
|
_outputValue(value) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
double _lastUpdateFrame = std::numeric_limits<double>::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<T>::type()) {
|
||||||
|
_needsUpdate = false;
|
||||||
|
_lastUpdateFrame = forFrame;
|
||||||
|
_outputValue = value.get<T>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void setNeedsUpdate() override {
|
||||||
|
_needsUpdate = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* ValueContainer_hpp */
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
#include "DashPatternInterpolator.hpp"
|
||||||
|
|
||||||
|
namespace lottie {
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,48 @@
|
|||||||
|
#ifndef DashPatternInterpolator_hpp
|
||||||
|
#define DashPatternInterpolator_hpp
|
||||||
|
|
||||||
|
#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/KeyframeInterpolator.hpp"
|
||||||
|
#include "Lottie/Public/Primitives/DashPattern.hpp"
|
||||||
|
|
||||||
|
namespace lottie {
|
||||||
|
|
||||||
|
/// A value provider that produces an array of values from an array of Keyframe Interpolators
|
||||||
|
class DashPatternInterpolator: public ValueProvider<DashPattern>, public std::enable_shared_from_this<DashPatternInterpolator> {
|
||||||
|
public:
|
||||||
|
/// Initialize with an array of array of keyframes.
|
||||||
|
DashPatternInterpolator(std::vector<std::vector<Keyframe<Vector1D>>> const &keyframeGroups) {
|
||||||
|
for (const auto &keyframeGroup : keyframeGroups) {
|
||||||
|
_keyframeInterpolators.push_back(std::make_shared<KeyframeInterpolator<Vector1D>>(keyframeGroup));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual ~DashPatternInterpolator() = default;
|
||||||
|
|
||||||
|
virtual AnyValue::Type valueType() const override {
|
||||||
|
return AnyValueType<DashPattern>::type();
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual DashPattern value(AnimationFrameTime frame) override {
|
||||||
|
std::vector<double> 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<std::shared_ptr<KeyframeInterpolator<Vector1D>>> _keyframeInterpolators;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* DashPatternInterpolator_hpp */
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
#include "KeyframeInterpolator.hpp"
|
||||||
|
|
||||||
|
namespace lottie {
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,452 @@
|
|||||||
|
#ifndef KeyframeInterpolator_hpp
|
||||||
|
#define KeyframeInterpolator_hpp
|
||||||
|
|
||||||
|
#include "Lottie/Public/DynamicProperties/AnyValueProvider.hpp"
|
||||||
|
|
||||||
|
namespace lottie {
|
||||||
|
|
||||||
|
/// A value provider that produces a value at Time from a group of keyframes
|
||||||
|
template<typename T>
|
||||||
|
class KeyframeInterpolator: public ValueProvider<T>, public std::enable_shared_from_this<KeyframeInterpolator<T>> {
|
||||||
|
public:
|
||||||
|
KeyframeInterpolator(std::vector<Keyframe<T>> const &keyframes_) :
|
||||||
|
keyframes(keyframes_) {
|
||||||
|
assert(!keyframes.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual ~KeyframeInterpolator() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
std::vector<Keyframe<T>> keyframes;
|
||||||
|
|
||||||
|
virtual AnyValue::Type valueType() const override {
|
||||||
|
return AnyValueType<T>::type();
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual T value(AnimationFrameTime frame) override {
|
||||||
|
// First set the keyframe span for the frame.
|
||||||
|
updateSpanIndices(frame);
|
||||||
|
lastUpdatedFrame = frame;
|
||||||
|
// If only one keyframe return its value
|
||||||
|
|
||||||
|
if (leadingKeyframe.has_value() &&
|
||||||
|
trailingKeyframe.has_value())
|
||||||
|
{
|
||||||
|
/// We have leading and trailing keyframe.
|
||||||
|
auto progress = leadingKeyframe->interpolatedProgress(trailingKeyframe.value(), frame);
|
||||||
|
return leadingKeyframe->interpolate(trailingKeyframe.value(), progress);
|
||||||
|
} else if (leadingKeyframe.has_value()) {
|
||||||
|
return leadingKeyframe->value;
|
||||||
|
} else if (trailingKeyframe.has_value()) {
|
||||||
|
return trailingKeyframe->value;
|
||||||
|
} else {
|
||||||
|
/// Satisfy the compiler.
|
||||||
|
return keyframes[0].value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true to trigger a frame update for this interpolator.
|
||||||
|
///
|
||||||
|
/// An interpolator will be asked if it needs to update every frame.
|
||||||
|
/// If the interpolator needs updating it will be asked to compute its value for
|
||||||
|
/// the given frame.
|
||||||
|
///
|
||||||
|
/// Cases a keyframe should not be updated:
|
||||||
|
/// - If time is in span and leading keyframe is hold
|
||||||
|
/// - If time is after the last keyframe.
|
||||||
|
/// - If time is before the first keyframe
|
||||||
|
///
|
||||||
|
/// Cases for updating a keyframe:
|
||||||
|
/// - If time is in the span, and is not a hold
|
||||||
|
/// - If time is outside of the span, and there are more keyframes
|
||||||
|
/// - If a value delegate is set
|
||||||
|
/// - If leading and trailing are both nil.
|
||||||
|
virtual bool hasUpdate(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<double> lastUpdatedFrame;
|
||||||
|
|
||||||
|
std::optional<int> leadingIndex;
|
||||||
|
std::optional<int> trailingIndex;
|
||||||
|
std::optional<Keyframe<T>> leadingKeyframe;
|
||||||
|
std::optional<Keyframe<T>> trailingKeyframe;
|
||||||
|
|
||||||
|
/// Finds the appropriate Leading and Trailing keyframe index for the given time.
|
||||||
|
void updateSpanIndices(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<Keyframe<BezierPath>> const &keyframes_) :
|
||||||
|
keyframes(keyframes_) {
|
||||||
|
assert(!keyframes.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
std::vector<Keyframe<BezierPath>> keyframes;
|
||||||
|
|
||||||
|
void update(AnimationFrameTime frame, BezierPath &outPath) {
|
||||||
|
// First set the keyframe span for the frame.
|
||||||
|
updateSpanIndices(frame);
|
||||||
|
lastUpdatedFrame = frame;
|
||||||
|
// If only one keyframe return its value
|
||||||
|
|
||||||
|
if (leadingKeyframe.has_value() &&
|
||||||
|
trailingKeyframe.has_value())
|
||||||
|
{
|
||||||
|
/// We have leading and trailing keyframe.
|
||||||
|
auto progress = leadingKeyframe->interpolatedProgress(trailingKeyframe.value(), frame);
|
||||||
|
interpolateInplace(leadingKeyframe.value(), trailingKeyframe.value(), progress, outPath);
|
||||||
|
} else if (leadingKeyframe.has_value()) {
|
||||||
|
setInplace(leadingKeyframe.value(), outPath);
|
||||||
|
} else if (trailingKeyframe.has_value()) {
|
||||||
|
setInplace(trailingKeyframe.value(), outPath);
|
||||||
|
} else {
|
||||||
|
/// Satisfy the compiler.
|
||||||
|
setInplace(keyframes[0], outPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true to trigger a frame update for this interpolator.
|
||||||
|
///
|
||||||
|
/// An interpolator will be asked if it needs to update every frame.
|
||||||
|
/// If the interpolator needs updating it will be asked to compute its value for
|
||||||
|
/// the given frame.
|
||||||
|
///
|
||||||
|
/// Cases a keyframe should not be updated:
|
||||||
|
/// - If time is in span and leading keyframe is hold
|
||||||
|
/// - If time is after the last keyframe.
|
||||||
|
/// - If time is before the first keyframe
|
||||||
|
///
|
||||||
|
/// Cases for updating a keyframe:
|
||||||
|
/// - If time is in the span, and is not a hold
|
||||||
|
/// - If time is outside of the span, and there are more keyframes
|
||||||
|
/// - If a value delegate is set
|
||||||
|
/// - If leading and trailing are both nil.
|
||||||
|
bool hasUpdate(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<double> lastUpdatedFrame;
|
||||||
|
|
||||||
|
std::optional<int> leadingIndex;
|
||||||
|
std::optional<int> trailingIndex;
|
||||||
|
std::optional<Keyframe<BezierPath>> leadingKeyframe;
|
||||||
|
std::optional<Keyframe<BezierPath>> trailingKeyframe;
|
||||||
|
|
||||||
|
/// Finds the appropriate Leading and Trailing keyframe index for the given time.
|
||||||
|
void updateSpanIndices(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<BezierPath> const &from, BezierPath &outPath) {
|
||||||
|
ValueInterpolator<BezierPath>::setInplace(from.value, outPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
void interpolateInplace(Keyframe<BezierPath> const &from, Keyframe<BezierPath> const &to, double progress, BezierPath &outPath) {
|
||||||
|
std::optional<Vector2D> spatialOutTangent2d;
|
||||||
|
if (from.spatialOutTangent) {
|
||||||
|
spatialOutTangent2d = Vector2D(from.spatialOutTangent->x, from.spatialOutTangent->y);
|
||||||
|
}
|
||||||
|
std::optional<Vector2D> spatialInTangent2d;
|
||||||
|
if (to.spatialInTangent) {
|
||||||
|
spatialInTangent2d = Vector2D(to.spatialInTangent->x, to.spatialInTangent->y);
|
||||||
|
}
|
||||||
|
ValueInterpolator<BezierPath>::interpolateInplace(from.value, to.value, progress, spatialOutTangent2d, spatialInTangent2d, outPath);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* KeyframeInterpolator_hpp */
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
#include "SingleValueProvider.hpp"
|
||||||
|
|
||||||
|
namespace lottie {
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,42 @@
|
|||||||
|
#ifndef SingleValueProvider_hpp
|
||||||
|
#define SingleValueProvider_hpp
|
||||||
|
|
||||||
|
#include "Lottie/Public/DynamicProperties/AnyValueProvider.hpp"
|
||||||
|
|
||||||
|
namespace lottie {
|
||||||
|
|
||||||
|
/// Returns a value for every frame.
|
||||||
|
template<typename T>
|
||||||
|
class SingleValueProvider: public ValueProvider<T> {
|
||||||
|
public:
|
||||||
|
SingleValueProvider(T const &value) :
|
||||||
|
_value(value) {
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual ~SingleValueProvider() = default;
|
||||||
|
|
||||||
|
void setValue(T const &value) {
|
||||||
|
_value = value;
|
||||||
|
_hasUpdate = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual T value(AnimationFrameTime frame) override {
|
||||||
|
return _value;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual AnyValue::Type valueType() const override {
|
||||||
|
return AnyValueType<T>::type();
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual bool hasUpdate(double frame) const override {
|
||||||
|
return _hasUpdate;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
T _value;
|
||||||
|
bool _hasUpdate = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* SingleValueProvider_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<NodeOutput> parent) :
|
||||||
|
_parent(parent) {
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual ~PassThroughOutputNode() = default;
|
||||||
|
|
||||||
|
virtual std::shared_ptr<NodeOutput> parent() override {
|
||||||
|
return _parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual bool isEnabled() const override {
|
||||||
|
return _isEnabled;
|
||||||
|
}
|
||||||
|
virtual void setIsEnabled(bool isEnabled) override {
|
||||||
|
_isEnabled = isEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual bool hasUpdate() override {
|
||||||
|
return _hasUpdate;
|
||||||
|
}
|
||||||
|
void setHasUpdate(bool hasUpdate) {
|
||||||
|
_hasUpdate = hasUpdate;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual std::shared_ptr<CGPath> 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<NodeOutput> _parent;
|
||||||
|
bool _hasUpdate = false;
|
||||||
|
bool _isEnabled = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* PassThroughOutputNode_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<DashElement> 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<std::vector<Keyframe<Vector1D>>> dashPatterns;
|
||||||
|
std::vector<Keyframe<Vector1D>> dashPhase;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* StrokeNode_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<TextAnimator> const &textAnimator) {
|
||||||
|
_keypathName = textAnimator->name.value_or("");
|
||||||
|
|
||||||
|
if (textAnimator->anchor) {
|
||||||
|
_anchor = std::make_shared<NodeProperty<Vector3D>>(std::make_shared<KeyframeInterpolator<Vector3D>>(textAnimator->anchor->keyframes));
|
||||||
|
_keypathProperties.insert(std::make_pair("Anchor", _anchor));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (textAnimator->position) {
|
||||||
|
_position = std::make_shared<NodeProperty<Vector3D>>(std::make_shared<KeyframeInterpolator<Vector3D>>(textAnimator->position->keyframes));
|
||||||
|
_keypathProperties.insert(std::make_pair("Position", _position));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (textAnimator->scale) {
|
||||||
|
_scale = std::make_shared<NodeProperty<Vector3D>>(std::make_shared<KeyframeInterpolator<Vector3D>>(textAnimator->scale->keyframes));
|
||||||
|
_keypathProperties.insert(std::make_pair("Scale", _scale));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (textAnimator->skew) {
|
||||||
|
_skew = std::make_shared<NodeProperty<Vector1D>>(std::make_shared<KeyframeInterpolator<Vector1D>>(textAnimator->skew->keyframes));
|
||||||
|
_keypathProperties.insert(std::make_pair("Skew", _skew));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (textAnimator->skewAxis) {
|
||||||
|
_skewAxis = std::make_shared<NodeProperty<Vector1D>>(std::make_shared<KeyframeInterpolator<Vector1D>>(textAnimator->skewAxis->keyframes));
|
||||||
|
_keypathProperties.insert(std::make_pair("Skew Axis", _skewAxis));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (textAnimator->rotation) {
|
||||||
|
_rotation = std::make_shared<NodeProperty<Vector1D>>(std::make_shared<KeyframeInterpolator<Vector1D>>(textAnimator->rotation->keyframes));
|
||||||
|
_keypathProperties.insert(std::make_pair("Rotation", _rotation));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (textAnimator->rotation) {
|
||||||
|
_opacity = std::make_shared<NodeProperty<Vector1D>>(std::make_shared<KeyframeInterpolator<Vector1D>>(textAnimator->opacity->keyframes));
|
||||||
|
_keypathProperties.insert(std::make_pair("Opacity", _opacity));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (textAnimator->strokeColor) {
|
||||||
|
_strokeColor = std::make_shared<NodeProperty<Color>>(std::make_shared<KeyframeInterpolator<Color>>(textAnimator->strokeColor->keyframes));
|
||||||
|
_keypathProperties.insert(std::make_pair("Stroke Color", _strokeColor));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (textAnimator->fillColor) {
|
||||||
|
_fillColor = std::make_shared<NodeProperty<Color>>(std::make_shared<KeyframeInterpolator<Color>>(textAnimator->fillColor->keyframes));
|
||||||
|
_keypathProperties.insert(std::make_pair("Fill Color", _fillColor));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (textAnimator->strokeWidth) {
|
||||||
|
_strokeWidth = std::make_shared<NodeProperty<Vector1D>>(std::make_shared<KeyframeInterpolator<Vector1D>>(textAnimator->strokeWidth->keyframes));
|
||||||
|
_keypathProperties.insert(std::make_pair("Stroke Width", _strokeWidth));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (textAnimator->tracking) {
|
||||||
|
_tracking = std::make_shared<NodeProperty<Vector1D>>(std::make_shared<KeyframeInterpolator<Vector1D>>(textAnimator->tracking->keyframes));
|
||||||
|
_keypathProperties.insert(std::make_pair("Tracking", _tracking));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto &it : _keypathProperties) {
|
||||||
|
_properties.push_back(it.second);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual ~TextAnimatorNodeProperties() = default;
|
||||||
|
|
||||||
|
virtual std::string keypathName() const override {
|
||||||
|
return _keypathName;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual std::map<std::string, std::shared_ptr<AnyNodeProperty>> keypathProperties() const override {
|
||||||
|
return _keypathProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual std::vector<std::shared_ptr<AnyNodeProperty>> &properties() override {
|
||||||
|
return _properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual std::vector<std::shared_ptr<KeypathSearchable>> const &childKeypaths() const override {
|
||||||
|
return _childKeypaths;
|
||||||
|
}
|
||||||
|
|
||||||
|
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<double> skew;
|
||||||
|
if (_skew) {
|
||||||
|
skew = _skew->value().value;
|
||||||
|
}
|
||||||
|
std::optional<double> skewAxis;
|
||||||
|
if (_skewAxis) {
|
||||||
|
skewAxis = _skewAxis->value().value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return CATransform3D::makeTransform(
|
||||||
|
anchor,
|
||||||
|
position,
|
||||||
|
scale,
|
||||||
|
rotation,
|
||||||
|
skew,
|
||||||
|
skewAxis
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual std::shared_ptr<CALayer> keypathLayer() const override {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
double opacity() {
|
||||||
|
if (_opacity) {
|
||||||
|
return _opacity->value().value;
|
||||||
|
} else {
|
||||||
|
return 100.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<Color> strokeColor() {
|
||||||
|
if (_strokeColor) {
|
||||||
|
return _strokeColor->value();
|
||||||
|
} else {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<Color> fillColor() {
|
||||||
|
if (_fillColor) {
|
||||||
|
return _fillColor->value();
|
||||||
|
} else {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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<NodeProperty<Vector3D>> _anchor;
|
||||||
|
std::shared_ptr<NodeProperty<Vector3D>> _position;
|
||||||
|
std::shared_ptr<NodeProperty<Vector3D>> _scale;
|
||||||
|
std::shared_ptr<NodeProperty<Vector1D>> _skew;
|
||||||
|
std::shared_ptr<NodeProperty<Vector1D>> _skewAxis;
|
||||||
|
std::shared_ptr<NodeProperty<Vector1D>> _rotation;
|
||||||
|
std::shared_ptr<NodeProperty<Vector1D>> _opacity;
|
||||||
|
std::shared_ptr<NodeProperty<Color>> _strokeColor;
|
||||||
|
std::shared_ptr<NodeProperty<Color>> _fillColor;
|
||||||
|
std::shared_ptr<NodeProperty<Vector1D>> _strokeWidth;
|
||||||
|
std::shared_ptr<NodeProperty<Vector1D>> _tracking;
|
||||||
|
|
||||||
|
std::map<std::string, std::shared_ptr<AnyNodeProperty>> _keypathProperties;
|
||||||
|
std::vector<std::shared_ptr<KeypathSearchable>> _childKeypaths;
|
||||||
|
std::vector<std::shared_ptr<AnyNodeProperty>> _properties;
|
||||||
|
};
|
||||||
|
|
||||||
|
class TextOutputNode: virtual public NodeOutput {
|
||||||
|
public:
|
||||||
|
TextOutputNode(std::shared_ptr<TextOutputNode> parent) :
|
||||||
|
_parentTextNode(parent) {
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual ~TextOutputNode() = default;
|
||||||
|
|
||||||
|
virtual std::shared_ptr<NodeOutput> parent() override {
|
||||||
|
return _parentTextNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
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<Color> strokeColor() {
|
||||||
|
if (_strokeColor.has_value()) {
|
||||||
|
return _strokeColor.value();
|
||||||
|
} else if (_parentTextNode) {
|
||||||
|
return _parentTextNode->strokeColor();
|
||||||
|
} else {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void setStrokeColor(std::optional<Color> strokeColor) {
|
||||||
|
_strokeColor = strokeColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<Color> fillColor() {
|
||||||
|
if (_fillColor.has_value()) {
|
||||||
|
return _fillColor.value();
|
||||||
|
} else if (_parentTextNode) {
|
||||||
|
return _parentTextNode->fillColor();
|
||||||
|
} else {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void setFillColor(std::optional<Color> fillColor) {
|
||||||
|
_fillColor = fillColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
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<CGPath> outputPath() override {
|
||||||
|
return _outputPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual bool isEnabled() const override {
|
||||||
|
return _isEnabled;
|
||||||
|
}
|
||||||
|
virtual void setIsEnabled(bool isEnabled) override {
|
||||||
|
_isEnabled = isEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::shared_ptr<TextOutputNode> _parentTextNode;
|
||||||
|
bool _isEnabled = true;
|
||||||
|
|
||||||
|
std::shared_ptr<CGPath> _outputPath;
|
||||||
|
|
||||||
|
std::optional<CATransform3D> _xform;
|
||||||
|
std::optional<double> _opacity;
|
||||||
|
std::optional<Color> _strokeColor;
|
||||||
|
std::optional<Color> _fillColor;
|
||||||
|
std::optional<double> _tracking;
|
||||||
|
std::optional<double> _strokeWidth;
|
||||||
|
};
|
||||||
|
|
||||||
|
class TextAnimatorNode: public AnimatorNode {
|
||||||
|
public:
|
||||||
|
TextAnimatorNode(std::shared_ptr<TextAnimatorNode> const &parentNode, std::shared_ptr<TextAnimator> const &textAnimator) :
|
||||||
|
AnimatorNode(parentNode) {
|
||||||
|
std::shared_ptr<TextOutputNode> parentOutputNode;
|
||||||
|
if (parentNode) {
|
||||||
|
parentOutputNode = parentNode->_textOutputNode;
|
||||||
|
}
|
||||||
|
_textOutputNode = std::make_shared<TextOutputNode>(parentOutputNode);
|
||||||
|
|
||||||
|
_textAnimatorProperties = std::make_shared<TextAnimatorNodeProperties>(textAnimator);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual ~TextAnimatorNode() = default;
|
||||||
|
|
||||||
|
virtual std::shared_ptr<NodeOutput> outputNode() override {
|
||||||
|
return _textOutputNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual std::shared_ptr<KeypathSearchableNodePropertyMap> propertyMap() const override {
|
||||||
|
return _textAnimatorProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual bool localUpdatesPermeateDownstream() override {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void rebuildOutputs(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> _textOutputNode;
|
||||||
|
|
||||||
|
std::shared_ptr<TextAnimatorNodeProperties> _textAnimatorProperties;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* TextAnimatorNode_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 <memory>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
namespace lottie {
|
||||||
|
|
||||||
|
class LayerTransformNode;
|
||||||
|
class PathNode;
|
||||||
|
class RenderNode;
|
||||||
|
|
||||||
|
/// The Animator Node is the base node in the render system tree.
|
||||||
|
///
|
||||||
|
/// It defines a single node that has an output path and option input node.
|
||||||
|
/// At animation time the root animation node is asked to update its contents for
|
||||||
|
/// the current frame.
|
||||||
|
/// The node reaches up its chain of nodes until the first node that does not need
|
||||||
|
/// updating is found. Then each node updates its contents down the render pipeline.
|
||||||
|
/// Each node adds its local path to its input path and passes it forward.
|
||||||
|
///
|
||||||
|
/// An animator node holds a group of interpolators. These interpolators determine
|
||||||
|
/// if the node needs an update for the current frame.
|
||||||
|
///
|
||||||
|
class AnimatorNode: public KeypathSearchable {
|
||||||
|
public:
|
||||||
|
AnimatorNode(std::shared_ptr<AnimatorNode> const &parentNode) :
|
||||||
|
_parentNode(parentNode) {
|
||||||
|
}
|
||||||
|
|
||||||
|
AnimatorNode(const AnimatorNode&) = delete;
|
||||||
|
AnimatorNode& operator=(AnimatorNode&) = delete;
|
||||||
|
|
||||||
|
/// The available properties of the Node.
|
||||||
|
///
|
||||||
|
/// These properties are automatically updated each frame.
|
||||||
|
/// These properties are also settable and gettable through the dynamic
|
||||||
|
/// property system.
|
||||||
|
///
|
||||||
|
virtual std::shared_ptr<KeypathSearchableNodePropertyMap> propertyMap() const = 0;
|
||||||
|
|
||||||
|
/// The upstream input node
|
||||||
|
std::shared_ptr<AnimatorNode> parentNode() {
|
||||||
|
return _parentNode;
|
||||||
|
}
|
||||||
|
void setParentNode(std::shared_ptr<AnimatorNode> const &parentNode) {
|
||||||
|
_parentNode = parentNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The output of the node.
|
||||||
|
virtual std::shared_ptr<NodeOutput> outputNode() = 0;
|
||||||
|
|
||||||
|
/// Update the outputs of the node. Called if local contents were update or if outputsNeedUpdate returns true.
|
||||||
|
virtual void rebuildOutputs(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<double> lastUpdateFrame() {
|
||||||
|
return _lastUpdateFrame;
|
||||||
|
}
|
||||||
|
virtual void setLastUpdateFrame(std::optional<double> 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<std::string, std::shared_ptr<AnyNodeProperty>> keypathProperties() const override {
|
||||||
|
return propertyMap()->keypathProperties();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Children Keypaths
|
||||||
|
virtual std::vector<std::shared_ptr<KeypathSearchable>> const &childKeypaths() const override {
|
||||||
|
return propertyMap()->childKeypaths();
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual std::shared_ptr<CALayer> keypathLayer() const override {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
virtual LayerTransformNode *asLayerTransformNode() {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual PathNode *asPathNode() {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual RenderNode *asRenderNode() {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::shared_ptr<AnimatorNode> _parentNode;
|
||||||
|
bool _isEnabled = true;
|
||||||
|
bool _hasLocalUpdates = false;
|
||||||
|
bool _hasUpstreamUpdates = false;
|
||||||
|
std::optional<double> _lastUpdateFrame;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* AnimatorNode_hpp */
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
#ifndef NodeOutput_hpp
|
||||||
|
#define NodeOutput_hpp
|
||||||
|
|
||||||
|
#include "Lottie/Public/Primitives/CGPath.hpp"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace lottie {
|
||||||
|
|
||||||
|
/// Defines the basic outputs of an animator node.
|
||||||
|
///
|
||||||
|
class NodeOutput {
|
||||||
|
public:
|
||||||
|
/// The parent node.
|
||||||
|
virtual std::shared_ptr<NodeOutput> parent() = 0;
|
||||||
|
|
||||||
|
/// Returns true if there are any updates upstream. OutputPath must be built before returning.
|
||||||
|
virtual bool hasOutputUpdates(double forFrame) = 0;
|
||||||
|
|
||||||
|
virtual std::shared_ptr<CGPath> outputPath() = 0;
|
||||||
|
|
||||||
|
virtual bool isEnabled() const = 0;
|
||||||
|
virtual void setIsEnabled(bool isEnabled) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* NodeOutput_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<CAShapeLayer> 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<CAShapeLayer> 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<Renderable> renderer() = 0;
|
||||||
|
virtual std::shared_ptr<NodeOutput> nodeOutput() = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* RenderNode_hpp */
|
||||||
@ -0,0 +1,99 @@
|
|||||||
|
#include "GetGradientParameters.hpp"
|
||||||
|
|
||||||
|
namespace lottie {
|
||||||
|
|
||||||
|
void getGradientParameters(int numberOfColors, GradientColorSet const &colors, std::vector<Color> &outColors, std::vector<double> &outLocations) {
|
||||||
|
std::vector<Color> alphaColors;
|
||||||
|
std::vector<double> alphaValues;
|
||||||
|
std::vector<double> alphaLocations;
|
||||||
|
|
||||||
|
std::vector<Color> gradientColors;
|
||||||
|
std::vector<double> 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<double> locations;
|
||||||
|
for (size_t i = 0; i < std::min(gradientColors.size(), colorLocations.size()); i++) {
|
||||||
|
if (std::find(locations.begin(), locations.end(), colorLocations[i]) == locations.end()) {
|
||||||
|
locations.push_back(colorLocations[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (size_t i = 0; i < std::min(alphaValues.size(), alphaLocations.size()); i++) {
|
||||||
|
if (std::find(locations.begin(), locations.end(), alphaLocations[i]) == locations.end()) {
|
||||||
|
locations.push_back(alphaLocations[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::sort(locations.begin(), locations.end());
|
||||||
|
if (locations[0] != 0.0) {
|
||||||
|
locations.insert(locations.begin(), 0.0);
|
||||||
|
}
|
||||||
|
if (locations[locations.size() - 1] != 1.0) {
|
||||||
|
locations.push_back(1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<Color> colors;
|
||||||
|
|
||||||
|
for (const auto location : locations) {
|
||||||
|
Color color = gradientColors[0];
|
||||||
|
for (size_t i = 0; i < std::min(gradientColors.size(), colorLocations.size()) - 1; i++) {
|
||||||
|
if (location >= colorLocations[i] && location <= colorLocations[i + 1]) {
|
||||||
|
double localLocation = 0.0;
|
||||||
|
if (colorLocations[i] != colorLocations[i + 1]) {
|
||||||
|
localLocation = remapDouble(location, colorLocations[i], colorLocations[i + 1], 0.0, 1.0);
|
||||||
|
}
|
||||||
|
color = ValueInterpolator<Color>::interpolate(gradientColors[i], gradientColors[i + 1], localLocation, std::nullopt, std::nullopt);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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<double>::interpolate(alphaValues[i], alphaValues[i + 1], localLocation, std::nullopt, std::nullopt);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
color.a = alpha;
|
||||||
|
|
||||||
|
colors.push_back(color);
|
||||||
|
}
|
||||||
|
|
||||||
|
gradientColors = colors;
|
||||||
|
colorLocations = locations;
|
||||||
|
}
|
||||||
|
|
||||||
|
outColors = gradientColors;
|
||||||
|
outLocations = colorLocations;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,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<Color> &outColors, std::vector<double> &outLocations);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* ShapeRenderLayer_hpp */
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
#include "Animation.hpp"
|
||||||
|
|
||||||
|
namespace lottie {
|
||||||
|
|
||||||
|
}
|
||||||
@ -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 <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <map>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
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<std::string> name_,
|
||||||
|
std::optional<int> tgs_,
|
||||||
|
AnimationFrameTime startFrame_,
|
||||||
|
AnimationFrameTime endFrame_,
|
||||||
|
double framerate_,
|
||||||
|
std::string const &version_,
|
||||||
|
std::optional<CoordinateSpace> type_,
|
||||||
|
int width_,
|
||||||
|
int height_,
|
||||||
|
std::vector<std::shared_ptr<LayerModel>> const &layers_,
|
||||||
|
std::optional<std::vector<std::shared_ptr<Glyph>>> glyphs_,
|
||||||
|
std::optional<std::shared_ptr<FontList>> fonts_,
|
||||||
|
std::shared_ptr<AssetLibrary> assetLibrary_,
|
||||||
|
std::optional<std::vector<Marker>> markers_,
|
||||||
|
std::optional<std::vector<FitzModifier>> fitzModifiers_,
|
||||||
|
std::optional<json11::Json> meta_,
|
||||||
|
std::optional<json11::Json> 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<std::string, Marker> 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<Animation> 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<CoordinateSpace> 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<std::shared_ptr<LayerModel>> 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<std::vector<std::shared_ptr<Glyph>>> glyphs;
|
||||||
|
if (const auto glyphDictionaries = getOptionalObjectArray(json, "chars")) {
|
||||||
|
glyphs = std::vector<std::shared_ptr<Glyph>>();
|
||||||
|
for (const auto &glyphDictionary : glyphDictionaries.value()) {
|
||||||
|
glyphs->push_back(std::make_shared<Glyph>(glyphDictionary));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
glyphs = std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<std::shared_ptr<FontList>> fonts;
|
||||||
|
if (const auto fontsDictionary = getOptionalObject(json, "fonts")) {
|
||||||
|
fonts = std::make_shared<FontList>(fontsDictionary.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<AssetLibrary> assetLibrary;
|
||||||
|
if (const auto assetLibraryData = getOptionalAny(json, "assets")) {
|
||||||
|
assetLibrary = std::make_shared<AssetLibrary>(assetLibraryData.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<std::vector<Marker>> markers;
|
||||||
|
if (const auto markerDictionaries = getOptionalObjectArray(json, "markers")) {
|
||||||
|
markers = std::vector<Marker>();
|
||||||
|
for (const auto &markerDictionary : markerDictionaries.value()) {
|
||||||
|
markers->push_back(Marker(markerDictionary));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::optional<std::vector<FitzModifier>> fitzModifiers;
|
||||||
|
if (const auto fitzModifierDictionaries = getOptionalObjectArray(json, "fitz")) {
|
||||||
|
fitzModifiers = std::vector<FitzModifier>();
|
||||||
|
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<Animation>(
|
||||||
|
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<std::string> markerNames() {
|
||||||
|
if (!markers.has_value()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
std::vector<std::string> result;
|
||||||
|
for (const auto &marker : markers.value()) {
|
||||||
|
result.push_back(marker.name);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Animation name
|
||||||
|
std::optional<std::string> name;
|
||||||
|
|
||||||
|
/// The version of the JSON Schema.
|
||||||
|
std::string version;
|
||||||
|
|
||||||
|
std::optional<int> tgs;
|
||||||
|
|
||||||
|
/// The coordinate space of the composition.
|
||||||
|
std::optional<CoordinateSpace> 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<std::shared_ptr<LayerModel>> layers;
|
||||||
|
|
||||||
|
/// The list of glyphs used for text rendering
|
||||||
|
std::optional<std::vector<std::shared_ptr<Glyph>>> glyphs;
|
||||||
|
|
||||||
|
/// The list of fonts used for text rendering
|
||||||
|
std::optional<std::shared_ptr<FontList>> fonts;
|
||||||
|
|
||||||
|
/// Asset Library
|
||||||
|
std::shared_ptr<AssetLibrary> assetLibrary;
|
||||||
|
|
||||||
|
/// Markers
|
||||||
|
std::optional<std::vector<Marker>> markers;
|
||||||
|
std::optional<std::map<std::string, Marker>> markerMap;
|
||||||
|
|
||||||
|
std::optional<std::vector<FitzModifier>> fitzModifiers;
|
||||||
|
|
||||||
|
std::optional<json11::Json> meta;
|
||||||
|
std::optional<json11::Json> comps;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* Animation_hpp */
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
#include "Asset.hpp"
|
||||||
|
|
||||||
|
namespace lottie {
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,50 @@
|
|||||||
|
#ifndef Asset_hpp
|
||||||
|
#define Asset_hpp
|
||||||
|
|
||||||
|
#include "Lottie/Private/Parsing/JsonParsing.hpp"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
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<std::string> objectName;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* Asset_hpp */
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
#include "AssetLibrary.hpp"
|
||||||
|
|
||||||
|
namespace lottie {
|
||||||
|
|
||||||
|
}
|
||||||
@ -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 <map>
|
||||||
|
|
||||||
|
namespace lottie {
|
||||||
|
|
||||||
|
class AssetLibrary {
|
||||||
|
public:
|
||||||
|
AssetLibrary(
|
||||||
|
std::map<std::string, std::shared_ptr<Asset>> const &assets_,
|
||||||
|
std::map<std::string, std::shared_ptr<ImageAsset>> const &imageAssets_,
|
||||||
|
std::map<std::string, std::shared_ptr<PrecompAsset>> 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<PrecompAsset>(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<ImageAsset>(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<std::shared_ptr<Asset>> assetList;
|
||||||
|
std::map<std::string, std::shared_ptr<Asset>> assets;
|
||||||
|
|
||||||
|
std::map<std::string, std::shared_ptr<ImageAsset>> imageAssets;
|
||||||
|
std::map<std::string, std::shared_ptr<PrecompAsset>> precompAssets;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* AssetLibrary_hpp */
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
#include "ImageAsset.hpp"
|
||||||
|
|
||||||
|
namespace lottie {
|
||||||
|
|
||||||
|
}
|
||||||
@ -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<int> _e;
|
||||||
|
std::optional<std::string> _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 */
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
#include "PrecompAsset.hpp"
|
||||||
|
|
||||||
|
namespace lottie {
|
||||||
|
|
||||||
|
}
|
||||||
@ -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 <vector>
|
||||||
|
|
||||||
|
namespace lottie {
|
||||||
|
|
||||||
|
class PrecompAsset: public Asset {
|
||||||
|
public:
|
||||||
|
PrecompAsset(
|
||||||
|
std::string const &id_,
|
||||||
|
std::vector<std::shared_ptr<LayerModel>> 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<std::shared_ptr<LayerModel>> layers;
|
||||||
|
|
||||||
|
std::optional<double> frameRate;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* PrecompAsset_hpp */
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
#include "KeyframeGroup.hpp"
|
||||||
|
|
||||||
|
namespace lottie {
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,150 @@
|
|||||||
|
#ifndef KeyframeGroup_hpp
|
||||||
|
#define KeyframeGroup_hpp
|
||||||
|
|
||||||
|
#include "Lottie/Public/Keyframes/Keyframe.hpp"
|
||||||
|
#include "Lottie/Private/Parsing/JsonParsing.hpp"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
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<typename T>
|
||||||
|
class KeyframeGroup {
|
||||||
|
public:
|
||||||
|
KeyframeGroup(std::vector<Keyframe<T>> &&keyframes_) :
|
||||||
|
keyframes(std::move(keyframes_)),
|
||||||
|
isSingle(false) {
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyframeGroup(T const &value_) :
|
||||||
|
keyframes({ Keyframe<T>(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<T>(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<KeyframeData<T>> previousKeyframeData;
|
||||||
|
for (const auto &containerItem : containerData.array_items()) {
|
||||||
|
// Ensure that Time and Value are present.
|
||||||
|
auto keyframeData = KeyframeData<T>(containerItem);
|
||||||
|
rawKeyframeData.push_back(keyframeData);
|
||||||
|
|
||||||
|
std::optional<T> 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<Vector2D> inTangent;
|
||||||
|
std::optional<Vector3D> 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<Keyframe<T>> keyframes;
|
||||||
|
std::optional<int> isAnimated;
|
||||||
|
|
||||||
|
std::optional<json11::Json> expression;
|
||||||
|
std::optional<int> expressionIndex;
|
||||||
|
std::vector<KeyframeData<T>> rawKeyframeData;
|
||||||
|
bool isSingle = false;
|
||||||
|
std::optional<int> _extraL;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* KeyframeGroup_hpp */
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
#include "ImageLayerModel.hpp"
|
||||||
|
|
||||||
|
namespace lottie {
|
||||||
|
|
||||||
|
}
|
||||||
@ -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<std::string> _sc;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* ImageLayerModel_hpp */
|
||||||
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -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 <string>
|
||||||
|
#include <optional>
|
||||||
|
#include <random>
|
||||||
|
|
||||||
|
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<Transform>(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<std::shared_ptr<Mask>>();
|
||||||
|
for (const auto &maskDictionary : maskDictionaries.value()) {
|
||||||
|
masks->push_back(std::make_shared<Mask>(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<std::string> name;
|
||||||
|
|
||||||
|
/// The index of the layer
|
||||||
|
std::optional<int> index;
|
||||||
|
|
||||||
|
/// The type of the layer.
|
||||||
|
LayerType type;
|
||||||
|
|
||||||
|
std::optional<int> autoOrient;
|
||||||
|
|
||||||
|
/// The coordinate space
|
||||||
|
std::optional<CoordinateSpace> 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> transform;
|
||||||
|
|
||||||
|
/// The index of the parent layer, if applicable.
|
||||||
|
std::optional<int> parent;
|
||||||
|
|
||||||
|
/// The blending mode for the layer
|
||||||
|
std::optional<BlendMode> blendMode;
|
||||||
|
|
||||||
|
/// An array of masks for the layer.
|
||||||
|
std::optional<std::vector<std::shared_ptr<Mask>>> masks;
|
||||||
|
|
||||||
|
/// A number that stretches time by a multiplier
|
||||||
|
std::optional<double> _timeStretch;
|
||||||
|
|
||||||
|
/// The type of matte if any.
|
||||||
|
std::optional<MatteType> matte;
|
||||||
|
|
||||||
|
std::optional<bool> hidden;
|
||||||
|
|
||||||
|
std::optional<bool> hasMask;
|
||||||
|
std::optional<int> td;
|
||||||
|
std::optional<json11::Json> effectsData;
|
||||||
|
std::optional<std::string> layerClass;
|
||||||
|
std::optional<json11::Json> _extraHidden;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* LayerModel_hpp */
|
||||||
@ -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<LayerModel> parseLayerModel(json11::Json::object const &json) noexcept(false) {
|
||||||
|
LayerType layerType = parseLayerType(json, "ty");
|
||||||
|
|
||||||
|
switch (layerType) {
|
||||||
|
case LayerType::Precomp:
|
||||||
|
return std::make_shared<PreCompLayerModel>(json);
|
||||||
|
case LayerType::Solid:
|
||||||
|
return std::make_shared<SolidLayerModel>(json);
|
||||||
|
case LayerType::Image:
|
||||||
|
return std::make_shared<ImageLayerModel>(json);
|
||||||
|
case LayerType::Null:
|
||||||
|
return std::make_shared<LayerModel>(json);
|
||||||
|
case LayerType::Shape:
|
||||||
|
try {
|
||||||
|
return std::make_shared<ShapeLayerModel>(json);
|
||||||
|
} catch(...) {
|
||||||
|
throw LottieParsingException();
|
||||||
|
}
|
||||||
|
case LayerType::Text:
|
||||||
|
return std::make_shared<TextLayerModel>(json);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -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<LayerModel> parseLayerModel(json11::Json::object const &json) noexcept(false);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* LayerModelSerialization_hpp */
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
#include "PreCompLayerModel.hpp"
|
||||||
|
|
||||||
|
namespace lottie {
|
||||||
|
|
||||||
|
}
|
||||||
@ -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 <optional>
|
||||||
|
|
||||||
|
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<Vector1D>(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<KeyframeGroup<Vector1D>> timeRemapping;
|
||||||
|
|
||||||
|
/// Precomp Width
|
||||||
|
double width;
|
||||||
|
|
||||||
|
/// Precomp Height
|
||||||
|
double height;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* PreCompLayerModel_hpp */
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
#include "ShapeLayerModel.hpp"
|
||||||
|
|
||||||
|
namespace lottie {
|
||||||
|
|
||||||
|
}
|
||||||
@ -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 <vector>
|
||||||
|
|
||||||
|
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<std::shared_ptr<ShapeItem>> items;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* ShapeLayerModel_hpp */
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
#include "SolidLayerModel.hpp"
|
||||||
|
|
||||||
|
namespace lottie {
|
||||||
|
|
||||||
|
}
|
||||||
@ -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 */
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
#include "TextLayerModel.hpp"
|
||||||
|
|
||||||
|
namespace lottie {
|
||||||
|
|
||||||
|
}
|
||||||
@ -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>(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<TextDocument>(textData);
|
||||||
|
|
||||||
|
if (auto animatorsData = getOptionalObjectArray(textContainer, "a")) {
|
||||||
|
for (const auto &animatorData : animatorsData.value()) {
|
||||||
|
animators.push_back(std::make_shared<TextAnimator>(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<TextDocument> text;
|
||||||
|
|
||||||
|
/// Text animators
|
||||||
|
std::vector<std::shared_ptr<TextAnimator>> animators;
|
||||||
|
|
||||||
|
std::optional<json11::Json> _extraM;
|
||||||
|
std::optional<json11::Json> _extraP;
|
||||||
|
std::optional<json11::Json> _extraA;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* TextLayerModel_hpp */
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
#include "DashElement.hpp"
|
||||||
|
|
||||||
|
namespace lottie {
|
||||||
|
|
||||||
|
}
|
||||||
@ -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<Vector1D> const &value_
|
||||||
|
) :
|
||||||
|
type(type_),
|
||||||
|
value(value_) {
|
||||||
|
}
|
||||||
|
|
||||||
|
explicit DashElement(json11::Json::object const &json) noexcept(false) :
|
||||||
|
type(DashElementType::Offset),
|
||||||
|
value(KeyframeGroup<Vector1D>(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<Vector1D>(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<Vector1D> value;
|
||||||
|
|
||||||
|
std::optional<std::string> name;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* DashElement_hpp */
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
#include "FitzModifier.hpp"
|
||||||
|
|
||||||
|
namespace lottie {
|
||||||
|
|
||||||
|
}
|
||||||
@ -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<double> type12;
|
||||||
|
std::optional<double> type3;
|
||||||
|
std::optional<double> type4;
|
||||||
|
std::optional<double> type5;
|
||||||
|
std::optional<double> type6;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* FitzModifier_hpp */
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
#include "Marker.hpp"
|
||||||
|
|
||||||
|
namespace lottie {
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,52 @@
|
|||||||
|
#ifndef Marker_hpp
|
||||||
|
#define Marker_hpp
|
||||||
|
|
||||||
|
#include "Lottie/Public/Primitives/AnimationTime.hpp"
|
||||||
|
#include "Lottie/Private/Parsing/JsonParsing.hpp"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
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<int> dr;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* Marker_hpp */
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
#include "Mask.hpp"
|
||||||
|
|
||||||
|
namespace lottie {
|
||||||
|
|
||||||
|
}
|
||||||
@ -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>(Vector1D(100.0))),
|
||||||
|
shape(KeyframeGroup<BezierPath>(BezierPath())),
|
||||||
|
inverted(false),
|
||||||
|
expansion(KeyframeGroup<Vector1D>(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<Vector1D>(opacityData.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
shape = KeyframeGroup<BezierPath>(getObject(json, "pt"));
|
||||||
|
|
||||||
|
if (const auto invertedData = getOptionalBool(json, "inv")) {
|
||||||
|
inverted = invertedData.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (const auto expansionData = getOptionalObject(json, "x")) {
|
||||||
|
expansion = KeyframeGroup<Vector1D>(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<MaskMode> _mode;
|
||||||
|
|
||||||
|
std::optional<KeyframeGroup<Vector1D>> opacity;
|
||||||
|
|
||||||
|
KeyframeGroup<BezierPath> shape;
|
||||||
|
|
||||||
|
std::optional<bool> inverted;
|
||||||
|
|
||||||
|
std::optional<KeyframeGroup<Vector1D>> expansion;
|
||||||
|
|
||||||
|
std::optional<std::string> name;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* Mask_hpp */
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
#include "Transform.hpp"
|
||||||
|
|
||||||
|
namespace lottie {
|
||||||
|
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user