Lottie refactoring

This commit is contained in:
Isaac 2024-06-06 17:11:16 +04:00
parent e54230f42c
commit 0b2d73d626
17 changed files with 160 additions and 1369 deletions

View File

@ -4,9 +4,6 @@
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <LottieCpp/LottieCpp.h>
#import <LottieCpp/LottieAnimationContainer.h>
#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

View File

@ -1,94 +0,0 @@
#ifndef Canvas_h
#define Canvas_h
#include <LottieCpp/LottieCpp.h>
#include <QuartzCore/QuartzCore.h>
#include <memory>
#include <vector>
#include <cassert>
#include <functional>
namespace lottieRendering {
class Image {
public:
virtual ~Image() = default;
};
class Gradient {
public:
Gradient(std::vector<lottie::Color> const &colors, std::vector<float> const &locations) :
_colors(colors),
_locations(locations) {
assert(_colors.size() == _locations.size());
}
std::vector<lottie::Color> const &colors() const {
return _colors;
}
std::vector<float> const &locations() const {
return _locations;
}
private:
std::vector<lottie::Color> _colors;
std::vector<float> _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<void(std::function<void(PathCommand const &)>)> CanvasPathEnumerator;
class Canvas {
public:
virtual ~Canvas() = default;
virtual int width() const = 0;
virtual int height() const = 0;
virtual std::shared_ptr<Canvas> 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<float> 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<float> 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<float> 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<Canvas> const &other, lottie::CGRect const &rect) = 0;
};
}
#endif

View File

@ -1,21 +1,24 @@
#ifndef CoreGraphicsCanvasImpl_h
#define CoreGraphicsCanvasImpl_h
#include "Canvas.h"
#include <LottieCpp/LottieCpp.h>
namespace lottieRendering {
#include <QuartzCore/QuartzCore.h>
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);

View File

@ -3,7 +3,7 @@
#include <LottieCpp/CGPathCocoa.h>
#include <LottieCpp/VectorsCocoa.h>
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<Image> CanvasImpl::makeImage() const {
std::shared_ptr<CanvasImpl::Image> CanvasImpl::makeImage() const {
::CGImageRef nativeImage = CGBitmapContextCreateImage(_context);
if (nativeImage) {
auto image = std::make_shared<ImageImpl>(nativeImage);
auto image = std::make_shared<CanvasImpl::Image>(nativeImage);
CFRelease(nativeImage);
return image;
} else {
@ -533,7 +532,7 @@ void CanvasImpl::draw(std::shared_ptr<Canvas> 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());
}
}

View File

@ -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<Canvas> 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<float> 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<float> 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<float> 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<Canvas> const &other, lottie::CGRect const &rect) override;
void flush();
private:
float _width = 0.0f;
float _height = 0.0f;
lottie::Transform2D _transform;
};
}
#endif

View File

@ -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<Canvas> NullCanvasImpl::makeLayer(int width, int height) {
return std::make_shared<NullCanvasImpl>(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<float> 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<float> 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<float> 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<Canvas> const &other, lottie::CGRect const &rect) {
}
void NullCanvasImpl::flush() {
}
}

View File

