From 0b2d73d62683f673456ebac71864ecc999a949a5 Mon Sep 17 00:00:00 2001 From: Isaac <> Date: Thu, 6 Jun 2024 17:11:16 +0400 Subject: [PATCH] Lottie refactoring --- .../SoftwareLottieRenderer.h | 10 +- .../SoftwareLottieRenderer/Sources/Canvas.h | 94 --- .../Sources/CoreGraphicsCanvasImpl.h | 25 +- .../Sources/CoreGraphicsCanvasImpl.mm | 15 +- .../Sources/NullCanvasImpl.h | 47 -- .../Sources/NullCanvasImpl.mm | 81 --- .../Sources/SoftwareLottieRenderer.mm | 605 ++---------------- ...orVGCanvasImpl.mm => ThorVGCanvasImpl.cpp} | 44 +- .../Sources/ThorVGCanvasImpl.h | 12 +- .../Sources/CompareToReferenceRendering.swift | 24 +- .../Sources/ViewController.swift | 30 +- submodules/LottieCpp/lottiecpp | 2 +- .../LottieMetalAnimatedStickerNode.swift | 13 +- .../LottieMetal/Sources/PathFrameState.swift | 3 +- .../Sources/PathRenderFillState.swift | 4 +- .../Sources/PathRenderStrokeState.swift | 4 +- .../Sources/RenderTreeSerialization.swift | 516 --------------- 17 files changed, 160 insertions(+), 1369 deletions(-) delete mode 100644 Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/Canvas.h delete mode 100644 Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/NullCanvasImpl.h delete mode 100644 Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/NullCanvasImpl.mm rename Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/{ThorVGCanvasImpl.mm => ThorVGCanvasImpl.cpp} (91%) diff --git a/Tests/LottieMetalTest/SoftwareLottieRenderer/PublicHeaders/SoftwareLottieRenderer/SoftwareLottieRenderer.h b/Tests/LottieMetalTest/SoftwareLottieRenderer/PublicHeaders/SoftwareLottieRenderer/SoftwareLottieRenderer.h index 42615a7219..acdb6cbdfe 100644 --- a/Tests/LottieMetalTest/SoftwareLottieRenderer/PublicHeaders/SoftwareLottieRenderer/SoftwareLottieRenderer.h +++ b/Tests/LottieMetalTest/SoftwareLottieRenderer/PublicHeaders/SoftwareLottieRenderer/SoftwareLottieRenderer.h @@ -4,9 +4,6 @@ #import #import -#import -#import - #ifdef __cplusplus extern "C" { #endif @@ -15,8 +12,13 @@ CGRect getPathNativeBoundingBox(CGPathRef _Nonnull path); @interface SoftwareLottieRenderer : NSObject -- (instancetype _Nonnull)initWithAnimationContainer:(LottieAnimationContainer * _Nonnull)animationContainer; +@property (nonatomic, readonly) NSInteger frameCount; +@property (nonatomic, readonly) NSInteger framesPerSecond; +@property (nonatomic, readonly) CGSize size; +- (instancetype _Nullable)initWithData:(NSData * _Nonnull)data; + +- (void)setFrame:(NSInteger)index; - (UIImage * _Nullable)renderForSize:(CGSize)size useReferenceRendering:(bool)useReferenceRendering; @end diff --git a/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/Canvas.h b/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/Canvas.h deleted file mode 100644 index ef420a5071..0000000000 --- a/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/Canvas.h +++ /dev/null @@ -1,94 +0,0 @@ -#ifndef Canvas_h -#define Canvas_h - -#include - -#include - -#include -#include -#include -#include - -namespace lottieRendering { - -class Image { -public: - virtual ~Image() = default; -}; - -class Gradient { -public: - Gradient(std::vector const &colors, std::vector const &locations) : - _colors(colors), - _locations(locations) { - assert(_colors.size() == _locations.size()); - } - - std::vector const &colors() const { - return _colors; - } - - std::vector const &locations() const { - return _locations; - } - -private: - std::vector _colors; - std::vector _locations; -}; - -enum class BlendMode { - Normal, - DestinationIn, - DestinationOut -}; - -enum class PathCommandType { - MoveTo, - LineTo, - CurveTo, - Close -}; - -typedef struct { - PathCommandType type; - CGPoint points[4]; -} PathCommand; - -typedef std::function)> CanvasPathEnumerator; - -class Canvas { -public: - virtual ~Canvas() = default; - - virtual int width() const = 0; - virtual int height() const = 0; - - virtual std::shared_ptr makeLayer(int width, int height) = 0; - - virtual void saveState() = 0; - virtual void restoreState() = 0; - - virtual void fillPath(CanvasPathEnumerator const &enumeratePath, lottie::FillRule fillRule, lottie::Color const &color) = 0; - virtual void linearGradientFillPath(CanvasPathEnumerator const &enumeratePath, lottie::FillRule fillRule, Gradient const &gradient, lottie::Vector2D const &start, lottie::Vector2D const &end) = 0; - virtual void radialGradientFillPath(CanvasPathEnumerator const &enumeratePath, lottie::FillRule fillRule, Gradient const &gradient, lottie::Vector2D const &startCenter, float startRadius, lottie::Vector2D const &endCenter, float endRadius) = 0; - - virtual void strokePath(CanvasPathEnumerator const &enumeratePath, float lineWidth, lottie::LineJoin lineJoin, lottie::LineCap lineCap, float dashPhase, std::vector const &dashPattern, lottie::Color const &color) = 0; - virtual void linearGradientStrokePath(CanvasPathEnumerator const &enumeratePath, float lineWidth, lottie::LineJoin lineJoin, lottie::LineCap lineCap, float dashPhase, std::vector const &dashPattern, Gradient const &gradient, lottie::Vector2D const &start, lottie::Vector2D const &end) = 0; - virtual void radialGradientStrokePath(CanvasPathEnumerator const &enumeratePath, float lineWidth, lottie::LineJoin lineJoin, lottie::LineCap lineCap, float dashPhase, std::vector const &dashPattern, Gradient const &gradient, lottie::Vector2D const &startCenter, float startRadius, lottie::Vector2D const &endCenter, float endRadius) = 0; - - virtual void fill(lottie::CGRect const &rect, lottie::Color const &fillColor) = 0; - virtual void setBlendMode(BlendMode blendMode) = 0; - - virtual void setAlpha(float alpha) = 0; - - virtual void concatenate(lottie::Transform2D const &transform) = 0; - - virtual void draw(std::shared_ptr const &other, lottie::CGRect const &rect) = 0; -}; - -} - -#endif - diff --git a/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/CoreGraphicsCanvasImpl.h b/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/CoreGraphicsCanvasImpl.h index db8064458a..0f4a32be04 100644 --- a/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/CoreGraphicsCanvasImpl.h +++ b/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/CoreGraphicsCanvasImpl.h @@ -1,21 +1,24 @@ #ifndef CoreGraphicsCanvasImpl_h #define CoreGraphicsCanvasImpl_h -#include "Canvas.h" +#include -namespace lottieRendering { +#include -class ImageImpl: public Image { -public: - ImageImpl(::CGImageRef image); - virtual ~ImageImpl(); - ::CGImageRef nativeImage() const; - -private: - CGImageRef _image = nil; -}; +namespace lottie { class CanvasImpl: public Canvas { +public: + class Image { + public: + Image(::CGImageRef image); + virtual ~Image(); + ::CGImageRef nativeImage() const; + + private: + CGImageRef _image = nil; + }; + public: CanvasImpl(int width, int height); CanvasImpl(CGContextRef context, int width, int height); diff --git a/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/CoreGraphicsCanvasImpl.mm b/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/CoreGraphicsCanvasImpl.mm index a798d2feb7..069155a98e 100644 --- a/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/CoreGraphicsCanvasImpl.mm +++ b/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/CoreGraphicsCanvasImpl.mm @@ -3,7 +3,7 @@ #include #include -namespace lottieRendering { +namespace lottie { namespace { @@ -62,19 +62,18 @@ bool addEnumeratedPath(CGContextRef context, CanvasPathEnumerator const &enumera } -ImageImpl::ImageImpl(::CGImageRef image) { +CanvasImpl::Image::Image(::CGImageRef image) { _image = CGImageRetain(image); } -ImageImpl::~ImageImpl() { +CanvasImpl::Image::~Image() { CFRelease(_image); } -::CGImageRef ImageImpl::nativeImage() const { +::CGImageRef CanvasImpl::Image::nativeImage() const { return _image; } - CanvasImpl::CanvasImpl(int width, int height) { _width = width; _height = height; @@ -516,10 +515,10 @@ void CanvasImpl::concatenate(lottie::Transform2D const &transform) { CGContextConcatCTM(_context, CATransform3DGetAffineTransform(nativeTransform(transform))); } -std::shared_ptr CanvasImpl::makeImage() const { +std::shared_ptr CanvasImpl::makeImage() const { ::CGImageRef nativeImage = CGBitmapContextCreateImage(_context); if (nativeImage) { - auto image = std::make_shared(nativeImage); + auto image = std::make_shared(nativeImage); CFRelease(nativeImage); return image; } else { @@ -533,7 +532,7 @@ void CanvasImpl::draw(std::shared_ptr const &other, lottie::CGRect const CGContextDrawLayerInRect(_context, CGRectMake(rect.x, rect.y, rect.width, rect.height), impl->_layer); } else { auto image = impl->makeImage(); - CGContextDrawImage(_context, CGRectMake(rect.x, rect.y, rect.width, rect.height), ((ImageImpl *)image.get())->nativeImage()); + CGContextDrawImage(_context, CGRectMake(rect.x, rect.y, rect.width, rect.height), ((CanvasImpl::Image *)image.get())->nativeImage()); } } diff --git a/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/NullCanvasImpl.h b/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/NullCanvasImpl.h deleted file mode 100644 index 56918703ae..0000000000 --- a/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/NullCanvasImpl.h +++ /dev/null @@ -1,47 +0,0 @@ -#ifndef NullCanvasImpl_h -#define NullCanvasImpl_h - -#include "Canvas.h" - -namespace lottieRendering { - -class NullCanvasImpl: public Canvas { -public: - NullCanvasImpl(int width, int height); - virtual ~NullCanvasImpl(); - - virtual int width() const override; - virtual int height() const override; - - virtual std::shared_ptr makeLayer(int width, int height) override; - - virtual void saveState() override; - virtual void restoreState() override; - - virtual void fillPath(CanvasPathEnumerator const &enumeratePath, lottie::FillRule fillRule, lottie::Color const &color) override; - virtual void linearGradientFillPath(CanvasPathEnumerator const &enumeratePath, lottie::FillRule fillRule, lottieRendering::Gradient const &gradient, lottie::Vector2D const &start, lottie::Vector2D const &end) override; - virtual void radialGradientFillPath(CanvasPathEnumerator const &enumeratePath, lottie::FillRule fillRule, lottieRendering::Gradient const &gradient, lottie::Vector2D const &startCenter, float startRadius, lottie::Vector2D const &endCenter, float endRadius) override; - virtual void strokePath(CanvasPathEnumerator const &enumeratePath, float lineWidth, lottie::LineJoin lineJoin, lottie::LineCap lineCap, float dashPhase, std::vector const &dashPattern, lottie::Color const &color) override; - virtual void linearGradientStrokePath(CanvasPathEnumerator const &enumeratePath, float lineWidth, lottie::LineJoin lineJoin, lottie::LineCap lineCap, float dashPhase, std::vector const &dashPattern, Gradient const &gradient, lottie::Vector2D const &start, lottie::Vector2D const &end) override; - virtual void radialGradientStrokePath(CanvasPathEnumerator const &enumeratePath, float lineWidth, lottie::LineJoin lineJoin, lottie::LineCap lineCap, float dashPhase, std::vector const &dashPattern, Gradient const &gradient, lottie::Vector2D const &startCenter, float startRadius, lottie::Vector2D const &endCenter, float endRadius) override; - virtual void fill(lottie::CGRect const &rect, lottie::Color const &fillColor) override; - - virtual void setBlendMode(BlendMode blendMode) override; - - virtual void setAlpha(float alpha) override; - - virtual void concatenate(lottie::Transform2D const &transform) override; - - virtual void draw(std::shared_ptr const &other, lottie::CGRect const &rect) override; - - void flush(); - -private: - float _width = 0.0f; - float _height = 0.0f; - lottie::Transform2D _transform; -}; - -} - -#endif diff --git a/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/NullCanvasImpl.mm b/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/NullCanvasImpl.mm deleted file mode 100644 index 6030201180..0000000000 --- a/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/NullCanvasImpl.mm +++ /dev/null @@ -1,81 +0,0 @@ -#include "NullCanvasImpl.h" - -namespace lottieRendering { - -namespace { - -void addEnumeratedPath(CanvasPathEnumerator const &enumeratePath) { - enumeratePath([&](PathCommand const &command) { - }); -} - -} - -NullCanvasImpl::NullCanvasImpl(int width, int height) : -_width(width), _height(height), _transform(lottie::Transform2D::identity()) { -} - -NullCanvasImpl::~NullCanvasImpl() { -} - -int NullCanvasImpl::width() const { - return _width; -} - -int NullCanvasImpl::height() const { - return _height; -} - -std::shared_ptr NullCanvasImpl::makeLayer(int width, int height) { - return std::make_shared(width, height); -} - -void NullCanvasImpl::saveState() { -} - -void NullCanvasImpl::restoreState() { -} - -void NullCanvasImpl::fillPath(CanvasPathEnumerator const &enumeratePath, lottie::FillRule fillRule, lottie::Color const &color) { - addEnumeratedPath(enumeratePath); -} - -void NullCanvasImpl::linearGradientFillPath(CanvasPathEnumerator const &enumeratePath, lottie::FillRule fillRule, Gradient const &gradient, lottie::Vector2D const &start, lottie::Vector2D const &end) { - addEnumeratedPath(enumeratePath); -} - -void NullCanvasImpl::radialGradientFillPath(CanvasPathEnumerator const &enumeratePath, lottie::FillRule fillRule, Gradient const &gradient, lottie::Vector2D const &startCenter, float startRadius, lottie::Vector2D const &endCenter, float endRadius) { - addEnumeratedPath(enumeratePath); -} - -void NullCanvasImpl::strokePath(CanvasPathEnumerator const &enumeratePath, float lineWidth, lottie::LineJoin lineJoin, lottie::LineCap lineCap, float dashPhase, std::vector const &dashPattern, lottie::Color const &color) { - addEnumeratedPath(enumeratePath); -} - -void NullCanvasImpl::linearGradientStrokePath(CanvasPathEnumerator const &enumeratePath, float lineWidth, lottie::LineJoin lineJoin, lottie::LineCap lineCap, float dashPhase, std::vector const &dashPattern, Gradient const &gradient, lottie::Vector2D const &start, lottie::Vector2D const &end) { - addEnumeratedPath(enumeratePath); -} - -void NullCanvasImpl::radialGradientStrokePath(CanvasPathEnumerator const &enumeratePath, float lineWidth, lottie::LineJoin lineJoin, lottie::LineCap lineCap, float dashPhase, std::vector const &dashPattern, Gradient const &gradient, lottie::Vector2D const &startCenter, float startRadius, lottie::Vector2D const &endCenter, float endRadius) { - addEnumeratedPath(enumeratePath); -} - -void NullCanvasImpl::fill(lottie::CGRect const &rect, lottie::Color const &fillColor) { -} - -void NullCanvasImpl::setBlendMode(BlendMode blendMode) { -} - -void NullCanvasImpl::setAlpha(float alpha) { -} - -void NullCanvasImpl::concatenate(lottie::Transform2D const &transform) { -} - -void NullCanvasImpl::draw(std::shared_ptr const &other, lottie::CGRect const &rect) { -} - -void NullCanvasImpl::flush() { -} - -} diff --git a/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/SoftwareLottieRenderer.mm b/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/SoftwareLottieRenderer.mm index a2f51e674a..08f3c85d12 100644 --- a/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/SoftwareLottieRenderer.mm +++ b/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/SoftwareLottieRenderer.mm @@ -1,577 +1,112 @@ #import -#import "Canvas.h" +#import +#import + #import "CoreGraphicsCanvasImpl.h" #import "ThorVGCanvasImpl.h" -#import "NullCanvasImpl.h" #include -#include #include #include -namespace { - -static constexpr float minVisibleAlpha = 0.5f / 255.0f; - -static constexpr float minGlobalRectCalculationSize = 200.0f; - -struct TransformedPath { - lottie::BezierPath path; - lottie::Transform2D transform; - - TransformedPath(lottie::BezierPath const &path_, lottie::Transform2D const &transform_) : - path(path_), - transform(transform_) { - } -}; - -static lottie::CGRect collectPathBoundingBoxes(std::shared_ptr item, size_t subItemLimit, lottie::Transform2D const &parentTransform, bool skipApplyTransform, lottie::BezierPathsBoundingBoxContext &bezierPathsBoundingBoxContext) { - //TODO:remove skipApplyTransform - lottie::Transform2D effectiveTransform = parentTransform; - if (!skipApplyTransform && item->isGroup) { - effectiveTransform = item->transform * effectiveTransform; - } - - size_t maxSubitem = std::min(item->subItems.size(), subItemLimit); - - lottie::CGRect boundingBox(0.0, 0.0, 0.0, 0.0); - if (item->path) { - if (item->path->needsBoundsRecalculation) { - item->path->bounds = lottie::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]; - - lottie::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 item, size_t subItemLimit, lottie::Transform2D const &parentTransform, bool skipApplyTransform, std::function const &onPath) { - //TODO:remove skipApplyTransform - lottie::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, 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); - } -} - -} - -namespace lottie { - -static std::optional getRenderContentItemGlobalRect(std::shared_ptr const &contentItem, lottie::Vector2D const &globalSize, lottie::Transform2D const &parentTransform, BezierPathsBoundingBoxContext &bezierPathsBoundingBoxContext) { - auto currentTransform = parentTransform; - Transform2D localTransform = contentItem->transform; - currentTransform = localTransform * currentTransform; - - std::optional globalRect; - for (const auto &shadingVariant : contentItem->shadings) { - lottie::CGRect shapeBounds = collectPathBoundingBoxes(contentItem, shadingVariant->subItemLimit, lottie::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 shapeGlobalBounds = shapeBounds.applyingTransform(currentTransform); - if (globalRect) { - globalRect = globalRect->unionWith(shapeGlobalBounds); - } else { - globalRect = shapeGlobalBounds; - } - } - - for (const auto &subItem : contentItem->subItems) { - auto subGlobalRect = getRenderContentItemGlobalRect(subItem, globalSize, currentTransform, bezierPathsBoundingBoxContext); - if (subGlobalRect) { - if (globalRect) { - globalRect = globalRect->unionWith(subGlobalRect.value()); - } else { - globalRect = subGlobalRect.value(); - } - } - } - - if (globalRect) { - CGRect integralGlobalRect( - std::floor(globalRect->x), - std::floor(globalRect->y), - std::ceil(globalRect->width + globalRect->x - floor(globalRect->x)), - std::ceil(globalRect->height + globalRect->y - floor(globalRect->y)) - ); - return integralGlobalRect.intersection(CGRect(0.0, 0.0, globalSize.x, globalSize.y)); - } else { - return std::nullopt; - } -} - -static std::optional getRenderNodeGlobalRect(std::shared_ptr const &node, lottie::Vector2D const &globalSize, lottie::Transform2D const &parentTransform, bool isInvertedMatte, BezierPathsBoundingBoxContext &bezierPathsBoundingBoxContext) { - if (node->isHidden() || node->alpha() < minVisibleAlpha) { - return std::nullopt; - } - - auto currentTransform = parentTransform; - Transform2D localTransform = node->transform(); - currentTransform = localTransform * currentTransform; - - std::optional globalRect; - if (node->_contentItem) { - globalRect = getRenderContentItemGlobalRect(node->_contentItem, globalSize, currentTransform, bezierPathsBoundingBoxContext); - } - - if (isInvertedMatte) { - CGRect globalBounds = CGRect(0.0f, 0.0f, node->size().x, node->size().y).applyingTransform(currentTransform); - if (globalRect) { - globalRect = globalRect->unionWith(globalBounds); - } else { - globalRect = globalBounds; - } - } - - for (const auto &subNode : node->subnodes()) { - auto subGlobalRect = getRenderNodeGlobalRect(subNode, globalSize, currentTransform, false, bezierPathsBoundingBoxContext); - if (subGlobalRect) { - if (globalRect) { - globalRect = globalRect->unionWith(subGlobalRect.value()); - } else { - globalRect = subGlobalRect.value(); - } - } - } - - if (globalRect) { - CGRect integralGlobalRect( - std::floor(globalRect->x), - std::floor(globalRect->y), - std::ceil(globalRect->width + globalRect->x - floor(globalRect->x)), - std::ceil(globalRect->height + globalRect->y - floor(globalRect->y)) - ); - return integralGlobalRect.intersection(CGRect(0.0, 0.0, globalSize.x, globalSize.y)); - } else { - return std::nullopt; - } -} - -} - -namespace { - -static void drawLottieContentItem(std::shared_ptr const &parentContext, std::shared_ptr item, float parentAlpha, lottie::Vector2D const &globalSize, lottie::Transform2D const &parentTransform, lottie::BezierPathsBoundingBoxContext &bezierPathsBoundingBoxContext) { - auto currentTransform = parentTransform; - lottie::Transform2D localTransform = item->transform; - currentTransform = localTransform * currentTransform; - - float normalizedOpacity = item->alpha; - float layerAlpha = ((float)normalizedOpacity) * parentAlpha; - - if (normalizedOpacity == 0.0f) { - return; - } - - parentContext->saveState(); - - std::shared_ptr const *currentContext; - std::shared_ptr tempContext; - - bool needsTempContext = false; - needsTempContext = layerAlpha != 1.0 && item->drawContentCount > 1; - - std::optional globalRect; - if (needsTempContext) { - if (globalSize.x <= minGlobalRectCalculationSize && globalSize.y <= minGlobalRectCalculationSize) { - globalRect = lottie::CGRect(0.0, 0.0, globalSize.x, globalSize.y); - } else { - globalRect = lottie::getRenderContentItemGlobalRect(item, globalSize, parentTransform, bezierPathsBoundingBoxContext); - } - if (!globalRect || globalRect->width <= 0.0f || globalRect->height <= 0.0f) { - parentContext->restoreState(); - return; - } - - auto tempContextValue = parentContext->makeLayer((int)(globalRect->width), (int)(globalRect->height)); - tempContext = tempContextValue; - - currentContext = &tempContext; - (*currentContext)->concatenate(lottie::Transform2D::identity().translated(lottie::Vector2D(-globalRect->x, -globalRect->y))); - - (*currentContext)->saveState(); - (*currentContext)->concatenate(currentTransform); - } else { - currentContext = &parentContext; - } - - parentContext->concatenate(item->transform); - - float renderAlpha = 1.0; - if (tempContext) { - renderAlpha = 1.0; - } else { - renderAlpha = layerAlpha; - } - - for (const auto &shading : item->shadings) { - lottieRendering::CanvasPathEnumerator iteratePaths; - iteratePaths = [&](std::function iterate) { - enumeratePaths(item, shading->subItemLimit, lottie::Transform2D::identity(), true, [&](lottie::BezierPath const &sourcePath, lottie::Transform2D const &transform) { - auto path = sourcePath.copyUsingTransform(transform); - - lottieRendering::PathCommand pathCommand; - std::optional previousElement; - for (const auto &element : path.elements()) { - if (previousElement.has_value()) { - if (previousElement->vertex.outTangentRelative().isZero() && element.vertex.inTangentRelative().isZero()) { - pathCommand.type = lottieRendering::PathCommandType::LineTo; - pathCommand.points[0] = CGPointMake(element.vertex.point.x, element.vertex.point.y); - iterate(pathCommand); - } else { - pathCommand.type = lottieRendering::PathCommandType::CurveTo; - pathCommand.points[2] = CGPointMake(element.vertex.point.x, element.vertex.point.y); - pathCommand.points[1] = CGPointMake(element.vertex.inTangent.x, element.vertex.inTangent.y); - pathCommand.points[0] = CGPointMake(previousElement->vertex.outTangent.x, previousElement->vertex.outTangent.y); - iterate(pathCommand); - } - } else { - pathCommand.type = lottieRendering::PathCommandType::MoveTo; - pathCommand.points[0] = CGPointMake(element.vertex.point.x, element.vertex.point.y); - iterate(pathCommand); - } - previousElement = element; - } - if (path.closed().value_or(true)) { - pathCommand.type = lottieRendering::PathCommandType::Close; - iterate(pathCommand); - } - }); - }; - - if (shading->stroke) { - if (shading->stroke->shading->type() == lottie::RenderTreeNodeContentItem::ShadingType::Solid) { - lottie::RenderTreeNodeContentItem::SolidShading *solidShading = (lottie::RenderTreeNodeContentItem::SolidShading *)shading->stroke->shading.get(); - - if (solidShading->opacity != 0.0) { - lottie::LineJoin lineJoin = lottie::LineJoin::Bevel; - switch (shading->stroke->lineJoin) { - case lottie::LineJoin::Bevel: { - lineJoin = lottie::LineJoin::Bevel; - break; - } - case lottie::LineJoin::Round: { - lineJoin = lottie::LineJoin::Round; - break; - } - case lottie::LineJoin::Miter: { - lineJoin = lottie::LineJoin::Miter; - break; - } - default: { - break; - } - } - - lottie::LineCap lineCap = lottie::LineCap::Square; - switch (shading->stroke->lineCap) { - case lottie::LineCap::Butt: { - lineCap = lottie::LineCap::Butt; - break; - } - case lottie::LineCap::Round: { - lineCap = lottie::LineCap::Round; - break; - } - case lottie::LineCap::Square: { - lineCap = lottie::LineCap::Square; - break; - } - default: { - break; - } - } - - std::vector dashPattern; - if (!shading->stroke->dashPattern.empty()) { - dashPattern = shading->stroke->dashPattern; - } - - (*currentContext)->strokePath(iteratePaths, shading->stroke->lineWidth, lineJoin, lineCap, shading->stroke->dashPhase, dashPattern, lottie::Color(solidShading->color.r, solidShading->color.g, solidShading->color.b, solidShading->color.a * solidShading->opacity * renderAlpha)); - } else if (shading->stroke->shading->type() == lottie::RenderTreeNodeContentItem::ShadingType::Gradient) { - //TODO:gradient stroke - } - } - } else if (shading->fill) { - lottie::FillRule rule = lottie::FillRule::NonZeroWinding; - switch (shading->fill->rule) { - case lottie::FillRule::EvenOdd: { - rule = lottie::FillRule::EvenOdd; - break; - } - case lottie::FillRule::NonZeroWinding: { - rule = lottie::FillRule::NonZeroWinding; - break; - } - default: { - break; - } - } - - if (shading->fill->shading->type() == lottie::RenderTreeNodeContentItem::ShadingType::Solid) { - lottie::RenderTreeNodeContentItem::SolidShading *solidShading = (lottie::RenderTreeNodeContentItem::SolidShading *)shading->fill->shading.get(); - if (solidShading->opacity != 0.0) { - (*currentContext)->fillPath(iteratePaths, rule, lottie::Color(solidShading->color.r, solidShading->color.g, solidShading->color.b, solidShading->color.a * solidShading->opacity * renderAlpha)); - } - } else if (shading->fill->shading->type() == lottie::RenderTreeNodeContentItem::ShadingType::Gradient) { - lottie::RenderTreeNodeContentItem::GradientShading *gradientShading = (lottie::RenderTreeNodeContentItem::GradientShading *)shading->fill->shading.get(); - - if (gradientShading->opacity != 0.0) { - std::vector colors; - std::vector locations; - for (const auto &color : gradientShading->colors) { - colors.push_back(lottie::Color(color.r, color.g, color.b, color.a * gradientShading->opacity * renderAlpha)); - } - locations = gradientShading->locations; - - lottieRendering::Gradient gradient(colors, locations); - lottie::Vector2D start(gradientShading->start.x, gradientShading->start.y); - lottie::Vector2D end(gradientShading->end.x, gradientShading->end.y); - - switch (gradientShading->gradientType) { - case lottie::GradientType::Linear: { - (*currentContext)->linearGradientFillPath(iteratePaths, rule, gradient, start, end); - break; - } - case lottie::GradientType::Radial: { - (*currentContext)->radialGradientFillPath(iteratePaths, rule, gradient, start, 0.0, start, start.distanceTo(end)); - break; - } - default: { - break; - } - } - } - } - } - } - - for (auto it = item->subItems.rbegin(); it != item->subItems.rend(); it++) { - const auto &subItem = *it; - drawLottieContentItem(*currentContext, subItem, renderAlpha, globalSize, currentTransform, bezierPathsBoundingBoxContext); - } - - if (tempContext) { - tempContext->restoreState(); - - parentContext->concatenate(currentTransform.inverted()); - parentContext->setAlpha(layerAlpha); - parentContext->draw(tempContext, globalRect.value()); - parentContext->setAlpha(1.0); - } - - parentContext->restoreState(); -} - -static void renderLottieRenderNode(std::shared_ptr node, std::shared_ptr const &parentContext, lottie::Vector2D const &globalSize, lottie::Transform2D const &parentTransform, float parentAlpha, bool isInvertedMatte, lottie::BezierPathsBoundingBoxContext &bezierPathsBoundingBoxContext) { - float normalizedOpacity = node->alpha(); - float layerAlpha = ((float)normalizedOpacity) * parentAlpha; - - if (node->isHidden() || normalizedOpacity < minVisibleAlpha) { - return; - } - - auto currentTransform = parentTransform; - lottie::Transform2D localTransform = node->transform(); - currentTransform = localTransform * currentTransform; - - std::shared_ptr maskContext; - std::shared_ptr currentContext; - std::shared_ptr tempContext; - - bool masksToBounds = node->masksToBounds(); - if (masksToBounds) { - lottie::CGRect effectiveGlobalBounds = lottie::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(lottie::CGRect(0.0, 0.0, globalSize.x, globalSize.y))) { - masksToBounds = false; - } - } - - parentContext->saveState(); - - bool needsTempContext = false; - if (node->mask() && !node->mask()->isHidden() && node->mask()->alpha() >= minVisibleAlpha) { - needsTempContext = true; - } else { - needsTempContext = layerAlpha != 1.0 || masksToBounds; - } - - std::optional globalRect; - if (needsTempContext) { - if (globalSize.x <= minGlobalRectCalculationSize && globalSize.y <= minGlobalRectCalculationSize) { - globalRect = lottie::CGRect(0.0, 0.0, globalSize.x, globalSize.y); - } else { - globalRect = lottie::getRenderNodeGlobalRect(node, globalSize, parentTransform, false, bezierPathsBoundingBoxContext); - } - if (!globalRect || globalRect->width <= 0.0f || globalRect->height <= 0.0f) { - parentContext->restoreState(); - return; - } - - if ((node->mask() && !node->mask()->isHidden() && node->mask()->alpha() >= minVisibleAlpha) || masksToBounds) { - auto maskBackingStorage = parentContext->makeLayer((int)(globalRect->width), (int)(globalRect->height)); - - maskBackingStorage->concatenate(lottie::Transform2D::identity().translated(lottie::Vector2D(-globalRect->x, -globalRect->y))); - maskBackingStorage->concatenate(currentTransform); - - if (masksToBounds) { - maskBackingStorage->fill(lottie::CGRect(0.0f, 0.0f, node->size().x, node->size().y), lottie::Color(1.0f, 1.0f, 1.0f, 1.0f)); - } - if (node->mask() && !node->mask()->isHidden() && node->mask()->alpha() >= minVisibleAlpha) { - renderLottieRenderNode(node->mask(), maskBackingStorage, globalSize, currentTransform, 1.0, node->invertMask(), bezierPathsBoundingBoxContext); - } - - maskContext = maskBackingStorage; - } - - auto tempContextValue = parentContext->makeLayer((int)(globalRect->width), (int)(globalRect->height)); - tempContext = tempContextValue; - - currentContext = tempContextValue; - currentContext->concatenate(lottie::Transform2D::identity().translated(lottie::Vector2D(-globalRect->x, -globalRect->y))); - - currentContext->saveState(); - currentContext->concatenate(currentTransform); - } else { - currentContext = parentContext; - } - - parentContext->concatenate(node->transform()); - - float renderAlpha = 1.0f; - if (tempContext) { - renderAlpha = 1.0f; - } else { - renderAlpha = layerAlpha; - } - - if (node->_contentItem) { - drawLottieContentItem(currentContext, node->_contentItem, renderAlpha, globalSize, currentTransform, bezierPathsBoundingBoxContext); - } - - if (isInvertedMatte) { - currentContext->fill(lottie::CGRect(0.0f, 0.0f, node->size().x, node->size().y), lottie::Color(0.0f, 0.0f, 0.0f, 1.0f)); - currentContext->setBlendMode(lottieRendering::BlendMode::DestinationOut); - } - - for (const auto &subnode : node->subnodes()) { - renderLottieRenderNode(subnode, currentContext, globalSize, currentTransform, renderAlpha, false, bezierPathsBoundingBoxContext); - } - - if (tempContext) { - tempContext->restoreState(); - - if (maskContext) { - tempContext->setBlendMode(lottieRendering::BlendMode::DestinationIn); - tempContext->draw(maskContext, lottie::CGRect(globalRect->x, globalRect->y, globalRect->width, globalRect->height)); - } - - parentContext->concatenate(currentTransform.inverted()); - parentContext->setAlpha(layerAlpha); - parentContext->draw(tempContext, globalRect.value()); - } - - parentContext->restoreState(); -} - -} - CGRect getPathNativeBoundingBox(CGPathRef _Nonnull path) { auto rect = calculatePathBoundingBox(path); return CGRectMake(rect.origin.x, rect.origin.y, rect.size.width, rect.size.height); } @interface SoftwareLottieRenderer() { - LottieAnimationContainer *_animationContainer; - std::shared_ptr _bezierPathsBoundingBoxContext; + std::shared_ptr _renderer; + std::shared_ptr _canvasRenderer; } @end @implementation SoftwareLottieRenderer -- (instancetype _Nonnull)initWithAnimationContainer:(LottieAnimationContainer * _Nonnull)animationContainer { +- (instancetype _Nullable)initWithData:(NSData * _Nonnull)data { self = [super init]; if (self != nil) { - _animationContainer = animationContainer; - _bezierPathsBoundingBoxContext = std::make_shared(); + _renderer = lottie::Renderer::make(std::string((uint8_t const *)data.bytes, ((uint8_t const *)data.bytes) + data.length)); + if (!_renderer) { + return nil; + } + + _canvasRenderer = std::make_shared(); } return self; } +- (NSInteger)frameCount { + return (NSInteger)_renderer->frameCount(); +} + +- (NSInteger)framesPerSecond { + return (NSInteger)_renderer->framesPerSecond(); +} + +- (CGSize)size { + lottie::Vector2D size = _renderer->size(); + return CGSizeMake(size.x, size.y); +} + +- (void)setFrame:(NSInteger)index { + _renderer->setFrame((int)index); +} + - (UIImage * _Nullable)renderForSize:(CGSize)size useReferenceRendering:(bool)useReferenceRendering { - LottieAnimation *animation = _animationContainer.animation; - std::shared_ptr renderNode = [_animationContainer internalGetRootRenderTreeNode]; + std::shared_ptr renderNode = _renderer->renderNode(); if (!renderNode) { return nil; } - lottie::Transform2D rootTransform = lottie::Transform2D::identity().scaled(lottie::Vector2D(size.width / (float)animation.size.width, size.height / (float)animation.size.height)); - if (useReferenceRendering) { - auto context = std::make_shared((int)size.width, (int)size.height); + auto context = std::make_shared((int)size.width, (int)size.height); - CGPoint scale = CGPointMake(size.width / (CGFloat)animation.size.width, size.height / (CGFloat)animation.size.height); - context->concatenate(lottie::Transform2D::makeScale(scale.x, scale.y)); - - renderLottieRenderNode(renderNode, context, lottie::Vector2D(context->width(), context->height()), rootTransform, 1.0, false, *_bezierPathsBoundingBoxContext.get()); + _canvasRenderer->render(_renderer, context, lottie::Vector2D(size.width, size.height)); auto image = context->makeImage(); - return [[UIImage alloc] initWithCGImage:std::static_pointer_cast(image)->nativeImage()]; + return [[UIImage alloc] initWithCGImage:std::static_pointer_cast(image)->nativeImage()]; } else { - //auto context = std::make_shared((int)size.width, (int)size.height); - auto context = std::make_shared((int)size.width, (int)size.height); - - CGPoint scale = CGPointMake(size.width / (CGFloat)animation.size.width, size.height / (CGFloat)animation.size.height); - context->concatenate(lottie::Transform2D::makeScale(scale.x, scale.y)); - - renderLottieRenderNode(renderNode, context, lottie::Vector2D(context->width(), context->height()), rootTransform, 1.0, false, *_bezierPathsBoundingBoxContext.get()); - - return nil; + if ((int64_t)"" > 0) { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + lottie::ThorVGCanvasImpl::initializeOnce(); + }); + + int bytesPerRow = ((int)size.width) * 4; + auto context = std::make_shared((int)size.width, (int)size.height, bytesPerRow); + + _canvasRenderer->render(_renderer, context, lottie::Vector2D(size.width, size.height)); + + context->flush(); + + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); + CGBitmapInfo bitmapInfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host; + + CGContextRef targetContext = CGBitmapContextCreate((void *)context->backingData(), (int)size.width, (int)size.height, 8, bytesPerRow, colorSpace, bitmapInfo); + CGColorSpaceRelease(colorSpace); + + //CGContextSetFillColorWithColor(targetContext, [UIColor blueColor].CGColor); + //CGContextFillRect(targetContext, CGRectMake(0.0f, 0.0f, size.width, size.height)); + + CGImageRef bitmapImage = CGBitmapContextCreateImage(targetContext); + UIImage *image = [[UIImage alloc] initWithCGImage:bitmapImage scale:1.0f orientation:UIImageOrientationDownMirrored]; + CGImageRelease(bitmapImage); + + CGContextRelease(targetContext); + + return image; + } else { + auto context = std::make_shared((int)size.width, (int)size.height); + + _canvasRenderer->render(_renderer, context, lottie::Vector2D(size.width, size.height)); + + return nil; + } } + return nil; } @end diff --git a/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/ThorVGCanvasImpl.mm b/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/ThorVGCanvasImpl.cpp similarity index 91% rename from Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/ThorVGCanvasImpl.mm rename to Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/ThorVGCanvasImpl.cpp index 3405a31b3a..1094b33ea6 100644 --- a/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/ThorVGCanvasImpl.mm +++ b/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/ThorVGCanvasImpl.cpp @@ -1,9 +1,6 @@ #include "ThorVGCanvasImpl.h" -#include -#include - -namespace lottieRendering { +namespace lottie { namespace { @@ -31,35 +28,34 @@ void tvgPath(CanvasPathEnumerator const &enumeratePath, tvg::Shape *shape) { } tvg::Matrix tvgTransform(lottie::Transform2D const &transform) { - CGAffineTransform affineTransform = CATransform3DGetAffineTransform(lottie::nativeTransform(transform)); tvg::Matrix result; - result.e11 = affineTransform.a; - result.e21 = affineTransform.b; - result.e31 = 0.0f; - result.e12 = affineTransform.c; - result.e22 = affineTransform.d; - result.e32 = 0.0f; - result.e13 = affineTransform.tx; - result.e23 = affineTransform.ty; - result.e33 = 1.0f; + result.e11 = transform.rows().columns[0][0]; + result.e21 = transform.rows().columns[0][1]; + result.e31 = transform.rows().columns[0][2]; + result.e12 = transform.rows().columns[1][0]; + result.e22 = transform.rows().columns[1][1]; + result.e32 = transform.rows().columns[1][2]; + result.e13 = transform.rows().columns[2][0]; + result.e23 = transform.rows().columns[2][1]; + result.e33 = transform.rows().columns[2][2]; + return result; } } -ThorVGCanvasImpl::ThorVGCanvasImpl(int width, int height) : +void ThorVGCanvasImpl::initializeOnce() { + tvg::Initializer::init(0); +} + +ThorVGCanvasImpl::ThorVGCanvasImpl(int width, int height, int bytesPerRow) : _width(width), _height(height), _transform(lottie::Transform2D::identity()) { - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - tvg::Initializer::init(0); - }); - _canvas = tvg::SwCanvas::gen(); - _bytesPerRow = width * 4; + _bytesPerRow = bytesPerRow; - static uint32_t *sharedBackingData = (uint32_t *)malloc(_bytesPerRow * height); - _backingData = sharedBackingData; + _backingData = (uint32_t *)malloc(_bytesPerRow * height); + memset(_backingData, 0, _bytesPerRow * height); _canvas->target(_backingData, _bytesPerRow / 4, width, height, tvg::SwCanvas::ARGB8888); } @@ -76,7 +72,7 @@ int ThorVGCanvasImpl::height() const { } std::shared_ptr ThorVGCanvasImpl::makeLayer(int width, int height) { - return std::make_shared(width, height); + return std::make_shared(width, height, width * 4); } void ThorVGCanvasImpl::saveState() { diff --git a/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/ThorVGCanvasImpl.h b/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/ThorVGCanvasImpl.h index dbd676b5e9..10cc56489b 100644 --- a/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/ThorVGCanvasImpl.h +++ b/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/ThorVGCanvasImpl.h @@ -1,15 +1,17 @@ #ifndef ThorVGCanvasImpl_h #define ThorVGCanvasImpl_h -#include "Canvas.h" +#include #include -namespace lottieRendering { +namespace lottie { class ThorVGCanvasImpl: public Canvas { public: - ThorVGCanvasImpl(int width, int height); + static void initializeOnce(); + + ThorVGCanvasImpl(int width, int height, int bytesPerRow); virtual ~ThorVGCanvasImpl(); virtual int width() const override; @@ -21,8 +23,8 @@ public: virtual void restoreState() override; virtual void fillPath(CanvasPathEnumerator const &enumeratePath, lottie::FillRule fillRule, lottie::Color const &color) override; - virtual void linearGradientFillPath(CanvasPathEnumerator const &enumeratePath, lottie::FillRule fillRule, lottieRendering::Gradient const &gradient, lottie::Vector2D const &start, lottie::Vector2D const &end) override; - virtual void radialGradientFillPath(CanvasPathEnumerator const &enumeratePath, lottie::FillRule fillRule, lottieRendering::Gradient const &gradient, lottie::Vector2D const &startCenter, float startRadius, lottie::Vector2D const &endCenter, float endRadius) override; + virtual void linearGradientFillPath(CanvasPathEnumerator const &enumeratePath, lottie::FillRule fillRule, lottie::Gradient const &gradient, lottie::Vector2D const &start, lottie::Vector2D const &end) override; + virtual void radialGradientFillPath(CanvasPathEnumerator const &enumeratePath, lottie::FillRule fillRule, lottie::Gradient const &gradient, lottie::Vector2D const &startCenter, float startRadius, lottie::Vector2D const &endCenter, float endRadius) override; virtual void strokePath(CanvasPathEnumerator const &enumeratePath, float lineWidth, lottie::LineJoin lineJoin, lottie::LineCap lineCap, float dashPhase, std::vector const &dashPattern, lottie::Color const &color) override; virtual void linearGradientStrokePath(CanvasPathEnumerator const &enumeratePath, float lineWidth, lottie::LineJoin lineJoin, lottie::LineCap lineCap, float dashPhase, std::vector const &dashPattern, Gradient const &gradient, lottie::Vector2D const &start, lottie::Vector2D const &end) override; virtual void radialGradientStrokePath(CanvasPathEnumerator const &enumeratePath, float lineWidth, lottie::LineJoin lineJoin, lottie::LineCap lineCap, float dashPhase, std::vector const &dashPattern, Gradient const &gradient, lottie::Vector2D const &startCenter, float startRadius, lottie::Vector2D const &endCenter, float endRadius) override; diff --git a/Tests/LottieMetalTest/Sources/CompareToReferenceRendering.swift b/Tests/LottieMetalTest/Sources/CompareToReferenceRendering.swift index 7b67ef5470..2d7babbfed 100644 --- a/Tests/LottieMetalTest/Sources/CompareToReferenceRendering.swift +++ b/Tests/LottieMetalTest/Sources/CompareToReferenceRendering.swift @@ -61,37 +61,33 @@ func areImagesEqual(_ lhs: UIImage, _ rhs: UIImage) -> UIImage? { } @available(iOS 13.0, *) -func processDrawAnimation(baseCachePath: String, path: String, name: String, size: CGSize, alwaysDraw: Bool, updateImage: @escaping (UIImage?, UIImage?) -> Void) async -> Bool { +func processDrawAnimation(baseCachePath: String, path: String, name: String, size: CGSize, alwaysDraw: Bool, useNonReferenceRendering: Bool, updateImage: @escaping (UIImage?, UIImage?) -> Void) async -> Bool { guard let data = try? Data(contentsOf: URL(fileURLWithPath: path)) else { print("Could not load \(path)") return false } - guard let animation = LottieAnimation(data: data) else { - print("Could not parse animation at \(path)") - return false - } - - let layer = LottieAnimationContainer(animation: animation) - let cacheFolderPath = cacheReferenceFolderPath(baseCachePath: baseCachePath, width: Int(size.width), name: name) if !FileManager.default.fileExists(atPath: cacheFolderPath) { let _ = await cacheReferenceAnimation(baseCachePath: baseCachePath, width: Int(size.width), path: path, name: name) } - let renderer = SoftwareLottieRenderer(animationContainer: layer) + guard let renderer = SoftwareLottieRenderer(data: data) else { + print("Could not parse animation at \(path)") + return false + } - for i in 0 ..< min(100000, animation.frameCount) { + for i in 0 ..< min(100000, renderer.frameCount) { let frameResult = autoreleasepool { - let frameIndex = i % animation.frameCount + let frameIndex = i % renderer.frameCount let referenceImageData = try! Data(contentsOf: URL(fileURLWithPath: cacheFolderPath + "/frame\(frameIndex)")) let referenceImage = decompressImageFrame(data: referenceImageData) - layer.update(frameIndex) - let image = renderer.render(for: size, useReferenceRendering: true)! + renderer.setFrame(frameIndex) + let image = renderer.render(for: size, useReferenceRendering: !useNonReferenceRendering)! - if let diffImage = areImagesEqual(image, referenceImage) { + if !useNonReferenceRendering, let diffImage = areImagesEqual(image, referenceImage) { updateImage(diffImage, referenceImage) print("Mismatch in frame \(frameIndex)") diff --git a/Tests/LottieMetalTest/Sources/ViewController.swift b/Tests/LottieMetalTest/Sources/ViewController.swift index efe7770baa..dafda6f7bf 100644 --- a/Tests/LottieMetalTest/Sources/ViewController.swift +++ b/Tests/LottieMetalTest/Sources/ViewController.swift @@ -14,7 +14,7 @@ private final class ReferenceCompareTest { private let imageView = UIImageView() private let referenceImageView = UIImageView() - init(view: UIView) { + init(view: UIView, testNonReference: Bool) { lottieSwift_getPathNativeBoundingBox = { path in return getPathNativeBoundingBox(path) } @@ -78,9 +78,9 @@ private final class ReferenceCompareTest { } var continueFromName: String? - continueFromName = "1137162165791227948.json" + //continueFromName = "1137162165791227948.json" - let _ = await processAnimationFolderAsync(basePath: bundlePath, path: "", stopOnFailure: true, process: { path, name, alwaysDraw in + let _ = await processAnimationFolderAsync(basePath: bundlePath, path: "", stopOnFailure: !testNonReference, process: { path, name, alwaysDraw in if let continueFromNameValue = continueFromName { if continueFromNameValue == name { continueFromName = nil @@ -91,7 +91,7 @@ private final class ReferenceCompareTest { let size = sizeMapping[name] ?? defaultSize - let result = await processDrawAnimation(baseCachePath: baseCachePath, path: path, name: name, size: CGSize(width: size, height: size), alwaysDraw: alwaysDraw, updateImage: { image, referenceImage in + let result = await processDrawAnimation(baseCachePath: baseCachePath, path: path, name: name, size: CGSize(width: size, height: size), alwaysDraw: alwaysDraw, useNonReferenceRendering: testNonReference, updateImage: { image, referenceImage in DispatchQueue.main.async { self.imageView.image = image self.referenceImageView.image = referenceImage @@ -119,12 +119,12 @@ public final class ViewController: UIViewController { self.view.layer.addSublayer(MetalEngine.shared.rootLayer) - if !"".isEmpty { + if "".isEmpty { if #available(iOS 13.0, *) { - self.test = ReferenceCompareTest(view: self.view) + self.test = ReferenceCompareTest(view: self.view, testNonReference: true) } } else if !"".isEmpty { - let cachedAnimation = cacheLottieMetalAnimation(path: filePath)! + /*let cachedAnimation = cacheLottieMetalAnimation(path: filePath)! let animation = parseCachedLottieMetalAnimation(data: cachedAnimation)! /*let animationData = try! Data(contentsOf: URL(fileURLWithPath: filePath)) @@ -146,29 +146,23 @@ public final class ViewController: UIViewController { self.link = SharedDisplayLinkDriver.shared.add(framesPerSecond: .max, { _ in lottieLayer.frameIndex = (lottieLayer.frameIndex + 1) % animation.frameCount lottieLayer.setNeedsUpdate() - }) + })*/ } else if "".isEmpty { Thread { let animationData = try! Data(contentsOf: URL(fileURLWithPath: filePath)) var startTime = CFAbsoluteTimeGetCurrent() - let animation = LottieAnimation(data: animationData)! + + let animationRenderer = SoftwareLottieRenderer(data: animationData)! print("Load time: \((CFAbsoluteTimeGetCurrent() - startTime) * 1000.0) ms") - startTime = CFAbsoluteTimeGetCurrent() - let animationContainer = LottieAnimationContainer(animation: animation) - animationContainer.update(0) - print("Build time: \((CFAbsoluteTimeGetCurrent() - startTime) * 1000.0) ms") - - let animationRenderer = SoftwareLottieRenderer(animationContainer: animationContainer) - startTime = CFAbsoluteTimeGetCurrent() var numUpdates: Int = 0 var frameIndex = 0 while true { - animationContainer.update(frameIndex) + animationRenderer.setFrame(frameIndex) let _ = animationRenderer.render(for: CGSize(width: CGFloat(performanceFrameSize), height: CGFloat(performanceFrameSize)), useReferenceRendering: false) - frameIndex = (frameIndex + 1) % animationContainer.animation.frameCount + frameIndex = (frameIndex + 1) % animationRenderer.frameCount numUpdates += 1 let timestamp = CFAbsoluteTimeGetCurrent() let deltaTime = timestamp - startTime diff --git a/submodules/LottieCpp/lottiecpp b/submodules/LottieCpp/lottiecpp index a540e0d91a..12787bcc9f 160000 --- a/submodules/LottieCpp/lottiecpp +++ b/submodules/LottieCpp/lottiecpp @@ -1 +1 @@ -Subproject commit a540e0d91a9cbe91968798310d515496c85bc043 +Subproject commit 12787bcc9fe73f6598e5b03882b64b074aff52b6 diff --git a/submodules/TelegramUI/Components/LottieMetal/Sources/LottieMetalAnimatedStickerNode.swift b/submodules/TelegramUI/Components/LottieMetal/Sources/LottieMetalAnimatedStickerNode.swift index 9e08a17d94..6d06528b45 100644 --- a/submodules/TelegramUI/Components/LottieMetal/Sources/LottieMetalAnimatedStickerNode.swift +++ b/submodules/TelegramUI/Components/LottieMetal/Sources/LottieMetalAnimatedStickerNode.swift @@ -34,7 +34,7 @@ func metalLibrary(device: MTLDevice) -> MTLLibrary? { return library } -private func generateTexture(device: MTLDevice, sideSize: Int, msaaSampleCount: Int) -> MTLTexture { +/*private func generateTexture(device: MTLDevice, sideSize: Int, msaaSampleCount: Int) -> MTLTexture { let textureDescriptor = MTLTextureDescriptor() textureDescriptor.sampleCount = msaaSampleCount if msaaSampleCount == 1 { @@ -53,7 +53,7 @@ private func generateTexture(device: MTLDevice, sideSize: Int, msaaSampleCount: } public func cacheLottieMetalAnimation(path: String) -> Data? { - if let data = try? Data(contentsOf: URL(fileURLWithPath: path)) { + /*if let data = try? Data(contentsOf: URL(fileURLWithPath: path)) { let decompressedData = TGGUnzipData(data, 8 * 1024 * 1024) ?? data if let lottieAnimation = LottieAnimation(data: decompressedData) { let animationContainer = LottieAnimationContainer(animation: lottieAnimation) @@ -92,7 +92,7 @@ public func cacheLottieMetalAnimation(path: String) -> Data? { return zippedData } - } + }*/ return nil } @@ -1030,7 +1030,7 @@ public final class LottieMetalAnimatedStickerNode: ASDisplayNode, AnimatedSticke if let serializedFrames { content = .serialized(frameMapping: serializedFrames.0, data: serializedFrames.1) } else { - guard let data = try? Data(contentsOf: URL(fileURLWithPath: path)) else { + /*guard let data = try? Data(contentsOf: URL(fileURLWithPath: path)) else { return } let decompressedData = TGGUnzipData(data, 8 * 1024 * 1024) ?? data @@ -1045,7 +1045,8 @@ public final class LottieMetalAnimatedStickerNode: ASDisplayNode, AnimatedSticke AnimationCacheState.shared.enqueue(path: path, cachePath: cachePathValue) } - content = .animation(lottieInstance) + content = .animation(lottieInstance)*/ + return } Queue.mainQueue().async { @@ -1165,4 +1166,4 @@ public final class LottieMetalAnimatedStickerNode: ASDisplayNode, AnimatedSticke public func setOverlayColor(_ color: UIColor?, replace: Bool, animated: Bool) { } -} +}*/ diff --git a/submodules/TelegramUI/Components/LottieMetal/Sources/PathFrameState.swift b/submodules/TelegramUI/Components/LottieMetal/Sources/PathFrameState.swift index 9013fff1da..1495c3b7d5 100644 --- a/submodules/TelegramUI/Components/LottieMetal/Sources/PathFrameState.swift +++ b/submodules/TelegramUI/Components/LottieMetal/Sources/PathFrameState.swift @@ -2,7 +2,7 @@ import Foundation import MetalKit import LottieCpp -private func alignUp(size: Int, align: Int) -> Int { +/*private func alignUp(size: Int, align: Int) -> Int { precondition(((align - 1) & align) == 0, "Align must be a power of two") let alignmentMask = align - 1 @@ -372,3 +372,4 @@ final class PathFrameState { computeEncoder.dispatchThreadgroups(MTLSize(width: dispatchSize, height: 1, depth: 1), threadsPerThreadgroup: MTLSize(width: 1, height: threadGroupHeight, depth: 1)) } } +*/ diff --git a/submodules/TelegramUI/Components/LottieMetal/Sources/PathRenderFillState.swift b/submodules/TelegramUI/Components/LottieMetal/Sources/PathRenderFillState.swift index f7f22cdd56..0140da0a9a 100644 --- a/submodules/TelegramUI/Components/LottieMetal/Sources/PathRenderFillState.swift +++ b/submodules/TelegramUI/Components/LottieMetal/Sources/PathRenderFillState.swift @@ -3,7 +3,7 @@ import MetalKit import simd import LottieCpp -enum PathShading { +/*enum PathShading { final class Gradient { enum GradientType { case linear @@ -274,4 +274,4 @@ final class PathRenderFillState { encoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: quadVertices.count) } } - +*/ diff --git a/submodules/TelegramUI/Components/LottieMetal/Sources/PathRenderStrokeState.swift b/submodules/TelegramUI/Components/LottieMetal/Sources/PathRenderStrokeState.swift index ce9accfe1f..264e984f08 100644 --- a/submodules/TelegramUI/Components/LottieMetal/Sources/PathRenderStrokeState.swift +++ b/submodules/TelegramUI/Components/LottieMetal/Sources/PathRenderStrokeState.swift @@ -2,7 +2,7 @@ import Foundation import MetalKit import LottieCpp -func evaluateBezier(p0: SIMD2, p1: SIMD2, p2: SIMD2, p3: SIMD2, t: Float) -> SIMD2 { +/*func evaluateBezier(p0: SIMD2, p1: SIMD2, p2: SIMD2, p3: SIMD2, t: Float) -> SIMD2 { let t2 = t * t let t3 = t * t * t @@ -422,4 +422,4 @@ final class PathRenderStrokeState { } } } -} +}*/ diff --git a/submodules/TelegramUI/Components/LottieMetal/Sources/RenderTreeSerialization.swift b/submodules/TelegramUI/Components/LottieMetal/Sources/RenderTreeSerialization.swift index c52b176986..5238b2d212 100644 --- a/submodules/TelegramUI/Components/LottieMetal/Sources/RenderTreeSerialization.swift +++ b/submodules/TelegramUI/Components/LottieMetal/Sources/RenderTreeSerialization.swift @@ -1,519 +1,3 @@ import Foundation import LottieCpp -final class WriteBuffer { - private(set) var data: Data - private var capacity: Int - var length: Int - - init() { - self.capacity = 1024 - self.data = Data(count: self.capacity) - self.length = 0 - } - - func trim() { - self.data.count = self.length - self.capacity = self.data.count - } - - func write(bytes: UnsafeRawBufferPointer) { - if self.data.count < self.length + bytes.count { - self.data.count = self.data.count * 2 - } - self.data.withUnsafeMutableBytes { buffer -> Void in - memcpy(buffer.baseAddress!.advanced(by: self.length), bytes.baseAddress!, bytes.count) - } - self.length += bytes.count - } - - func write(uInt32 value: UInt32) { - var value = value - withUnsafeBytes(of: &value, { bytes in - self.write(bytes: bytes) - }) - } - - func write(uInt16 value: UInt16) { - var value = value - withUnsafeBytes(of: &value, { bytes in - self.write(bytes: bytes) - }) - } - - func write(uInt8 value: UInt8) { - var value = value - withUnsafeBytes(of: &value, { bytes in - self.write(bytes: bytes) - }) - } - - func write(float value: Float) { - var value = value - withUnsafeBytes(of: &value, { bytes in - self.write(bytes: bytes) - }) - } - - func write(point: CGPoint) { - self.write(float: Float(point.x)) - self.write(float: Float(point.y)) - } - - func write(size: CGSize) { - self.write(float: Float(size.width)) - self.write(float: Float(size.height)) - } - - func write(rect: CGRect) { - self.write(point: rect.origin) - self.write(size: rect.size) - } - - func write(transform: CATransform3D) { - self.write(float: Float(transform.m11)) - self.write(float: Float(transform.m12)) - self.write(float: Float(transform.m13)) - self.write(float: Float(transform.m14)) - self.write(float: Float(transform.m21)) - self.write(float: Float(transform.m22)) - self.write(float: Float(transform.m23)) - self.write(float: Float(transform.m24)) - self.write(float: Float(transform.m31)) - self.write(float: Float(transform.m32)) - self.write(float: Float(transform.m33)) - self.write(float: Float(transform.m34)) - self.write(float: Float(transform.m41)) - self.write(float: Float(transform.m42)) - self.write(float: Float(transform.m43)) - self.write(float: Float(transform.m44)) - } -} - -final class ReadBuffer { - private let data: Data - private var offset: Int - - init(data: Data) { - self.data = data - self.offset = 0 - } - - func read(bytes: UnsafeMutableRawBufferPointer) { - if self.offset + bytes.count <= self.data.count { - self.data.withUnsafeBytes { buffer -> Void in - memcpy(bytes.baseAddress!, buffer.baseAddress!.advanced(by: self.offset), bytes.count) - } - self.offset += bytes.count - } else { - preconditionFailure() - } - } - - func readUInt32() -> UInt32 { - var value: UInt32 = 0 - withUnsafeMutableBytes(of: &value, { bytes in - self.read(bytes: bytes) - }) - return value - } - - func readUInt16() -> UInt16 { - var value: UInt16 = 0 - withUnsafeMutableBytes(of: &value, { bytes in - self.read(bytes: bytes) - }) - return value - } - - func readUInt8() -> UInt8 { - var value: UInt8 = 0 - withUnsafeMutableBytes(of: &value, { bytes in - self.read(bytes: bytes) - }) - return value - } - - func readFloat() -> Float { - var value: Float = 0 - withUnsafeMutableBytes(of: &value, { bytes in - self.read(bytes: bytes) - }) - return value - } - - func readPoint() -> CGPoint { - return CGPoint(x: CGFloat(self.readFloat()), y: CGFloat(self.readFloat())) - } - - func readSize() -> CGSize { - return CGSize(width: CGFloat(self.readFloat()), height: CGFloat(self.readFloat())) - } - - func readRect() -> CGRect { - return CGRect(origin: self.readPoint(), size: self.readSize()) - } - - func readTransform() -> CATransform3D { - return CATransform3D( - m11: CGFloat(self.readFloat()), - m12: CGFloat(self.readFloat()), - m13: CGFloat(self.readFloat()), - m14: CGFloat(self.readFloat()), - m21: CGFloat(self.readFloat()), - m22: CGFloat(self.readFloat()), - m23: CGFloat(self.readFloat()), - m24: CGFloat(self.readFloat()), - m31: CGFloat(self.readFloat()), - m32: CGFloat(self.readFloat()), - m33: CGFloat(self.readFloat()), - m34: CGFloat(self.readFloat()), - m41: CGFloat(self.readFloat()), - m42: CGFloat(self.readFloat()), - m43: CGFloat(self.readFloat()), - m44: CGFloat(self.readFloat()) - ) - } -} - -private extension LottieColor { - init(argb: UInt32) { - self.init(r: CGFloat((argb >> 16) & 0xff) / 255.0, g: CGFloat((argb >> 8) & 0xff) / 255.0, b: CGFloat(argb & 0xff) / 255.0, a: CGFloat((argb >> 24) & 0xff) / 255.0) - } - - var argb: UInt32 { - return (UInt32(self.a * 255.0) << 24) | (UInt32(max(0.0, self.r) * 255.0) << 16) | (UInt32(max(0.0, self.g) * 255.0) << 8) | (UInt32(max(0.0, self.b) * 255.0)) - } -} - -private struct NodeFlags: OptionSet { - var rawValue: UInt8 - - init(rawValue: UInt8) { - self.rawValue = rawValue - } - - static let masksToBounds = NodeFlags(rawValue: 1 << 0) - static let isHidden = NodeFlags(rawValue: 1 << 1) - static let hasSimpleContents = NodeFlags(rawValue: 1 << 2) - static let isInvertedMatte = NodeFlags(rawValue: 1 << 3) - - static let hasRenderContent = NodeFlags(rawValue: 1 << 4) - static let hasSubnodes = NodeFlags(rawValue: 1 << 5) - static let hasMask = NodeFlags(rawValue: 1 << 6) -} - -private struct LottieContentFlags: OptionSet { - var rawValue: UInt8 - - init(rawValue: UInt8) { - self.rawValue = rawValue - } - - static let hasStroke = LottieContentFlags(rawValue: 1 << 0) - static let hasFill = LottieContentFlags(rawValue: 1 << 1) -} - -func serializePath(buffer: WriteBuffer, path: LottiePath) { - let lengthOffset = buffer.length - buffer.write(uInt32: 0) - - path.enumerateItems { pathItem in - switch pathItem.pointee.type { - case .moveTo: - let point = pathItem.pointee.points.0 - buffer.write(uInt8: 0) - buffer.write(point: point) - case .lineTo: - let point = pathItem.pointee.points.0 - buffer.write(uInt8: 1) - buffer.write(point: point) - case .curveTo: - let cp1 = pathItem.pointee.points.0 - let cp2 = pathItem.pointee.points.1 - let point = pathItem.pointee.points.2 - - buffer.write(uInt8: 2) - buffer.write(point: cp1) - buffer.write(point: cp2) - buffer.write(point: point) - case .close: - buffer.write(uInt8: 3) - @unknown default: - break - } - } - - let dataLength = buffer.length - lengthOffset - 4 - - let previousLength = buffer.length - buffer.length = lengthOffset - buffer.write(uInt32: UInt32(dataLength)) - buffer.length = previousLength -} - -func deserializePath(buffer: ReadBuffer) -> LottiePath { - let itemDataLength = Int(buffer.readUInt32()) - var itemData = Data(count: itemDataLength) - itemData.withUnsafeMutableBytes { bytes in - buffer.read(bytes: bytes) - } - - return LottiePath(customData: itemData) -} - -func serializeContentShading(buffer: WriteBuffer, shading: LottieRenderContentShading) { - if let shading = shading as? LottieRenderContentSolidShading { - buffer.write(uInt8: 0) - buffer.write(uInt32: shading.color.argb) - buffer.write(uInt8: UInt8(clamping: Int(shading.opacity * 255.0))) - } else if let shading = shading as? LottieRenderContentGradientShading { - buffer.write(uInt8: 1) - buffer.write(uInt8: UInt8(clamping: Int(shading.opacity * 255.0))) - buffer.write(uInt8: UInt8(shading.gradientType.rawValue)) - let colorStopCount = min(shading.colorStops.count, 255) - buffer.write(uInt8: UInt8(colorStopCount)) - for i in 0 ..< colorStopCount { - buffer.write(uInt32: shading.colorStops[i].color.argb) - buffer.write(float: Float(shading.colorStops[i].location)) - } - buffer.write(point: shading.start) - buffer.write(point: shading.end) - } else { - buffer.write(uInt8: 0) - buffer.write(uInt8: UInt8(clamping: Int(1.0 * 255.0))) - } -} - -func deserializeContentShading(buffer: ReadBuffer) -> LottieRenderContentShading { - switch buffer.readUInt8() { - case 0: - return LottieRenderContentSolidShading( - color: LottieColor(argb: buffer.readUInt32()), - opacity: CGFloat(buffer.readUInt8()) / 255.0 - ) - case 1: - let opacity = CGFloat(buffer.readUInt8()) / 255.0 - let gradientType = LottieGradientType(rawValue: UInt(buffer.readUInt8()))! - - var colorStops: [LottieColorStop] = [] - let colorStopCount = Int(buffer.readUInt8()) - for _ in 0 ..< colorStopCount { - colorStops.append(LottieColorStop( - color: LottieColor(argb: buffer.readUInt32()), - location: CGFloat(buffer.readFloat()) - )) - } - - let start = buffer.readPoint() - let end = buffer.readPoint() - - return LottieRenderContentGradientShading( - opacity: opacity, - gradientType: gradientType, - colorStops: colorStops, - start: start, - end: end - ) - default: - preconditionFailure() - } -} - -func serializeStroke(buffer: WriteBuffer, stroke: LottieRenderContentStroke) { - serializeContentShading(buffer: buffer, shading: stroke.shading) - buffer.write(float: Float(stroke.lineWidth)) - buffer.write(uInt8: UInt8(stroke.lineJoin.rawValue)) - buffer.write(uInt8: UInt8(stroke.lineCap.rawValue)) - buffer.write(float: Float(stroke.miterLimit)) -} - -func deserializeStroke(buffer: ReadBuffer) -> LottieRenderContentStroke { - return LottieRenderContentStroke( - shading: deserializeContentShading(buffer: buffer), - lineWidth: CGFloat(buffer.readFloat()), - lineJoin: CGLineJoin(rawValue: Int32(buffer.readUInt8()))!, - lineCap: CGLineCap(rawValue: Int32(buffer.readUInt8()))!, - miterLimit: CGFloat(buffer.readFloat()), - dashPhase: 0.0, - dashPattern: nil - ) -} - -func serializeFill(buffer: WriteBuffer, fill: LottieRenderContentFill) { - serializeContentShading(buffer: buffer, shading: fill.shading) - buffer.write(uInt8: UInt8(fill.fillRule.rawValue)) -} - -func deserializeFill(buffer: ReadBuffer) -> LottieRenderContentFill { - return LottieRenderContentFill( - shading: deserializeContentShading(buffer: buffer), - fillRule: LottieFillRule(rawValue: UInt(buffer.readUInt8()))! - ) -} - -func serializeRenderContent(buffer: WriteBuffer, renderContent: LottieRenderContent) { - var flags: LottieContentFlags = [] - if renderContent.stroke != nil { - flags.insert(.hasStroke) - } - if renderContent.fill != nil { - flags.insert(.hasFill) - } - buffer.write(uInt8: flags.rawValue) - - serializePath(buffer: buffer, path: renderContent.path) - if let stroke = renderContent.stroke { - serializeStroke(buffer: buffer, stroke: stroke) - } - if let fill = renderContent.fill { - serializeFill(buffer: buffer, fill: fill) - } -} - -func deserializeRenderContent(buffer: ReadBuffer) -> LottieRenderContent { - let flags = LottieContentFlags(rawValue: buffer.readUInt8()) - - let path = deserializePath(buffer: buffer) - - var stroke: LottieRenderContentStroke? - if flags.contains(.hasStroke) { - stroke = deserializeStroke(buffer: buffer) - } - - var fill: LottieRenderContentFill? - if flags.contains(.hasFill) { - fill = deserializeFill(buffer: buffer) - } - - return LottieRenderContent( - path: path, - stroke: stroke, - fill: fill - ) -} - -func serializeNode(buffer: WriteBuffer, node: LottieRenderNode) { - var flags: NodeFlags = [] - if node.masksToBounds { - flags.insert(.masksToBounds) - } - if node.isHidden { - flags.insert(.isHidden) - } - if node.hasSimpleContents { - flags.insert(.hasSimpleContents) - } - if node.isInvertedMatte { - flags.insert(.isInvertedMatte) - } - if node.renderContent != nil { - flags.insert(.hasRenderContent) - } - if !node.subnodes.isEmpty { - flags.insert(.hasSubnodes) - } - if node.mask != nil { - flags.insert(.hasMask) - } - - buffer.write(uInt8: flags.rawValue) - - buffer.write(point: node.position) - buffer.write(rect: node.bounds) - buffer.write(transform: node.transform) - buffer.write(uInt8: UInt8(clamping: Int(node.opacity * 255.0))) - buffer.write(rect: node.globalRect) - buffer.write(transform: node.globalTransform) - - if let renderContent = node.renderContent { - serializeRenderContent(buffer: buffer, renderContent: renderContent) - } - if !node.subnodes.isEmpty { - let count = min(node.subnodes.count, 4095) - buffer.write(uInt16: UInt16(count)) - for i in 0 ..< count { - serializeNode(buffer: buffer, node: node.subnodes[i]) - } - } - if let mask = node.mask { - serializeNode(buffer: buffer, node: mask) - } -} - -func deserializeNode(buffer: ReadBuffer) -> LottieRenderNode { - let flags = NodeFlags(rawValue: buffer.readUInt8()) - - let position = buffer.readPoint() - let bounds = buffer.readRect() - let transform = buffer.readTransform() - let opacity = CGFloat(buffer.readUInt8()) / 255.0 - let globalRect = buffer.readRect() - let globalTransform = buffer.readTransform() - - var renderContent: LottieRenderContent? - if flags.contains(.hasRenderContent) { - renderContent = deserializeRenderContent(buffer: buffer) - } - var subnodes: [LottieRenderNode] = [] - if flags.contains(.hasSubnodes) { - let count = Int(buffer.readUInt16()) - for _ in 0 ..< count { - subnodes.append(deserializeNode(buffer: buffer)) - } - } - var mask: LottieRenderNode? - if flags.contains(.hasMask) { - mask = deserializeNode(buffer: buffer) - } - - return LottieRenderNode( - position: position, - bounds: bounds, - transform: transform, - opacity: opacity, - masksToBounds: flags.contains(.masksToBounds), - isHidden: flags.contains(.isHidden), - globalRect: globalRect, - globalTransform: globalTransform, - renderContent: renderContent, - hasSimpleContents: flags.contains(.hasSimpleContents), - isInvertedMatte: flags.contains(.isInvertedMatte), - subnodes: subnodes, - mask: mask - ) -} - -public struct SerializedLottieMetalFrameMapping { - var size: CGSize = CGSize() - var frameCount: Int = 0 - var framesPerSecond: Int = 0 - var frameRanges: [Int: Range] = [:] -} - -func serializeFrameMapping(buffer: WriteBuffer, frameMapping: SerializedLottieMetalFrameMapping) { - buffer.write(size: frameMapping.size) - buffer.write(uInt32: UInt32(frameMapping.frameCount)) - buffer.write(uInt32: UInt32(frameMapping.framesPerSecond)) - for (frame, range) in frameMapping.frameRanges.sorted(by: { $0.key < $1.key }) { - buffer.write(uInt32: UInt32(frame)) - buffer.write(uInt32: UInt32(range.lowerBound)) - buffer.write(uInt32: UInt32(range.upperBound)) - } -} - -func deserializeFrameMapping(buffer: ReadBuffer) -> SerializedLottieMetalFrameMapping { - var frameMapping = SerializedLottieMetalFrameMapping() - - frameMapping.size = buffer.readSize() - frameMapping.frameCount = Int(buffer.readUInt32()) - frameMapping.framesPerSecond = Int(buffer.readUInt32()) - for _ in 0 ..< frameMapping.frameCount { - let frame = Int(buffer.readUInt32()) - let lowerBound = Int(buffer.readUInt32()) - let upperBound = Int(buffer.readUInt32()) - frameMapping.frameRanges[frame] = lowerBound ..< upperBound - } - - return frameMapping -}