Lottie updates

This commit is contained in:
Isaac 2024-06-14 11:49:48 +04:00
parent 8df43bb775
commit b5b772a41b
11 changed files with 416 additions and 310 deletions

View File

@ -19,7 +19,7 @@ CGRect getPathNativeBoundingBox(CGPathRef _Nonnull path);
- (instancetype _Nullable)initWithData:(NSData * _Nonnull)data; - (instancetype _Nullable)initWithData:(NSData * _Nonnull)data;
- (void)setFrame:(NSInteger)index; - (void)setFrame:(NSInteger)index;
- (UIImage * _Nullable)renderForSize:(CGSize)size useReferenceRendering:(bool)useReferenceRendering; - (UIImage * _Nullable)renderForSize:(CGSize)size useReferenceRendering:(bool)useReferenceRendering canUseMoreMemory:(bool)canUseMoreMemory skipImageGeneration:(bool)skipImageGeneration;
@end @end

View File

@ -23,30 +23,25 @@ public:
public: public:
CoreGraphicsCanvasImpl(int width, int height); CoreGraphicsCanvasImpl(int width, int height);
CoreGraphicsCanvasImpl(CGContextRef context, int width, int height);
virtual ~CoreGraphicsCanvasImpl(); virtual ~CoreGraphicsCanvasImpl();
std::shared_ptr<Canvas> makeLayer(int width, int height) override;
virtual void saveState() override; virtual void saveState() override;
virtual void restoreState() override; virtual void restoreState() override;
virtual void fillPath(CanvasPathEnumerator const &enumeratePath, lottie::FillRule fillRule, lottie::Color const &color) override; virtual void fillPath(CanvasPathEnumerator const &enumeratePath, lottie::FillRule fillRule, lottie::Color const &color) override;
virtual void linearGradientFillPath(CanvasPathEnumerator const &enumeratePath, lottie::FillRule fillRule, Gradient const &gradient, lottie::Vector2D const &start, lottie::Vector2D const &end) override; virtual void linearGradientFillPath(CanvasPathEnumerator const &enumeratePath, lottie::FillRule fillRule, Gradient const &gradient, lottie::Vector2D const &start, lottie::Vector2D const &end) override;
virtual void radialGradientFillPath(CanvasPathEnumerator const &enumeratePath, lottie::FillRule fillRule, Gradient const &gradient, lottie::Vector2D const &startCenter, float startRadius, lottie::Vector2D const &endCenter, float endRadius) override; virtual void radialGradientFillPath(CanvasPathEnumerator const &enumeratePath, lottie::FillRule fillRule, Gradient const &gradient, Vector2D const &center, float radius) 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 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 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 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 clip(CGRect const &rect) override;
virtual void setBlendMode(BlendMode blendMode) override;
virtual void concatenate(lottie::Transform2D const &transform) override; virtual void concatenate(lottie::Transform2D const &transform) override;
virtual std::shared_ptr<Image> makeImage(); virtual std::shared_ptr<Image> makeImage();
virtual void draw(std::shared_ptr<Canvas> const &other, float alpha, lottie::CGRect const &rect) override;
virtual void pushLayer(CGRect const &rect) override; virtual bool pushLayer(CGRect const &rect, float alpha, std::optional<MaskMode> maskMode) override;
virtual void popLayer() override; virtual void popLayer() override;
std::vector<uint8_t> &backingData(); std::vector<uint8_t> &backingData();
@ -56,6 +51,8 @@ private:
std::shared_ptr<Layer> &currentLayer(); std::shared_ptr<Layer> &currentLayer();
private: private:
int _width = 0;
int _height = 0;
CGContextRef _topContext = nil; CGContextRef _topContext = nil;
std::vector<std::shared_ptr<Layer>> _layerStack; std::vector<std::shared_ptr<Layer>> _layerStack;
}; };

View File