@ -1,577 +1,112 @@
#import <SoftwareLottieRenderer/SoftwareLottieRenderer.h>
#import "Canvas.h"
#import <LottieCpp/LottieCpp.h>
#import <LottieCpp/NullCanvasImpl.h>
#import "CoreGraphicsCanvasImpl.h"
#import "ThorVGCanvasImpl.h"
#import "NullCanvasImpl.h"
#include <LottieCpp/RenderTreeNode.h>
#include <LottieCpp/LottieAnimationContainer.h>
#include <LottieCpp/CGPathCocoa.h>
#include <LottieCpp/VectorsCocoa.h>
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<lottie::RenderTreeNodeContentItem> 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<lottie::RenderTreeNodeContentItem> item, size_t subItemLimit, lottie::Transform2D const &parentTransform, bool skipApplyTransform, std::function<void(lottie::BezierPath const &path, lottie::Transform2D const &transform)> 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<CGRect> getRenderContentItemGlobalRect(std::shared_ptr<RenderTreeNodeContentItem> const &contentItem, lottie::Vector2D const &globalSize, lottie::Transform2D const &parentTransform, BezierPathsBoundingBoxContext &bezierPathsBoundingBoxContext) {
auto currentTransform = parentTransform;
Transform2D localTransform = contentItem->transform;
currentTransform = localTransform * currentTransform;
std::optional<CGRect> 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<CGRect> getRenderNodeGlobalRect(std::shared_ptr<RenderTreeNode> 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<CGRect> 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<lottieRendering::Canvas> const &parentContext, std::shared_ptr<lottie::RenderTreeNodeContentItem> 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<lottieRendering::Canvas> const *currentContext;
std::shared_ptr<lottieRendering::Canvas> tempContext;
bool needsTempContext = false;
needsTempContext = layerAlpha != 1.0 && item->drawContentCount > 1;
std::optional<lottie::CGRect> 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<void(lottieRendering::PathCommand const &)> 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<lottie::PathElement> 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<float> 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<lottie::Color> colors;
std::vector<float> 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<lottie::RenderTreeNode> node, std::shared_ptr<lottieRendering::Canvas> 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<lottieRendering::Canvas> maskContext;
std::shared_ptr<lottieRendering::Canvas> currentContext;
std::shared_ptr<lottieRendering::Canvas> 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<lottie::CGRect> 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<lottie::BezierPathsBoundingBoxContext> _bezierPathsBoundingBoxContext;
std::shared_ptr<lottie::Renderer> _renderer;
std::shared_ptr<lottie::CanvasRenderer> _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<lottie::BezierPathsBoundingBoxContext>();
_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<lottie::CanvasRenderer>();
}
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<lottie::RenderTreeNode> renderNode = [_animationContainer internalGetRootRenderTreeNode];
std::shared_ptr<lottie::RenderTreeNode> 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<lottieRendering::CanvasImpl>((int)size.width, (int)size.height);
auto context = std::make_shared<lottie::CanvasImpl>((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<lottieRendering::ImageImpl>(image)->nativeImage()];
return [[UIImage alloc] initWithCGImage:std::static_pointer_cast<lottie::CanvasImpl::Image>(image)->nativeImage()];
} else {
//auto context = std::make_shared<lottieRendering::ThorVGCanvasImpl>((int)size.width, (int)size.height);
auto context = std::make_shared<lottieRendering::NullCanvasImpl>((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<lottie::ThorVGCanvasImpl>((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<lottie::NullCanvasImpl>((int)size.width, (int)size.height);
_canvasRenderer->render(_renderer, context, lottie::Vector2D(size.width, size.height));
return nil;
}
}
return nil;
}
@end

View File

@ -1,9 +1,6 @@
#include "ThorVGCanvasImpl.h"
#include <LottieCpp/CGPathCocoa.h>
#include <LottieCpp/VectorsCocoa.h>
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<Canvas> ThorVGCanvasImpl::makeLayer(int width, int height) {
return std::make_shared<ThorVGCanvasImpl>(width, height);
return std::make_shared<ThorVGCanvasImpl>(width, height, width * 4);
}
void ThorVGCanvasImpl::saveState() {

View File

@ -1,15 +1,17 @@
#ifndef ThorVGCanvasImpl_h
#define ThorVGCanvasImpl_h
#include "Canvas.h"
#include <LottieCpp/LottieCpp.h>
#include <thorvg/thorvg.h>
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<float> 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<float> 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<float> const &dashPattern, Gradient const &gradient, lottie::Vector2D const &startCenter, float startRadius, lottie::Vector2D const &endCenter, float endRadius) override;

View File

@ -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)")

View File

@ -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

@ -1 +1 @@
Subproject commit a540e0d91a9cbe91968798310d515496c85bc043
Subproject commit 12787bcc9fe73f6598e5b03882b64b074aff52b6

View File

@ -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) {
}
}
}*/

View File

@ -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))
}
}
*/

View File

@ -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)
}
}
*/

View File

@ -2,7 +2,7 @@ import Foundation
import MetalKit
import LottieCpp
func evaluateBezier(p0: SIMD2<Float>, p1: SIMD2<Float>, p2: SIMD2<Float>, p3: SIMD2<Float>, t: Float) -> SIMD2<Float> {
/*func evaluateBezier(p0: SIMD2<Float>, p1: SIMD2<Float>, p2: SIMD2<Float>, p3: SIMD2<Float>, t: Float) -> SIMD2<Float> {
let t2 = t * t
let t3 = t * t * t
@ -422,4 +422,4 @@ final class PathRenderStrokeState {
}
}
}
}
}*/

View File

@ -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<Int>] = [:]
}
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
}