GLEGram 12.5 — Initial public release

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

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

See CHANGELOG_12.5.md for full details.
This commit is contained in:
Leeksov
2026-04-06 09:48:12 +03:00
commit 4647310322
39685 changed files with 11052678 additions and 0 deletions
@@ -0,0 +1,623 @@
#include <LottieCpp/CanvasRenderer.h>
#include <cmath>
namespace lottie {
namespace {
// Values below that are invisible
static constexpr float minVisibleAlpha = 0.5f / 255.0f;
// Sometimes calculating path bounds is slower than just allocating the whole canvas
static constexpr float minGlobalRectCalculationSize = 200.0f;
struct TransformedPath {
BezierPath path;
Transform2D transform;
TransformedPath(BezierPath const &path_, Transform2D const &transform_) :
path(path_),
transform(transform_) {
}
};
static CGRect collectPathBoundingBoxes(std::shared_ptr<RenderTreeNodeContentItem> item, size_t subItemLimit, Transform2D const &parentTransform, bool skipApplyTransform, BezierPathsBoundingBoxContext &bezierPathsBoundingBoxContext) {
//TODO:remove skipApplyTransform
Transform2D effectiveTransform = parentTransform;
if (!skipApplyTransform && item->isGroup) {
effectiveTransform = item->transform * effectiveTransform;
}
size_t maxSubitem = std::min(item->subItems.size(), subItemLimit);
CGRect boundingBox(0.0, 0.0, 0.0, 0.0);
if (item->trimmedPaths) {
for (const auto &path : item->trimmedPaths.value()) {
if (path->needsBoundsRecalculation) {
path->bounds = bezierPathsBoundingBoxParallel(bezierPathsBoundingBoxContext, path->path);
path->needsBoundsRecalculation = false;
}
CGRect subpathBoundingBox = path->bounds.applyingTransform(effectiveTransform);
if (boundingBox.empty()) {
boundingBox = subpathBoundingBox;
} else {
boundingBox = boundingBox.unionWith(subpathBoundingBox);
}
}
} else {
if (item->path) {
if (item->path->needsBoundsRecalculation) {
item->path->bounds = bezierPathsBoundingBoxParallel(bezierPathsBoundingBoxContext, item->path->path);
item->path->needsBoundsRecalculation = false;
}
boundingBox = item->path->bounds.applyingTransform(effectiveTransform);
}
for (size_t i = 0; i < maxSubitem; i++) {
auto &subItem = item->subItems[i];
CGRect subItemBoundingBox = collectPathBoundingBoxes(subItem, INT32_MAX, effectiveTransform, false, bezierPathsBoundingBoxContext);
if (boundingBox.empty()) {
boundingBox = subItemBoundingBox;
} else {
boundingBox = boundingBox.unionWith(subItemBoundingBox);
}
}
}
return boundingBox;
}
static void enumeratePaths(std::shared_ptr<RenderTreeNodeContentItem> item, size_t subItemLimit, Transform2D const &parentTransform, bool skipApplyTransform, std::function<void(BezierPath const &path, Transform2D const &transform)> const &onPath) {
//TODO:remove skipApplyTransform
Transform2D effectiveTransform = parentTransform;
if (!skipApplyTransform && item->isGroup) {
effectiveTransform = item->transform * effectiveTransform;
}
size_t maxSubitem = std::min(item->subItems.size(), subItemLimit);
if (item->trimmedPaths) {
for (const auto &path : item->trimmedPaths.value()) {
onPath(path->path, effectiveTransform);
}
return;
}
if (item->path) {
onPath(item->path->path, effectiveTransform);
}
for (size_t i = 0; i < maxSubitem; i++) {
auto &subItem = item->subItems[i];
enumeratePaths(subItem, INT32_MAX, effectiveTransform, false, onPath);
}
}
}
static std::optional<CGRect> getRenderContentItemLocalRect(std::shared_ptr<RenderTreeNodeContentItem> const &contentItem, BezierPathsBoundingBoxContext &bezierPathsBoundingBoxContext) {
std::optional<CGRect> localRect;
for (const auto &shadingVariant : contentItem->shadings) {
CGRect shapeBounds = collectPathBoundingBoxes(contentItem, shadingVariant->subItemLimit, Transform2D::identity(), true, bezierPathsBoundingBoxContext);
if (shadingVariant->stroke) {
shapeBounds = shapeBounds.insetBy(-shadingVariant->stroke->lineWidth / 2.0, -shadingVariant->stroke->lineWidth / 2.0);
} else if (shadingVariant->fill) {
} else {
continue;
}
CGRect shapeLocalBounds = shapeBounds;
if (localRect) {
localRect = localRect->unionWith(shapeLocalBounds);
} else {
localRect = shapeLocalBounds;
}
}
for (const auto &subItem : contentItem->subItems) {
auto subLocalRect = getRenderContentItemLocalRect(subItem, bezierPathsBoundingBoxContext);
if (subLocalRect) {
CGRect transformedSubLocalRect = subLocalRect->applyingTransform(subItem->transform);
if (localRect) {
localRect = localRect->unionWith(transformedSubLocalRect);
} else {
localRect = transformedSubLocalRect;
}
}
}
return localRect;
}
static std::optional<CGRect> getRenderNodeLocalRect(std::shared_ptr<RenderTreeNode> const &node, bool isInvertedMatte, BezierPathsBoundingBoxContext &bezierPathsBoundingBoxContext) {
if (node->isHidden() || node->alpha() < minVisibleAlpha) {
return std::nullopt;
}
std::optional<CGRect> localRect;
if (node->_contentItem) {
localRect = getRenderContentItemLocalRect(node->_contentItem, bezierPathsBoundingBoxContext);
}
if (isInvertedMatte) {
CGRect localBounds = CGRect(0.0f, 0.0f, node->size().x, node->size().y);
if (localRect) {
localRect = localRect->unionWith(localBounds);
} else {
localRect = localBounds;
}
}
for (const auto &subNode : node->subnodes()) {
auto subLocalRect = getRenderNodeLocalRect(subNode, false, bezierPathsBoundingBoxContext);
if (subLocalRect) {
auto transformedSubLocalRect = subLocalRect->applyingTransform(subNode->transform());
if (localRect) {
localRect = localRect->unionWith(transformedSubLocalRect);
} else {
localRect = transformedSubLocalRect;
}
}
}
return localRect;
}
namespace {
static void drawLottieContentItem(std::shared_ptr<Canvas> const &canvas, std::shared_ptr<RenderTreeNodeContentItem> item, float parentAlpha, Vector2D const &globalSize, Transform2D const &parentTransform, BezierPathsBoundingBoxContext &bezierPathsBoundingBoxContext, CanvasRenderer::Configuration const &configuration) {
auto currentTransform = parentTransform;
Transform2D localTransform = item->transform;
currentTransform = localTransform * currentTransform;
float normalizedOpacity = item->alpha;
float layerAlpha = ((float)normalizedOpacity) * parentAlpha;
if (normalizedOpacity == 0.0f) {
return;
}
canvas->saveState();
canvas->concatenate(item->transform);
bool needsTempContext = false;
if (!configuration.disableGroupTransparency) {
needsTempContext = layerAlpha != 1.0 && item->drawContentCount > 1;
}
if (needsTempContext) {
std::optional<CGRect> localRect;
if (configuration.canUseMoreMemory && globalSize.x <= minGlobalRectCalculationSize && globalSize.y <= minGlobalRectCalculationSize) {
localRect = CGRect::veryLarge();
} else {
localRect = getRenderContentItemLocalRect(item, bezierPathsBoundingBoxContext);
}
if (!localRect) {
canvas->restoreState();
return;
}
if (!canvas->pushLayer(localRect.value(), layerAlpha, std::nullopt)) {
canvas->restoreState();
return;
}
}
float renderAlpha = 1.0;
if (needsTempContext) {
renderAlpha = 1.0;
} else {
renderAlpha = layerAlpha;
}
for (const auto &shading : item->shadings) {
CanvasPathEnumerator iteratePaths;
iteratePaths = [&](std::function<void(PathCommand const &)> &&iterate) {
enumeratePaths(item, shading->subItemLimit, Transform2D::identity(), true, [&](BezierPath const &sourcePath, Transform2D const &transform) {
auto path = sourcePath.copyUsingTransform(transform);
PathCommand pathCommand;
std::optional<PathElement> previousElement;
for (const auto &element : path.elements()) {
if (previousElement.has_value()) {
if (previousElement->vertex.outTangentRelative().isZero() && element.vertex.inTangentRelative().isZero()) {
pathCommand.type = PathCommandType::LineTo;
pathCommand.points[0] = element.vertex.point;
iterate(pathCommand);
} else {
pathCommand.type = PathCommandType::CurveTo;
pathCommand.points[2] = element.vertex.point;
pathCommand.points[1] = element.vertex.inTangent;
pathCommand.points[0] = previousElement->vertex.outTangent;
iterate(pathCommand);
}
} else {
pathCommand.type = PathCommandType::MoveTo;
pathCommand.points[0] = element.vertex.point;
iterate(pathCommand);
}
previousElement = element;
}
if (path.closed().value_or(true)) {
pathCommand.type = PathCommandType::Close;
iterate(pathCommand);
}
});
};
if (shading->stroke) {
if (shading->stroke->shading->type() == RenderTreeNodeContentItem::ShadingType::Solid) {
RenderTreeNodeContentItem::SolidShading *solidShading = (RenderTreeNodeContentItem::SolidShading *)shading->stroke->shading.get();
if (solidShading->opacity != 0.0) {
LineJoin lineJoin = LineJoin::Bevel;
switch (shading->stroke->lineJoin) {
case LineJoin::Bevel: {
lineJoin = LineJoin::Bevel;
break;
}
case LineJoin::Round: {
lineJoin = LineJoin::Round;
break;
}
case LineJoin::Miter: {
lineJoin = LineJoin::Miter;
break;
}
default: {
break;
}
}
LineCap lineCap = LineCap::Square;
switch (shading->stroke->lineCap) {
case LineCap::Butt: {
lineCap = LineCap::Butt;
break;
}
case LineCap::Round: {
lineCap = LineCap::Round;
break;
}
case LineCap::Square: {
lineCap = LineCap::Square;
break;
}
default: {
break;
}
}
std::vector<float> dashPattern;
if (!shading->stroke->dashPattern.empty()) {
dashPattern = shading->stroke->dashPattern;
}
canvas->strokePath(iteratePaths, shading->stroke->lineWidth, lineJoin, lineCap, shading->stroke->dashPhase, dashPattern, Color(solidShading->color.r, solidShading->color.g, solidShading->color.b, solidShading->color.a * solidShading->opacity * renderAlpha));
} else if (shading->stroke->shading->type() == RenderTreeNodeContentItem::ShadingType::Gradient) {
//TODO:gradient stroke
}
}
} else if (shading->fill) {
FillRule rule = FillRule::NonZeroWinding;
switch (shading->fill->rule) {
case FillRule::EvenOdd: {
rule = FillRule::EvenOdd;
break;
}
case FillRule::NonZeroWinding: {
rule = FillRule::NonZeroWinding;
break;
}
default: {
break;
}
}
if (shading->fill->shading->type() == RenderTreeNodeContentItem::ShadingType::Solid) {
RenderTreeNodeContentItem::SolidShading *solidShading = (RenderTreeNodeContentItem::SolidShading *)shading->fill->shading.get();
if (solidShading->opacity != 0.0) {
canvas->fillPath(iteratePaths, rule, Color(solidShading->color.r, solidShading->color.g, solidShading->color.b, solidShading->color.a * solidShading->opacity * renderAlpha));
}
} else if (shading->fill->shading->type() == RenderTreeNodeContentItem::ShadingType::Gradient) {
RenderTreeNodeContentItem::GradientShading *gradientShading = (RenderTreeNodeContentItem::GradientShading *)shading->fill->shading.get();
if (gradientShading->opacity != 0.0) {
std::vector<Color> colors;
std::vector<float> locations;
for (const auto &color : gradientShading->colors) {
colors.push_back(Color(color.r, color.g, color.b, color.a * gradientShading->opacity * renderAlpha));
}
locations = gradientShading->locations;
Gradient gradient(colors, locations);
Vector2D start(gradientShading->start.x, gradientShading->start.y);
Vector2D end(gradientShading->end.x, gradientShading->end.y);
switch (gradientShading->gradientType) {
case GradientType::Linear: {
canvas->linearGradientFillPath(iteratePaths, rule, gradient, start, end);
break;
}
case GradientType::Radial: {
canvas->radialGradientFillPath(iteratePaths, rule, gradient, start, start.distanceTo(end));
break;
}
default: {
break;
}
}
}
}
}
}
for (auto it = item->subItems.rbegin(); it != item->subItems.rend(); it++) {
const auto &subItem = *it;
drawLottieContentItem(canvas, subItem, renderAlpha, globalSize, currentTransform, bezierPathsBoundingBoxContext, configuration);
}
if (needsTempContext) {
canvas->popLayer();
}
canvas->restoreState();
}
static bool clipToMaskItemIfPossible(std::shared_ptr<Canvas> const &canvas, std::shared_ptr<RenderTreeNodeContentItem> item, Transform2D const &parentTransform) {
auto currentTransform = parentTransform;
Transform2D localTransform = item->transform;
currentTransform = localTransform * currentTransform;
float normalizedOpacity = item->alpha;
if (normalizedOpacity < 1.0f - minVisibleAlpha) {
return false;
}
if (item->shadings.size() > 1) {
return false;
}
for (const auto &shading : item->shadings) {
CanvasPathEnumerator iteratePaths;
iteratePaths = [&](std::function<void(PathCommand const &)> &&iterate) {
enumeratePaths(item, shading->subItemLimit, Transform2D::identity(), true, [&](BezierPath const &sourcePath, Transform2D const &transform) {
auto path = sourcePath.copyUsingTransform(transform);
PathCommand pathCommand;
std::optional<PathElement> previousElement;
for (const auto &element : path.elements()) {
if (previousElement.has_value()) {
if (previousElement->vertex.outTangentRelative().isZero() && element.vertex.inTangentRelative().isZero()) {
pathCommand.type = PathCommandType::LineTo;
pathCommand.points[0] = element.vertex.point;
iterate(pathCommand);
} else {
pathCommand.type = PathCommandType::CurveTo;
pathCommand.points[2] = element.vertex.point;
pathCommand.points[1] = element.vertex.inTangent;
pathCommand.points[0] = previousElement->vertex.outTangent;
iterate(pathCommand);
}
} else {
pathCommand.type = PathCommandType::MoveTo;
pathCommand.points[0] = element.vertex.point;
iterate(pathCommand);
}
previousElement = element;
}
if (path.closed().value_or(true)) {
pathCommand.type = PathCommandType::Close;
iterate(pathCommand);
}
});
};
if (shading->stroke) {
return false;
} else if (shading->fill) {
FillRule rule = FillRule::NonZeroWinding;
switch (shading->fill->rule) {
case FillRule::EvenOdd: {
rule = FillRule::EvenOdd;
break;
}
case FillRule::NonZeroWinding: {
rule = FillRule::NonZeroWinding;
break;
}
default: {
break;
}
}
if (shading->fill->shading->type() == RenderTreeNodeContentItem::ShadingType::Solid) {
RenderTreeNodeContentItem::SolidShading *solidShading = (RenderTreeNodeContentItem::SolidShading *)shading->fill->shading.get();
if (solidShading->opacity <= 1.0f - minVisibleAlpha) {
return false;
}
return canvas->clipPath(iteratePaths, rule, currentTransform);
} else if (shading->fill->shading->type() == RenderTreeNodeContentItem::ShadingType::Gradient) {
return false;
}
}
}
for (auto it = item->subItems.rbegin(); it != item->subItems.rend(); it++) {
const auto &subItem = *it;
if (clipToMaskItemIfPossible(canvas, subItem, currentTransform)) {
return true;
}
}
return false;
}
static bool clipToMaskIfPossible(std::shared_ptr<Canvas> const &canvas, std::shared_ptr<RenderTreeNode> mask, bool invertMask, Transform2D const &parentTransform) {
if (invertMask) {
return false;
}
if (mask->alpha() < 1.0f - minVisibleAlpha) {
return false;
}
if (mask->drawContentCount != 1) {
return false;
}
auto currentTransform = parentTransform;
Transform2D localTransform = mask->transform();
currentTransform = localTransform * currentTransform;
if (mask->_contentItem) {
return clipToMaskItemIfPossible(canvas, mask->_contentItem, currentTransform);
}
for (const auto &subnode : mask->subnodes()) {
if (clipToMaskIfPossible(canvas, subnode, invertMask, currentTransform)) {
return true;
}
}
return false;
}
static void renderLottieRenderNode(std::shared_ptr<RenderTreeNode> node, std::shared_ptr<Canvas> const &canvas, Vector2D const &globalSize, Transform2D const &parentTransform, float parentAlpha, BezierPathsBoundingBoxContext &bezierPathsBoundingBoxContext, CanvasRenderer::Configuration const &configuration) {
float normalizedOpacity = node->alpha();
float layerAlpha = ((float)normalizedOpacity) * parentAlpha;
if (node->isHidden() || normalizedOpacity < minVisibleAlpha) {
return;
}
auto currentTransform = parentTransform;
Transform2D localTransform = node->transform();
currentTransform = localTransform * currentTransform;
bool masksToBounds = node->masksToBounds();
if (masksToBounds) {
CGRect effectiveGlobalBounds = CGRect(0.0f, 0.0f, node->size().x, node->size().y).applyingTransform(currentTransform);
if (effectiveGlobalBounds.width <= 0.0f || effectiveGlobalBounds.height <= 0.0f) {
return;
}
if (effectiveGlobalBounds.contains(CGRect(0.0, 0.0, globalSize.x, globalSize.y))) {
masksToBounds = false;
}
}
canvas->saveState();
canvas->concatenate(node->transform());
if (masksToBounds) {
canvas->clip(lottie::CGRect(0.0f, 0.0f, node->size().x, node->size().y));
}
bool needsTempContext = false;
bool didClipToMask = false;
if (node->mask() && !node->mask()->isHidden() && node->mask()->alpha() >= minVisibleAlpha) {
if (clipToMaskIfPossible(canvas, node->mask(), node->invertMask(), Transform2D::identity())) {
didClipToMask = true;
} else {
needsTempContext = true;
}
}
if (layerAlpha != 1.0 && node->drawContentCount > 1 && !configuration.disableGroupTransparency) {
needsTempContext = true;
}
std::optional<CGRect> localRect;
if (needsTempContext) {
if (configuration.canUseMoreMemory && globalSize.x <= minGlobalRectCalculationSize && globalSize.y <= minGlobalRectCalculationSize) {
localRect = CGRect::veryLarge();
} else {
localRect = getRenderNodeLocalRect(node, false, bezierPathsBoundingBoxContext);
}
if (!localRect) {
canvas->restoreState();
return;
}
if (!canvas->pushLayer(localRect.value(), layerAlpha, std::nullopt)) {
canvas->restoreState();
return;
}
// Will restore to this state when applying the mask over current contents
canvas->saveState();
}
float renderAlpha = 1.0f;
if (needsTempContext) {
renderAlpha = 1.0f;
} else {
renderAlpha = layerAlpha;
}
if (node->_contentItem) {
drawLottieContentItem(canvas, node->_contentItem, renderAlpha, globalSize, currentTransform, bezierPathsBoundingBoxContext, configuration);
}
for (const auto &subnode : node->subnodes()) {
renderLottieRenderNode(subnode, canvas, globalSize, currentTransform, renderAlpha, bezierPathsBoundingBoxContext, configuration);
}
if (needsTempContext) {
canvas->restoreState();
if (!didClipToMask && (node->mask() && !node->mask()->isHidden() && node->mask()->alpha() >= minVisibleAlpha)) {
canvas->pushLayer(localRect.value(), 1.0, node->invertMask() ? lottie::Canvas::MaskMode::Inverse : lottie::Canvas::MaskMode::Normal);
if (node->mask() && !node->mask()->isHidden() && node->mask()->alpha() >= minVisibleAlpha) {
renderLottieRenderNode(node->mask(), canvas, globalSize, currentTransform, 1.0, bezierPathsBoundingBoxContext, configuration);
}
canvas->popLayer();
}
canvas->popLayer();
}
canvas->restoreState();
}
}
class CanvasRenderer::Impl {
public:
Impl() {
_bezierPathsBoundingBoxContext = std::make_shared<BezierPathsBoundingBoxContext>();
}
public:
std::shared_ptr<BezierPathsBoundingBoxContext> bezierPathsBoundingBoxContext() const {
return _bezierPathsBoundingBoxContext;
}
private:
std::shared_ptr<BezierPathsBoundingBoxContext> _bezierPathsBoundingBoxContext;
};
CanvasRenderer::CanvasRenderer() :
_impl(std::make_shared<Impl>()) {
}
void CanvasRenderer::render(std::shared_ptr<Renderer> renderer, std::shared_ptr<Canvas> canvas, Vector2D const &size, CanvasRenderer::Configuration const &configuration) {
std::shared_ptr<RenderTreeNode> renderNode = renderer->renderNode();
if (!renderNode) {
return;
}
Vector2D scale = Vector2D(size.x / (float)renderer->size().x, size.y / (float)renderer->size().y);
canvas->saveState();
canvas->concatenate(Transform2D::makeScale(scale.x, scale.y));
Transform2D rootTransform = Transform2D::identity().scaled(Vector2D(size.x / (float)renderer->size().x, size.y / (float)renderer->size().y));
renderLottieRenderNode(renderNode, canvas, size, rootTransform, 1.0, *_impl->bezierPathsBoundingBoxContext().get(), configuration);
canvas->restoreState();
}
}
@@ -0,0 +1,17 @@
#include "CompositionLayer.hpp"
namespace lottie {
InvertedMatteLayer::InvertedMatteLayer(std::shared_ptr<CompositionLayer> inputMatte) :
_inputMatte(inputMatte) {
setSize(inputMatte->size());
addSublayer(_inputMatte);
}
std::shared_ptr<InvertedMatteLayer> makeInvertedMatteLayer(std::shared_ptr<CompositionLayer> compositionLayer) {
auto result = std::make_shared<InvertedMatteLayer>(compositionLayer);
return result;
}
}
@@ -0,0 +1,201 @@
#ifndef CompositionLayer_hpp
#define CompositionLayer_hpp
#include <LottieCpp/Vectors.h>
#include "Lottie/Public/Primitives/CALayer.hpp"
#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/KeypathSearchable.hpp"
#include "Lottie/Private/Model/Layers/LayerModel.hpp"
#include "Lottie/Private/MainThread/LayerContainers/Utility/LayerTransformNode.hpp"
#include "Lottie/Private/MainThread/LayerContainers/CompLayers/MaskContainerLayer.hpp"
#include <memory>
namespace lottie {
class CompositionLayer;
class InvertedMatteLayer;
/// A layer that inverses the alpha output of its input layer.
class InvertedMatteLayer: public CALayer {
public:
InvertedMatteLayer(std::shared_ptr<CompositionLayer> inputMatte);
std::shared_ptr<CompositionLayer> _inputMatte;
virtual bool isInvertedMatte() const override {
return true;
}
};
std::shared_ptr<InvertedMatteLayer> makeInvertedMatteLayer(std::shared_ptr<CompositionLayer> compositionLayer);
/// The base class for a child layer of CompositionContainer
class CompositionLayer: public CALayer, public KeypathSearchable {
public:
CompositionLayer(std::shared_ptr<LayerModel> const &layer, Vector2D size) {
_contentsLayer = std::make_shared<CALayer>();
_transformNode = std::make_shared<LayerTransformNode>(layer->transform);
if (layer->masks.has_value()) {
_maskLayer = std::make_shared<MaskContainerLayer>(layer->masks.value());
} else {
_maskLayer = nullptr;
}
_matteType = layer->matte;
_inFrame = layer->inFrame;
_outFrame = layer->outFrame;
_timeStretch = layer->timeStretch();
_startFrame = layer->startTime;
if (layer->name.has_value()) {
_keypathName = layer->name.value();
} else {
_keypathName = "Layer";
}
_childKeypaths.push_back(_transformNode->transformProperties());
_contentsLayer->setSize(size);
if (layer->blendMode.has_value() && layer->blendMode.value() != BlendMode::Normal) {
setCompositingFilter(layer->blendMode);
}
addSublayer(_contentsLayer);
if (_maskLayer) {
_contentsLayer->setMask(_maskLayer);
}
}
virtual std::string keypathName() const override {
return _keypathName;
}
virtual std::map<std::string, std::shared_ptr<AnyNodeProperty>> keypathProperties() const override {
return {};
}
virtual std::shared_ptr<CALayer> keypathLayer() const override {
return _contentsLayer;
}
void displayWithFrame(float frame, bool forceUpdates, BezierPathsBoundingBoxContext &boundingBoxContext) {
bool layerVisible = isInRangeOrEqual(frame, _inFrame, _outFrame);
if (_transformNode->updateTree(frame, forceUpdates) || _contentsLayer->isHidden() != !layerVisible) {
_contentsLayer->setTransform(_transformNode->globalTransform());
_contentsLayer->setOpacity(_transformNode->opacity());
_contentsLayer->setIsHidden(!layerVisible);
updateContentsLayerParameters();
}
/// Only update contents if current time is within the layers time bounds.
if (layerVisible) {
displayContentsWithFrame(frame, forceUpdates, boundingBoxContext);
if (_maskLayer) {
_maskLayer->updateWithFrame(frame, forceUpdates);
}
}
}
virtual void updateContentsLayerParameters() {
}
virtual void displayContentsWithFrame(float frame, bool forceUpdates, BezierPathsBoundingBoxContext &boundingBoxContext) {
/// To be overridden by subclass
}
virtual std::vector<std::shared_ptr<KeypathSearchable>> const &childKeypaths() const override {
return _childKeypaths;
}
std::shared_ptr<CompositionLayer> _matteLayer;
void setMatteLayer(std::shared_ptr<CompositionLayer> matteLayer) {
_matteLayer = matteLayer;
if (matteLayer) {
if (_matteType.has_value() && _matteType.value() == MatteType::Invert) {
setMask(makeInvertedMatteLayer(matteLayer));
} else {
setMask(matteLayer);
}
} else {
setMask(nullptr);
}
}
std::shared_ptr<CALayer> const &contentsLayer() const {
return _contentsLayer;
}
std::shared_ptr<MaskContainerLayer> const &maskLayer() const {
return _maskLayer;
}
void setMaskLayer(std::shared_ptr<MaskContainerLayer> const &maskLayer) {
_maskLayer = maskLayer;
}
std::optional<MatteType> const &matteType() const {
return _matteType;
}
float inFrame() const {
return _inFrame;
}
float outFrame() const {
return _outFrame;
}
float startFrame() const {
return _startFrame;
}
float timeStretch() const {
return _timeStretch;
}
virtual std::shared_ptr<RenderTreeNode> renderTreeNode(BezierPathsBoundingBoxContext &boundingBoxContext) {
return nullptr;
}
public:
std::shared_ptr<LayerTransformNode> const transformNode() const {
return _transformNode;
}
protected:
std::shared_ptr<CALayer> _contentsLayer;
std::optional<MatteType> _matteType;
private:
std::shared_ptr<LayerTransformNode> _transformNode;
std::shared_ptr<MaskContainerLayer> _maskLayer;
float _inFrame = 0.0;
float _outFrame = 0.0;
float _startFrame = 0.0;
float _timeStretch = 0.0;
// MARK: Keypath Searchable
std::string _keypathName;
public:
virtual bool isImageCompositionLayer() const {
return false;
}
virtual bool isTextCompositionLayer() const {
return false;
}
protected:
std::vector<std::shared_ptr<KeypathSearchable>> _childKeypaths;
};
}
#endif /* CompositionLayer_hpp */
@@ -0,0 +1,5 @@
#include "ImageCompositionLayer.hpp"
namespace lottie {
}
@@ -0,0 +1,43 @@
#ifndef ImageCompositionLayer_hpp
#define ImageCompositionLayer_hpp
#include "Lottie/Private/MainThread/LayerContainers/CompLayers/CompositionLayer.hpp"
#include "Lottie/Private/Model/Assets/ImageAsset.hpp"
#include "Lottie/Private/Model/Layers/ImageLayerModel.hpp"
namespace lottie {
class ImageCompositionLayer: public CompositionLayer {
public:
ImageCompositionLayer(std::shared_ptr<ImageLayerModel> const &imageLayer, Vector2D const &size) :
CompositionLayer(imageLayer, size) {
_imageReferenceID = imageLayer->referenceID;
contentsLayer()->setMasksToBounds(true);
}
std::shared_ptr<Image> image() {
return _image;
}
void setImage(std::shared_ptr<Image> image) {
_image = image;
//contentsLayer()->setContents(image);
}
std::string const &imageReferenceID() {
return _imageReferenceID;
}
public:
virtual bool isImageCompositionLayer() const override {
return true;
}
private:
std::string _imageReferenceID;
std::shared_ptr<Image> _image;
};
}
#endif /* ImageCompositionLayer_hpp */
@@ -0,0 +1,5 @@
#include "MaskContainerLayer.hpp"
namespace lottie {
}
@@ -0,0 +1,178 @@
#ifndef MaskContainerLayer_hpp
#define MaskContainerLayer_hpp
#include "Lottie/Private/Model/Objects/Mask.hpp"
#include "Lottie/Public/Primitives/CALayer.hpp"
#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/NodePropertyMap.hpp"
#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/NodeProperty.hpp"
#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/KeyframeInterpolator.hpp"
namespace lottie {
inline MaskMode usableMaskMode(MaskMode mode) {
switch (mode) {
case MaskMode::Add:
return MaskMode::Add;
case MaskMode::Subtract:
return MaskMode::Subtract;
case MaskMode::Intersect:
return MaskMode::Intersect;
case MaskMode::Lighten:
return MaskMode::Add;
case MaskMode::Darken:
return MaskMode::Darken;
case MaskMode::Difference:
return MaskMode::Intersect;
case MaskMode::None:
return MaskMode::None;
}
}
class MaskNodeProperties: public NodePropertyMap {
public:
MaskNodeProperties(std::shared_ptr<Mask> const &mask) :
_mode(mask->mode()),
_inverted(mask->inverted) {
_opacity = std::make_shared<NodeProperty<Vector1D>>(std::make_shared<KeyframeInterpolator<Vector1D>>(mask->opacity->keyframes));
_shape = std::make_shared<NodeProperty<BezierPath>>(std::make_shared<KeyframeInterpolator<BezierPath>>(mask->shape.keyframes));
_expansion = std::make_shared<NodeProperty<Vector1D>>(std::make_shared<KeyframeInterpolator<Vector1D>>(mask->expansion->keyframes));
_propertyMap.insert(std::make_pair("Opacity", _opacity));
_propertyMap.insert(std::make_pair("Shape", _shape));
_propertyMap.insert(std::make_pair("Expansion", _expansion));
for (const auto &it : _propertyMap) {
_properties.push_back(it.second);
}
}
virtual std::vector<std::shared_ptr<AnyNodeProperty>> &properties() override {
return _properties;
}
virtual std::vector<std::shared_ptr<KeypathSearchable>> const &childKeypaths() const override {
return _childKeypaths;
}
std::shared_ptr<NodeProperty<Vector1D>> const &opacity() const {
return _opacity;
}
std::shared_ptr<NodeProperty<BezierPath>> const &shape() const {
return _shape;
}
std::shared_ptr<NodeProperty<Vector1D>> const &expansion() const {
return _expansion;
}
MaskMode mode() const {
return _mode;
}
bool inverted() const {
return _inverted;
}
private:
std::map<std::string, std::shared_ptr<AnyNodeProperty>> _propertyMap;
std::vector<std::shared_ptr<KeypathSearchable>> _childKeypaths;
std::vector<std::shared_ptr<AnyNodeProperty>> _properties;
MaskMode _mode = MaskMode::Add;
bool _inverted = false;
std::shared_ptr<NodeProperty<Vector1D>> _opacity;
std::shared_ptr<NodeProperty<BezierPath>> _shape;
std::shared_ptr<NodeProperty<Vector1D>> _expansion;
};
class MaskLayer: public CALayer {
public:
MaskLayer(std::shared_ptr<Mask> const &mask) :
_properties(mask) {
/*_maskLayer = std::make_shared<CAShapeLayer>();
addSublayer(_maskLayer);
if (mask->mode() == MaskMode::Add) {
_maskLayer->setFillColor(Color(1.0, 0.0, 0.0, 1.0));
} else {
_maskLayer->setFillColor(Color(0.0, 1.0, 0.0, 1.0));
}
_maskLayer->setFillRule(FillRule::EvenOdd);*/
}
virtual ~MaskLayer() = default;
void updateWithFrame(float frame, bool forceUpdates) {
if (_properties.opacity()->needsUpdate(frame) || forceUpdates) {
_properties.opacity()->update(frame);
setOpacity(_properties.opacity()->value().value);
}
if (_properties.shape()->needsUpdate(frame) || forceUpdates) {
_properties.shape()->update(frame);
_properties.expansion()->update(frame);
/*auto path = _properties.shape()->value().cgPath();
auto usableMode = usableMaskMode(_properties.mode());
if ((usableMode == MaskMode::Subtract && !_properties.inverted()) ||
(usableMode == MaskMode::Add && _properties.inverted())) {
/// Add a bounds rect to invert the mask
auto newPath = CGPath::makePath();
newPath->addRect(CGRect::veryLarge());
newPath->addPath(path);
path = std::static_pointer_cast<CGPath>(newPath);
}
_maskLayer->setPath(path);*/
}
}
private:
MaskNodeProperties _properties;
//std::shared_ptr<CAShapeLayer> _maskLayer;
};
class MaskContainerLayer: public CALayer {
public:
MaskContainerLayer(std::vector<std::shared_ptr<Mask>> const &masks) {
auto containerLayer = std::make_shared<CALayer>();
bool firstObject = true;
for (const auto &mask : masks) {
auto maskLayer = std::make_shared<MaskLayer>(mask);
_maskLayers.push_back(maskLayer);
auto usableMode = usableMaskMode(mask->mode());
if (usableMode == MaskMode::None) {
continue;
} else if (usableMode == MaskMode::Add || firstObject) {
firstObject = false;
containerLayer->addSublayer(maskLayer);
} else {
containerLayer->setMask(maskLayer);
auto newContainer = std::make_shared<CALayer>();
newContainer->addSublayer(containerLayer);
containerLayer = newContainer;
}
}
addSublayer(containerLayer);
}
// MARK: Internal
void updateWithFrame(float frame, bool forceUpdates) {
for (const auto &maskLayer : _maskLayers) {
maskLayer->updateWithFrame(frame, forceUpdates);
}
}
private:
std::vector<std::shared_ptr<MaskLayer>> _maskLayers;
};
}
#endif /* MaskContainerLayer_hpp */
@@ -0,0 +1,5 @@
#include "NullCompositionLayer.hpp"
namespace lottie {
}
@@ -0,0 +1,17 @@
#ifndef NullCompositionLayer_hpp
#define NullCompositionLayer_hpp
#include "Lottie/Private/MainThread/LayerContainers/CompLayers/CompositionLayer.hpp"
namespace lottie {
class NullCompositionLayer: public CompositionLayer {
public:
NullCompositionLayer(std::shared_ptr<LayerModel> const &layer) :
CompositionLayer(layer, Vector2D::Zero()) {
}
};
}
#endif /* NullCompositionLayer_hpp */
@@ -0,0 +1,5 @@
#include "PreCompositionLayer.hpp"
namespace lottie {
}
@@ -0,0 +1,202 @@
#ifndef PreCompositionLayer_hpp
#define PreCompositionLayer_hpp
#include "Lottie/Private/MainThread/LayerContainers/CompLayers/CompositionLayer.hpp"
#include "Lottie/Private/Model/Layers/PreCompLayerModel.hpp"
#include "Lottie/Private/Model/Assets/PrecompAsset.hpp"
#include "Lottie/Private/MainThread/LayerContainers/Utility/LayerImageProvider.hpp"
#include "Lottie/Public/TextProvider/AnimationTextProvider.hpp"
#include "Lottie/Public/FontProvider/AnimationFontProvider.hpp"
#include "Lottie/Private/Model/Assets/AssetLibrary.hpp"
#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/NodeProperty.hpp"
#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/KeyframeInterpolator.hpp"
#include "Lottie/Private/MainThread/LayerContainers/Utility/CompositionLayersInitializer.hpp"
namespace lottie {
class PreCompositionLayer: public CompositionLayer {
public:
PreCompositionLayer(
std::shared_ptr<PreCompLayerModel> const &precomp,
PrecompAsset const &asset,
std::shared_ptr<LayerImageProvider> const &layerImageProvider,
std::shared_ptr<AnimationTextProvider> const &textProvider,
std::shared_ptr<AnimationFontProvider> const &fontProvider,
std::shared_ptr<AssetLibrary> const &assetLibrary,
float frameRate
) : CompositionLayer(precomp, Vector2D(precomp->width, precomp->height)) {
if (precomp->timeRemapping) {
_remappingNode = std::make_shared<NodeProperty<Vector1D>>(std::make_shared<KeyframeInterpolator<Vector1D>>(precomp->timeRemapping->keyframes));
}
_frameRate = frameRate;
setSize(Vector2D(precomp->width, precomp->height));
contentsLayer()->setMasksToBounds(true);
contentsLayer()->setSize(size());
auto layers = initializeCompositionLayers(
asset.layers,
assetLibrary,
layerImageProvider,
textProvider,
fontProvider,
frameRate
);
std::vector<std::shared_ptr<ImageCompositionLayer>> imageLayers;
std::shared_ptr<CompositionLayer> mattedLayer;
for (auto layerIt = layers.rbegin(); layerIt != layers.rend(); layerIt++) {
std::shared_ptr<CompositionLayer> layer = *layerIt;
layer->setSize(size());
_animationLayers.push_back(layer);
if (layer->isImageCompositionLayer()) {
imageLayers.push_back(std::static_pointer_cast<ImageCompositionLayer>(layer));
}
if (mattedLayer) {
/// The previous layer requires this layer to be its matte
mattedLayer->setMatteLayer(layer);
mattedLayer = nullptr;
continue;
}
if (layer->matteType().has_value() && (layer->matteType().value() == MatteType::Add || layer->matteType().value() == MatteType::Invert)) {
/// We have a layer that requires a matte.
mattedLayer = layer;
}
contentsLayer()->addSublayer(layer);
}
for (const auto &layer : layers) {
_childKeypaths.push_back(layer);
}
layerImageProvider->addImageLayers(imageLayers);
}
virtual std::map<std::string, std::shared_ptr<AnyNodeProperty>> keypathProperties() const override {
if (!_remappingNode) {
return {};
}
std::map<std::string, std::shared_ptr<AnyNodeProperty>> result;
result.insert(std::make_pair("Time Remap", _remappingNode));
return result;
}
virtual void displayContentsWithFrame(float frame, bool forceUpdates, BezierPathsBoundingBoxContext &boundingBoxContext) override {
float localFrame = 0.0;
if (_remappingNode) {
_remappingNode->update(frame);
localFrame = _remappingNode->value().value * _frameRate;
} else {
localFrame = (frame - startFrame()) / timeStretch();
}
for (const auto &animationLayer : _animationLayers) {
animationLayer->displayWithFrame(localFrame, forceUpdates, boundingBoxContext);
}
}
virtual std::shared_ptr<RenderTreeNode> renderTreeNode(BezierPathsBoundingBoxContext &boundingBoxContext) override {
if (!_renderTreeNode) {
std::vector<std::shared_ptr<RenderTreeNode>> renderTreeSubnodes;
for (const auto &animationLayer : _animationLayers) {
bool found = false;
for (const auto &sublayer : contentsLayer()->sublayers()) {
if (animationLayer == sublayer) {
found = true;
break;
}
}
if (found) {
auto node = animationLayer->renderTreeNode(boundingBoxContext);
if (node) {
renderTreeSubnodes.push_back(node);
}
}
}
std::vector<std::shared_ptr<RenderTreeNode>> renderTreeValue;
auto renderTreeContentItem = std::make_shared<RenderTreeNode>(
Vector2D(0.0, 0.0),
Transform2D::identity(),
1.0,
false,
false,
renderTreeSubnodes,
nullptr,
false
);
if (renderTreeContentItem) {
renderTreeValue.push_back(renderTreeContentItem);
}
_contentsTreeNode = std::make_shared<RenderTreeNode>(
Vector2D(0.0, 0.0),
Transform2D::identity(),
1.0,
false,
false,
renderTreeValue,
nullptr,
false
);
std::vector<std::shared_ptr<RenderTreeNode>> subnodes;
subnodes.push_back(_contentsTreeNode);
std::shared_ptr<RenderTreeNode> maskNode;
bool invertMask = false;
if (_matteLayer) {
maskNode = _matteLayer->renderTreeNode(boundingBoxContext);
if (maskNode && _matteType.has_value() && _matteType.value() == MatteType::Invert) {
invertMask = true;
}
}
_renderTreeNode = std::make_shared<RenderTreeNode>(
Vector2D(0.0, 0.0),
Transform2D::identity(),
1.0,
false,
false,
subnodes,
maskNode,
invertMask
);
}
_contentsTreeNode->_size = _contentsLayer->size();
_contentsTreeNode->_masksToBounds = _contentsLayer->masksToBounds();
_renderTreeNode->_size = size();
_renderTreeNode->_transform = transform();
_renderTreeNode->_alpha = opacity();
_renderTreeNode->_masksToBounds = masksToBounds();
_renderTreeNode->_isHidden = isHidden();
return _renderTreeNode;
}
virtual void updateContentsLayerParameters() override {
_contentsTreeNode->_transform = _contentsLayer->transform();
_contentsTreeNode->_alpha = _contentsLayer->opacity();
_contentsTreeNode->_isHidden = _contentsLayer->isHidden();
}
private:
float _frameRate = 0.0;
std::shared_ptr<NodeProperty<Vector1D>> _remappingNode;
std::vector<std::shared_ptr<CompositionLayer>> _animationLayers;
std::shared_ptr<RenderTreeNode> _renderTreeNode;
std::shared_ptr<RenderTreeNode> _contentsTreeNode;
};
}
#endif /* PreCompositionLayer_hpp */
@@ -0,0 +1,36 @@
#ifndef ShapeCompositionLayer_hpp
#define ShapeCompositionLayer_hpp
#include "Lottie/Private/MainThread/LayerContainers/CompLayers/CompositionLayer.hpp"
#include "Lottie/Private/Model/Layers/ShapeLayerModel.hpp"
#include "Lottie/Private/Model/Layers/SolidLayerModel.hpp"
#include "Lottie/Private/MainThread/NodeRenderSystem/Protocols/AnimatorNode.hpp"
namespace lottie {
class ShapeLayerPresentationTree;
/// A CompositionLayer responsible for initializing and rendering shapes
class ShapeCompositionLayer: public CompositionLayer {
public:
ShapeCompositionLayer(std::shared_ptr<ShapeLayerModel> const &shapeLayer);
ShapeCompositionLayer(std::shared_ptr<SolidLayerModel> const &solidLayer);
virtual void displayContentsWithFrame(float frame, bool forceUpdates, BezierPathsBoundingBoxContext &boundingBoxContext) override;
virtual std::shared_ptr<RenderTreeNode> renderTreeNode(BezierPathsBoundingBoxContext &boundingBoxContext) override;
void initializeContentsLayerParameters();
virtual void updateContentsLayerParameters() override;
private:
std::shared_ptr<ShapeLayerPresentationTree> _contentTree;
AnimationFrameTime _frameTime = 0.0;
bool _frameTimeInitialized = false;
std::shared_ptr<RenderTreeNode> _renderTreeNode;
std::shared_ptr<RenderTreeNode> _contentRenderTreeNode;
};
}
#endif /* ShapeCompositionLayer_hpp */
@@ -0,0 +1,487 @@
#include "BezierPathUtils.hpp"
namespace lottie {
BezierPath makeEllipseBezierPath(
Vector2D const &size,
Vector2D const &center,
PathDirection direction
) {
const float ControlPointConstant = 0.55228;
Vector2D half = size * 0.5;
if (direction == PathDirection::CounterClockwise) {
half.x = half.x * -1.0;
}
Vector2D q1(center.x, center.y - half.y);
Vector2D q2(center.x + half.x, center.y);
Vector2D q3(center.x, center.y + half.y);
Vector2D q4(center.x - half.x, center.y);
Vector2D cp = half * ControlPointConstant;
BezierPath path(CurveVertex::relative(
q1,
Vector2D(-cp.x, 0),
Vector2D(cp.x, 0)));
path.addVertex(CurveVertex::relative(
q2,
Vector2D(0, -cp.y),
Vector2D(0, cp.y)));
path.addVertex(CurveVertex::relative(
q3,
Vector2D(cp.x, 0),
Vector2D(-cp.x, 0)));
path.addVertex(CurveVertex::relative(
q4,
Vector2D(0, cp.y),
Vector2D(0, -cp.y)));
path.addVertex(CurveVertex::relative(
q1,
Vector2D(-cp.x, 0),
Vector2D(cp.x, 0)));
path.close();
return path;
}
BezierPath makeRectangleBezierPath(
Vector2D const &position,
Vector2D const &inputSize,
float cornerRadius,
PathDirection direction
) {
const float ControlPointConstant = 0.55228;
Vector2D size = inputSize * 0.5;
float radius = std::min(std::min(cornerRadius, (float)size.x), (float)size.y);
BezierPath bezierPath;
std::vector<CurveVertex> points;
if (radius <= 0.0) {
/// No Corners
points = {
/// Lead In
CurveVertex::relative(
Vector2D(size.x, -size.y),
Vector2D::Zero(),
Vector2D::Zero())
.translated(position),
/// Corner 1
CurveVertex::relative(
Vector2D(size.x, size.y),
Vector2D::Zero(),
Vector2D::Zero())
.translated(position),
/// Corner 2
CurveVertex::relative(
Vector2D(-size.x, size.y),
Vector2D::Zero(),
Vector2D::Zero())
.translated(position),
/// Corner 3
CurveVertex::relative(
Vector2D(-size.x, -size.y),
Vector2D::Zero(),
Vector2D::Zero())
.translated(position),
/// Corner 4
CurveVertex::relative(
Vector2D(size.x, -size.y),
Vector2D::Zero(),
Vector2D::Zero())
.translated(position)
};
} else {
float controlPoint = radius * ControlPointConstant;
points = {
/// Lead In
CurveVertex::absolute(
Vector2D(radius, 0),
Vector2D(radius, 0),
Vector2D(radius, 0))
.translated(Vector2D(-radius, radius))
.translated(Vector2D(size.x, -size.y))
.translated(position),
/// Corner 1
CurveVertex::absolute(
Vector2D(radius, 0), // Point
Vector2D(radius, 0), // In tangent
Vector2D(radius, controlPoint))
.translated(Vector2D(-radius, -radius))
.translated(Vector2D(size.x, size.y))
.translated(position),
CurveVertex::absolute(
Vector2D(0, radius), // Point
Vector2D(controlPoint, radius), // In tangent
Vector2D(0, radius)) // Out Tangent
.translated(Vector2D(-radius, -radius))
.translated(Vector2D(size.x, size.y))
.translated(position),
/// Corner 2
CurveVertex::absolute(
Vector2D(0, radius), // Point
Vector2D(0, radius), // In tangent
Vector2D(-controlPoint, radius))// Out tangent
.translated(Vector2D(radius, -radius))
.translated(Vector2D(-size.x, size.y))
.translated(position),
CurveVertex::absolute(
Vector2D(-radius, 0), // Point
Vector2D(-radius, controlPoint), // In tangent
Vector2D(-radius, 0)) // Out tangent
.translated(Vector2D(radius, -radius))
.translated(Vector2D(-size.x, size.y))
.translated(position),
/// Corner 3
CurveVertex::absolute(
Vector2D(-radius, 0), // Point
Vector2D(-radius, 0), // In tangent
Vector2D(-radius, -controlPoint)) // Out tangent
.translated(Vector2D(radius, radius))
.translated(Vector2D(-size.x, -size.y))
.translated(position),
CurveVertex::absolute(
Vector2D(0, -radius), // Point
Vector2D(-controlPoint, -radius), // In tangent
Vector2D(0, -radius)) // Out tangent
.translated(Vector2D(radius, radius))
.translated(Vector2D(-size.x, -size.y))
.translated(position),
/// Corner 4
CurveVertex::absolute(
Vector2D(0, -radius), // Point
Vector2D(0, -radius), // In tangent
Vector2D(controlPoint, -radius)) // Out tangent
.translated(Vector2D(-radius, radius))
.translated(Vector2D(size.x, -size.y))
.translated(position),
CurveVertex::absolute(
Vector2D(radius, 0), // Point
Vector2D(radius, -controlPoint), // In tangent
Vector2D(radius, 0)) // Out tangent
.translated(Vector2D(-radius, radius))
.translated(Vector2D(size.x, -size.y))
.translated(position)
};
}
bool reversed = direction == PathDirection::CounterClockwise;
if (reversed) {
for (auto vertexIt = points.rbegin(); vertexIt != points.rend(); vertexIt++) {
bezierPath.addVertex((*vertexIt).reversed());
}
} else {
for (auto vertexIt = points.begin(); vertexIt != points.end(); vertexIt++) {
bezierPath.addVertex(*vertexIt);
}
}
bezierPath.close();
return bezierPath;
}
/// Magic number needed for building path data
static constexpr float StarNodePolystarConstant = 0.47829;
BezierPath makeStarBezierPath(
Vector2D const &position,
float outerRadius,
float innerRadius,
float inputOuterRoundedness,
float inputInnerRoundedness,
float numberOfPoints,
float rotation,
PathDirection direction
) {
float currentAngle = degreesToRadians(rotation - 90.0);
float anglePerPoint = (2.0 * M_PI) / numberOfPoints;
float halfAnglePerPoint = anglePerPoint / 2.0;
float partialPointAmount = numberOfPoints - floor(numberOfPoints);
float outerRoundedness = inputOuterRoundedness * 0.01;
float innerRoundedness = inputInnerRoundedness * 0.01;
Vector2D point = Vector2D::Zero();
float partialPointRadius = 0.0;
if (partialPointAmount != 0.0) {
currentAngle += halfAnglePerPoint * (1 - partialPointAmount);
partialPointRadius = innerRadius + partialPointAmount * (outerRadius - innerRadius);
point.x = (partialPointRadius * cos(currentAngle));
point.y = (partialPointRadius * sin(currentAngle));
currentAngle += anglePerPoint * partialPointAmount / 2;
} else {
point.x = (outerRadius * cos(currentAngle));
point.y = (outerRadius * sin(currentAngle));
currentAngle += halfAnglePerPoint;
}
std::vector<CurveVertex> vertices;
vertices.push_back(CurveVertex::relative(point + position, Vector2D::Zero(), Vector2D::Zero()));
Vector2D previousPoint = point;
bool longSegment = false;
int numPoints = (int)(ceil(numberOfPoints) * 2.0);
for (int i = 0; i < numPoints; i++) {
float radius = longSegment ? outerRadius : innerRadius;
float dTheta = halfAnglePerPoint;
if (partialPointRadius != 0.0 && i == numPoints - 2) {
dTheta = anglePerPoint * partialPointAmount / 2;
}
if (partialPointRadius != 0.0 && i == numPoints - 1) {
radius = partialPointRadius;
}
previousPoint = point;
point.x = (radius * cos(currentAngle));
point.y = (radius * sin(currentAngle));
if (innerRoundedness == 0.0 && outerRoundedness == 0.0) {
vertices.push_back(CurveVertex::relative(point + position, Vector2D::Zero(), Vector2D::Zero()));
} else {
float cp1Theta = (atan2(previousPoint.y, previousPoint.x) - M_PI / 2.0);
float cp1Dx = cos(cp1Theta);
float cp1Dy = sin(cp1Theta);
float cp2Theta = (atan2(point.y, point.x) - M_PI / 2.0);
float cp2Dx = cos(cp2Theta);
float cp2Dy = sin(cp2Theta);
float cp1Roundedness = longSegment ? innerRoundedness : outerRoundedness;
float cp2Roundedness = longSegment ? outerRoundedness : innerRoundedness;
float cp1Radius = longSegment ? innerRadius : outerRadius;
float cp2Radius = longSegment ? outerRadius : innerRadius;
Vector2D cp1(
cp1Radius * cp1Roundedness * StarNodePolystarConstant * cp1Dx,
cp1Radius * cp1Roundedness * StarNodePolystarConstant * cp1Dy
);
Vector2D cp2(
cp2Radius * cp2Roundedness * StarNodePolystarConstant * cp2Dx,
cp2Radius * cp2Roundedness * StarNodePolystarConstant * cp2Dy
);
if (partialPointAmount != 0.0) {
if (i == 0) {
cp1 = cp1 * partialPointAmount;
} else if (i == numPoints - 1) {
cp2 = cp2 * partialPointAmount;
}
}
auto previousVertex = vertices[vertices.size() - 1];
vertices[vertices.size() - 1] = CurveVertex::absolute(
previousVertex.point,
previousVertex.inTangent,
previousVertex.point - cp1
);
vertices.push_back(CurveVertex::relative(point + position, cp2, Vector2D::Zero()));
}
currentAngle += dTheta;
longSegment = !longSegment;
}
bool reverse = direction == PathDirection::CounterClockwise;
BezierPath path;
if (reverse) {
for (auto vertexIt = vertices.rbegin(); vertexIt != vertices.rend(); vertexIt++) {
path.addVertex((*vertexIt).reversed());
}
} else {
for (auto vertexIt = vertices.begin(); vertexIt != vertices.end(); vertexIt++) {
path.addVertex(*vertexIt);
}
}
path.close();
return path;
}
CompoundBezierPath trimCompoundPath(CompoundBezierPath sourcePath, float start, float end, float offset, TrimType type) {
/// No need to trim, it's a full path
if (start == 0.0 && end == 1.0) {
return sourcePath;
}
/// All paths are empty.
if (start == end) {
return CompoundBezierPath();
}
if (type == TrimType::Simultaneously) {
CompoundBezierPath result;
for (BezierPath &path : sourcePath.paths) {
CompoundBezierPath tempPath;
tempPath.appendPath(path);
auto subPaths = tempPath.trim(start, end, offset);
for (const auto &subPath : subPaths->paths) {
result.appendPath(subPath);
}
}
return result;
}
/// Individual path trimming.
/// Brace yourself for the below code.
/// Normalize lengths with offset.
float startPosition = fmod(start + offset, 1.0);
float endPosition = fmod(end + offset, 1.0);
if (startPosition < 0.0) {
startPosition = 1.0 + startPosition;
}
if (endPosition < 0.0) {
endPosition = 1.0 + endPosition;
}
if (startPosition == 1.0) {
startPosition = 0.0;
}
if (endPosition == 0.0) {
endPosition = 1.0;
}
/// First get the total length of all paths.
float totalLength = 0.0;
for (auto &upstreamPath : sourcePath.paths) {
totalLength += upstreamPath.length();
}
/// Now determine the start and end cut lengths
float startLength = startPosition * totalLength;
float endLength = endPosition * totalLength;
float pathStart = 0.0;
CompoundBezierPath result;
/// Now loop through all path containers
for (auto &pathContainer : sourcePath.paths) {
auto pathEnd = pathStart + pathContainer.length();
if (!isInRange(startLength, pathStart, pathEnd) &&
isInRange(endLength, pathStart, pathEnd)) {
// pathStart|=======E----------------------|pathEnd
// Cut path components, removing after end.
float pathCutLength = endLength - pathStart;
float subpathStart = 0.0;
float subpathEnd = subpathStart + pathContainer.length();
if (pathCutLength < subpathEnd) {
/// This is the subpath that needs to be cut.
float cutLength = pathCutLength - subpathStart;
CompoundBezierPath tempPath;
tempPath.appendPath(pathContainer);
auto newPaths = tempPath.trim(0, cutLength / pathContainer.length(), 0);
for (const auto &newPath : newPaths->paths) {
result.appendPath(newPath);
}
} else {
/// Add to container and move on
result.appendPath(pathContainer);
}
/*if (pathCutLength == subpathEnd) {
/// Right on the end. The next subpath is not included. Break.
break;
}
subpathStart = subpathEnd;*/
} else if (!isInRange(endLength, pathStart, pathEnd) &&
isInRange(startLength, pathStart, pathEnd)) {
// pathStart|-------S======================|pathEnd
//
// Cut path components, removing before beginning.
float pathCutLength = startLength - pathStart;
// Clear paths from container
float subpathStart = 0.0;
float subpathEnd = subpathStart + pathContainer.length();
if (subpathStart < pathCutLength && pathCutLength < subpathEnd) {
/// This is the subpath that needs to be cut.
float cutLength = pathCutLength - subpathStart;
CompoundBezierPath tempPath;
tempPath.appendPath(pathContainer);
auto newPaths = tempPath.trim(cutLength / pathContainer.length(), 1, 0);
for (const auto &newPath : newPaths->paths) {
result.appendPath(newPath);
}
} else if (pathCutLength <= subpathStart) {
result.appendPath(pathContainer);
}
//subpathStart = subpathEnd;
} else if (isInRange(endLength, pathStart, pathEnd) &&
isInRange(startLength, pathStart, pathEnd)) {
// pathStart|-------S============E---------|endLength
// pathStart|=====E----------------S=======|endLength
// trim from path beginning to endLength.
// Cut path components, removing before beginnings.
float startCutLength = startLength - pathStart;
float endCutLength = endLength - pathStart;
float subpathStart = 0.0;
float subpathEnd = subpathStart + pathContainer.length();
if (!isInRange(startCutLength, subpathStart, subpathEnd) &&
!isInRange(endCutLength, subpathStart, subpathEnd))
{
// The whole path is included. Add
// S|==============================|E
result.appendPath(pathContainer);
} else if (isInRange(startCutLength, subpathStart, subpathEnd) &&
!isInRange(endCutLength, subpathStart, subpathEnd)) {
/// The start of the path needs to be trimmed
// |-------S======================|E
float cutLength = startCutLength - subpathStart;
CompoundBezierPath tempPath;
tempPath.appendPath(pathContainer);
auto newPaths = tempPath.trim(cutLength / pathContainer.length(), 1, 0);
for (const auto &newPath : newPaths->paths) {
result.appendPath(newPath);
}
} else if (!isInRange(startCutLength, subpathStart, subpathEnd) &&
isInRange(endCutLength, subpathStart, subpathEnd)) {
// S|=======E----------------------|
float cutLength = endCutLength - subpathStart;
CompoundBezierPath tempPath;
tempPath.appendPath(pathContainer);
auto newPaths = tempPath.trim(0, cutLength / pathContainer.length(), 0);
for (const auto &newPath : newPaths->paths) {
result.appendPath(newPath);
}
} else if (isInRange(startCutLength, subpathStart, subpathEnd) &&
isInRange(endCutLength, subpathStart, subpathEnd)) {
// |-------S============E---------|
float cutFromLength = startCutLength - subpathStart;
float cutToLength = endCutLength - subpathStart;
CompoundBezierPath tempPath;
tempPath.appendPath(pathContainer);
auto newPaths = tempPath.trim(
cutFromLength / pathContainer.length(),
cutToLength / pathContainer.length(),
0
);
for (const auto &newPath : newPaths->paths) {
result.appendPath(newPath);
}
}
} else if ((endLength <= pathStart && pathEnd <= startLength) ||
(startLength <= pathStart && endLength <= pathStart) ||
(pathEnd <= startLength && pathEnd <= endLength)) {
/// The Path needs to be cleared
} else {
result.appendPath(pathContainer);
}
pathStart = pathEnd;
}
return result;
}
}
@@ -0,0 +1,39 @@
#ifndef BezierPaths_h
#define BezierPaths_h
#include "Lottie/Private/Model/ShapeItems/Ellipse.hpp"
#include <LottieCpp/BezierPath.h>
#include "Lottie/Private/Utility/Primitives/CompoundBezierPath.hpp"
#include "Lottie/Private/Model/ShapeItems/Trim.hpp"
namespace lottie {
BezierPath makeEllipseBezierPath(
Vector2D const &size,
Vector2D const &center,
PathDirection direction
);
BezierPath makeRectangleBezierPath(
Vector2D const &position,
Vector2D const &inputSize,
float cornerRadius,
PathDirection direction
);
BezierPath makeStarBezierPath(
Vector2D const &position,
float outerRadius,
float innerRadius,
float inputOuterRoundedness,
float inputInnerRoundedness,
float numberOfPoints,
float rotation,
PathDirection direction
);
CompoundBezierPath trimCompoundPath(CompoundBezierPath sourcePath, float start, float end, float offset, TrimType type);
}
#endif /* BezierPaths_h */
@@ -0,0 +1,5 @@
#include "TextCompositionLayer.hpp"
namespace lottie {
}
@@ -0,0 +1,81 @@
#ifndef TextCompositionLayer_hpp
#define TextCompositionLayer_hpp
#include "Lottie/Private/MainThread/LayerContainers/CompLayers/CompositionLayer.hpp"
#include "Lottie/Private/Model/Layers/TextLayerModel.hpp"
#include "Lottie/Public/TextProvider/AnimationTextProvider.hpp"
#include "Lottie/Public/FontProvider/AnimationFontProvider.hpp"
#include "Lottie/Private/MainThread/NodeRenderSystem/Nodes/Text/TextAnimatorNode.hpp"
namespace lottie {
class TextCompositionLayer: public CompositionLayer {
public:
TextCompositionLayer(std::shared_ptr<TextLayerModel> const &textLayer, std::shared_ptr<AnimationTextProvider> textProvider, std::shared_ptr<AnimationFontProvider> fontProvider) :
CompositionLayer(textLayer, Vector2D::Zero()) {
std::shared_ptr<TextAnimatorNode> rootNode;
for (const auto &animator : textLayer->animators) {
rootNode = std::make_shared<TextAnimatorNode>(rootNode, animator);
}
_rootNode = rootNode;
_textDocument = std::make_shared<KeyframeInterpolator<TextDocument>>(textLayer->text.keyframes);
_textProvider = textProvider;
_fontProvider = fontProvider;
if (_rootNode) {
_childKeypaths.push_back(rootNode);
}
}
std::shared_ptr<AnimationTextProvider> const &textProvider() const {
return _textProvider;
}
void setTextProvider(std::shared_ptr<AnimationTextProvider> const &textProvider) {
_textProvider = textProvider;
}
std::shared_ptr<AnimationFontProvider> const &fontProvider() const {
return _fontProvider;
}
void setFontProvider(std::shared_ptr<AnimationFontProvider> const &fontProvider) {
_fontProvider = fontProvider;
}
virtual void displayContentsWithFrame(float frame, bool forceUpdates, BezierPathsBoundingBoxContext &boundingBoxContext) override {
if (!_textDocument) {
return;
}
bool documentUpdate = _textDocument->hasUpdate(frame);
bool animatorUpdate = false;
if (_rootNode) {
animatorUpdate = _rootNode->updateContents(frame, forceUpdates);
}
if (!(documentUpdate || animatorUpdate)) {
return;
}
if (_rootNode) {
_rootNode->rebuildOutputs(frame);
}
}
public:
virtual bool isTextCompositionLayer() const override {
return true;
}
private:
std::shared_ptr<TextAnimatorNode> _rootNode;
std::shared_ptr<KeyframeInterpolator<TextDocument>> _textDocument;
std::shared_ptr<AnimationTextProvider> _textProvider;
std::shared_ptr<AnimationFontProvider> _fontProvider;
};
}
#endif /* TextCompositionLayer_hpp */
@@ -0,0 +1,5 @@
#include "MainThreadAnimationLayer.hpp"
namespace lottie {
}
@@ -0,0 +1,266 @@
#ifndef MainThreadAnimationLayer_hpp
#define MainThreadAnimationLayer_hpp
#include "Lottie/Public/Primitives/CALayer.hpp"
#include "Lottie/Public/ImageProvider/AnimationImageProvider.hpp"
#include "Lottie/Private/Model/Animation.hpp"
#include "Lottie/Public/TextProvider/AnimationTextProvider.hpp"
#include "Lottie/Public/FontProvider/AnimationFontProvider.hpp"
#include "Lottie/Private/MainThread/LayerContainers/Utility/LayerImageProvider.hpp"
#include "Lottie/Private/MainThread/LayerContainers/Utility/LayerTextProvider.hpp"
#include "Lottie/Private/MainThread/LayerContainers/Utility/CompositionLayersInitializer.hpp"
#include "Lottie/Private/MainThread/LayerContainers/Utility/LayerFontProvider.hpp"
#include "Lottie/Public/DynamicProperties/AnyValueProvider.hpp"
#include "Lottie/Public/DynamicProperties/AnimationKeypath.hpp"
namespace lottie {
class BlankImageProvider: public AnimationImageProvider {
public:
virtual ~BlankImageProvider() = default;
std::shared_ptr<Image> imageForAsset(ImageAsset const &asset) {
return nullptr;
}
};
class MainThreadAnimationLayer: public CALayer {
public:
MainThreadAnimationLayer(
Animation const &animation,
std::shared_ptr<AnimationImageProvider> const &imageProvider,
std::shared_ptr<AnimationTextProvider> const &textProvider,
std::shared_ptr<AnimationFontProvider> const &fontProvider
) {
if (animation.assetLibrary) {
_layerImageProvider = std::make_shared<LayerImageProvider>(imageProvider, animation.assetLibrary->imageAssets);
} else {
std::map<std::string, std::shared_ptr<ImageAsset>> imageAssets;
_layerImageProvider = std::make_shared<LayerImageProvider>(imageProvider, imageAssets);
}
_layerTextProvider = std::make_shared<LayerTextProvider>(textProvider);
_layerFontProvider = std::make_shared<LayerFontProvider>(fontProvider);
setSize(Vector2D(animation.width, animation.height));
auto layers = initializeCompositionLayers(
animation.layers,
animation.assetLibrary,
_layerImageProvider,
textProvider,
fontProvider,
animation.framerate
);
std::vector<std::shared_ptr<ImageCompositionLayer>> imageLayers;
std::vector<std::shared_ptr<TextCompositionLayer>> textLayers;
std::shared_ptr<CompositionLayer> mattedLayer;
for (auto layerIt = layers.rbegin(); layerIt != layers.rend(); layerIt++) {
std::shared_ptr<CompositionLayer> const &layer = *layerIt;
layer->setSize(size());
_animationLayers.push_back(layer);
if (layer->isImageCompositionLayer()) {
imageLayers.push_back(std::static_pointer_cast<ImageCompositionLayer>(layer));
}
if (layer->isTextCompositionLayer()) {
textLayers.push_back(std::static_pointer_cast<TextCompositionLayer>(layer));
}
if (mattedLayer) {
/// The previous layer requires this layer to be its matte
mattedLayer->setMatteLayer(layer);
mattedLayer = nullptr;
continue;
}
if (layer->matteType().has_value() && (layer->matteType() == MatteType::Add || layer->matteType() == MatteType::Invert)) {
/// We have a layer that requires a matte.
mattedLayer = layer;
}
addSublayer(layer);
}
_layerImageProvider->addImageLayers(imageLayers);
_layerImageProvider->reloadImages();
_layerTextProvider->addTextLayers(textLayers);
_layerTextProvider->reloadTexts();
_layerFontProvider->addTextLayers(textLayers);
_layerFontProvider->reloadTexts();
renderTreeNode();
}
void setRespectAnimationFrameRate(bool respectAnimationFrameRate) {
_respectAnimationFrameRate = respectAnimationFrameRate;
}
void display() {
float newFrame = currentFrame();
if (_respectAnimationFrameRate) {
newFrame = floor(newFrame);
}
for (const auto &layer : _animationLayers) {
layer->displayWithFrame(newFrame, false, _boundingBoxContext);
}
}
std::vector<std::shared_ptr<CompositionLayer>> const &animationLayers() const {
return _animationLayers;
}
void reloadImages() {
_layerImageProvider->reloadImages();
}
/// Forces the view to update its drawing.
void forceDisplayUpdate() {
for (const auto &layer : _animationLayers) {
layer->displayWithFrame(currentFrame(), true, _boundingBoxContext);
}
}
void logHierarchyKeypaths() {
printf("Lottie: Logging Animation Keypaths\n");
assert(false);
//animationLayers.forEach({ $0.logKeypaths(for: nil) })
}
void setValueProvider(std::shared_ptr<AnyValueProvider> const &valueProvider, AnimationKeypath const &keypath) {
/*for (const auto &layer : _animationLayers) {
assert(false);
if let foundProperties = layer.nodeProperties(for: keypath) {
for property in foundProperties {
property.setProvider(provider: valueProvider)
}
layer.displayWithFrame(frame: presentation()?.currentFrame ?? currentFrame, forceUpdates: true)
}
}*/
}
std::optional<AnyValue> getValue(AnimationKeypath const &keypath, std::optional<float> atFrame) {
/*for (const auto &layer : _animationLayers) {
assert(false);
if
let foundProperties = layer.nodeProperties(for: keypath),
let first = foundProperties.first
{
return first.valueProvider.value(frame: atFrame ?? currentFrame)
}
}*/
return std::nullopt;
}
std::optional<AnyValue> getOriginalValue(AnimationKeypath const &keypath, std::optional<float> atFrame) {
/*for (const auto &layer : _animationLayers) {
assert(false);
if
let foundProperties = layer.nodeProperties(for: keypath),
let first = foundProperties.first
{
return first.originalValueProvider.value(frame: atFrame ?? currentFrame)
}
}*/
return std::nullopt;
}
std::vector<std::shared_ptr<AnimatorNode>> animatorNodesForKeypath(AnimationKeypath const &keypath) {
std::vector<std::shared_ptr<AnimatorNode>> results;
/*for (const auto &layer : _animationLayers) {
if let nodes = layer.animatorNodes(for: keypath) {
results.append(contentsOf: nodes)
}
}*/
return results;
}
float currentFrame() const {
return _currentFrame;
}
void setCurrentFrame(float currentFrame) {
_currentFrame = currentFrame;
for (size_t i = 0; i < _animationLayers.size(); i++) {
_animationLayers[i]->displayWithFrame(_currentFrame, false, _boundingBoxContext);
}
}
std::shared_ptr<AnimationImageProvider> imageProvider() const {
return _layerImageProvider->imageProvider();
}
void setImageProvider(std::shared_ptr<AnimationImageProvider> const &imageProvider) {
_layerImageProvider->setImageProvider(imageProvider);
}
std::shared_ptr<AnimationTextProvider> textProvider() const {
return _layerTextProvider->textProvider();
}
void setTextProvider(std::shared_ptr<AnimationTextProvider> const &textProvider) {
_layerTextProvider->setTextProvider(textProvider);
}
std::shared_ptr<AnimationFontProvider> fontProvider() const {
return _layerFontProvider->fontProvider();
}
void setFontProvider(std::shared_ptr<AnimationFontProvider> const &fontProvider) {
_layerFontProvider->setFontProvider(fontProvider);
}
virtual std::shared_ptr<RenderTreeNode> renderTreeNode() {
if (!_renderTreeNode) {
std::vector<std::shared_ptr<RenderTreeNode>> subnodes;
for (const auto &animationLayer : _animationLayers) {
bool found = false;
for (const auto &sublayer : sublayers()) {
if (animationLayer == sublayer) {
found = true;
break;
}
}
if (found) {
auto node = animationLayer->renderTreeNode(_boundingBoxContext);
if (node) {
subnodes.push_back(node);
}
}
}
_renderTreeNode = std::make_shared<RenderTreeNode>(
size(),
Transform2D::identity(),
1.0,
false,
false,
subnodes,
nullptr,
false
);
}
return _renderTreeNode;
}
private:
float _currentFrame = 0.0;
std::shared_ptr<AnimationImageProvider> _imageProvider;
std::shared_ptr<AnimationTextProvider> _textProvider;
std::shared_ptr<AnimationFontProvider> _fontProvider;
bool _respectAnimationFrameRate = true;
std::vector<std::shared_ptr<CompositionLayer>> _animationLayers;
std::shared_ptr<LayerImageProvider> _layerImageProvider;
std::shared_ptr<LayerTextProvider> _layerTextProvider;
std::shared_ptr<LayerFontProvider> _layerFontProvider;
std::shared_ptr<RenderTreeNode> _renderTreeNode;
BezierPathsBoundingBoxContext _boundingBoxContext;
};
}
#endif /* MainThreadAnimationLayer_hpp */
@@ -0,0 +1,111 @@
#include "CompositionLayersInitializer.hpp"
#include "Lottie/Private/MainThread/LayerContainers/CompLayers/NullCompositionLayer.hpp"
#include "Lottie/Private/MainThread/LayerContainers/CompLayers/ShapeCompositionLayer.hpp"
#include "Lottie/Private/MainThread/LayerContainers/CompLayers/PreCompositionLayer.hpp"
#include "Lottie/Private/MainThread/LayerContainers/CompLayers/ImageCompositionLayer.hpp"
#include "Lottie/Private/MainThread/LayerContainers/CompLayers/TextCompositionLayer.hpp"
namespace lottie {
std::vector<std::shared_ptr<CompositionLayer>> initializeCompositionLayers(
std::vector<std::shared_ptr<LayerModel>> const &layers,
std::shared_ptr<AssetLibrary> const &assetLibrary,
std::shared_ptr<LayerImageProvider> const &layerImageProvider,
std::shared_ptr<AnimationTextProvider> const &textProvider,
std::shared_ptr<AnimationFontProvider> const &fontProvider,
float frameRate
) {
std::vector<std::shared_ptr<CompositionLayer>> compositionLayers;
std::map<int, std::shared_ptr<CompositionLayer>> layerMap;
std::vector<std::shared_ptr<LayerModel>> childLayers;
for (const auto &layer : layers) {
if (layer->hidden) {
auto genericLayer = std::make_shared<NullCompositionLayer>(layer);
compositionLayers.push_back(genericLayer);
if (layer->index) {
layerMap.insert(std::make_pair(layer->index.value(), genericLayer));
}
} else if (layer->type == LayerType::Shape) {
auto shapeContainer = std::make_shared<ShapeCompositionLayer>(std::static_pointer_cast<ShapeLayerModel>(layer));
compositionLayers.push_back(shapeContainer);
if (layer->index) {
layerMap.insert(std::make_pair(layer->index.value(), shapeContainer));
}
} else if (layer->type == LayerType::Solid) {
auto shapeContainer = std::make_shared<ShapeCompositionLayer>(std::static_pointer_cast<SolidLayerModel>(layer));
compositionLayers.push_back(shapeContainer);
if (layer->index) {
layerMap.insert(std::make_pair(layer->index.value(), shapeContainer));
}
} else if (layer->type == LayerType::Precomp && assetLibrary) {
auto precompLayer = std::static_pointer_cast<PreCompLayerModel>(layer);
auto precompAssetIt = assetLibrary->precompAssets.find(precompLayer->referenceID);
if (precompAssetIt != assetLibrary->precompAssets.end()) {
auto precompContainer = std::make_shared<PreCompositionLayer>(
precompLayer,
*(precompAssetIt->second),
layerImageProvider,
textProvider,
fontProvider,
assetLibrary,
frameRate
);
compositionLayers.push_back(precompContainer);
if (layer->index) {
layerMap.insert(std::make_pair(layer->index.value(), precompContainer));
}
}
} else if (layer->type == LayerType::Image && assetLibrary) {
auto imageLayer = std::static_pointer_cast<ImageLayerModel>(layer);
auto imageAssetIt = assetLibrary->imageAssets.find(imageLayer->referenceID);
if (imageAssetIt != assetLibrary->imageAssets.end()) {
auto imageContainer = std::make_shared<ImageCompositionLayer>(
imageLayer,
Vector2D((*imageAssetIt->second).width, (*imageAssetIt->second).height)
);
compositionLayers.push_back(imageContainer);
if (layer->index) {
layerMap.insert(std::make_pair(layer->index.value(), imageContainer));
}
}
} else if (layer->type == LayerType::Text) {
auto textContainer = std::make_shared<TextCompositionLayer>(std::static_pointer_cast<TextLayerModel>(layer), textProvider, fontProvider);
compositionLayers.push_back(textContainer);
if (layer->index) {
layerMap.insert(std::make_pair(layer->index.value(), textContainer));
}
} else {
auto genericLayer = std::make_shared<NullCompositionLayer>(layer);
compositionLayers.push_back(genericLayer);
if (layer->index) {
layerMap.insert(std::make_pair(layer->index.value(), genericLayer));
}
}
if (layer->parent) {
childLayers.push_back(layer);
}
}
/// Now link children with their parents
for (const auto &layerModel : childLayers) {
if (!layerModel->index.has_value()) {
continue;
}
if (const auto parentID = layerModel->parent) {
auto childLayerIt = layerMap.find(layerModel->index.value());
if (childLayerIt != layerMap.end()) {
auto parentLayerIt = layerMap.find(parentID.value());
if (parentLayerIt != layerMap.end()) {
childLayerIt->second->transformNode()->setParentNode(parentLayerIt->second->transformNode());
}
}
}
}
return compositionLayers;
}
}
@@ -0,0 +1,23 @@
#ifndef CompositionLayersInitializer_hpp
#define CompositionLayersInitializer_hpp
#include "Lottie/Private/MainThread/LayerContainers/CompLayers/CompositionLayer.hpp"
#include "Lottie/Private/Model/Assets/AssetLibrary.hpp"
#include "Lottie/Private/MainThread/LayerContainers/Utility/LayerImageProvider.hpp"
#include "Lottie/Public/TextProvider/AnimationTextProvider.hpp"
#include "Lottie/Public/FontProvider/AnimationFontProvider.hpp"
namespace lottie {
std::vector<std::shared_ptr<CompositionLayer>> initializeCompositionLayers(
std::vector<std::shared_ptr<LayerModel>> const &layers,
std::shared_ptr<AssetLibrary> const &assetLibrary,
std::shared_ptr<LayerImageProvider> const &layerImageProvider,
std::shared_ptr<AnimationTextProvider> const &textProvider,
std::shared_ptr<AnimationFontProvider> const &fontProvider,
float frameRate
);
}
#endif /* CompositionLayersInitializer_hpp */
@@ -0,0 +1,5 @@
#include "LayerFontProvider.hpp"
namespace lottie {
}
@@ -0,0 +1,45 @@
#ifndef LayerFontProvider_hpp
#define LayerFontProvider_hpp
#include "Lottie/Public/FontProvider/AnimationFontProvider.hpp"
#include "Lottie/Private/MainThread/LayerContainers/CompLayers/TextCompositionLayer.hpp"
namespace lottie {
/// Connects a LottieFontProvider to a group of text layers
class LayerFontProvider {
public:
LayerFontProvider(std::shared_ptr<AnimationFontProvider> const &fontProvider) {
_fontProvider = fontProvider;
reloadTexts();
}
std::shared_ptr<AnimationFontProvider> const &fontProvider() const {
return _fontProvider;
}
void setFontProvider(std::shared_ptr<AnimationFontProvider> const &fontProvider) {
_fontProvider = fontProvider;
reloadTexts();
}
void addTextLayers(std::vector<std::shared_ptr<TextCompositionLayer>> const &layers) {
for (const auto &layer : layers) {
_textLayers.push_back(layer);
}
}
void reloadTexts() {
for (const auto &layer : _textLayers) {
layer->setFontProvider(_fontProvider);
}
}
private:
std::vector<std::shared_ptr<TextCompositionLayer>> _textLayers;
std::shared_ptr<AnimationFontProvider> _fontProvider;
};
}
#endif /* LayerFontProvider_hpp */
@@ -0,0 +1,5 @@
#include "LayerImageProvider.hpp"
namespace lottie {
}
@@ -0,0 +1,58 @@
#ifndef LayerImageProvider_hpp
#define LayerImageProvider_hpp
#include "Lottie/Public/ImageProvider/AnimationImageProvider.hpp"
#include "Lottie/Private/Model/Assets/ImageAsset.hpp"
#include "Lottie/Private/MainThread/LayerContainers/CompLayers/ImageCompositionLayer.hpp"
namespace lottie {
/// Connects a LottieImageProvider to a group of image layers
class LayerImageProvider {
public:
LayerImageProvider(std::shared_ptr<AnimationImageProvider> const &imageProvider, std::map<std::string, std::shared_ptr<ImageAsset>> const &assets) :
_imageProvider(imageProvider),
_imageAssets(assets) {
reloadImages();
}
std::shared_ptr<AnimationImageProvider> imageProvider() const {
return _imageProvider;
}
void setImageProvider(std::shared_ptr<AnimationImageProvider> const &imageProvider) {
_imageProvider = imageProvider;
reloadImages();
}
std::vector<std::shared_ptr<ImageCompositionLayer>> const &imageLayers() const {
return _imageLayers;
}
void addImageLayers(std::vector<std::shared_ptr<ImageCompositionLayer>> const &layers) {
for (const auto &layer : layers) {
auto it = _imageAssets.find(layer->imageReferenceID());
if (it != _imageAssets.end()) {
_imageLayers.push_back(layer);
}
}
}
void reloadImages() {
for (const auto &imageLayer : imageLayers()) {
auto it = _imageAssets.find(imageLayer->imageReferenceID());
if (it != _imageAssets.end()) {
imageLayer->setImage(_imageProvider->imageForAsset(*it->second));
}
}
}
private:
std::shared_ptr<AnimationImageProvider> _imageProvider;
std::vector<std::shared_ptr<ImageCompositionLayer>> _imageLayers;
std::map<std::string, std::shared_ptr<ImageAsset>> _imageAssets;
};
}
#endif /* LayerImageProvider_hpp */
@@ -0,0 +1,5 @@
#include "LayerTextProvider.hpp"
namespace lottie {
}
@@ -0,0 +1,45 @@
#ifndef LayerTextProvider_hpp
#define LayerTextProvider_hpp
#include "Lottie/Public/TextProvider/AnimationTextProvider.hpp"
#include "Lottie/Private/MainThread/LayerContainers/CompLayers/TextCompositionLayer.hpp"
namespace lottie {
/// Connects a LottieTextProvider to a group of text layers
class LayerTextProvider {
public:
LayerTextProvider(std::shared_ptr<AnimationTextProvider> const &textProvider) {
_textProvider = textProvider;
reloadTexts();
}
std::shared_ptr<AnimationTextProvider> const &textProvider() const {
return _textProvider;
}
void setTextProvider(std::shared_ptr<AnimationTextProvider> const &textProvider) {
_textProvider = textProvider;
reloadTexts();
}
void addTextLayers(std::vector<std::shared_ptr<TextCompositionLayer>> const &layers) {
for (const auto &layer : layers) {
_textLayers.push_back(layer);
}
}
void reloadTexts() {
for (const auto &layer : _textLayers) {
layer->setTextProvider(_textProvider);
}
}
private:
std::vector<std::shared_ptr<TextCompositionLayer>> _textLayers;
std::shared_ptr<AnimationTextProvider> _textProvider;
};
}
#endif /* LayerTextProvider_hpp */
@@ -0,0 +1,5 @@
#include "LayerTransformNode.hpp"
namespace lottie {
}
@@ -0,0 +1,205 @@
#ifndef LayerTransformNode_hpp
#define LayerTransformNode_hpp
#include "Lottie/Private/Model/Objects/Transform.hpp"
#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/NodePropertyMap.hpp"
#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/KeypathSearchable.hpp"
#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/NodeProperty.hpp"
#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/KeyframeInterpolator.hpp"
#include "Lottie/Private/MainThread/NodeRenderSystem/Protocols/AnimatorNode.hpp"
#include "Lottie/Private/MainThread/NodeRenderSystem/Protocols/NodeOutput.hpp"
#include "Lottie/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/PassThroughOutputNode.hpp"
namespace lottie {
class LayerTransformProperties: public KeypathSearchableNodePropertyMap {
public:
LayerTransformProperties(std::shared_ptr<Transform> transform) {
_anchor = std::make_shared<NodeProperty<Vector3D>>(std::make_shared<KeyframeInterpolator<Vector3D>>(transform->anchorPoint().keyframes));
_scale = std::make_shared<NodeProperty<Vector3D>>(std::make_shared<KeyframeInterpolator<Vector3D>>(transform->scale().keyframes));
_rotation = std::make_shared<NodeProperty<Vector1D>>(std::make_shared<KeyframeInterpolator<Vector1D>>(transform->rotation().keyframes));
_opacity = std::make_shared<NodeProperty<Vector1D>>(std::make_shared<KeyframeInterpolator<Vector1D>>(transform->opacity().keyframes));
std::map<std::string, std::shared_ptr<AnyNodeProperty>> propertyMap;
_keypathProperties.insert(std::make_pair("Anchor Point", _anchor));
_keypathProperties.insert(std::make_pair("Scale", _scale));
_keypathProperties.insert(std::make_pair("Rotation", _rotation));
_keypathProperties.insert(std::make_pair("Opacity", _opacity));
if (transform->positionX().has_value() && transform->positionY().has_value()) {
auto xPosition = std::make_shared<NodeProperty<Vector1D>>(std::make_shared<KeyframeInterpolator<Vector1D>>(transform->positionX()->keyframes));
auto yPosition = std::make_shared<NodeProperty<Vector1D>>(std::make_shared<KeyframeInterpolator<Vector1D>>(transform->positionY()->keyframes));
_keypathProperties.insert(std::make_pair("X Position", xPosition));
_keypathProperties.insert(std::make_pair("Y Position", yPosition));
_positionX = xPosition;
_positionY = yPosition;
_position = nullptr;
} else if (transform->position().has_value()) {
auto position = std::make_shared<NodeProperty<Vector3D>>(std::make_shared<KeyframeInterpolator<Vector3D>>(transform->position()->keyframes));
_keypathProperties.insert(std::make_pair("Position", position));
_position = position;
_positionX = nullptr;
_positionY = nullptr;
} else {
_position = nullptr;
_positionX = nullptr;
_positionY = nullptr;
}
for (const auto &it : _keypathProperties) {
_properties.push_back(it.second);
}
}
virtual ~LayerTransformProperties() = default;
virtual std::vector<std::shared_ptr<AnyNodeProperty>> &properties() override {
return _properties;
}
virtual std::vector<std::shared_ptr<KeypathSearchable>> const &childKeypaths() const override {
return _childKeypaths;
}
virtual std::string keypathName() const override {
return "Transform";
}
virtual std::map<std::string, std::shared_ptr<AnyNodeProperty>> keypathProperties() const override {
return _keypathProperties;
}
virtual std::shared_ptr<CALayer> keypathLayer() const override {
return nullptr;
}
std::shared_ptr<NodeProperty<Vector3D>> const &anchor() {
return _anchor;
}
std::shared_ptr<NodeProperty<Vector3D>> const &scale() {
return _scale;
}
std::shared_ptr<NodeProperty<Vector1D>> const &rotation() {
return _rotation;
}
std::shared_ptr<NodeProperty<Vector3D>> const &position() {
return _position;
}
std::shared_ptr<NodeProperty<Vector1D>> const &positionX() {
return _positionX;
}
std::shared_ptr<NodeProperty<Vector1D>> const &positionY() {
return _positionY;
}
std::shared_ptr<NodeProperty<Vector1D>> const &opacity() {
return _opacity;
}
private:
std::map<std::string, std::shared_ptr<AnyNodeProperty>> _keypathProperties;
std::vector<std::shared_ptr<KeypathSearchable>> _childKeypaths;
std::vector<std::shared_ptr<AnyNodeProperty>> _properties;
std::shared_ptr<NodeProperty<Vector3D>> _anchor;
std::shared_ptr<NodeProperty<Vector3D>> _scale;
std::shared_ptr<NodeProperty<Vector1D>> _rotation;
std::shared_ptr<NodeProperty<Vector3D>> _position;
std::shared_ptr<NodeProperty<Vector1D>> _positionX;
std::shared_ptr<NodeProperty<Vector1D>> _positionY;
std::shared_ptr<NodeProperty<Vector1D>> _opacity;
};
class LayerTransformNode: public AnimatorNode {
public:
LayerTransformNode(std::shared_ptr<Transform> transform) :
AnimatorNode(nullptr),
_transformProperties(std::make_shared<LayerTransformProperties>(transform)) {
_outputNode = std::make_shared<PassThroughOutputNode>(nullptr);
}
virtual ~LayerTransformNode() = default;
virtual std::shared_ptr<NodeOutput> outputNode() override {
return _outputNode;
}
virtual std::shared_ptr<KeypathSearchableNodePropertyMap> propertyMap() const override {
return _transformProperties;
}
virtual bool shouldRebuildOutputs(float frame) override {
return hasLocalUpdates() || hasUpstreamUpdates();
}
virtual void rebuildOutputs(float frame) override {
_opacity = ((float)_transformProperties->opacity()->value().value) * 0.01f;
Vector2D position(0.0, 0.0);
if (_transformProperties->position()) {
auto position3d = _transformProperties->position()->value();
position.x = position3d.x;
position.y = position3d.y;
} else if (_transformProperties->positionX() && _transformProperties->positionY()) {
position = Vector2D(
_transformProperties->positionX()->value().value,
_transformProperties->positionY()->value().value
);
}
Vector3D anchor = _transformProperties->anchor()->value();
Vector3D scale = _transformProperties->scale()->value();
_localTransform = Transform2D::makeTransform(
Vector2D(anchor.x, anchor.y),
position,
Vector2D(scale.x, scale.y),
_transformProperties->rotation()->value().value,
std::nullopt,
std::nullopt
);
if (parentNode() && parentNode()->asLayerTransformNode()) {
_globalTransform = _localTransform * parentNode()->asLayerTransformNode()->_globalTransform;
} else {
_globalTransform = _localTransform;
}
}
std::shared_ptr<LayerTransformProperties> const &transformProperties() {
return _transformProperties;
}
float opacity() {
return _opacity;
}
Transform2D const &globalTransform() {
return _globalTransform;
}
private:
std::shared_ptr<NodeOutput> _outputNode;
std::shared_ptr<LayerTransformProperties> _transformProperties;
float _opacity = 1.0;
Transform2D _localTransform = Transform2D::identity();
Transform2D _globalTransform = Transform2D::identity();
public:
virtual LayerTransformNode *asLayerTransformNode() override {
return this;
}
};
}
#endif /* LayerTransformNode_hpp */
@@ -0,0 +1,5 @@
#include "NodeProperty.hpp"
namespace lottie {
}
@@ -0,0 +1,54 @@
#ifndef NodeProperty_hpp
#define NodeProperty_hpp
#include "Lottie/Public/Primitives/AnyValue.hpp"
#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/AnyNodeProperty.hpp"
#include "Lottie/Public/DynamicProperties/AnyValueProvider.hpp"
#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueContainer.hpp"
namespace lottie {
/// A node property that holds a reference to a T ValueProvider and a T ValueContainer.
template<typename T>
class NodeProperty: public AnyNodeProperty {
public:
NodeProperty(std::shared_ptr<ValueProvider<T>> provider) :
_typedContainer(provider->value(0.0)),
_valueProvider(provider) {
_typedContainer.setNeedsUpdate();
}
public:
virtual AnyValue::Type valueType() const override {
return AnyValueType<T>::type();
}
virtual T value() {
return _typedContainer.outputValue();
}
virtual bool needsUpdate(float frame) const override {
return _typedContainer.needsUpdate() || _valueProvider->hasUpdate(frame);
}
virtual void setProvider(std::shared_ptr<AnyValueProvider> provider) override {
/*if (provider->valueType() != valueType()) {
return;
}
_valueProvider = provider;
_typedContainer.setNeedsUpdate();*/
}
virtual void update(float frame) override {
_typedContainer.setValue(_valueProvider->value(frame), frame);
}
private:
ValueContainer<T> _typedContainer;
std::shared_ptr<ValueProvider<T>> _valueProvider;
//std::shared_ptr<AnyValueProvider> _originalValueProvider;
};
}
#endif /* NodeProperty_hpp */
@@ -0,0 +1,5 @@
#include "AnyNodeProperty.hpp"
namespace lottie {
}
@@ -0,0 +1,33 @@
#ifndef AnyNodeProperty_hpp
#define AnyNodeProperty_hpp
#include "Lottie/Public/Primitives/AnyValue.hpp"
#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/AnyValueContainer.hpp"
#include "Lottie/Public/DynamicProperties/AnyValueProvider.hpp"
#include <memory>
namespace lottie {
/// A property of a node. The node property holds a provider and a container
class AnyNodeProperty {
public:
virtual ~AnyNodeProperty() = default;
public:
/// Returns true if the property needs to recompute its stored value
virtual bool needsUpdate(float frame) const = 0;
/// Updates the property for the frame
virtual void update(float frame) = 0;
/// The Type of the value provider
virtual AnyValue::Type valueType() const = 0;
/// Sets the value provider for the property.
virtual void setProvider(std::shared_ptr<AnyValueProvider> provider) = 0;
};
}
#endif /* AnyNodeProperty_hpp */
@@ -0,0 +1,5 @@
#include "AnyValueContainer.hpp"
namespace lottie {
}
@@ -0,0 +1,25 @@
#ifndef AnyValueContainer_hpp
#define AnyValueContainer_hpp
#include "Lottie/Public/Primitives/AnyValue.hpp"
namespace lottie {
class AnyValueContainer {
public:
/// The stored value of the container
virtual AnyValue value() const = 0;
/// Notifies the provider that it should update its container
virtual void setNeedsUpdate() = 0;
/// When true the container needs to have its value updated by its provider
virtual bool needsUpdate() const = 0;
/// The frame time of the last provided update
virtual float lastUpdateFrame() const = 0;
};
}
#endif /* AnyValueContainer_hpp */
@@ -0,0 +1,13 @@
#ifndef HasRenderUpdates_hpp
#define HasRenderUpdates_hpp
namespace lottie {
class HasRenderUpdates {
public:
virtual bool hasRenderUpdates(float forFrame) = 0;
};
}
#endif /* HasRenderUpdates_hpp */
@@ -0,0 +1,14 @@
#ifndef HasUpdate_hpp
#define HasUpdate_hpp
namespace lottie {
class HasUpdate {
public:
/// The last frame in which this node was updated.
virtual bool hasUpdate() = 0;
};
}
#endif /* HasUpdate_hpp */
@@ -0,0 +1,5 @@
#include "KeypathSearchable.hpp"
namespace lottie {
}
@@ -0,0 +1,36 @@
#ifndef KeypathSearchable_hpp
#define KeypathSearchable_hpp
#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/AnyNodeProperty.hpp"
#include "Lottie/Public/Primitives/CALayer.hpp"
#include <string>
#include <vector>
#include <map>
#include <memory>
namespace lottie {
class KeypathSearchable;
class HasChildKeypaths {
public:
/// Children Keypaths
virtual std::vector<std::shared_ptr<KeypathSearchable>> const &childKeypaths() const = 0;
};
/// Protocol that provides keypath search functionality. Returns all node properties associated with a keypath.
class KeypathSearchable: virtual public HasChildKeypaths {
public:
/// The name of the Keypath
virtual std::string keypathName() const = 0;
/// A list of properties belonging to the keypath.
virtual std::map<std::string, std::shared_ptr<AnyNodeProperty>> keypathProperties() const = 0;
virtual std::shared_ptr<CALayer> keypathLayer() const = 0;
};
}
#endif /* KeypathSearchable_hpp */
@@ -0,0 +1,5 @@
#include "NodePropertyMap.hpp"
namespace lottie {
}
@@ -0,0 +1,41 @@
#ifndef NodePropertyMap_hpp
#define NodePropertyMap_hpp
#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/AnyNodeProperty.hpp"
#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/KeypathSearchable.hpp"
#include "Lottie/Public/Primitives/CALayer.hpp"
#include <vector>
namespace lottie {
class NodePropertyMap: virtual public HasChildKeypaths {
public:
virtual std::vector<std::shared_ptr<AnyNodeProperty>> &properties() = 0;
bool needsLocalUpdate(float frame) {
for (auto &property : properties()) {
if (property->needsUpdate(frame)) {
return true;
}
}
return false;
}
void updateNodeProperties(float frame) {
for (auto &property : properties()) {
property->update(frame);
}
}
};
class KeypathSearchableNodePropertyMap: virtual public NodePropertyMap, virtual public KeypathSearchable {
public:
virtual std::shared_ptr<CALayer> keypathLayer() const override {
return nullptr;
}
};
}
#endif /* NodePropertyMap_hpp */
@@ -0,0 +1,5 @@
#include "ValueContainer.hpp"
namespace lottie {
}
@@ -0,0 +1,58 @@
#ifndef ValueContainer_hpp
#define ValueContainer_hpp
#include "Lottie/Public/Primitives/AnyValue.hpp"
#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/AnyValueContainer.hpp"
namespace lottie {
/// A container for a node value that is Typed to T.
template<typename T>
class ValueContainer: public AnyValueContainer {
public:
ValueContainer(T value) :
_outputValue(value) {
}
public:
float _lastUpdateFrame = std::numeric_limits<float>::infinity();
bool _needsUpdate = true;
virtual AnyValue value() const override {
return AnyValue(_outputValue);
}
virtual bool needsUpdate() const override {
return _needsUpdate;
}
virtual float lastUpdateFrame() const override {
return _lastUpdateFrame;
}
T _outputValue;
T outputValue() {
return _outputValue;
}
void setOutputValue(T value) {
_outputValue = value;
_needsUpdate = false;
}
void setValue(AnyValue value, float forFrame) {
if (value.type() == AnyValueType<T>::type()) {
_needsUpdate = false;
_lastUpdateFrame = forFrame;
_outputValue = value.get<T>();
}
}
virtual void setNeedsUpdate() override {
_needsUpdate = true;
}
};
}
#endif /* ValueContainer_hpp */
@@ -0,0 +1,5 @@
#include "DashPatternInterpolator.hpp"
namespace lottie {
}
@@ -0,0 +1,48 @@
#ifndef DashPatternInterpolator_hpp
#define DashPatternInterpolator_hpp
#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/KeyframeInterpolator.hpp"
#include "Lottie/Public/Primitives/DashPattern.hpp"
namespace lottie {
/// A value provider that produces an array of values from an array of Keyframe Interpolators
class DashPatternInterpolator: public ValueProvider<DashPattern>, public std::enable_shared_from_this<DashPatternInterpolator> {
public:
/// Initialize with an array of array of keyframes.
DashPatternInterpolator(std::vector<std::vector<Keyframe<Vector1D>>> const &keyframeGroups) {
for (const auto &keyframeGroup : keyframeGroups) {
_keyframeInterpolators.push_back(std::make_shared<KeyframeInterpolator<Vector1D>>(keyframeGroup));
}
}
virtual ~DashPatternInterpolator() = default;
virtual AnyValue::Type valueType() const override {
return AnyValueType<DashPattern>::type();
}
virtual DashPattern value(AnimationFrameTime frame) override {
std::vector<float> values;
for (const auto &interpolator : _keyframeInterpolators) {
values.push_back(interpolator->value(frame).value);
}
return DashPattern(std::move(values));
}
virtual bool hasUpdate(float frame) const override {
for (const auto &interpolator : _keyframeInterpolators) {
if (interpolator->hasUpdate(frame)) {
return true;
}
}
return false;
}
private:
std::vector<std::shared_ptr<KeyframeInterpolator<Vector1D>>> _keyframeInterpolators;
};
}
#endif /* DashPatternInterpolator_hpp */
@@ -0,0 +1,5 @@
#include "KeyframeInterpolator.hpp"
namespace lottie {
}
@@ -0,0 +1,452 @@
#ifndef KeyframeInterpolator_hpp
#define KeyframeInterpolator_hpp
#include "Lottie/Public/DynamicProperties/AnyValueProvider.hpp"
namespace lottie {
/// A value provider that produces a value at Time from a group of keyframes
template<typename T>
class KeyframeInterpolator: public ValueProvider<T>, public std::enable_shared_from_this<KeyframeInterpolator<T>> {
public:
KeyframeInterpolator(std::vector<Keyframe<T>> const &keyframes_) :
keyframes(keyframes_) {
assert(!keyframes.empty());
}
virtual ~KeyframeInterpolator() {
}
public:
std::vector<Keyframe<T>> keyframes;
virtual AnyValue::Type valueType() const override {
return AnyValueType<T>::type();
}
virtual T value(AnimationFrameTime frame) override {
// First set the keyframe span for the frame.
updateSpanIndices(frame);
lastUpdatedFrame = frame;
// If only one keyframe return its value
if (leadingKeyframe.has_value() &&
trailingKeyframe.has_value())
{
/// We have leading and trailing keyframe.
auto progress = leadingKeyframe->interpolatedProgress(trailingKeyframe.value(), frame);
return leadingKeyframe->interpolate(trailingKeyframe.value(), progress);
} else if (leadingKeyframe.has_value()) {
return leadingKeyframe->value;
} else if (trailingKeyframe.has_value()) {
return trailingKeyframe->value;
} else {
/// Satisfy the compiler.
return keyframes[0].value;
}
}
/// Returns true to trigger a frame update for this interpolator.
///
/// An interpolator will be asked if it needs to update every frame.
/// If the interpolator needs updating it will be asked to compute its value for
/// the given frame.
///
/// Cases a keyframe should not be updated:
/// - If time is in span and leading keyframe is hold
/// - If time is after the last keyframe.
/// - If time is before the first keyframe
///
/// Cases for updating a keyframe:
/// - If time is in the span, and is not a hold
/// - If time is outside of the span, and there are more keyframes
/// - If a value delegate is set
/// - If leading and trailing are both nil.
virtual bool hasUpdate(float frame) const override {
if (!lastUpdatedFrame.has_value()) {
return true;
}
if (leadingKeyframe.has_value() &&
!trailingKeyframe.has_value() &&
leadingKeyframe->time < frame)
{
/// Frame is after bounds of keyframes
return false;
}
if (trailingKeyframe.has_value() &&
!leadingKeyframe.has_value() &&
frame < trailingKeyframe->time)
{
/// Frame is before bounds of keyframes
return false;
}
if (leadingKeyframe.has_value() &&
trailingKeyframe.has_value() &&
leadingKeyframe->isHold &&
leadingKeyframe->time < frame &&
frame < trailingKeyframe->time)
{
return false;
}
return true;
}
// MARK: Fileprivate
std::optional<float> lastUpdatedFrame;
std::optional<int> leadingIndex;
std::optional<int> trailingIndex;
std::optional<Keyframe<T>> leadingKeyframe;
std::optional<Keyframe<T>> trailingKeyframe;
/// Finds the appropriate Leading and Trailing keyframe index for the given time.
void updateSpanIndices(float frame) {
if (keyframes.empty()) {
leadingIndex = std::nullopt;
trailingIndex = std::nullopt;
leadingKeyframe = std::nullopt;
trailingKeyframe = std::nullopt;
return;
}
// This function searches through the array to find the span of two keyframes
// that contain the current time.
//
// We could use Array.first(where:) but that would search through the entire array
// each frame.
// Instead we track the last used index and search either forwards or
// backwards from there. This reduces the iterations and complexity from
//
// O(n), where n is the length of the sequence to
// O(n), where n is the number of items after or before the last used index.
//
if (keyframes.size() == 1) {
/// Only one keyframe. Set it as first and move on.
leadingIndex = 0;
trailingIndex = std::nullopt;
leadingKeyframe = keyframes[0];
trailingKeyframe = std::nullopt;
return;
}
/// Sets the initial keyframes. This is often only needed for the first check.
if
(!leadingIndex.has_value() &&
!trailingIndex.has_value())
{
if (frame < keyframes[0].time) {
/// Time is before the first keyframe. Set it as the trailing.
trailingIndex = 0;
} else {
/// Time is after the first keyframe. Set the keyframe and the trailing.
leadingIndex = 0;
trailingIndex = 1;
}
}
if
(trailingIndex.has_value() &&
keyframes[trailingIndex.value()].time <= frame)
{
/// Time is after the current span. Iterate forward.
auto newLeading = trailingIndex.value();
bool keyframeFound = false;
while (!keyframeFound) {
leadingIndex = newLeading;
if (newLeading + 1 >= 0 && newLeading + 1 < keyframes.size()) {
trailingIndex = newLeading + 1;
} else {
trailingIndex = std::nullopt;
}
if (!trailingIndex.has_value()) {
/// We have reached the end of our keyframes. Time is after the last keyframe.
keyframeFound = true;
continue;
}
if (frame < keyframes[trailingIndex.value()].time) {
/// Keyframe in current span.
keyframeFound = true;
continue;
}
/// Advance the array.
newLeading = trailingIndex.value();
}
} else if
(leadingIndex.has_value() &&
frame < keyframes[leadingIndex.value()].time)
{
/// Time is before the current span. Iterate backwards
auto newTrailing = leadingIndex.value();
bool keyframeFound = false;
while (!keyframeFound) {
if (newTrailing - 1 >= 0 && newTrailing - 1 < keyframes.size()) {
leadingIndex = newTrailing - 1;
} else {
leadingIndex = std::nullopt;
}
trailingIndex = newTrailing;
if (!leadingIndex.has_value()) {
/// We have reached the end of our keyframes. Time is after the last keyframe.
keyframeFound = true;
continue;
}
if (keyframes[leadingIndex.value()].time <= frame) {
/// Keyframe in current span.
keyframeFound = true;
continue;
}
/// Step back
newTrailing = leadingIndex.value();
}
}
if (const auto keyFrame = leadingIndex) {
leadingKeyframe = keyframes[keyFrame.value()];
} else {
leadingKeyframe = std::nullopt;
}
if (const auto keyFrame = trailingIndex) {
trailingKeyframe = keyframes[keyFrame.value()];
} else {
trailingKeyframe = std::nullopt;
}
}
};
class BezierPathKeyframeInterpolator {
public:
BezierPathKeyframeInterpolator(std::vector<Keyframe<BezierPath>> const &keyframes_) :
keyframes(keyframes_) {
assert(!keyframes.empty());
}
public:
std::vector<Keyframe<BezierPath>> keyframes;
void update(AnimationFrameTime frame, BezierPath &outPath) {
// First set the keyframe span for the frame.
updateSpanIndices(frame);
lastUpdatedFrame = frame;
// If only one keyframe return its value
if (leadingKeyframe.has_value() &&
trailingKeyframe.has_value())
{
/// We have leading and trailing keyframe.
auto progress = leadingKeyframe->interpolatedProgress(trailingKeyframe.value(), frame);
interpolateInplace(leadingKeyframe.value(), trailingKeyframe.value(), progress, outPath);
} else if (leadingKeyframe.has_value()) {
setInplace(leadingKeyframe.value(), outPath);
} else if (trailingKeyframe.has_value()) {
setInplace(trailingKeyframe.value(), outPath);
} else {
/// Satisfy the compiler.
setInplace(keyframes[0], outPath);
}
}
/// Returns true to trigger a frame update for this interpolator.
///
/// An interpolator will be asked if it needs to update every frame.
/// If the interpolator needs updating it will be asked to compute its value for
/// the given frame.
///
/// Cases a keyframe should not be updated:
/// - If time is in span and leading keyframe is hold
/// - If time is after the last keyframe.
/// - If time is before the first keyframe
///
/// Cases for updating a keyframe:
/// - If time is in the span, and is not a hold
/// - If time is outside of the span, and there are more keyframes
/// - If a value delegate is set
/// - If leading and trailing are both nil.
bool hasUpdate(float frame) const {
if (!lastUpdatedFrame.has_value()) {
return true;
}
if (leadingKeyframe.has_value() &&
!trailingKeyframe.has_value() &&
leadingKeyframe->time < frame)
{
/// Frame is after bounds of keyframes
return false;
}
if (trailingKeyframe.has_value() &&
!leadingKeyframe.has_value() &&
frame < trailingKeyframe->time)
{
/// Frame is before bounds of keyframes
return false;
}
if (leadingKeyframe.has_value() &&
trailingKeyframe.has_value() &&
leadingKeyframe->isHold &&
leadingKeyframe->time < frame &&
frame < trailingKeyframe->time)
{
return false;
}
return true;
}
// MARK: Fileprivate
std::optional<float> lastUpdatedFrame;
std::optional<int> leadingIndex;
std::optional<int> trailingIndex;
std::optional<Keyframe<BezierPath>> leadingKeyframe;
std::optional<Keyframe<BezierPath>> trailingKeyframe;
/// Finds the appropriate Leading and Trailing keyframe index for the given time.
void updateSpanIndices(float frame) {
if (keyframes.empty()) {
leadingIndex = std::nullopt;
trailingIndex = std::nullopt;
leadingKeyframe = std::nullopt;
trailingKeyframe = std::nullopt;
return;
}
// This function searches through the array to find the span of two keyframes
// that contain the current time.
//
// We could use Array.first(where:) but that would search through the entire array
// each frame.
// Instead we track the last used index and search either forwards or
// backwards from there. This reduces the iterations and complexity from
//
// O(n), where n is the length of the sequence to
// O(n), where n is the number of items after or before the last used index.
//
if (keyframes.size() == 1) {
/// Only one keyframe. Set it as first and move on.
leadingIndex = 0;
trailingIndex = std::nullopt;
leadingKeyframe = keyframes[0];
trailingKeyframe = std::nullopt;
return;
}
/// Sets the initial keyframes. This is often only needed for the first check.
if
(!leadingIndex.has_value() &&
!trailingIndex.has_value())
{
if (frame < keyframes[0].time) {
/// Time is before the first keyframe. Set it as the trailing.
trailingIndex = 0;
} else {
/// Time is after the first keyframe. Set the keyframe and the trailing.
leadingIndex = 0;
trailingIndex = 1;
}
}
if
(trailingIndex.has_value() &&
keyframes[trailingIndex.value()].time <= frame)
{
/// Time is after the current span. Iterate forward.
auto newLeading = trailingIndex.value();
bool keyframeFound = false;
while (!keyframeFound) {
leadingIndex = newLeading;
if (newLeading + 1 >= 0 && newLeading + 1 < keyframes.size()) {
trailingIndex = newLeading + 1;
} else {
trailingIndex = std::nullopt;
}
if (!trailingIndex.has_value()) {
/// We have reached the end of our keyframes. Time is after the last keyframe.
keyframeFound = true;
continue;
}
if (frame < keyframes[trailingIndex.value()].time) {
/// Keyframe in current span.
keyframeFound = true;
continue;
}
/// Advance the array.
newLeading = trailingIndex.value();
}
} else if
(leadingIndex.has_value() &&
frame < keyframes[leadingIndex.value()].time)
{
/// Time is before the current span. Iterate backwards
auto newTrailing = leadingIndex.value();
bool keyframeFound = false;
while (!keyframeFound) {
if (newTrailing - 1 >= 0 && newTrailing - 1 < keyframes.size()) {
leadingIndex = newTrailing - 1;
} else {
leadingIndex = std::nullopt;
}
trailingIndex = newTrailing;
if (!leadingIndex.has_value()) {
/// We have reached the end of our keyframes. Time is after the last keyframe.
keyframeFound = true;
continue;
}
if (keyframes[leadingIndex.value()].time <= frame) {
/// Keyframe in current span.
keyframeFound = true;
continue;
}
/// Step back
newTrailing = leadingIndex.value();
}
}
if (const auto keyFrame = leadingIndex) {
leadingKeyframe = keyframes[keyFrame.value()];
} else {
leadingKeyframe = std::nullopt;
}
if (const auto keyFrame = trailingIndex) {
trailingKeyframe = keyframes[keyFrame.value()];
} else {
trailingKeyframe = std::nullopt;
}
}
private:
void setInplace(Keyframe<BezierPath> const &from, BezierPath &outPath) {
ValueInterpolator<BezierPath>::setInplace(from.value, outPath);
}
void interpolateInplace(Keyframe<BezierPath> const &from, Keyframe<BezierPath> const &to, float progress, BezierPath &outPath) {
std::optional<Vector2D> spatialOutTangent2d;
if (from.spatialOutTangent) {
spatialOutTangent2d = Vector2D(from.spatialOutTangent->x, from.spatialOutTangent->y);
}
std::optional<Vector2D> spatialInTangent2d;
if (to.spatialInTangent) {
spatialInTangent2d = Vector2D(to.spatialInTangent->x, to.spatialInTangent->y);
}
ValueInterpolator<BezierPath>::interpolateInplace(from.value, to.value, progress, spatialOutTangent2d, spatialInTangent2d, outPath);
}
};
}
#endif /* KeyframeInterpolator_hpp */
@@ -0,0 +1,5 @@
#include "SingleValueProvider.hpp"
namespace lottie {
}
@@ -0,0 +1,42 @@
#ifndef SingleValueProvider_hpp
#define SingleValueProvider_hpp
#include "Lottie/Public/DynamicProperties/AnyValueProvider.hpp"
namespace lottie {
/// Returns a value for every frame.
template<typename T>
class SingleValueProvider: public ValueProvider<T> {
public:
SingleValueProvider(T const &value) :
_value(value) {
}
virtual ~SingleValueProvider() = default;
void setValue(T const &value) {
_value = value;
_hasUpdate = true;
}
virtual T value(AnimationFrameTime frame) override {
return _value;
}
virtual AnyValue::Type valueType() const override {
return AnyValueType<T>::type();
}
virtual bool hasUpdate(float frame) const override {
return _hasUpdate;
}
private:
T _value;
bool _hasUpdate = true;
};
}
#endif /* SingleValueProvider_hpp */
@@ -0,0 +1,65 @@
#ifndef PassThroughOutputNode_hpp
#define PassThroughOutputNode_hpp
#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/HasRenderUpdates.hpp"
#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/HasUpdate.hpp"
#include "Lottie/Private/MainThread/NodeRenderSystem/Protocols/NodeOutput.hpp"
namespace lottie {
class PassThroughOutputNode: virtual public NodeOutput, virtual public HasRenderUpdates, virtual public HasUpdate {
public:
PassThroughOutputNode(std::shared_ptr<NodeOutput> parent) :
_parent(parent) {
}
virtual ~PassThroughOutputNode() = default;
virtual std::shared_ptr<NodeOutput> parent() override {
return _parent;
}
virtual bool isEnabled() const override {
return _isEnabled;
}
virtual void setIsEnabled(bool isEnabled) override {
_isEnabled = isEnabled;
}
virtual bool hasUpdate() override {
return _hasUpdate;
}
void setHasUpdate(bool hasUpdate) {
_hasUpdate = hasUpdate;
}
virtual bool hasOutputUpdates(float forFrame) override {
/// Changes to this node do not affect downstream nodes.
bool parentUpdate = false;
if (_parent) {
parentUpdate = _parent->hasOutputUpdates(forFrame);
}
/// Changes to upstream nodes do, however, affect this nodes state.
_hasUpdate = _hasUpdate || parentUpdate;
return parentUpdate;
}
virtual bool hasRenderUpdates(float forFrame) override {
/// Return true if there are upstream updates or if this node has updates
bool upstreamUpdates = false;
if (_parent) {
upstreamUpdates = _parent->hasOutputUpdates(forFrame);
}
_hasUpdate = _hasUpdate || upstreamUpdates;
return _hasUpdate;
}
private:
std::shared_ptr<NodeOutput> _parent;
bool _hasUpdate = false;
bool _isEnabled = true;
};
}
#endif /* PassThroughOutputNode_hpp */
@@ -0,0 +1,33 @@
#ifndef StrokeNode_hpp
#define StrokeNode_hpp
#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/NodePropertyMap.hpp"
#include "Lottie/Private/Model/ShapeItems/Stroke.hpp"
#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/NodeProperty.hpp"
#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/KeyframeInterpolator.hpp"
#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/SingleValueProvider.hpp"
#include "Lottie/Private/MainThread/NodeRenderSystem/Protocols/AnimatorNode.hpp"
#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/DashPatternInterpolator.hpp"
namespace lottie {
class StrokeShapeDashConfiguration {
public:
StrokeShapeDashConfiguration(std::vector<DashElement> const &elements) {
for (const auto &dash : elements) {
if (dash.type == DashElementType::Offset) {
dashPhase = dash.value.keyframes;
} else {
dashPatterns.push_back(dash.value.keyframes);
}
}
}
public:
std::vector<std::vector<Keyframe<Vector1D>>> dashPatterns;
std::vector<Keyframe<Vector1D>> dashPhase;
};
}
#endif /* StrokeNode_hpp */
@@ -0,0 +1,361 @@
#ifndef TextAnimatorNode_hpp
#define TextAnimatorNode_hpp
#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/NodePropertyMap.hpp"
#include "Lottie/Private/Model/Text/TextAnimator.hpp"
#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/NodeProperty.hpp"
#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/KeyframeInterpolator.hpp"
#include "Lottie/Private/MainThread/NodeRenderSystem/Protocols/NodeOutput.hpp"
#include "Lottie/Private/MainThread/NodeRenderSystem/Protocols/AnimatorNode.hpp"
namespace lottie {
class TextAnimatorNodeProperties: public KeypathSearchableNodePropertyMap {
public:
TextAnimatorNodeProperties(std::shared_ptr<TextAnimator> const &textAnimator) {
_keypathName = textAnimator->name.value_or("");
if (textAnimator->anchor) {
_anchor = std::make_shared<NodeProperty<Vector3D>>(std::make_shared<KeyframeInterpolator<Vector3D>>(textAnimator->anchor->keyframes));
_keypathProperties.insert(std::make_pair("Anchor", _anchor));
}
if (textAnimator->position) {
_position = std::make_shared<NodeProperty<Vector3D>>(std::make_shared<KeyframeInterpolator<Vector3D>>(textAnimator->position->keyframes));
_keypathProperties.insert(std::make_pair("Position", _position));
}
if (textAnimator->scale) {
_scale = std::make_shared<NodeProperty<Vector3D>>(std::make_shared<KeyframeInterpolator<Vector3D>>(textAnimator->scale->keyframes));
_keypathProperties.insert(std::make_pair("Scale", _scale));
}
if (textAnimator->skew) {
_skew = std::make_shared<NodeProperty<Vector1D>>(std::make_shared<KeyframeInterpolator<Vector1D>>(textAnimator->skew->keyframes));
_keypathProperties.insert(std::make_pair("Skew", _skew));
}
if (textAnimator->skewAxis) {
_skewAxis = std::make_shared<NodeProperty<Vector1D>>(std::make_shared<KeyframeInterpolator<Vector1D>>(textAnimator->skewAxis->keyframes));
_keypathProperties.insert(std::make_pair("Skew Axis", _skewAxis));
}
if (textAnimator->rotation) {
_rotation = std::make_shared<NodeProperty<Vector1D>>(std::make_shared<KeyframeInterpolator<Vector1D>>(textAnimator->rotation->keyframes));
_keypathProperties.insert(std::make_pair("Rotation", _rotation));
}
if (textAnimator->rotation) {
_opacity = std::make_shared<NodeProperty<Vector1D>>(std::make_shared<KeyframeInterpolator<Vector1D>>(textAnimator->opacity->keyframes));
_keypathProperties.insert(std::make_pair("Opacity", _opacity));
}
if (textAnimator->strokeColor) {
_strokeColor = std::make_shared<NodeProperty<Color>>(std::make_shared<KeyframeInterpolator<Color>>(textAnimator->strokeColor->keyframes));
_keypathProperties.insert(std::make_pair("Stroke Color", _strokeColor));
}
if (textAnimator->fillColor) {
_fillColor = std::make_shared<NodeProperty<Color>>(std::make_shared<KeyframeInterpolator<Color>>(textAnimator->fillColor->keyframes));
_keypathProperties.insert(std::make_pair("Fill Color", _fillColor));
}
if (textAnimator->strokeWidth) {
_strokeWidth = std::make_shared<NodeProperty<Vector1D>>(std::make_shared<KeyframeInterpolator<Vector1D>>(textAnimator->strokeWidth->keyframes));
_keypathProperties.insert(std::make_pair("Stroke Width", _strokeWidth));
}
if (textAnimator->tracking) {
_tracking = std::make_shared<NodeProperty<Vector1D>>(std::make_shared<KeyframeInterpolator<Vector1D>>(textAnimator->tracking->keyframes));
_keypathProperties.insert(std::make_pair("Tracking", _tracking));
}
for (const auto &it : _keypathProperties) {
_properties.push_back(it.second);
}
}
virtual ~TextAnimatorNodeProperties() = default;
virtual std::string keypathName() const override {
return _keypathName;
}
virtual std::map<std::string, std::shared_ptr<AnyNodeProperty>> keypathProperties() const override {
return _keypathProperties;
}
virtual std::vector<std::shared_ptr<AnyNodeProperty>> &properties() override {
return _properties;
}
virtual std::vector<std::shared_ptr<KeypathSearchable>> const &childKeypaths() const override {
return _childKeypaths;
}
Transform2D caTransform() {
Vector2D anchor = Vector2D::Zero();
if (_anchor) {
auto anchor3d = _anchor->value();
anchor = Vector2D(anchor3d.x, anchor3d.y);
}
Vector2D position = Vector2D::Zero();
if (_position) {
auto position3d = _position->value();
position = Vector2D(position3d.x, position3d.y);
}
Vector2D scale = Vector2D(100.0, 100.0);
if (_scale) {
auto scale3d = _scale->value();
scale = Vector2D(scale3d.x, scale3d.y);
}
float rotation = 0.0;
if (_rotation) {
rotation = _rotation->value().value;
}
std::optional<float> skew;
if (_skew) {
skew = _skew->value().value;
}
std::optional<float> skewAxis;
if (_skewAxis) {
skewAxis = _skewAxis->value().value;
}
return Transform2D::makeTransform(
anchor,
position,
scale,
rotation,
skew,
skewAxis
);
}
virtual std::shared_ptr<CALayer> keypathLayer() const override {
return nullptr;
}
float opacity() {
if (_opacity) {
return _opacity->value().value;
} else {
return 100.0;
}
}
std::optional<Color> strokeColor() {
if (_strokeColor) {
return _strokeColor->value();
} else {
return std::nullopt;
}
}
std::optional<Color> fillColor() {
if (_fillColor) {
return _fillColor->value();
} else {
return std::nullopt;
}
}
float tracking() {
if (_tracking) {
return _tracking->value().value;
} else {
return 1.0;
}
}
float strokeWidth() {
if (_strokeWidth) {
return _strokeWidth->value().value;
} else {
return 0.0;
}
}
private:
std::string _keypathName;
std::shared_ptr<NodeProperty<Vector3D>> _anchor;
std::shared_ptr<NodeProperty<Vector3D>> _position;
std::shared_ptr<NodeProperty<Vector3D>> _scale;
std::shared_ptr<NodeProperty<Vector1D>> _skew;
std::shared_ptr<NodeProperty<Vector1D>> _skewAxis;
std::shared_ptr<NodeProperty<Vector1D>> _rotation;
std::shared_ptr<NodeProperty<Vector1D>> _opacity;
std::shared_ptr<NodeProperty<Color>> _strokeColor;
std::shared_ptr<NodeProperty<Color>> _fillColor;
std::shared_ptr<NodeProperty<Vector1D>> _strokeWidth;
std::shared_ptr<NodeProperty<Vector1D>> _tracking;
std::map<std::string, std::shared_ptr<AnyNodeProperty>> _keypathProperties;
std::vector<std::shared_ptr<KeypathSearchable>> _childKeypaths;
std::vector<std::shared_ptr<AnyNodeProperty>> _properties;
};
class TextOutputNode: virtual public NodeOutput {
public:
TextOutputNode(std::shared_ptr<TextOutputNode> parent) :
_parentTextNode(parent) {
}
virtual ~TextOutputNode() = default;
virtual std::shared_ptr<NodeOutput> parent() override {
return _parentTextNode;
}
Transform2D xform() {
if (_xform.has_value()) {
return _xform.value();
} else if (_parentTextNode) {
return _parentTextNode->xform();
} else {
return Transform2D::identity();
}
}
void setXform(Transform2D const &xform) {
_xform = xform;
}
float opacity() {
if (_opacity.has_value()) {
return _opacity.value();
} else if (_parentTextNode) {
return _parentTextNode->opacity();
} else {
return 1.0;
}
}
void setOpacity(float opacity) {
_opacity = opacity;
}
std::optional<Color> strokeColor() {
if (_strokeColor.has_value()) {
return _strokeColor.value();
} else if (_parentTextNode) {
return _parentTextNode->strokeColor();
} else {
return std::nullopt;
}
}
void setStrokeColor(std::optional<Color> strokeColor) {
_strokeColor = strokeColor;
}
std::optional<Color> fillColor() {
if (_fillColor.has_value()) {
return _fillColor.value();
} else if (_parentTextNode) {
return _parentTextNode->fillColor();
} else {
return std::nullopt;
}
}
void setFillColor(std::optional<Color> fillColor) {
_fillColor = fillColor;
}
float tracking() {
if (_tracking.has_value()) {
return _tracking.value();
} else if (_parentTextNode) {
return _parentTextNode->tracking();
} else {
return 0.0;
}
}
void setTracking(float tracking) {
_tracking = tracking;
}
float strokeWidth() {
if (_strokeWidth.has_value()) {
return _strokeWidth.value();
} else if (_parentTextNode) {
return _parentTextNode->strokeWidth();
} else {
return 0.0;
}
}
void setStrokeWidth(float strokeWidth) {
_strokeWidth = strokeWidth;
}
virtual bool hasOutputUpdates(float frame) override {
// TODO Fix This
return true;
}
virtual bool isEnabled() const override {
return _isEnabled;
}
virtual void setIsEnabled(bool isEnabled) override {
_isEnabled = isEnabled;
}
private:
std::shared_ptr<TextOutputNode> _parentTextNode;
bool _isEnabled = true;
std::optional<Transform2D> _xform;
std::optional<float> _opacity;
std::optional<Color> _strokeColor;
std::optional<Color> _fillColor;
std::optional<float> _tracking;
std::optional<float> _strokeWidth;
};
class TextAnimatorNode: public AnimatorNode {
public:
TextAnimatorNode(std::shared_ptr<TextAnimatorNode> const &parentNode, std::shared_ptr<TextAnimator> const &textAnimator) :
AnimatorNode(parentNode) {
std::shared_ptr<TextOutputNode> parentOutputNode;
if (parentNode) {
parentOutputNode = parentNode->_textOutputNode;
}
_textOutputNode = std::make_shared<TextOutputNode>(parentOutputNode);
_textAnimatorProperties = std::make_shared<TextAnimatorNodeProperties>(textAnimator);
}
virtual ~TextAnimatorNode() = default;
virtual std::shared_ptr<NodeOutput> outputNode() override {
return _textOutputNode;
}
virtual std::shared_ptr<KeypathSearchableNodePropertyMap> propertyMap() const override {
return _textAnimatorProperties;
}
virtual bool localUpdatesPermeateDownstream() override {
return true;
}
virtual void rebuildOutputs(float frame) override {
_textOutputNode->setXform(_textAnimatorProperties->caTransform());
_textOutputNode->setOpacity(((float)_textAnimatorProperties->opacity()) * 0.01f);
_textOutputNode->setStrokeColor(_textAnimatorProperties->strokeColor());
_textOutputNode->setFillColor(_textAnimatorProperties->fillColor());
_textOutputNode->setTracking(_textAnimatorProperties->tracking());
_textOutputNode->setStrokeWidth(_textAnimatorProperties->strokeWidth());
}
private:
std::shared_ptr<TextOutputNode> _textOutputNode;
std::shared_ptr<TextAnimatorNodeProperties> _textAnimatorProperties;
};
}
#endif /* TextAnimatorNode_hpp */
@@ -0,0 +1,238 @@
#ifndef AnimatorNode_hpp
#define AnimatorNode_hpp
#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/KeypathSearchable.hpp"
#include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/NodePropertyMap.hpp"
#include "Lottie/Private/MainThread/NodeRenderSystem/Protocols/NodeOutput.hpp"
#include <memory>
#include <optional>
namespace lottie {
class LayerTransformNode;
class PathNode;
class RenderNode;
/// The Animator Node is the base node in the render system tree.
///
/// It defines a single node that has an output path and option input node.
/// At animation time the root animation node is asked to update its contents for
/// the current frame.
/// The node reaches up its chain of nodes until the first node that does not need
/// updating is found. Then each node updates its contents down the render pipeline.
/// Each node adds its local path to its input path and passes it forward.
///
/// An animator node holds a group of interpolators. These interpolators determine
/// if the node needs an update for the current frame.
///
class AnimatorNode: public KeypathSearchable {
public:
AnimatorNode(std::shared_ptr<AnimatorNode> const &parentNode) :
_parentNode(parentNode) {
}
AnimatorNode(const AnimatorNode&) = delete;
AnimatorNode& operator=(AnimatorNode&) = delete;
/// The available properties of the Node.
///
/// These properties are automatically updated each frame.
/// These properties are also settable and gettable through the dynamic
/// property system.
///
virtual std::shared_ptr<KeypathSearchableNodePropertyMap> propertyMap() const = 0;
/// The upstream input node
std::shared_ptr<AnimatorNode> parentNode() {
return _parentNode;
}
void setParentNode(std::shared_ptr<AnimatorNode> const &parentNode) {
_parentNode = parentNode;
}
/// The output of the node.
virtual std::shared_ptr<NodeOutput> outputNode() = 0;
/// Update the outputs of the node. Called if local contents were update or if outputsNeedUpdate returns true.
virtual void rebuildOutputs(float frame) = 0;
/// Setters for marking current node state.
bool isEnabled() {
return _isEnabled;
}
virtual void setIsEnabled(bool isEnabled) {
_isEnabled = isEnabled;
}
bool hasLocalUpdates() {
return _hasLocalUpdates;
}
virtual void setHasLocalUpdates(bool hasLocalUpdates) {
_hasLocalUpdates = hasLocalUpdates;
}
bool hasUpstreamUpdates() {
return _hasUpstreamUpdates;
}
virtual void setHasUpstreamUpdates(bool hasUpstreamUpdates) {
_hasUpstreamUpdates = hasUpstreamUpdates;
}
std::optional<float> lastUpdateFrame() {
return _lastUpdateFrame;
}
virtual void setLastUpdateFrame(std::optional<float> lastUpdateFrame) {
_lastUpdateFrame = lastUpdateFrame;
}
/// Marks if updates to this node affect nodes downstream.
virtual bool localUpdatesPermeateDownstream() {
/// Optional override
return true;
}
virtual bool forceUpstreamOutputUpdates() {
/// Optional
return false;
}
/// Called at the end of this nodes update cycle. Always called. Optional.
virtual bool performAdditionalLocalUpdates(float frame, bool forceLocalUpdate) {
/// Optional
return forceLocalUpdate;
}
virtual void performAdditionalOutputUpdates(float frame, bool forceOutputUpdate) {
/// Optional
}
/// The default simply returns `hasLocalUpdates`
virtual bool shouldRebuildOutputs(float frame) {
return hasLocalUpdates();
}
virtual bool updateOutputs(float frame, bool forceOutputUpdate) {
if (!isEnabled()) {
setLastUpdateFrame(frame);
if (const auto parentNodeValue = parentNode()) {
return parentNodeValue->updateOutputs(frame, forceOutputUpdate);
} else {
return false;
}
}
if (!forceOutputUpdate && lastUpdateFrame().has_value() && lastUpdateFrame().value() == frame) {
/// This node has already updated for this frame. Go ahead and return the results.
return hasUpstreamUpdates() || hasLocalUpdates();
}
/// Ask if this node should force output updates upstream.
bool forceUpstreamUpdates = forceOutputUpdate || forceUpstreamOutputUpdates();
/// Perform upstream output updates. Optionally mark upstream updates if any.
if (const auto parentNodeValue = parentNode()) {
setHasUpstreamUpdates(parentNodeValue->updateOutputs(frame, forceUpstreamUpdates) || hasUpstreamUpdates());
} else {
setHasUpstreamUpdates(hasUpstreamUpdates());
}
/// Perform additional local output updates
performAdditionalOutputUpdates(frame, forceUpstreamUpdates);
/// If there are local updates, or if updates have been force, rebuild outputs
if (forceUpstreamUpdates || shouldRebuildOutputs(frame)) {
setLastUpdateFrame(frame);
rebuildOutputs(frame);
}
return hasUpstreamUpdates() || hasLocalUpdates();
}
/// Rebuilds the content of this node, and upstream nodes if necessary.
virtual bool updateContents(float frame, bool forceLocalUpdate) {
if (!isEnabled()) {
// Disabled node, pass through.
if (const auto parentNodeValue = parentNode()) {
return parentNodeValue->updateContents(frame, forceLocalUpdate);
} else {
return false;
}
}
if (forceLocalUpdate == false && lastUpdateFrame().has_value() && lastUpdateFrame().value() == frame) {
/// This node has already updated for this frame. Go ahead and return the results.
return localUpdatesPermeateDownstream() ? hasUpstreamUpdates() || hasLocalUpdates() : hasUpstreamUpdates();
}
/// Are there local updates? If so mark the node.
setHasLocalUpdates(forceLocalUpdate ? forceLocalUpdate : propertyMap()->needsLocalUpdate(frame));
/// Were there upstream updates? If so mark the node
if (const auto parentNodeValue = parentNode()) {
setHasUpstreamUpdates(parentNodeValue->updateContents(frame, forceLocalUpdate));
} else {
setHasUpstreamUpdates(false);
}
/// Perform property updates if necessary.
if (hasLocalUpdates()) {
/// Rebuild local properties
propertyMap()->updateNodeProperties(frame);
}
/// Ask the node to perform any other updates it might have.
setHasUpstreamUpdates(performAdditionalLocalUpdates(frame, forceLocalUpdate) || hasUpstreamUpdates());
/// If the node can update nodes downstream, notify them, otherwise pass on any upstream updates downstream.
return localUpdatesPermeateDownstream() ? hasUpstreamUpdates() || hasLocalUpdates() : hasUpstreamUpdates();
}
bool updateTree(float frame, bool forceUpdates) {
if (updateContents(frame, forceUpdates)) {
return updateOutputs(frame, forceUpdates);
} else {
return false;
}
}
/// The name of the Keypath
virtual std::string keypathName() const override {
return propertyMap()->keypathName();
}
/// A list of properties belonging to the keypath.
virtual std::map<std::string, std::shared_ptr<AnyNodeProperty>> keypathProperties() const override {
return propertyMap()->keypathProperties();
}
/// Children Keypaths
virtual std::vector<std::shared_ptr<KeypathSearchable>> const &childKeypaths() const override {
return propertyMap()->childKeypaths();
}
virtual std::shared_ptr<CALayer> keypathLayer() const override {
return nullptr;
}
public:
virtual LayerTransformNode *asLayerTransformNode() {
return nullptr;
}
virtual PathNode *asPathNode() {
return nullptr;
}
virtual RenderNode *asRenderNode() {
return nullptr;
}
private:
std::shared_ptr<AnimatorNode> _parentNode;
bool _isEnabled = true;
bool _hasLocalUpdates = false;
bool _hasUpstreamUpdates = false;
std::optional<float> _lastUpdateFrame;
};
}
#endif /* AnimatorNode_hpp */
@@ -0,0 +1,24 @@
#ifndef NodeOutput_hpp
#define NodeOutput_hpp
#include <memory>
namespace lottie {
/// Defines the basic outputs of an animator node.
///
class NodeOutput {
public:
/// The parent node.
virtual std::shared_ptr<NodeOutput> parent() = 0;
/// Returns true if there are any updates upstream.
virtual bool hasOutputUpdates(float forFrame) = 0;
virtual bool isEnabled() const = 0;
virtual void setIsEnabled(bool isEnabled) = 0;
};
}
#endif /* NodeOutput_hpp */
@@ -0,0 +1,103 @@
#include "GetGradientParameters.hpp"
#include <LottieCpp/Color.h>
#include "Lottie/Public/Keyframes/Interpolatable.hpp"
#include "Lottie/Public/Keyframes/ValueInterpolators.hpp"
namespace lottie {
void getGradientParameters(int numberOfColors, GradientColorSet const &colors, std::vector<Color> &outColors, std::vector<float> &outLocations) {
std::vector<Color> alphaColors;
std::vector<float> alphaValues;
std::vector<float> alphaLocations;
std::vector<Color> gradientColors;
std::vector<float> colorLocations;
for (int i = 0; i < numberOfColors; i++) {
int ix = i * 4;
if (colors.colors.size() > ix) {
Color color(
colors.colors[ix + 1],
colors.colors[ix + 2],
colors.colors[ix + 3],
1
);
gradientColors.push_back(color);
colorLocations.push_back(colors.colors[ix]);
}
}
bool drawMask = false;
for (int i = numberOfColors * 4; i < (int)colors.colors.size(); i += 2) {
float alpha = colors.colors[i + 1];
if (alpha < 1.0) {
drawMask = true;
}
alphaLocations.push_back(colors.colors[i]);
alphaColors.push_back(Color(alpha, alpha, alpha, 1.0));
alphaValues.push_back(alpha);
}
if (drawMask) {
std::vector<float> locations;
for (size_t i = 0; i < std::min(gradientColors.size(), colorLocations.size()); i++) {
if (std::find(locations.begin(), locations.end(), colorLocations[i]) == locations.end()) {
locations.push_back(colorLocations[i]);
}
}
for (size_t i = 0; i < std::min(alphaValues.size(), alphaLocations.size()); i++) {
if (std::find(locations.begin(), locations.end(), alphaLocations[i]) == locations.end()) {
locations.push_back(alphaLocations[i]);
}
}
std::sort(locations.begin(), locations.end());
if (locations[0] != 0.0) {
locations.insert(locations.begin(), 0.0);
}
if (locations[locations.size() - 1] != 1.0) {
locations.push_back(1.0);
}
std::vector<Color> colors;
for (const auto location : locations) {
Color color = gradientColors[0];
for (size_t i = 0; i < std::min(gradientColors.size(), colorLocations.size()) - 1; i++) {
if (location >= colorLocations[i] && location <= colorLocations[i + 1]) {
float localLocation = 0.0;
if (colorLocations[i] != colorLocations[i + 1]) {
localLocation = remapFloat(location, colorLocations[i], colorLocations[i + 1], 0.0, 1.0);
}
color = ValueInterpolator<Color>::interpolate(gradientColors[i], gradientColors[i + 1], localLocation, std::nullopt, std::nullopt);
break;
}
}
float alpha = 1.0;
for (size_t i = 0; i < std::min(alphaValues.size(), alphaLocations.size()) - 1; i++) {
if (location >= alphaLocations[i] && location <= alphaLocations[i + 1]) {
float localLocation = 0.0;
if (alphaLocations[i] != alphaLocations[i + 1]) {
localLocation = remapFloat(location, alphaLocations[i], alphaLocations[i + 1], 0.0, 1.0);
}
alpha = ValueInterpolator<float>::interpolate(alphaValues[i], alphaValues[i + 1], localLocation, std::nullopt, std::nullopt);
break;
}
}
color.a = alpha;
colors.push_back(color);
}
gradientColors = colors;
colorLocations = locations;
}
outColors = gradientColors;
outLocations = colorLocations;
}
}
@@ -0,0 +1,14 @@
#ifndef ShapeRenderLayer_hpp
#define ShapeRenderLayer_hpp
#include <LottieCpp/Color.h>
#include "Lottie/Private/MainThread/NodeRenderSystem/Protocols/NodeOutput.hpp"
#include "Lottie/Public/Primitives/GradientColorSet.hpp"
namespace lottie {
void getGradientParameters(int numberOfColors, GradientColorSet const &colors, std::vector<Color> &outColors, std::vector<float> &outLocations);
}
#endif /* ShapeRenderLayer_hpp */
@@ -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 <LottieCpp/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_,
float 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<lottiejson11::Json> meta_,
std::optional<lottiejson11::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(lottiejson11::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 = (float)getDouble(json, "ip");
AnimationFrameTime endFrame = (float)getDouble(json, "op");
float framerate = (float)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
);
}
lottiejson11::Json::object toJson() const {
lottiejson11::Json::object result;
if (name.has_value()) {
result.insert(std::make_pair("nm", name.value()));
}
result.insert(std::make_pair("v", lottiejson11::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", lottiejson11::Json(0)));
break;
case CoordinateSpace::Type3d:
result.insert(std::make_pair("ddd", lottiejson11::Json(1)));
break;
}
}
result.insert(std::make_pair("ip", lottiejson11::Json(startFrame)));
result.insert(std::make_pair("op", lottiejson11::Json(endFrame)));
result.insert(std::make_pair("fr", lottiejson11::Json(framerate)));
result.insert(std::make_pair("w", lottiejson11::Json(width)));
result.insert(std::make_pair("h", lottiejson11::Json(height)));
lottiejson11::Json::array layersArray;
for (const auto &layer : layers) {
lottiejson11::Json::object layerJson;
layer->toJson(layerJson);
layersArray.push_back(layerJson);
}
result.insert(std::make_pair("layers", lottiejson11::Json(layersArray)));
if (glyphs.has_value()) {
lottiejson11::Json::array glyphArray;
for (const auto &glyph : glyphs.value()) {
glyphArray.push_back(glyph->toJson());
}
result.insert(std::make_pair("chars", lottiejson11::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()) {
lottiejson11::Json::array markerArray;
for (const auto &marker : markers.value()) {
markerArray.push_back(marker.toJson());
}
result.insert(std::make_pair("markers", lottiejson11::Json(markerArray)));
}
if (fitzModifiers.has_value()) {
lottiejson11::Json::array fitzModifierArray;
for (const auto &fitzModifier : fitzModifiers.value()) {
fitzModifierArray.push_back(fitzModifier.toJson());
}
result.insert(std::make_pair("fitz", lottiejson11::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.
float 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<lottiejson11::Json> meta;
std::optional<lottiejson11::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(lottiejson11::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(lottiejson11::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(lottiejson11::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));
}
}
}
lottiejson11::Json::array toJson() const {
lottiejson11::Json::array result;
for (const auto &asset : assetList) {
lottiejson11::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,125 @@
#ifndef ImageAsset_hpp
#define ImageAsset_hpp
#include "Lottie/Private/Model/Assets/Asset.hpp"
#include "Lottie/Private/Parsing/JsonParsing.hpp"
namespace lottie {
class Image {
public:
Image() {
}
};
class ImageAsset: public Asset {
public:
ImageAsset(
std::string id_,
std::string name_,
std::string directory_,
float width_,
float height_
) : Asset(id_),
name(name_),
directory(directory_),
width(width_),
height(height_) {
}
virtual ~ImageAsset() = default;
explicit ImageAsset(lottiejson11::Json::object const &json) noexcept(false) :
Asset(json) {
name = getString(json, "p");
directory = getString(json, "u");
width = (float)getDouble(json, "w");
height = (float)getDouble(json, "h");
_e = getOptionalInt(json, "e");
_t = getOptionalString(json, "t");
}
virtual void toJson(lottiejson11::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
float width;
float 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,66 @@
#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(lottiejson11::Json::object const &json) noexcept(false) :
Asset(json) {
if (const auto frameRateValue = getOptionalDouble(json, "fr")) {
frameRate = (float)frameRateValue.value();
}
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(lottiejson11::Json::object &json) const override {
Asset::toJson(json);
lottiejson11::Json::array layerArray;
for (const auto &layer : layers) {
lottiejson11::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<float> frameRate;
};
}
#endif /* PrecompAsset_hpp */
@@ -0,0 +1,5 @@
#include "KeyframeGroup.hpp"
namespace lottie {
}
@@ -0,0 +1,148 @@
#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(lottiejson11::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;
}
}
lottiejson11::Json::object toJson() const {
lottiejson11::Json::object result;
if (keyframes.size() == 1 && isSingle) {
result.insert(std::make_pair("k", keyframes[0].value.toJson()));
} else {
lottiejson11::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<lottiejson11::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(lottiejson11::Json::object const &json) noexcept(false) :
LayerModel(json) {
referenceID = getString(json, "refId");
_sc = getOptionalString(json, "sc");
}
virtual ~ImageLayerModel() = default;
virtual void toJson(lottiejson11::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(lottiejson11::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(lottiejson11::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(lottiejson11::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 = (float)getDouble(json, "ip");
outFrame = (float)getDouble(json, "op");
startTime = (float)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 = (float)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(lottiejson11::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()) {
lottiejson11::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()));
}
}
float 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.
float inFrame;
/// The out time of the layer in frames.
float outFrame;
/// The start time of the layer in frames.
float 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<float> _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<lottiejson11::Json> effectsData;
std::optional<std::string> layerClass;
std::optional<lottiejson11::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(lottiejson11::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 <LottieCpp/lottiejson11.hpp>
#include "Lottie/Private/Parsing/JsonParsing.hpp"
#include "Lottie/Private/Model/Layers/LayerModel.hpp"
namespace lottie {
std::shared_ptr<LayerModel> parseLayerModel(lottiejson11::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 <LottieCpp/Vectors.h>
#include "Lottie/Private/Parsing/JsonParsing.hpp"
#include <optional>
namespace lottie {
/// A layer that holds another animation composition.
class PreCompLayerModel: public LayerModel {
public:
PreCompLayerModel(lottiejson11::Json::object const &json) :
LayerModel(json) {
referenceID = getString(json, "refId");
if (const auto timeRemappingData = getOptionalObject(json, "tm")) {
timeRemapping = KeyframeGroup<Vector1D>(timeRemappingData.value());
}
width = (float)getDouble(json, "w");
height = (float)getDouble(json, "h");
}
virtual ~PreCompLayerModel() = default;
virtual void toJson(lottiejson11::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
float width;
/// Precomp Height
float 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(lottiejson11::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(lottiejson11::Json::object &json) const override {
LayerModel::toJson(json);
lottiejson11::Json::array shapeItemArray;
for (const auto &item : items) {
lottiejson11::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(lottiejson11::Json::object const &json) noexcept(false) :
LayerModel(json) {
colorHex = getString(json, "sc");
width = (float)getDouble(json, "sw");
height = (float)getDouble(json, "sh");
}
virtual ~SolidLayerModel() = default;
virtual void toJson(lottiejson11::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
float width;
/// The height of the color layer
float 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(lottiejson11::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(lottiejson11::Json::object &json) const override {
LayerModel::toJson(json);
lottiejson11::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()));
}
lottiejson11::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<lottiejson11::Json> _extraM;
std::optional<lottiejson11::Json> _extraP;
std::optional<lottiejson11::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(lottiejson11::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");
}
lottiejson11::Json::object toJson() const {
lottiejson11::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(lottiejson11::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");
}
lottiejson11::Json::object toJson() const {
lottiejson11::Json::object result;
result.insert(std::make_pair("o", (float)original));
if (type12.has_value()) {
result.insert(std::make_pair("f12", (float)type12.value()));
}
if (type3.has_value()) {
result.insert(std::make_pair("f3", (float)type3.value()));
}
if (type4.has_value()) {
result.insert(std::make_pair("f4", (float)type4.value()));
}
if (type5.has_value()) {
result.insert(std::make_pair("f5", (float)type5.value()));
}
if (type6.has_value()) {
result.insert(std::make_pair("f6", (float)type6.value()));
}
return result;
}
public:
float original;
std::optional<float> type12;
std::optional<float> type3;
std::optional<float> type4;
std::optional<float> type5;
std::optional<float> 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(lottiejson11::Json::object const &json) noexcept(false) {
name = getString(json, "cm");
frameTime = (float)getDouble(json, "tm");
dr = getOptionalInt(json, "dr");
}
lottiejson11::Json::object toJson() const {
lottiejson11::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 <LottieCpp/BezierPath.h>
#include "Lottie/Private/Parsing/JsonParsing.hpp"
namespace lottie {
enum class MaskMode {
Add,
Subtract,
Intersect,
Lighten,
Darken,
Difference,
None
};
class Mask {
public:
explicit Mask(lottiejson11::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");
}
lottiejson11::Json::object toJson() const {
lottiejson11::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 {
}
@@ -0,0 +1,267 @@
#ifndef Transform_hpp
#define Transform_hpp
#include "Lottie/Private/Model/Keyframes/KeyframeGroup.hpp"
#include "Lottie/Private/Parsing/JsonParsing.hpp"
#include <optional>
namespace lottie {
class Transform {
public:
enum class PositionInternalRepresentation {
None,
TopLevelXY,
TopLevelCombined,
NestedXY
};
enum class RotationZInternalRepresentation {
RZ,
R
};
public:
Transform(
std::optional<KeyframeGroup<Vector3D>> anchorPoint_,
std::optional<KeyframeGroup<Vector3D>> position_,
std::optional<KeyframeGroup<Vector1D>> positionX_,
std::optional<KeyframeGroup<Vector1D>> positionY_,
std::optional<KeyframeGroup<Vector3D>> scale_,
std::optional<KeyframeGroup<Vector1D>> rotation_,
std::optional<KeyframeGroup<Vector1D>> &opacity_,
std::optional<KeyframeGroup<Vector1D>> rotationZ_
) :
_anchorPoint(anchorPoint_),
_position(position_),
_positionX(positionX_),
_positionY(positionY_),
_scale(scale_),
_rotation(rotation_),
_opacity(opacity_),
_rotationZ(rotationZ_) {
}
explicit Transform(lottiejson11::Json::object const &json) noexcept(false) {
// AnchorPoint
if (const auto anchorPointDictionary = getOptionalObject(json, "a")) {
_anchorPoint = KeyframeGroup<Vector3D>(anchorPointDictionary.value());
}
try {
auto xDictionary = getOptionalObject(json, "px");
auto yDictionary = getOptionalObject(json, "py");
if (xDictionary.has_value() && yDictionary.has_value()) {
_positionX = KeyframeGroup<Vector1D>(xDictionary.value());
_positionY = KeyframeGroup<Vector1D>(yDictionary.value());
_position = std::nullopt;
_positionInternalRepresentation = PositionInternalRepresentation::TopLevelXY;
} else if (const auto positionData = getOptionalObject(json, "p")) {
try {
LottieParsingException::Guard expectedGuard;
_position = KeyframeGroup<Vector3D>(positionData.value());
_positionX = std::nullopt;
_positionX = std::nullopt;
_positionInternalRepresentation = PositionInternalRepresentation::TopLevelCombined;
} catch(...) {
auto xData = getObject(positionData.value(), "x");
auto yData = getObject(positionData.value(), "y");
_positionX = KeyframeGroup<Vector1D>(xData);
_positionY = KeyframeGroup<Vector1D>(yData);
_position = std::nullopt;
_positionInternalRepresentation = PositionInternalRepresentation::NestedXY;
_extra_positionS = getOptionalBool(positionData.value(), "s");
}
} else {
_position = std::nullopt;
_positionX = std::nullopt;
_positionY = std::nullopt;
_positionInternalRepresentation = PositionInternalRepresentation::None;
}
} catch(...) {
throw LottieParsingException();
}
try {
// Scale
if (const auto scaleData = getOptionalObject(json, "s")) {
_scale = KeyframeGroup<Vector3D>(scaleData.value());
}
// Rotation
if (const auto rotationZData = getOptionalObject(json, "rz")) {
_rotationZ = KeyframeGroup<Vector1D>(rotationZData.value());
_rotationZInternalRepresentation = RotationZInternalRepresentation::RZ;
} else if (const auto rotationData = getOptionalObject(json, "r")) {
_rotation = KeyframeGroup<Vector1D>(rotationData.value());
_rotationZInternalRepresentation = RotationZInternalRepresentation::R;
}
// Opacity
if (const auto opacityData = getOptionalObject(json, "o")) {
_opacity = KeyframeGroup<Vector1D>(opacityData.value());
}
} catch(...) {
throw LottieParsingException();
}
_extraTy = getOptionalString(json, "ty");
_extraSa = getOptionalAny(json, "sa");
_extraSk = getOptionalAny(json, "sk");
}
lottiejson11::Json::object toJson() const {
lottiejson11::Json::object result;
if (_anchorPoint.has_value()) {
result.insert(std::make_pair("a", _anchorPoint->toJson()));
}
switch (_positionInternalRepresentation) {
case PositionInternalRepresentation::None:
assert(!_positionX.has_value());
assert(!_positionY.has_value());
assert(!_position.has_value());
break;
case PositionInternalRepresentation::TopLevelXY:
assert(_positionX.has_value());
assert(_positionY.has_value());
assert(!_position.has_value());
result.insert(std::make_pair("x", _positionX->toJson()));
result.insert(std::make_pair("y", _positionY->toJson()));
break;
case PositionInternalRepresentation::TopLevelCombined:
assert(_position.has_value());
assert(!_positionX.has_value());
assert(!_positionY.has_value());
result.insert(std::make_pair("p", _position->toJson()));
break;
case PositionInternalRepresentation::NestedXY:
lottiejson11::Json::object nestedPosition;
assert(_positionX.has_value());
assert(_positionY.has_value());
assert(!_position.has_value());
nestedPosition.insert(std::make_pair("x", _positionX->toJson()));
nestedPosition.insert(std::make_pair("y", _positionY->toJson()));
if (_extra_positionS.has_value()) {
nestedPosition.insert(std::make_pair("s", _extra_positionS.value()));
}
result.insert(std::make_pair("p", nestedPosition));
break;
}
if (_scale.has_value()) {
result.insert(std::make_pair("s", _scale->toJson()));
}
if (_rotation.has_value()) {
switch (_rotationZInternalRepresentation) {
case RotationZInternalRepresentation::RZ:
result.insert(std::make_pair("rz", _rotation->toJson()));
break;
case RotationZInternalRepresentation::R:
result.insert(std::make_pair("r", _rotation->toJson()));
break;
}
}
if (_opacity.has_value()) {
result.insert(std::make_pair("o", _opacity->toJson()));
}
if (_extraTy.has_value()) {
result.insert(std::make_pair("ty", _extraTy.value()));
}
if (_extraSa.has_value()) {
result.insert(std::make_pair("sa", _extraSa.value()));
}
if (_extraSk.has_value()) {
result.insert(std::make_pair("sk", _extraSk.value()));
}
return result;
}
KeyframeGroup<Vector3D> anchorPoint() {
if (_anchorPoint.has_value()) {
return _anchorPoint.value();
} else {
return KeyframeGroup<Vector3D>(Vector3D(0.0, 0.0, 0.0));
}
}
KeyframeGroup<Vector3D> scale() const {
if (_scale) {
return _scale.value();
} else {
return KeyframeGroup<Vector3D>(Vector3D(100.0, 100.0, 100.0));
}
}
KeyframeGroup<Vector1D> rotation() const {
if (_rotation) {
return _rotation.value();
} else {
return KeyframeGroup<Vector1D>(Vector1D(0.0));
}
}
KeyframeGroup<Vector1D> opacity() const {
if (_opacity) {
return _opacity.value();
} else {
return KeyframeGroup<Vector1D>(Vector1D(100.0));
}
}
std::optional<KeyframeGroup<Vector3D>> const &position() const {
return _position;
}
std::optional<KeyframeGroup<Vector1D>> const &positionX() const {
return _positionX;
}
std::optional<KeyframeGroup<Vector1D>> const &positionY() const {
return _positionY;
}
private:
/// The anchor point of the transform.
std::optional<KeyframeGroup<Vector3D>> _anchorPoint;
/// The position of the transform. This is nil if the position data was split.
std::optional<KeyframeGroup<Vector3D>> _position;
/// The positionX of the transform. This is nil if the position property is set.
std::optional<KeyframeGroup<Vector1D>> _positionX;
/// The positionY of the transform. This is nil if the position property is set.
std::optional<KeyframeGroup<Vector1D>> _positionY;
PositionInternalRepresentation _positionInternalRepresentation = PositionInternalRepresentation::None;
/// The scale of the transform
std::optional<KeyframeGroup<Vector3D>> _scale;
/// The rotation of the transform. Note: This is single dimensional rotation.
std::optional<KeyframeGroup<Vector1D>> _rotation;
/// The opacity of the transform.
std::optional<KeyframeGroup<Vector1D>> _opacity;
/// Should always be nil.
std::optional<KeyframeGroup<Vector1D>> _rotationZ;
RotationZInternalRepresentation _rotationZInternalRepresentation;
std::optional<bool> _extra_positionS;
std::optional<std::string> _extraTy;
std::optional<lottiejson11::Json> _extraSa;
std::optional<lottiejson11::Json> _extraSk;
};
}
#endif /* Transform_hpp */
@@ -0,0 +1,5 @@
#include "Ellipse.hpp"
namespace lottie {
}
@@ -0,0 +1,64 @@
#ifndef Ellipse_hpp
#define Ellipse_hpp
#include "Lottie/Private/Model/ShapeItems/ShapeItem.hpp"
#include "Lottie/Private/Model/Keyframes/KeyframeGroup.hpp"
#include <LottieCpp/Vectors.h>
#include "Lottie/Private/Parsing/JsonParsing.hpp"
namespace lottie {
enum class PathDirection: int {
Clockwise = 1,
UserSetClockwise = 2,
CounterClockwise = 3
};
/// An item that define an ellipse shape
class Ellipse: public ShapeItem {
public:
explicit Ellipse(lottiejson11::Json::object const &json) noexcept(false) :
ShapeItem(json),
position(KeyframeGroup<Vector3D>(Vector3D(0.0, 0.0, 0.0))),
size(KeyframeGroup<Vector3D>(Vector3D(0.0, 0.0, 0.0))) {
if (const auto directionRawValue = getOptionalInt(json, "d")) {
switch (directionRawValue.value()) {
case 1:
direction = PathDirection::Clockwise;
break;
case 2:
direction = PathDirection::UserSetClockwise;
break;
case 3:
direction = PathDirection::CounterClockwise;
break;
default:
throw LottieParsingException();
}
}
position = KeyframeGroup<Vector3D>(getObject(json, "p"));
size = KeyframeGroup<Vector3D>(getObject(json, "s"));
}
virtual ~Ellipse() = default;
virtual void toJson(lottiejson11::Json::object &json) const override {
ShapeItem::toJson(json);
if (direction.has_value()) {
json.insert(std::make_pair("d", (int)direction.value()));
}
json.insert(std::make_pair("p", position.toJson()));
json.insert(std::make_pair("s", size.toJson()));
}
public:
std::optional<PathDirection> direction;
KeyframeGroup<Vector3D> position;
KeyframeGroup<Vector3D> size;
};
}
#endif /* Ellipse_hpp */
@@ -0,0 +1,6 @@
#include "Fill.hpp"
namespace lottie {
}
@@ -0,0 +1,71 @@
#ifndef Fill_hpp
#define Fill_hpp
#include "Lottie/Private/Model/Keyframes/KeyframeGroup.hpp"
#include "Lottie/Private/Model/ShapeItems/ShapeItem.hpp"
#import <LottieCpp/Color.h>
#include <LottieCpp/Vectors.h>
#include "Lottie/Private/Parsing/JsonParsing.hpp"
#include <LottieCpp/ShapeAttributes.h>
namespace lottie {
class Fill: public ShapeItem {
public:
explicit Fill(lottiejson11::Json::object const &json) noexcept(false) :
ShapeItem(json),
opacity(KeyframeGroup<Vector1D>(Vector1D(0.0))),
color(KeyframeGroup<Color>(Color(0.0, 0.0, 0.0, 0.0))) {
opacity = KeyframeGroup<Vector1D>(getObject(json, "o"));
color = KeyframeGroup<Color>(getObject(json, "c"));
if (const auto fillRuleRawValue = getOptionalInt(json, "r")) {
switch (fillRuleRawValue.value()) {
case 0:
fillRule = FillRule::None;
break;
case 1:
fillRule = FillRule::NonZeroWinding;
break;
case 2:
fillRule = FillRule::EvenOdd;
break;
default:
throw LottieParsingException();
}
}
fillEnabled = getOptionalBool(json, "fillEnabled");
}
virtual ~Fill() = default;
virtual void toJson(lottiejson11::Json::object &json) const override {
ShapeItem::toJson(json);
json.insert(std::make_pair("o", opacity.toJson()));
json.insert(std::make_pair("c", color.toJson()));
if (fillRule.has_value()) {
json.insert(std::make_pair("r", (int)fillRule.value()));
}
if (fillEnabled.has_value()) {
json.insert(std::make_pair("fillEnabled", fillEnabled.value()));
}
}
public:
KeyframeGroup<Vector1D> opacity;
/// The color keyframes for the fill
KeyframeGroup<Color> color;
std::optional<FillRule> fillRule;
std::optional<bool> fillEnabled;
};
}
#endif /* Fill_hpp */
@@ -0,0 +1,5 @@
#include "GradientFill.hpp"
namespace lottie {
}
@@ -0,0 +1,113 @@
#ifndef GradientFill_hpp
#define GradientFill_hpp
#include "Lottie/Private/Model/ShapeItems/ShapeItem.hpp"
#include "Lottie/Private/Model/Keyframes/KeyframeGroup.hpp"
#include "Lottie/Private/Parsing/JsonParsing.hpp"
#include "Lottie/Public/Primitives/GradientColorSet.hpp"
#include <LottieCpp/ShapeAttributes.h>
namespace lottie {
/// An item that define a gradient fill
class GradientFill: public ShapeItem {
public:
explicit GradientFill(lottiejson11::Json::object const &json) noexcept(false) :
ShapeItem(json),
opacity(KeyframeGroup<Vector1D>(Vector1D(100.0))),
startPoint(KeyframeGroup<Vector3D>(Vector3D(0.0, 0.0, 0.0))),
endPoint(KeyframeGroup<Vector3D>(Vector3D(0.0, 0.0, 0.0))),
gradientType(GradientType::None),
numberOfColors(0),
colors(KeyframeGroup<GradientColorSet>(GradientColorSet())) {
opacity = KeyframeGroup<Vector1D>(getObject(json, "o"));
startPoint = KeyframeGroup<Vector3D>(getObject(json, "s"));
endPoint = KeyframeGroup<Vector3D>(getObject(json, "e"));
auto gradientTypeRawValue = getInt(json, "t");
switch (gradientTypeRawValue) {
case 0:
gradientType = GradientType::None;
break;
case 1:
gradientType = GradientType::Linear;
break;
case 2:
gradientType = GradientType::Radial;
break;
default:
throw LottieParsingException();
}
if (const auto highlightLengthData = getOptionalObject(json, "h")) {
highlightLength = KeyframeGroup<Vector1D>(highlightLengthData.value());
}
if (const auto highlightAngleData = getOptionalObject(json, "a")) {
highlightAngle = KeyframeGroup<Vector1D>(highlightAngleData.value());
}
auto colorsContainer = getObject(json, "g");
numberOfColors = getInt(colorsContainer, "p");
colors = KeyframeGroup<GradientColorSet>(getObject(colorsContainer, "k"));
rValue = getOptionalInt(json, "r");
}
virtual ~GradientFill() = default;
virtual void toJson(lottiejson11::Json::object &json) const override {
ShapeItem::toJson(json);
json.insert(std::make_pair("o", opacity.toJson()));
json.insert(std::make_pair("s", startPoint.toJson()));
json.insert(std::make_pair("e", endPoint.toJson()));
json.insert(std::make_pair("t", (int)gradientType));
if (highlightLength.has_value()) {
json.insert(std::make_pair("h", highlightLength->toJson()));
}
if (highlightAngle.has_value()) {
json.insert(std::make_pair("a", highlightAngle->toJson()));
}
lottiejson11::Json::object colorsContainer;
colorsContainer.insert(std::make_pair("p", numberOfColors));
colorsContainer.insert(std::make_pair("k", colors.toJson()));
json.insert(std::make_pair("g", colorsContainer));
if (rValue.has_value()) {
json.insert(std::make_pair("r", rValue.value()));
}
}
public:
/// The opacity of the fill
KeyframeGroup<Vector1D> opacity;
/// The start of the gradient
KeyframeGroup<Vector3D> startPoint;
/// The end of the gradient
KeyframeGroup<Vector3D> endPoint;
/// The type of gradient
GradientType gradientType;
/// Gradient Highlight Length. Only if type is Radial
std::optional<KeyframeGroup<Vector1D>> highlightLength;
/// Highlight Angle. Only if type is Radial
std::optional<KeyframeGroup<Vector1D>> highlightAngle;
/// The number of color points in the gradient
int numberOfColors;
/// The Colors of the gradient.
KeyframeGroup<GradientColorSet> colors;
std::optional<int> rValue;
};
}
#endif /* GradientFill_hpp */
@@ -0,0 +1,5 @@
#include "GradientStroke.hpp"
namespace lottie {
}
@@ -0,0 +1,193 @@
#ifndef GradientStroke_hpp
#define GradientStroke_hpp
#include "Lottie/Private/Model/ShapeItems/ShapeItem.hpp"
#include "Lottie/Private/Model/ShapeItems/GradientFill.hpp"
#include "Lottie/Private/Model/Keyframes/KeyframeGroup.hpp"
#include "Lottie/Private/Model/Objects/DashElement.hpp"
#include "Lottie/Private/Parsing/JsonParsing.hpp"
#include <LottieCpp/ShapeAttributes.h>
namespace lottie {
/// An item that define a gradient stroke
class GradientStroke: public ShapeItem {
public:
explicit GradientStroke(lottiejson11::Json::object const &json) noexcept(false) :
ShapeItem(json),
opacity(KeyframeGroup<Vector1D>(Vector1D(100.0))),
startPoint(KeyframeGroup<Vector3D>(Vector3D(0.0, 0.0, 0.0))),
endPoint(KeyframeGroup<Vector3D>(Vector3D(0.0, 0.0, 0.0))),
gradientType(GradientType::None),
numberOfColors(0),
colors(KeyframeGroup<GradientColorSet>(GradientColorSet())),
width(KeyframeGroup<Vector1D>(Vector1D(0.0))),
lineCap(LineCap::Round),
lineJoin(LineJoin::Round) {
opacity = KeyframeGroup<Vector1D>(getObject(json, "o"));
startPoint = KeyframeGroup<Vector3D>(getObject(json, "s"));
endPoint = KeyframeGroup<Vector3D>(getObject(json, "e"));
auto gradientTypeRawValue = getInt(json, "t");
switch (gradientTypeRawValue) {
case 0:
gradientType = GradientType::None;
break;
case 1:
gradientType = GradientType::Linear;
break;
case 2:
gradientType = GradientType::Radial;
break;
default:
throw LottieParsingException();
}
if (const auto highlightLengthData = getOptionalObject(json, "h")) {
highlightLength = KeyframeGroup<Vector1D>(highlightLengthData.value());
}
if (const auto highlightAngleData = getOptionalObject(json, "a")) {
highlightAngle = KeyframeGroup<Vector1D>(highlightAngleData.value());
}
width = KeyframeGroup<Vector1D>(getObject(json, "w"));
if (const auto lineCapRawValue = getOptionalInt(json, "lc")) {
switch (lineCapRawValue.value()) {
case 0:
lineCap = LineCap::None;
break;
case 1:
lineCap = LineCap::Butt;
break;
case 2:
lineCap = LineCap::Round;
break;
case 3:
lineCap = LineCap::Square;
break;
default:
throw LottieParsingException();
}
}
if (const auto lineJoinRawValue = getOptionalInt(json, "lj")) {
switch (lineJoinRawValue.value()) {
case 0:
lineJoin = LineJoin::None;
break;
case 1:
lineJoin = LineJoin::Miter;
break;
case 2:
lineJoin = LineJoin::Round;
break;
case 3:
lineJoin = LineJoin::Bevel;
break;
default:
throw LottieParsingException();
}
}
if (const auto miterLimitData = getOptionalDouble(json, "ml")) {
miterLimit = (float)miterLimitData.value();
}
auto colorsContainer = getObject(json, "g");
numberOfColors = getInt(colorsContainer, "p");
auto colorsData = getObject(colorsContainer, "k");
colors = KeyframeGroup<GradientColorSet>(colorsData);
if (const auto dashElementsData = getOptionalObjectArray(json, "d")) {
dashPattern = std::vector<DashElement>();
for (const auto &dashElementData : dashElementsData.value()) {
dashPattern->push_back(DashElement(dashElementData));
}
}
}
virtual ~GradientStroke() = default;
virtual void toJson(lottiejson11::Json::object &json) const override {
ShapeItem::toJson(json);
json.insert(std::make_pair("o", opacity.toJson()));
json.insert(std::make_pair("s", startPoint.toJson()));
json.insert(std::make_pair("e", endPoint.toJson()));
json.insert(std::make_pair("t", (int)gradientType));
if (highlightLength.has_value()) {
json.insert(std::make_pair("h", highlightLength->toJson()));
}
if (highlightAngle.has_value()) {
json.insert(std::make_pair("a", highlightAngle->toJson()));
}
json.insert(std::make_pair("w", width.toJson()));
json.insert(std::make_pair("lc", (int)lineCap));
json.insert(std::make_pair("lj", (int)lineJoin));
if (miterLimit.has_value()) {
json.insert(std::make_pair("ml", miterLimit.value()));
}
lottiejson11::Json::object colorsContainer;
colorsContainer.insert(std::make_pair("p", numberOfColors));
colorsContainer.insert(std::make_pair("k", colors.toJson()));
json.insert(std::make_pair("g", colorsContainer));
if (dashPattern.has_value()) {
lottiejson11::Json::array dashElements;
for (const auto &dashElement : dashPattern.value()) {
dashElements.push_back(dashElement.toJson());
}
json.insert(std::make_pair("d", dashElements));
}
}
public:
/// The opacity of the fill
KeyframeGroup<Vector1D> opacity;
/// The start of the gradient
KeyframeGroup<Vector3D> startPoint;
/// The end of the gradient
KeyframeGroup<Vector3D> endPoint;
/// The type of gradient
GradientType gradientType;
/// Gradient Highlight Length. Only if type is Radial
std::optional<KeyframeGroup<Vector1D>> highlightLength;
/// Highlight Angle. Only if type is Radial
std::optional<KeyframeGroup<Vector1D>> highlightAngle;
/// The number of color points in the gradient
int numberOfColors;
/// The Colors of the gradient.
KeyframeGroup<GradientColorSet> colors;
/// The width of the stroke
KeyframeGroup<Vector1D> width;
/// Line Cap
LineCap lineCap;
/// Line Join
LineJoin lineJoin;
/// Miter Limit
std::optional<float> miterLimit;
/// The dash pattern of the stroke
std::optional<std::vector<DashElement>> dashPattern;
};
}
#endif /* GradientStroke_hpp */

Some files were not shown because too many files have changed in this diff Show More