@ -64,9 +64,23 @@ bool addEnumeratedPath(CGContextRef context, CanvasPathEnumerator const &enumera
class CoreGraphicsCanvasImpl::Layer { class CoreGraphicsCanvasImpl::Layer {
public: public:
explicit Layer(int width, int height) { struct Composition {
CGRect rect;
float alpha;
Transform2D transform;
std::optional<Canvas::MaskMode> maskMode;
Composition(CGRect rect_, float alpha_, Transform2D transform_, std::optional<Canvas::MaskMode> maskMode_) :
rect(rect_), alpha(alpha_), transform(transform_), maskMode(maskMode_) {
}
};
public:
explicit Layer(int width, int height, std::optional<Composition> composition) {
_width = width; _width = width;
_height = height; _height = height;
_composition = composition;
_bytesPerRow = alignUp(width * 4, 16); _bytesPerRow = alignUp(width * 4, 16);
_backingData.resize(_bytesPerRow * _height); _backingData.resize(_bytesPerRow * _height);
memset(_backingData.data(), 0, _backingData.size()); memset(_backingData.data(), 0, _backingData.size());
@ -77,7 +91,6 @@ public:
CFRelease(colorSpace); CFRelease(colorSpace);
CGContextClearRect(_context, CGRectMake(0.0, 0.0, _width, _height)); CGContextClearRect(_context, CGRectMake(0.0, 0.0, _width, _height));
} }
~Layer() { ~Layer() {
@ -88,12 +101,29 @@ public:
return _context; return _context;
} }
std::optional<Composition> composition() const {
return _composition;
}
std::shared_ptr<CoreGraphicsCanvasImpl::Image> makeImage() {
::CGImageRef nativeImage = CGBitmapContextCreateImage(_context);
if (nativeImage) {
auto image = std::make_shared<CoreGraphicsCanvasImpl::Image>(nativeImage);
CFRelease(nativeImage);
return image;
} else {
return nil;
}
}
public: public:
CGContextRef _context = nil; CGContextRef _context = nil;
int _width = 0; int _width = 0;
int _height = 0; int _height = 0;
int _bytesPerRow = 0; int _bytesPerRow = 0;
std::vector<uint8_t> _backingData; std::vector<uint8_t> _backingData;
std::optional<Composition> _composition;
}; };
CoreGraphicsCanvasImpl::Image::Image(::CGImageRef image) { CoreGraphicsCanvasImpl::Image::Image(::CGImageRef image) {
@ -108,24 +138,13 @@ CoreGraphicsCanvasImpl::Image::~Image() {
return _image; return _image;
} }
CoreGraphicsCanvasImpl::CoreGraphicsCanvasImpl(int width, int height) { CoreGraphicsCanvasImpl::CoreGraphicsCanvasImpl(int width, int height) :
_layerStack.push_back(std::make_shared<Layer>(width, height)); _width(width),
_topContext = CGContextRetain(currentLayer()->context()); _height(height) {
} _layerStack.push_back(std::make_shared<Layer>(width, height, std::nullopt));
CoreGraphicsCanvasImpl::CoreGraphicsCanvasImpl(CGContextRef context, int width, int height) {
_layerStack.push_back(std::make_shared<Layer>(width, height));
_topContext = CGContextRetain(context);
} }
CoreGraphicsCanvasImpl::~CoreGraphicsCanvasImpl() { CoreGraphicsCanvasImpl::~CoreGraphicsCanvasImpl() {
if (_topContext) {
CFRelease(_topContext);
}
}
std::shared_ptr<Canvas> CoreGraphicsCanvasImpl::makeLayer(int width, int height) {
return std::make_shared<CoreGraphicsCanvasImpl>(_topContext, width, height);
} }
void CoreGraphicsCanvasImpl::saveState() { void CoreGraphicsCanvasImpl::saveState() {
@ -142,7 +161,7 @@ void CoreGraphicsCanvasImpl::fillPath(CanvasPathEnumerator const &enumeratePath,
} }
CGFloat components[4] = { color.r, color.g, color.b, color.a }; CGFloat components[4] = { color.r, color.g, color.b, color.a };
CGColorRef nativeColor = CGColorCreate(CGBitmapContextGetColorSpace(_topContext), components); CGColorRef nativeColor = CGColorCreate(CGBitmapContextGetColorSpace(currentLayer()->context()), components);
CGContextSetFillColorWithColor(currentLayer()->context(), nativeColor); CGContextSetFillColorWithColor(currentLayer()->context(), nativeColor);
CFRelease(nativeColor); CFRelease(nativeColor);
@ -194,7 +213,7 @@ void CoreGraphicsCanvasImpl::linearGradientFillPath(CanvasPathEnumerator const &
locations.push_back(location); locations.push_back(location);
} }
CGGradientRef nativeGradient = CGGradientCreateWithColorComponents(CGBitmapContextGetColorSpace(_topContext), components.data(), locations.data(), locations.size()); CGGradientRef nativeGradient = CGGradientCreateWithColorComponents(CGBitmapContextGetColorSpace(currentLayer()->context()), components.data(), locations.data(), locations.size());
if (nativeGradient) { if (nativeGradient) {
CGContextDrawLinearGradient(currentLayer()->context(), nativeGradient, CGPointMake(start.x, start.y), CGPointMake(end.x, end.y), kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation); CGContextDrawLinearGradient(currentLayer()->context(), nativeGradient, CGPointMake(start.x, start.y), CGPointMake(end.x, end.y), kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation);
CFRelease(nativeGradient); CFRelease(nativeGradient);
@ -204,7 +223,7 @@ void CoreGraphicsCanvasImpl::linearGradientFillPath(CanvasPathEnumerator const &
CGContextRestoreGState(currentLayer()->context()); CGContextRestoreGState(currentLayer()->context());
} }
void CoreGraphicsCanvasImpl::radialGradientFillPath(CanvasPathEnumerator const &enumeratePath, lottie::FillRule fillRule, Gradient const &gradient, lottie::Vector2D const &startCenter, float startRadius, lottie::Vector2D const &endCenter, float endRadius) { void CoreGraphicsCanvasImpl::radialGradientFillPath(CanvasPathEnumerator const &enumeratePath, lottie::FillRule fillRule, Gradient const &gradient, Vector2D const &center, float radius) {
CGContextSaveGState(currentLayer()->context()); CGContextSaveGState(currentLayer()->context());
if (!addEnumeratedPath(currentLayer()->context(), enumeratePath)) { if (!addEnumeratedPath(currentLayer()->context(), enumeratePath)) {
@ -240,9 +259,9 @@ void CoreGraphicsCanvasImpl::radialGradientFillPath(CanvasPathEnumerator const &
locations.push_back(location); locations.push_back(location);
} }
CGGradientRef nativeGradient = CGGradientCreateWithColorComponents(CGBitmapContextGetColorSpace(_topContext), components.data(), locations.data(), locations.size()); CGGradientRef nativeGradient = CGGradientCreateWithColorComponents(CGBitmapContextGetColorSpace(currentLayer()->context()), components.data(), locations.data(), locations.size());
if (nativeGradient) { if (nativeGradient) {
CGContextDrawRadialGradient(currentLayer()->context(), nativeGradient, CGPointMake(startCenter.x, startCenter.y), startRadius, CGPointMake(endCenter.x, endCenter.y), endRadius, kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation); CGContextDrawRadialGradient(currentLayer()->context(), nativeGradient, CGPointMake(center.x, center.y), 0.0, CGPointMake(center.x, center.y), radius, kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation);
CFRelease(nativeGradient); CFRelease(nativeGradient);
} }
@ -256,7 +275,7 @@ void CoreGraphicsCanvasImpl::strokePath(CanvasPathEnumerator const &enumeratePat
} }
CGFloat components[4] = { color.r, color.g, color.b, color.a }; CGFloat components[4] = { color.r, color.g, color.b, color.a };
CGColorRef nativeColor = CGColorCreate(CGBitmapContextGetColorSpace(_topContext), components); CGColorRef nativeColor = CGColorCreate(CGBitmapContextGetColorSpace(currentLayer()->context()), components);
CGContextSetStrokeColorWithColor(currentLayer()->context(), nativeColor); CGContextSetStrokeColorWithColor(currentLayer()->context(), nativeColor);
CFRelease(nativeColor); CFRelease(nativeColor);
@ -385,7 +404,7 @@ void CoreGraphicsCanvasImpl::linearGradientStrokePath(CanvasPathEnumerator const
locations.push_back(location); locations.push_back(location);
} }
CGGradientRef nativeGradient = CGGradientCreateWithColorComponents(CGBitmapContextGetColorSpace(_topContext), components.data(), locations.data(), locations.size()); CGGradientRef nativeGradient = CGGradientCreateWithColorComponents(CGBitmapContextGetColorSpace(currentLayer()->context()), components.data(), locations.data(), locations.size());
if (nativeGradient) { if (nativeGradient) {
CGContextDrawLinearGradient(currentLayer()->context(), nativeGradient, CGPointMake(start.x, start.y), CGPointMake(end.x, end.y), kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation); CGContextDrawLinearGradient(currentLayer()->context(), nativeGradient, CGPointMake(start.x, start.y), CGPointMake(end.x, end.y), kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation);
CFRelease(nativeGradient); CFRelease(nativeGradient);
@ -470,7 +489,7 @@ void CoreGraphicsCanvasImpl::radialGradientStrokePath(CanvasPathEnumerator const
locations.push_back(location); locations.push_back(location);
} }
CGGradientRef nativeGradient = CGGradientCreateWithColorComponents(CGBitmapContextGetColorSpace(_topContext), components.data(), locations.data(), locations.size()); CGGradientRef nativeGradient = CGGradientCreateWithColorComponents(CGBitmapContextGetColorSpace(currentLayer()->context()), components.data(), locations.data(), locations.size());
if (nativeGradient) { if (nativeGradient) {
CGContextDrawRadialGradient(currentLayer()->context(), nativeGradient, CGPointMake(startCenter.x, startCenter.y), startRadius, CGPointMake(endCenter.x, endCenter.y), endRadius, kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation); CGContextDrawRadialGradient(currentLayer()->context(), nativeGradient, CGPointMake(startCenter.x, startCenter.y), startRadius, CGPointMake(endCenter.x, endCenter.y), endRadius, kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation);
CFRelease(nativeGradient); CFRelease(nativeGradient);
@ -480,32 +499,8 @@ void CoreGraphicsCanvasImpl::radialGradientStrokePath(CanvasPathEnumerator const
CGContextRestoreGState(currentLayer()->context()); CGContextRestoreGState(currentLayer()->context());
} }
void CoreGraphicsCanvasImpl::fill(lottie::CGRect const &rect, lottie::Color const &fillColor) { void CoreGraphicsCanvasImpl::clip(CGRect const &rect) {
CGFloat components[4] = { fillColor.r, fillColor.g, fillColor.b, fillColor.a }; CGContextClipToRect(currentLayer()->context(), CGRectMake(rect.x, rect.y, rect.width, rect.height));
CGColorRef nativeColor = CGColorCreate(CGBitmapContextGetColorSpace(_topContext), components);
CGContextSetFillColorWithColor(currentLayer()->context(), nativeColor);
CFRelease(nativeColor);
CGContextFillRect(currentLayer()->context(), CGRectMake(rect.x, rect.y, rect.width, rect.height));
}
void CoreGraphicsCanvasImpl::setBlendMode(BlendMode blendMode) {
::CGBlendMode nativeMode = kCGBlendModeNormal;
switch (blendMode) {
case BlendMode::Normal: {
nativeMode = kCGBlendModeNormal;
break;
}
case BlendMode::DestinationIn: {
nativeMode = kCGBlendModeDestinationIn;
break;
}
case BlendMode::DestinationOut: {
nativeMode = kCGBlendModeDestinationOut;
break;
}
}
CGContextSetBlendMode(currentLayer()->context(), nativeMode);
} }
void CoreGraphicsCanvasImpl::concatenate(lottie::Transform2D const &transform) { void CoreGraphicsCanvasImpl::concatenate(lottie::Transform2D const &transform) {
@ -513,29 +508,70 @@ void CoreGraphicsCanvasImpl::concatenate(lottie::Transform2D const &transform) {
} }
std::shared_ptr<CoreGraphicsCanvasImpl::Image> CoreGraphicsCanvasImpl::makeImage() { std::shared_ptr<CoreGraphicsCanvasImpl::Image> CoreGraphicsCanvasImpl::makeImage() {
::CGImageRef nativeImage = CGBitmapContextCreateImage(currentLayer()->context()); return currentLayer()->makeImage();
if (nativeImage) { }
auto image = std::make_shared<CoreGraphicsCanvasImpl::Image>(nativeImage);
CFRelease(nativeImage); bool CoreGraphicsCanvasImpl::pushLayer(CGRect const &rect, float alpha, std::optional<Canvas::MaskMode> maskMode) {
return image; auto currentTransform = fromNativeTransform(CATransform3DMakeAffineTransform(CGContextGetCTM(currentLayer()->context())));
CGRect globalRect(0.0f, 0.0f, 0.0f, 0.0f);
if (rect == CGRect::veryLarge()) {
globalRect = CGRect(0.0f, 0.0f, (float)_width, (float)_height);
} else { } else {
return nil; CGRect transformedRect = rect.applyingTransform(currentTransform);
CGRect integralTransformedRect(
std::floor(transformedRect.x),
std::floor(transformedRect.y),
std::ceil(transformedRect.width + transformedRect.x - floor(transformedRect.x)),
std::ceil(transformedRect.height + transformedRect.y - floor(transformedRect.y))
);
globalRect = integralTransformedRect.intersection(CGRect(0.0, 0.0, (CGFloat)_width, (CGFloat)_height));
} }
} if (globalRect.width <= 0.0f || globalRect.height <= 0.0f) {
return false;
void CoreGraphicsCanvasImpl::draw(std::shared_ptr<Canvas> const &other, float alpha, lottie::CGRect const &rect) { }
CGContextSetAlpha(currentLayer()->context(), alpha);
CoreGraphicsCanvasImpl *impl = (CoreGraphicsCanvasImpl *)other.get(); _layerStack.push_back(std::make_shared<Layer>(globalRect.width, globalRect.height, Layer::Composition(globalRect, alpha, currentTransform, maskMode)));
auto image = impl->makeImage(); concatenate(Transform2D::identity().translated(Vector2D(-globalRect.x, -globalRect.y)));
CGContextDrawImage(currentLayer()->context(), CGRectMake(rect.x, rect.y, rect.width, rect.height), ((CoreGraphicsCanvasImpl::Image *)image.get())->nativeImage()); concatenate(currentTransform);
CGContextSetAlpha(currentLayer()->context(), 1.0);
} return true;
void CoreGraphicsCanvasImpl::pushLayer(CGRect const &rect) {
} }
void CoreGraphicsCanvasImpl::popLayer() { void CoreGraphicsCanvasImpl::popLayer() {
auto layer = _layerStack[_layerStack.size() - 1];
_layerStack.pop_back();
if (const auto composition = layer->composition()) {
saveState();
concatenate(composition->transform.inverted());
CGContextSetAlpha(currentLayer()->context(), composition->alpha);
if (composition->maskMode) {
switch (composition->maskMode.value()) {
case Canvas::MaskMode::Normal: {
CGContextSetBlendMode(currentLayer()->context(), kCGBlendModeDestinationIn);
break;
}
case Canvas::MaskMode::Inverse: {
CGContextSetBlendMode(currentLayer()->context(), kCGBlendModeDestinationOut);
break;
}
default: {
break;
}
}
}
auto image = layer->makeImage();
CGContextDrawImage(currentLayer()->context(), CGRectMake(composition->rect.x, composition->rect.y, composition->rect.width, composition->rect.height), ((CoreGraphicsCanvasImpl::Image *)image.get())->nativeImage());
CGContextSetAlpha(currentLayer()->context(), 1.0);
CGContextSetBlendMode(currentLayer()->context(), kCGBlendModeNormal);
restoreState();
}
} }
std::shared_ptr<CoreGraphicsCanvasImpl::Layer> &CoreGraphicsCanvasImpl::currentLayer() { std::shared_ptr<CoreGraphicsCanvasImpl::Layer> &CoreGraphicsCanvasImpl::currentLayer() {

View File

@ -17,6 +17,8 @@
#include "include/effects/SkDashPathEffect.h" #include "include/effects/SkDashPathEffect.h"
#include "include/effects/SkGradientShader.h" #include "include/effects/SkGradientShader.h"
#include <cfloat>
namespace lottie { namespace lottie {
namespace { namespace {
@ -89,10 +91,6 @@ SkiaCanvasImpl::~SkiaCanvasImpl() {
} }
} }
std::shared_ptr<Canvas> SkiaCanvasImpl::makeLayer(int width, int height) {
return std::make_shared<SkiaCanvasImpl>(width, height);
}
void SkiaCanvasImpl::saveState() { void SkiaCanvasImpl::saveState() {
_canvas->save(); _canvas->save();
} }
@ -105,7 +103,6 @@ void SkiaCanvasImpl::fillPath(CanvasPathEnumerator const &enumeratePath, lottie:
SkPaint paint; SkPaint paint;
paint.setColor(skColor(color)); paint.setColor(skColor(color));
paint.setAntiAlias(true); paint.setAntiAlias(true);
paint.setBlendMode(_blendMode);
SkPath nativePath; SkPath nativePath;
skPath(enumeratePath, nativePath); skPath(enumeratePath, nativePath);
@ -117,7 +114,6 @@ void SkiaCanvasImpl::fillPath(CanvasPathEnumerator const &enumeratePath, lottie:
void SkiaCanvasImpl::linearGradientFillPath(CanvasPathEnumerator const &enumeratePath, lottie::FillRule fillRule, lottie::Gradient const &gradient, lottie::Vector2D const &start, lottie::Vector2D const &end) { void SkiaCanvasImpl::linearGradientFillPath(CanvasPathEnumerator const &enumeratePath, lottie::FillRule fillRule, lottie::Gradient const &gradient, lottie::Vector2D const &start, lottie::Vector2D const &end) {
SkPaint paint; SkPaint paint;
paint.setAntiAlias(true); paint.setAntiAlias(true);
paint.setBlendMode(_blendMode);
paint.setDither(false); paint.setDither(false);
paint.setStyle(SkPaint::Style::kFill_Style); paint.setStyle(SkPaint::Style::kFill_Style);
@ -136,7 +132,7 @@ void SkiaCanvasImpl::linearGradientFillPath(CanvasPathEnumerator const &enumerat
locations.push_back(location); locations.push_back(location);
} }
paint.setShader(SkGradientShader::MakeLinear(linearPoints, colors.data(), locations.data(), (int)colors.size(), SkTileMode::kMirror)); paint.setShader(SkGradientShader::MakeLinear(linearPoints, colors.data(), locations.data(), (int)colors.size(), SkTileMode::kClamp));
SkPath nativePath; SkPath nativePath;
skPath(enumeratePath, nativePath); skPath(enumeratePath, nativePath);
@ -145,11 +141,9 @@ void SkiaCanvasImpl::linearGradientFillPath(CanvasPathEnumerator const &enumerat
_canvas->drawPath(nativePath, paint); _canvas->drawPath(nativePath, paint);
} }
void SkiaCanvasImpl::radialGradientFillPath(CanvasPathEnumerator const &enumeratePath, lottie::FillRule fillRule, lottie::Gradient const &gradient, lottie::Vector2D const &startCenter, float startRadius, lottie::Vector2D const &endCenter, float endRadius) { void SkiaCanvasImpl::radialGradientFillPath(CanvasPathEnumerator const &enumeratePath, lottie::FillRule fillRule, lottie::Gradient const &gradient, Vector2D const &center, float radius) {
SkPaint paint; SkPaint paint;
paint.setAntiAlias(true); paint.setAntiAlias(true);
paint.setBlendMode(_blendMode);
paint.setDither(false);
paint.setStyle(SkPaint::Style::kFill_Style); paint.setStyle(SkPaint::Style::kFill_Style);
std::vector<SkColor> colors; std::vector<SkColor> colors;
@ -162,7 +156,7 @@ void SkiaCanvasImpl::radialGradientFillPath(CanvasPathEnumerator const &enumerat
locations.push_back(location); locations.push_back(location);
} }
paint.setShader(SkGradientShader::MakeRadial(SkPoint::Make(startCenter.x, startCenter.y), endRadius, colors.data(), locations.data(), (int)colors.size(), SkTileMode::kMirror)); paint.setShader(SkGradientShader::MakeRadial(SkPoint::Make(center.x, center.y), radius, colors.data(), locations.data(), (int)colors.size(), SkTileMode::kClamp));
SkPath nativePath; SkPath nativePath;
skPath(enumeratePath, nativePath); skPath(enumeratePath, nativePath);
@ -172,9 +166,11 @@ void SkiaCanvasImpl::radialGradientFillPath(CanvasPathEnumerator const &enumerat
} }
void SkiaCanvasImpl::strokePath(CanvasPathEnumerator const &enumeratePath, float lineWidth, lottie::LineJoin lineJoin, lottie::LineCap lineCap, float dashPhase, std::vector<float> const &dashPattern, lottie::Color const &color) { void SkiaCanvasImpl::strokePath(CanvasPathEnumerator const &enumeratePath, float lineWidth, lottie::LineJoin lineJoin, lottie::LineCap lineCap, float dashPhase, std::vector<float> const &dashPattern, lottie::Color const &color) {
if (lineWidth <= FLT_EPSILON) {
return;
}
SkPaint paint; SkPaint paint;
paint.setAntiAlias(true); paint.setAntiAlias(true);
paint.setBlendMode(_blendMode);
paint.setColor(skColor(color)); paint.setColor(skColor(color));
paint.setStyle(SkPaint::Style::kStroke_Style); paint.setStyle(SkPaint::Style::kStroke_Style);
@ -223,6 +219,9 @@ void SkiaCanvasImpl::strokePath(CanvasPathEnumerator const &enumeratePath, float
for (auto value : dashPattern) { for (auto value : dashPattern) {
intervals.push_back(value); intervals.push_back(value);
} }
if (intervals.size() == 1) {
intervals.push_back(intervals[0]);
}
paint.setPathEffect(SkDashPathEffect::Make(intervals.data(), (int)intervals.size(), dashPhase)); paint.setPathEffect(SkDashPathEffect::Make(intervals.data(), (int)intervals.size(), dashPhase));
} }
@ -240,34 +239,8 @@ void SkiaCanvasImpl::radialGradientStrokePath(CanvasPathEnumerator const &enumer
assert(false); assert(false);
} }
void SkiaCanvasImpl::fill(lottie::CGRect const &rect, lottie::Color const &fillColor) { void SkiaCanvasImpl::clip(CGRect const &rect) {
SkPaint paint; _canvas->clipRect(SkRect::MakeXYWH(rect.x, rect.y, rect.width, rect.height), true);
paint.setAntiAlias(true);
paint.setColor(skColor(fillColor));
paint.setBlendMode(_blendMode);
_canvas->drawRect(SkRect::MakeXYWH(rect.x, rect.y, rect.width, rect.height), paint);
}
void SkiaCanvasImpl::setBlendMode(BlendMode blendMode) {
switch (blendMode) {
case BlendMode::Normal: {
_blendMode = SkBlendMode::kSrcOver;
break;
}
case BlendMode::DestinationIn: {
_blendMode = SkBlendMode::kDstIn;
break;
}
case BlendMode::DestinationOut: {
_blendMode = SkBlendMode::kDstOut;
break;
}
default: {
_blendMode = SkBlendMode::kSrcOver;
break;
}
}
} }
void SkiaCanvasImpl::concatenate(lottie::Transform2D const &transform) { void SkiaCanvasImpl::concatenate(lottie::Transform2D const &transform) {
@ -281,13 +254,32 @@ void SkiaCanvasImpl::concatenate(lottie::Transform2D const &transform) {
_canvas->concat(matrix); _canvas->concat(matrix);
} }
void SkiaCanvasImpl::draw(std::shared_ptr<Canvas> const &other, float alpha, lottie::CGRect const &rect) { bool SkiaCanvasImpl::pushLayer(CGRect const &rect, float alpha, std::optional<MaskMode> maskMode) {
SkiaCanvasImpl *impl = (SkiaCanvasImpl *)other.get();
auto image = impl->surface()->makeImageSnapshot();
SkPaint paint; SkPaint paint;
paint.setBlendMode(_blendMode); paint.setAntiAlias(true);
paint.setAlphaf(alpha); paint.setAlphaf(alpha);
_canvas->drawImageRect(image.get(), SkRect::MakeXYWH(rect.x, rect.y, rect.width, rect.height), SkSamplingOptions(SkFilterMode::kLinear), &paint); if (maskMode) {
switch (maskMode.value()) {
case Canvas::MaskMode::Normal: {
paint.setBlendMode(SkBlendMode::kDstIn);
break;
}
case Canvas::MaskMode::Inverse: {
paint.setBlendMode(SkBlendMode::kDstOut);
break;
}
default: {
break;
}
}
}
_canvas->saveLayer(SkRect::MakeXYWH(rect.x, rect.y, rect.width, rect.height), &paint);
return true;
}
void SkiaCanvasImpl::popLayer() {
_canvas->restore();
} }
void SkiaCanvasImpl::flush() { void SkiaCanvasImpl::flush() {

View File

@ -14,24 +14,21 @@ public:
SkiaCanvasImpl(int width, int height, int bytesPerRow, void *pixelData); SkiaCanvasImpl(int width, int height, int bytesPerRow, void *pixelData);
virtual ~SkiaCanvasImpl(); virtual ~SkiaCanvasImpl();
virtual std::shared_ptr<Canvas> makeLayer(int width, int height) override;
virtual void saveState() override; virtual void saveState() override;
virtual void restoreState() override; virtual void restoreState() override;
virtual void fillPath(CanvasPathEnumerator const &enumeratePath, lottie::FillRule fillRule, lottie::Color const &color) override; virtual void fillPath(CanvasPathEnumerator const &enumeratePath, lottie::FillRule fillRule, lottie::Color const &color) 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 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 radialGradientFillPath(CanvasPathEnumerator const &enumeratePath, lottie::FillRule fillRule, lottie::Gradient const &gradient, Vector2D const &center, float radius) 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 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 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 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 clip(CGRect const &rect) override;
virtual void concatenate(lottie::Transform2D const &transform) override; virtual void concatenate(lottie::Transform2D const &transform) override;
virtual void draw(std::shared_ptr<Canvas> const &other, float alpha, lottie::CGRect const &rect) override; virtual bool pushLayer(CGRect const &rect, float alpha, std::optional<MaskMode> maskMode) override;
virtual void popLayer() override;
void flush(); void flush();
sk_sp<SkSurface> surface() const; sk_sp<SkSurface> surface() const;
@ -41,7 +38,6 @@ private:
bool _ownsPixelData = false; bool _ownsPixelData = false;
sk_sp<SkSurface> _surface; sk_sp<SkSurface> _surface;
SkCanvas *_canvas = nullptr; SkCanvas *_canvas = nullptr;
SkBlendMode _blendMode = SkBlendMode::kSrcOver;
}; };
} }

View File

@ -57,98 +57,62 @@ CGRect getPathNativeBoundingBox(CGPathRef _Nonnull path) {
_renderer->setFrame((int)index); _renderer->setFrame((int)index);
} }
- (UIImage * _Nullable)renderForSize:(CGSize)size useReferenceRendering:(bool)useReferenceRendering { - (UIImage * _Nullable)renderForSize:(CGSize)size useReferenceRendering:(bool)useReferenceRendering canUseMoreMemory:(bool)canUseMoreMemory skipImageGeneration:(bool)skipImageGeneration {
std::shared_ptr<lottie::RenderTreeNode> renderNode = _renderer->renderNode(); std::shared_ptr<lottie::RenderTreeNode> renderNode = _renderer->renderNode();
if (!renderNode) { if (!renderNode) {
return nil; return nil;
} }
lottie::CanvasRenderer::Configuration configuration;
configuration.canUseMoreMemory = canUseMoreMemory;
//configuration.canUseMoreMemory = true;
//configuration.disableGroupTransparency = true;
if (useReferenceRendering) { if (useReferenceRendering) {
auto context = std::make_shared<lottie::CoreGraphicsCanvasImpl>((int)size.width, (int)size.height); auto context = std::make_shared<lottie::CoreGraphicsCanvasImpl>((int)size.width, (int)size.height);
_canvasRenderer->render(_renderer, context, lottie::Vector2D(size.width, size.height)); _canvasRenderer->render(_renderer, context, lottie::Vector2D(size.width, size.height), configuration);
auto image = context->makeImage(); auto image = context->makeImage();
return [[UIImage alloc] initWithCGImage:std::static_pointer_cast<lottie::CoreGraphicsCanvasImpl::Image>(image)->nativeImage()]; return [[UIImage alloc] initWithCGImage:std::static_pointer_cast<lottie::CoreGraphicsCanvasImpl::Image>(image)->nativeImage()];
} else { } else {
if ((int64_t)"" > 0) { if ((int64_t)"" > 0) {
/*auto surface = SkSurfaces::Raster(SkImageInfo::MakeN32Premul((int)size.width, (int)size.height));
int bytesPerRow = ((int)size.width) * 4;
void *pixelData = malloc(bytesPerRow * (int)size.height);
sk_sp<SkSurface> surface2 = SkSurfaces::WrapPixels(
SkImageInfo::MakeN32Premul((int)size.width, (int)size.height),
pixelData,
bytesPerRow,
nullptr
);
SkCanvas *canvas = surface->getCanvas();
SkPaint paint;
paint.setAntiAlias(true);
SkPath path;
path.moveTo(124, 108);
path.lineTo(172, 24);
path.addCircle(50, 50, 30);
path.moveTo(36, 148);
path.quadTo(66, 188, 120, 136);
canvas->drawPath(path, paint);
paint.setStyle(SkPaint::kStroke_Style);
paint.setColor(SK_ColorBLUE);
paint.setStrokeWidth(3);
canvas->drawPath(path, paint);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGBitmapInfo bitmapInfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host;
CGContextRef targetContext = CGBitmapContextCreate(pixelData, (int)size.width, (int)size.height, 8, bytesPerRow, colorSpace, bitmapInfo);
CGColorSpaceRelease(colorSpace);
CGImageRef bitmapImage = CGBitmapContextCreateImage(targetContext);
UIImage *image = [[UIImage alloc] initWithCGImage:bitmapImage scale:1.0f orientation:UIImageOrientationDownMirrored];
CGImageRelease(bitmapImage);
CGContextRelease(targetContext);
free(pixelData);
return image;*/
int bytesPerRow = ((int)size.width) * 4; int bytesPerRow = ((int)size.width) * 4;
void *pixelData = malloc(bytesPerRow * (int)size.height); void *pixelData = malloc(bytesPerRow * (int)size.height);
auto context = std::make_shared<lottie::SkiaCanvasImpl>((int)size.width, (int)size.height, bytesPerRow, pixelData); auto context = std::make_shared<lottie::SkiaCanvasImpl>((int)size.width, (int)size.height, bytesPerRow, pixelData);
_canvasRenderer->render(_renderer, context, lottie::Vector2D(size.width, size.height)); _canvasRenderer->render(_renderer, context, lottie::Vector2D(size.width, size.height), configuration);
context->flush(); context->flush();
vImage_Buffer src; if (skipImageGeneration) {
src.data = (void *)pixelData; free(pixelData);
src.width = (int)size.width; } else {
src.height = (int)size.height; vImage_Buffer src;
src.rowBytes = bytesPerRow; src.data = (void *)pixelData;
src.width = (int)size.width;
uint8_t permuteMap[4] = {2, 1, 0, 3}; src.height = (int)size.height;
vImagePermuteChannels_ARGB8888(&src, &src, permuteMap, kvImageDoNotTile); src.rowBytes = bytesPerRow;
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); uint8_t permuteMap[4] = {2, 1, 0, 3};
CGBitmapInfo bitmapInfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host; vImagePermuteChannels_ARGB8888(&src, &src, permuteMap, kvImageDoNotTile);
CGContextRef targetContext = CGBitmapContextCreate(pixelData, (int)size.width, (int)size.height, 8, bytesPerRow, colorSpace, bitmapInfo); CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGColorSpaceRelease(colorSpace); CGBitmapInfo bitmapInfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host;
CGImageRef bitmapImage = CGBitmapContextCreateImage(targetContext); CGContextRef targetContext = CGBitmapContextCreate(pixelData, (int)size.width, (int)size.height, 8, bytesPerRow, colorSpace, bitmapInfo);
UIImage *image = [[UIImage alloc] initWithCGImage:bitmapImage scale:1.0f orientation:UIImageOrientationDownMirrored]; CGColorSpaceRelease(colorSpace);
CGImageRelease(bitmapImage);
CGImageRef bitmapImage = CGBitmapContextCreateImage(targetContext);
CGContextRelease(targetContext); UIImage *image = [[UIImage alloc] initWithCGImage:bitmapImage scale:1.0f orientation:UIImageOrientationDownMirrored];
CGImageRelease(bitmapImage);
free(pixelData);
CGContextRelease(targetContext);
return image;
free(pixelData);
return image;
}
} else if ((int64_t)"" < 0) { } else if ((int64_t)"" < 0) {
static dispatch_once_t onceToken; static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{ dispatch_once(&onceToken, ^{
@ -158,7 +122,7 @@ CGRect getPathNativeBoundingBox(CGPathRef _Nonnull path) {
int bytesPerRow = ((int)size.width) * 4; int bytesPerRow = ((int)size.width) * 4;
auto context = std::make_shared<lottie::ThorVGCanvasImpl>((int)size.width, (int)size.height, bytesPerRow); auto context = std::make_shared<lottie::ThorVGCanvasImpl>((int)size.width, (int)size.height, bytesPerRow);
_canvasRenderer->render(_renderer, context, lottie::Vector2D(size.width, size.height)); _canvasRenderer->render(_renderer, context, lottie::Vector2D(size.width, size.height), configuration);
context->flush(); context->flush();
@ -177,7 +141,7 @@ CGRect getPathNativeBoundingBox(CGPathRef _Nonnull path) {
return image; return image;
} else { } else {
auto context = std::make_shared<lottie::NullCanvasImpl>((int)size.width, (int)size.height); auto context = std::make_shared<lottie::NullCanvasImpl>((int)size.width, (int)size.height);
_canvasRenderer->render(_renderer, context, lottie::Vector2D(size.width, size.height)); _canvasRenderer->render(_renderer, context, lottie::Vector2D(size.width, size.height), configuration);
return nil; return nil;
} }

View File

@ -63,10 +63,6 @@ _transform(lottie::Transform2D::identity()) {
ThorVGCanvasImpl::~ThorVGCanvasImpl() { ThorVGCanvasImpl::~ThorVGCanvasImpl() {
} }
std::shared_ptr<Canvas> ThorVGCanvasImpl::makeLayer(int width, int height) {
return std::make_shared<ThorVGCanvasImpl>(width, height, width * 4);
}
void ThorVGCanvasImpl::saveState() { void ThorVGCanvasImpl::saveState() {
_stateStack.push_back(_transform); _stateStack.push_back(_transform);
} }
@ -120,14 +116,14 @@ void ThorVGCanvasImpl::linearGradientFillPath(CanvasPathEnumerator const &enumer
_canvas->push(std::move(shape)); _canvas->push(std::move(shape));
} }
void ThorVGCanvasImpl::radialGradientFillPath(CanvasPathEnumerator const &enumeratePath, lottie::FillRule fillRule, Gradient const &gradient, lottie::Vector2D const &startCenter, float startRadius, lottie::Vector2D const &endCenter, float endRadius) { void ThorVGCanvasImpl::radialGradientFillPath(CanvasPathEnumerator const &enumeratePath, lottie::FillRule fillRule, Gradient const &gradient, Vector2D const &center, float radius) {
auto shape = tvg::Shape::gen(); auto shape = tvg::Shape::gen();
tvgPath(enumeratePath, shape.get()); tvgPath(enumeratePath, shape.get());
shape->transform(tvgTransform(_transform)); shape->transform(tvgTransform(_transform));
auto fill = tvg::RadialGradient::gen(); auto fill = tvg::RadialGradient::gen();
fill->radial(startCenter.x, startCenter.y, endRadius); fill->radial(center.x, center.y, radius);
std::vector<tvg::Fill::ColorStop> colors; std::vector<tvg::Fill::ColorStop> colors;
for (size_t i = 0; i < gradient.colors().size(); i++) { for (size_t i = 0; i < gradient.colors().size(); i++) {
@ -214,55 +210,8 @@ void ThorVGCanvasImpl::linearGradientStrokePath(CanvasPathEnumerator const &enum
void ThorVGCanvasImpl::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) { void ThorVGCanvasImpl::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) {
} }
void ThorVGCanvasImpl::fill(lottie::CGRect const &rect, lottie::Color const &fillColor) {
auto shape = tvg::Shape::gen();
shape->appendRect(rect.x, rect.y, rect.width, rect.height, 0.0f, 0.0f);
shape->transform(tvgTransform(_transform));
shape->fill((int)(fillColor.r * 255.0), (int)(fillColor.g * 255.0), (int)(fillColor.b * 255.0), (int)(fillColor.a * 255.0));
_canvas->push(std::move(shape));
}
void ThorVGCanvasImpl::setBlendMode(BlendMode blendMode) {
/*switch (blendMode) {
case CGBlendMode::Normal: {
_blendMode = SkBlendMode::kSrcOver;
break;
}
case CGBlendMode::DestinationIn: {
_blendMode = SkBlendMode::kDstIn;
break;
}
case CGBlendMode::DestinationOut: {
_blendMode = SkBlendMode::kDstOut;
break;
}
default: {
_blendMode = SkBlendMode::kSrcOver;
break;
}
}*/
}
void ThorVGCanvasImpl::concatenate(lottie::Transform2D const &transform) { void ThorVGCanvasImpl::concatenate(lottie::Transform2D const &transform) {
_transform = transform * _transform; _transform = transform * _transform;
/*_canvas->concat(SkM44(
transform.m11, transform.m21, transform.m31, transform.m41,
transform.m12, transform.m22, transform.m32, transform.m42,
transform.m13, transform.m23, transform.m33, transform.m43,
transform.m14, transform.m24, transform.m34, transform.m44
));*/
}
void ThorVGCanvasImpl::draw(std::shared_ptr<Canvas> const &other, float alpha, lottie::CGRect const &rect) {
/*ThorVGCanvasImpl *impl = (ThorVGCanvasImpl *)other.get();
auto image = impl->surface()->makeImageSnapshot();
SkPaint paint;
paint.setBlendMode(_blendMode);
paint.setAlphaf(_alpha);
_canvas->drawImageRect(image.get(), SkRect::MakeXYWH(rect.x, rect.y, rect.width, rect.height), SkSamplingOptions(SkFilterMode::kLinear), &paint);*/
} }
void ThorVGCanvasImpl::flush() { void ThorVGCanvasImpl::flush() {

View File

@ -14,25 +14,18 @@ public:
ThorVGCanvasImpl(int width, int height, int bytesPerRow); ThorVGCanvasImpl(int width, int height, int bytesPerRow);
virtual ~ThorVGCanvasImpl(); virtual ~ThorVGCanvasImpl();
virtual std::shared_ptr<Canvas> makeLayer(int width, int height) override;
virtual void saveState() override; virtual void saveState() override;
virtual void restoreState() override; virtual void restoreState() override;
virtual void fillPath(CanvasPathEnumerator const &enumeratePath, lottie::FillRule fillRule, lottie::Color const &color) override; virtual void fillPath(CanvasPathEnumerator const &enumeratePath, lottie::FillRule fillRule, lottie::Color const &color) 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 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 radialGradientFillPath(CanvasPathEnumerator const &enumeratePath, lottie::FillRule fillRule, lottie::Gradient const &gradient, Vector2D const &center, float radius) 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 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 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 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 concatenate(lottie::Transform2D const &transform) override; virtual void concatenate(lottie::Transform2D const &transform) override;
virtual void draw(std::shared_ptr<Canvas> const &other, float alpha, lottie::CGRect const &rect) override;
uint32_t *backingData() { uint32_t *backingData() {
return _backingData; return _backingData;
} }

View File

@ -10,17 +10,26 @@ import SoftwareLottieRenderer
import LottieSwift import LottieSwift
@available(iOS 13.0, *) @available(iOS 13.0, *)
func areImagesEqual(_ lhs: UIImage, _ rhs: UIImage) -> UIImage? { func areImagesEqual(_ lhs: UIImage, _ rhs: UIImage, allowedDifference: Double) -> (UIImage?, UIImage) {
let lhsBuffer = try! vImage_Buffer(cgImage: lhs.cgImage!) let lhsBuffer = try! vImage_Buffer(cgImage: lhs.cgImage!)
let rhsBuffer = try! vImage_Buffer(cgImage: rhs.cgImage!) let rhsBuffer = try! vImage_Buffer(cgImage: rhs.cgImage!)
let deltaBuffer = try! vImage_Buffer(cgImage: lhs.cgImage!)
defer {
lhsBuffer.free()
rhsBuffer.free()
deltaBuffer.free()
}
let maxDifferenceCount = Int((Double(Int(lhs.size.width) * Int(lhs.size.height)) * 0.01)) memset(deltaBuffer.data, 0, Int(deltaBuffer.height) * deltaBuffer.rowBytes)
let maxDifferenceCount = Int((Double(Int(lhs.size.width) * Int(lhs.size.height)) * allowedDifference))
var foundDifferenceCount = 0 var foundDifferenceCount = 0
outer: for y in 0 ..< Int(lhs.size.height) { outer: for y in 0 ..< Int(lhs.size.height) {
let lhsRowPixels = lhsBuffer.data.assumingMemoryBound(to: UInt8.self).advanced(by: y * lhsBuffer.rowBytes) let lhsRowPixels = lhsBuffer.data.assumingMemoryBound(to: UInt8.self).advanced(by: y * lhsBuffer.rowBytes)
let rhsRowPixels = rhsBuffer.data.assumingMemoryBound(to: UInt8.self).advanced(by: y * lhsBuffer.rowBytes) let rhsRowPixels = rhsBuffer.data.assumingMemoryBound(to: UInt8.self).advanced(by: y * lhsBuffer.rowBytes)
let deltaRowPixels = deltaBuffer.data.assumingMemoryBound(to: UInt8.self).advanced(by: y * lhsBuffer.rowBytes)
for x in 0 ..< Int(lhs.size.width) { for x in 0 ..< Int(lhs.size.width) {
let lhs0 = lhsRowPixels.advanced(by: x * 4 + 0).pointee let lhs0 = lhsRowPixels.advanced(by: x * 4 + 0).pointee
@ -36,32 +45,30 @@ func areImagesEqual(_ lhs: UIImage, _ rhs: UIImage) -> UIImage? {
let maxDiff = 25 let maxDiff = 25
if abs(Int(lhs0) - Int(rhs0)) > maxDiff || abs(Int(lhs1) - Int(rhs1)) > maxDiff || abs(Int(lhs2) - Int(rhs2)) > maxDiff || abs(Int(lhs3) - Int(rhs3)) > maxDiff { if abs(Int(lhs0) - Int(rhs0)) > maxDiff || abs(Int(lhs1) - Int(rhs1)) > maxDiff || abs(Int(lhs2) - Int(rhs2)) > maxDiff || abs(Int(lhs3) - Int(rhs3)) > maxDiff {
/*if false { deltaRowPixels.advanced(by: x * 4 + 0).pointee = 255
lhsRowPixels.advanced(by: x * 4 + 0).pointee = 255 deltaRowPixels.advanced(by: x * 4 + 1).pointee = 0
lhsRowPixels.advanced(by: x * 4 + 1).pointee = 0 deltaRowPixels.advanced(by: x * 4 + 2).pointee = 0
lhsRowPixels.advanced(by: x * 4 + 2).pointee = 0 deltaRowPixels.advanced(by: x * 4 + 3).pointee = 255
lhsRowPixels.advanced(by: x * 4 + 3).pointee = 255
}*/
foundDifferenceCount += 1 foundDifferenceCount += 1
} }
} }
} }
lhsBuffer.free() let colorSpace = Unmanaged<CGColorSpace>.passRetained(lhs.cgImage!.colorSpace!)
rhsBuffer.free() let deltaImage = try! deltaBuffer.createCGImage(format: vImage_CGImageFormat(bitsPerComponent: 8, bitsPerPixel: 32, colorSpace: colorSpace, bitmapInfo: lhs.cgImage!.bitmapInfo, version: 0, decode: nil, renderingIntent: .defaultIntent), flags: .doNotTile)
if foundDifferenceCount > maxDifferenceCount { if foundDifferenceCount > maxDifferenceCount {
let colorSpace = Unmanaged<CGColorSpace>.passRetained(lhs.cgImage!.colorSpace!)
let diffImage = try! lhsBuffer.createCGImage(format: vImage_CGImageFormat(bitsPerComponent: 8, bitsPerPixel: 32, colorSpace: colorSpace, bitmapInfo: lhs.cgImage!.bitmapInfo, version: 0, decode: nil, renderingIntent: .defaultIntent), flags: .doNotTile) let diffImage = try! lhsBuffer.createCGImage(format: vImage_CGImageFormat(bitsPerComponent: 8, bitsPerPixel: 32, colorSpace: colorSpace, bitmapInfo: lhs.cgImage!.bitmapInfo, version: 0, decode: nil, renderingIntent: .defaultIntent), flags: .doNotTile)
return UIImage(cgImage: diffImage)
return (UIImage(cgImage: diffImage), UIImage(cgImage: deltaImage))
} else { } else {
return nil return (nil, UIImage(cgImage: deltaImage))
} }
} }
@available(iOS 13.0, *) @available(iOS 13.0, *)
func processDrawAnimation(baseCachePath: String, path: String, name: String, size: CGSize, alwaysDraw: Bool, useNonReferenceRendering: Bool, updateImage: @escaping (UIImage?, UIImage?) -> Void) async -> Bool { func processDrawAnimation(baseCachePath: String, path: String, name: String, size: CGSize, allowedDifference: Double, alwaysDraw: Bool, useNonReferenceRendering: Bool, updateImage: @escaping (UIImage?, UIImage?, UIImage?) -> Void) async -> Bool {
guard let data = try? Data(contentsOf: URL(fileURLWithPath: path)) else { guard let data = try? Data(contentsOf: URL(fileURLWithPath: path)) else {
print("Could not load \(path)") print("Could not load \(path)")
return false return false
@ -85,16 +92,18 @@ func processDrawAnimation(baseCachePath: String, path: String, name: String, siz
let referenceImage = decompressImageFrame(data: referenceImageData) let referenceImage = decompressImageFrame(data: referenceImageData)
renderer.setFrame(frameIndex) renderer.setFrame(frameIndex)
let image = renderer.render(for: size, useReferenceRendering: !useNonReferenceRendering)! let image = renderer.render(for: size, useReferenceRendering: !useNonReferenceRendering, canUseMoreMemory: false, skipImageGeneration: false)!
if !useNonReferenceRendering, let diffImage = areImagesEqual(image, referenceImage) { let (diffImage, deltaImage) = areImagesEqual(image, referenceImage, allowedDifference: allowedDifference)
updateImage(diffImage, referenceImage)
if !useNonReferenceRendering, let diffImage {
updateImage(diffImage, referenceImage, deltaImage)
print("Mismatch in frame \(frameIndex)") print("Mismatch in frame \(frameIndex)")
return false return false
} else { } else {
if alwaysDraw { if alwaysDraw {
updateImage(image, referenceImage) updateImage(image, referenceImage, diffImage)
} }
return true return true
} }
@ -279,6 +288,43 @@ func decompressImageFrame(data: Data) -> UIImage {
return decodeImageQOI(data)! return decodeImageQOI(data)!
} }
final class ReferenceLottieAnimationItem {
private let referenceAnimation: Animation
private let referenceLayer: MainThreadAnimationLayer
let frameCount: Int
init?(path: String) {
guard let referenceAnimation = Animation.filepath(path) else {
return nil
}
self.referenceAnimation = referenceAnimation
self.referenceLayer = MainThreadAnimationLayer(animation: referenceAnimation, imageProvider: BlankImageProvider(), textProvider: DefaultTextProvider(), fontProvider: DefaultFontProvider())
self.referenceLayer.position = referenceAnimation.bounds.center
self.referenceLayer.isOpaque = false
self.referenceLayer.backgroundColor = nil
self.frameCount = Int(referenceAnimation.endFrame - referenceAnimation.startFrame)
}
func setFrame(index: Int) {
self.referenceLayer.currentFrame = self.referenceAnimation.startFrame + CGFloat(index)
self.referenceLayer.displayUpdate()
}
func makeImage(width: Int, height: Int) -> UIImage? {
let size = CGSize(width: CGFloat(width), height: CGFloat(width))
let referenceContext = ImageContext(width: width, height: height)
referenceContext.context.clear(CGRect(origin: CGPoint(), size: size))
referenceContext.context.scaleBy(x: size.width / CGFloat(self.referenceAnimation.width), y: size.height / CGFloat(self.referenceAnimation.height))
referenceLayer.render(in: referenceContext.context)
return referenceContext.makeImage()
}
}
@MainActor @MainActor
func cacheReferenceAnimation(baseCachePath: String, width: Int, path: String, name: String) -> String { func cacheReferenceAnimation(baseCachePath: String, width: Int, path: String, name: String) -> String {
let targetFolderPath = cacheReferenceFolderPath(baseCachePath: baseCachePath, width: width, name: name) let targetFolderPath = cacheReferenceFolderPath(baseCachePath: baseCachePath, width: width, name: name)
@ -286,34 +332,19 @@ func cacheReferenceAnimation(baseCachePath: String, width: Int, path: String, na
return targetFolderPath return targetFolderPath
} }
guard let referenceAnimation = Animation.filepath(path) else { guard let referenceItem = ReferenceLottieAnimationItem(path: path) else {
preconditionFailure("Could not parse reference animation at \(path)") preconditionFailure("Could not load reference animation at \(path)")
} }
let referenceLayer = MainThreadAnimationLayer(animation: referenceAnimation, imageProvider: BlankImageProvider(), textProvider: DefaultTextProvider(), fontProvider: DefaultFontProvider())
let cacheFolderPath = NSTemporaryDirectory() + "\(UInt64.random(in: 0 ... UInt64.max))" let cacheFolderPath = NSTemporaryDirectory() + "\(UInt64.random(in: 0 ... UInt64.max))"
let _ = try? FileManager.default.createDirectory(atPath: cacheFolderPath, withIntermediateDirectories: true) let _ = try? FileManager.default.createDirectory(atPath: cacheFolderPath, withIntermediateDirectories: true)
let frameCount = Int(referenceAnimation.endFrame - referenceAnimation.startFrame) for i in 0 ..< min(100000, referenceItem.frameCount) {
let frameIndex = i % referenceItem.frameCount
let size = CGSize(width: CGFloat(width), height: CGFloat(width))
for i in 0 ..< min(100000, frameCount) {
let frameIndex = i % frameCount
referenceLayer.currentFrame = CGFloat(frameIndex) referenceItem.setFrame(index: frameIndex)
referenceLayer.displayUpdate()
referenceLayer.position = referenceAnimation.bounds.center
referenceLayer.isOpaque = false let referenceImage = referenceItem.makeImage(width: width, height: width)!
referenceLayer.backgroundColor = nil
let referenceContext = ImageContext(width: width, height: width)
referenceContext.context.clear(CGRect(origin: CGPoint(), size: size))
referenceContext.context.scaleBy(x: size.width / CGFloat(referenceAnimation.width), y: size.height / CGFloat(referenceAnimation.height))
referenceLayer.render(in: referenceContext.context)
let referenceImage = referenceContext.makeImage()
try! compressImageFrame(image: referenceImage).write(to: URL(fileURLWithPath: cacheFolderPath + "/frame\(i)")) try! compressImageFrame(image: referenceImage).write(to: URL(fileURLWithPath: cacheFolderPath + "/frame\(i)"))
} }

View File

@ -13,6 +13,7 @@ private final class ReferenceCompareTest {
private let view: UIView private let view: UIView
private let imageView = UIImageView() private let imageView = UIImageView()
private let referenceImageView = UIImageView() private let referenceImageView = UIImageView()
private let deltaImageView = UIImageView()
init(view: UIView, testNonReference: Bool) { init(view: UIView, testNonReference: Bool) {
lottieSwift_getPathNativeBoundingBox = { path in lottieSwift_getPathNativeBoundingBox = { path in
@ -37,6 +38,12 @@ private final class ReferenceCompareTest {
self.referenceImageView.backgroundColor = self.view.backgroundColor self.referenceImageView.backgroundColor = self.view.backgroundColor
self.referenceImageView.transform = CGAffineTransform.init(scaleX: 1.0, y: -1.0) self.referenceImageView.transform = CGAffineTransform.init(scaleX: 1.0, y: -1.0)
self.view.addSubview(self.deltaImageView)
self.deltaImageView.layer.magnificationFilter = .nearest
self.deltaImageView.frame = CGRect(origin: CGPoint(x: 10.0, y: topInset + 256.0 + 1.0 + 256.0 + 1.0), size: CGSize(width: 256.0, height: 256.0))
self.deltaImageView.backgroundColor = self.view.backgroundColor
self.deltaImageView.transform = CGAffineTransform.init(scaleX: 1.0, y: -1.0)
let bundlePath = Bundle.main.path(forResource: "TestDataBundle", ofType: "bundle")! let bundlePath = Bundle.main.path(forResource: "TestDataBundle", ofType: "bundle")!
Task.detached { Task.detached {
@ -67,6 +74,9 @@ private final class ReferenceCompareTest {
"1391391008142393350.json": 1024 "1391391008142393350.json": 1024
] ]
let allowedDifferences: [String: Double] = [
"1258816259754165.json": 0.04
]
let defaultSize = 128 let defaultSize = 128
let baseCachePath = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true).path + "/frame-cache" let baseCachePath = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true).path + "/frame-cache"
@ -78,7 +88,7 @@ private final class ReferenceCompareTest {
} }
var continueFromName: String? var continueFromName: String?
//continueFromName = "5089561049196134821.json" //continueFromName = "1258816259754165.json"
let _ = await processAnimationFolderAsync(basePath: bundlePath, path: "", stopOnFailure: !testNonReference, process: { path, name, alwaysDraw in let _ = await processAnimationFolderAsync(basePath: bundlePath, path: "", stopOnFailure: !testNonReference, process: { path, name, alwaysDraw in
if let continueFromNameValue = continueFromName { if let continueFromNameValue = continueFromName {
@ -91,10 +101,11 @@ private final class ReferenceCompareTest {
let size = sizeMapping[name] ?? defaultSize let size = sizeMapping[name] ?? defaultSize
let result = await processDrawAnimation(baseCachePath: baseCachePath, path: path, name: name, size: CGSize(width: size, height: size), alwaysDraw: alwaysDraw, useNonReferenceRendering: testNonReference, updateImage: { image, referenceImage in let result = await processDrawAnimation(baseCachePath: baseCachePath, path: path, name: name, size: CGSize(width: size, height: size), allowedDifference: allowedDifferences[name] ?? 0.01, alwaysDraw: alwaysDraw, useNonReferenceRendering: testNonReference, updateImage: { image, referenceImage, differenceImage in
DispatchQueue.main.async { DispatchQueue.main.async {
self.imageView.image = image self.imageView.image = image
self.referenceImageView.image = referenceImage self.referenceImageView.image = referenceImage
self.deltaImageView.image = differenceImage
} }
}) })
return result return result
@ -103,6 +114,139 @@ private final class ReferenceCompareTest {
} }
} }
@available(iOS 13.0, *)
private final class ManualReferenceCompareTest {
private final class Item {
let renderer: SoftwareLottieRenderer
let referenceRenderer: ReferenceLottieAnimationItem
init(renderer: SoftwareLottieRenderer, referenceRenderer: ReferenceLottieAnimationItem) {
self.renderer = renderer
self.referenceRenderer = referenceRenderer
}
}
private let view: UIView
private let imageView = UIImageView()
private let referenceImageView = UIImageView()
private let labelView = UILabel()
private let renderSize: CGSize
private let testNonReference: Bool
private let fileList: [(filePath: String, fileName: String)]
private var currentFileIndex: Int = 0
private var currentItem: Item?
private var frameDisplayLink: SharedDisplayLinkDriver.Link?
init(view: UIView) {
self.testNonReference = true
self.currentFileIndex = 0
lottieSwift_getPathNativeBoundingBox = { path in
return getPathNativeBoundingBox(path)
}
let bundlePath = Bundle.main.path(forResource: "TestDataBundle", ofType: "bundle")!
self.fileList = buildAnimationFolderItems(basePath: bundlePath, path: "")
self.renderSize = CGSize(width: 128.0, height: 128.0)
self.view = view
self.view.backgroundColor = .white
self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
let topInset: CGFloat = 50.0
self.view.addSubview(self.imageView)
self.imageView.layer.magnificationFilter = .nearest
self.imageView.frame = CGRect(origin: CGPoint(x: 10.0, y: topInset), size: CGSize(width: 256.0, height: 256.0))
self.imageView.backgroundColor = self.view.backgroundColor
self.imageView.transform = CGAffineTransform.init(scaleX: 1.0, y: -1.0)
self.view.addSubview(self.referenceImageView)
self.referenceImageView.layer.magnificationFilter = .nearest
self.referenceImageView.frame = CGRect(origin: CGPoint(x: 10.0, y: topInset + 256.0 + 1.0), size: CGSize(width: 256.0, height: 256.0))
self.referenceImageView.backgroundColor = self.view.backgroundColor
self.referenceImageView.transform = CGAffineTransform.init(scaleX: 1.0, y: -1.0)
self.view.addSubview(self.labelView)
self.updateCurrentAnimation()
}
@objc private func tapGesture(_ recognizer: UITapGestureRecognizer) {
if case .ended = recognizer.state {
if recognizer.location(in: self.view).x <= self.view.bounds.width * 0.5 {
if self.currentFileIndex != 0 {
self.currentFileIndex = self.currentFileIndex - 1
}
} else {
self.currentFileIndex = (self.currentFileIndex + 1) % self.fileList.count
}
self.updateCurrentAnimation()
}
}
private func updateCurrentAnimation() {
self.imageView.image = nil
self.referenceImageView.image = nil
self.currentItem = nil
self.labelView.text = "\(self.currentFileIndex + 1) / \(self.fileList.count)"
self.labelView.sizeToFit()
self.labelView.center = CGPoint(x: self.view.bounds.midX, y: self.view.bounds.height - 10.0 - self.labelView.bounds.height)
self.frameDisplayLink?.invalidate()
self.frameDisplayLink = nil
let (filePath, _) = self.fileList[self.currentFileIndex]
guard let data = try? Data(contentsOf: URL(fileURLWithPath: filePath)) else {
print("Could not load \(filePath)")
return
}
guard let renderer = SoftwareLottieRenderer(data: data) else {
print("Could not load animation at \(filePath)")
return
}
guard let referenceRenderer = ReferenceLottieAnimationItem(path: filePath) else {
print("Could not load reference animation at \(filePath)")
return
}
let currentItem = Item(renderer: renderer, referenceRenderer: referenceRenderer)
self.currentItem = currentItem
var animationTime = 0.0
let secondsPerFrame = 1.0 / Double(renderer.framesPerSecond)
let frameDisplayLink = SharedDisplayLinkDriver.shared.add { [weak self] deltaTime in
guard let self, let currentItem = self.currentItem else {
return
}
var frameIndex = Int(animationTime / secondsPerFrame)
frameIndex = frameIndex % currentItem.renderer.frameCount
currentItem.renderer.setFrame(frameIndex)
let image = currentItem.renderer.render(for: self.renderSize, useReferenceRendering: !self.testNonReference, canUseMoreMemory: false, skipImageGeneration: false)!
self.imageView.image = image
currentItem.referenceRenderer.setFrame(index: frameIndex)
let referenceImage = currentItem.referenceRenderer.makeImage(width: Int(self.renderSize.width), height: Int(self.renderSize.height))!
self.referenceImageView.image = referenceImage
animationTime += deltaTime
}
self.frameDisplayLink = frameDisplayLink
frameDisplayLink.isPaused = false
}
}
public final class ViewController: UIViewController { public final class ViewController: UIViewController {
private var link: SharedDisplayLinkDriver.Link? private var link: SharedDisplayLinkDriver.Link?
private var test: AnyObject? private var test: AnyObject?
@ -115,14 +259,18 @@ public final class ViewController: UIViewController {
let bundlePath = Bundle.main.path(forResource: "TestDataBundle", ofType: "bundle")! let bundlePath = Bundle.main.path(forResource: "TestDataBundle", ofType: "bundle")!
let filePath = bundlePath + "/fire.json" let filePath = bundlePath + "/fire.json"
let performanceFrameSize = 512 let performanceFrameSize = 128
self.view.layer.addSublayer(MetalEngine.shared.rootLayer) self.view.layer.addSublayer(MetalEngine.shared.rootLayer)
if "".isEmpty { if !"".isEmpty {
if #available(iOS 13.0, *) { if #available(iOS 13.0, *) {
self.test = ReferenceCompareTest(view: self.view, testNonReference: false) self.test = ReferenceCompareTest(view: self.view, testNonReference: false)
} }
} else if !"".isEmpty {
if #available(iOS 13.0, *) {
self.test = ManualReferenceCompareTest(view: self.view)
}
} else if !"".isEmpty { } else if !"".isEmpty {
/*let cachedAnimation = cacheLottieMetalAnimation(path: filePath)! /*let cachedAnimation = cacheLottieMetalAnimation(path: filePath)!
let animation = parseCachedLottieMetalAnimation(data: cachedAnimation)! let animation = parseCachedLottieMetalAnimation(data: cachedAnimation)!
@ -161,7 +309,7 @@ public final class ViewController: UIViewController {
var frameIndex = 0 var frameIndex = 0
while true { while true {
animationRenderer.setFrame(frameIndex) animationRenderer.setFrame(frameIndex)
let _ = animationRenderer.render(for: CGSize(width: CGFloat(performanceFrameSize), height: CGFloat(performanceFrameSize)), useReferenceRendering: false) let _ = animationRenderer.render(for: CGSize(width: CGFloat(performanceFrameSize), height: CGFloat(performanceFrameSize)), useReferenceRendering: false, canUseMoreMemory: true, skipImageGeneration: true)
frameIndex = (frameIndex + 1) % animationRenderer.frameCount frameIndex = (frameIndex + 1) % animationRenderer.frameCount
numUpdates += 1 numUpdates += 1
let timestamp = CFAbsoluteTimeGetCurrent() let timestamp = CFAbsoluteTimeGetCurrent()

@ -1 +1 @@
Subproject commit 97ed00c853de5078fd2a7f68d0da0b8872ac95b8 Subproject commit 4aa40846d24842e1168375c3db79d85e913ca1e9