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;
- (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

View File

@ -23,30 +23,25 @@ public:
public:
CoreGraphicsCanvasImpl(int width, int height);
CoreGraphicsCanvasImpl(CGContextRef context, int width, int height);
virtual ~CoreGraphicsCanvasImpl();
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, 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 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 clip(CGRect const &rect) override;
virtual void concatenate(lottie::Transform2D const &transform) override;
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;
std::vector<uint8_t> &backingData();
@ -56,6 +51,8 @@ private:
std::shared_ptr<Layer> &currentLayer();
private:
int _width = 0;
int _height = 0;
CGContextRef _topContext = nil;
std::vector<std::shared_ptr<Layer>> _layerStack;
};

View File

@ -64,9 +64,23 @@ bool addEnumeratedPath(CGContextRef context, CanvasPathEnumerator const &enumera
class CoreGraphicsCanvasImpl::Layer {
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;
_height = height;
_composition = composition;
_bytesPerRow = alignUp(width * 4, 16);
_backingData.resize(_bytesPerRow * _height);
memset(_backingData.data(), 0, _backingData.size());
@ -77,7 +91,6 @@ public:
CFRelease(colorSpace);
CGContextClearRect(_context, CGRectMake(0.0, 0.0, _width, _height));
}
~Layer() {
@ -88,12 +101,29 @@ public:
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:
CGContextRef _context = nil;
int _width = 0;
int _height = 0;
int _bytesPerRow = 0;
std::vector<uint8_t> _backingData;
std::optional<Composition> _composition;
};
CoreGraphicsCanvasImpl::Image::Image(::CGImageRef image) {
@ -108,24 +138,13 @@ CoreGraphicsCanvasImpl::Image::~Image() {
return _image;
}
CoreGraphicsCanvasImpl::CoreGraphicsCanvasImpl(int width, int height) {
_layerStack.push_back(std::make_shared<Layer>(width, height));
_topContext = CGContextRetain(currentLayer()->context());
}
CoreGraphicsCanvasImpl::CoreGraphicsCanvasImpl(CGContextRef context, int width, int height) {
_layerStack.push_back(std::make_shared<Layer>(width, height));
_topContext = CGContextRetain(context);
CoreGraphicsCanvasImpl::CoreGraphicsCanvasImpl(int width, int height) :
_width(width),
_height(height) {
_layerStack.push_back(std::make_shared<Layer>(width, height, std::nullopt));
}
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() {
@ -142,7 +161,7 @@ void CoreGraphicsCanvasImpl::fillPath(CanvasPathEnumerator const &enumeratePath,
}
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);
CFRelease(nativeColor);
@ -194,7 +213,7 @@ void CoreGraphicsCanvasImpl::linearGradientFillPath(CanvasPathEnumerator const &
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) {
CGContextDrawLinearGradient(currentLayer()->context(), nativeGradient, CGPointMake(start.x, start.y), CGPointMake(end.x, end.y), kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation);
CFRelease(nativeGradient);
@ -204,7 +223,7 @@ void CoreGraphicsCanvasImpl::linearGradientFillPath(CanvasPathEnumerator const &
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());
if (!addEnumeratedPath(currentLayer()->context(), enumeratePath)) {
@ -240,9 +259,9 @@ void CoreGraphicsCanvasImpl::radialGradientFillPath(CanvasPathEnumerator const &
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) {
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);
}
@ -256,7 +275,7 @@ void CoreGraphicsCanvasImpl::strokePath(CanvasPathEnumerator const &enumeratePat
}
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);
CFRelease(nativeColor);
@ -385,7 +404,7 @@ void CoreGraphicsCanvasImpl::linearGradientStrokePath(CanvasPathEnumerator const
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) {
CGContextDrawLinearGradient(currentLayer()->context(), nativeGradient, CGPointMake(start.x, start.y), CGPointMake(end.x, end.y), kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation);
CFRelease(nativeGradient);
@ -470,7 +489,7 @@ void CoreGraphicsCanvasImpl::radialGradientStrokePath(CanvasPathEnumerator const
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) {
CGContextDrawRadialGradient(currentLayer()->context(), nativeGradient, CGPointMake(startCenter.x, startCenter.y), startRadius, CGPointMake(endCenter.x, endCenter.y), endRadius, kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation);
CFRelease(nativeGradient);
@ -480,32 +499,8 @@ void CoreGraphicsCanvasImpl::radialGradientStrokePath(CanvasPathEnumerator const
CGContextRestoreGState(currentLayer()->context());
}
void CoreGraphicsCanvasImpl::fill(lottie::CGRect const &rect, lottie::Color const &fillColor) {
CGFloat components[4] = { fillColor.r, fillColor.g, fillColor.b, fillColor.a };
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::clip(CGRect const &rect) {
CGContextClipToRect(currentLayer()->context(), CGRectMake(rect.x, rect.y, rect.width, rect.height));
}
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() {
::CGImageRef nativeImage = CGBitmapContextCreateImage(currentLayer()->context());
if (nativeImage) {
auto image = std::make_shared<CoreGraphicsCanvasImpl::Image>(nativeImage);
CFRelease(nativeImage);
return image;
return currentLayer()->makeImage();
}
bool CoreGraphicsCanvasImpl::pushLayer(CGRect const &rect, float alpha, std::optional<Canvas::MaskMode> maskMode) {
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 {
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));
}
}
void CoreGraphicsCanvasImpl::draw(std::shared_ptr<Canvas> const &other, float alpha, lottie::CGRect const &rect) {
CGContextSetAlpha(currentLayer()->context(), alpha);
CoreGraphicsCanvasImpl *impl = (CoreGraphicsCanvasImpl *)other.get();
auto image = impl->makeImage();
CGContextDrawImage(currentLayer()->context(), CGRectMake(rect.x, rect.y, rect.width, rect.height), ((CoreGraphicsCanvasImpl::Image *)image.get())->nativeImage());
CGContextSetAlpha(currentLayer()->context(), 1.0);
}
void CoreGraphicsCanvasImpl::pushLayer(CGRect const &rect) {
if (globalRect.width <= 0.0f || globalRect.height <= 0.0f) {
return false;
}
_layerStack.push_back(std::make_shared<Layer>(globalRect.width, globalRect.height, Layer::Composition(globalRect, alpha, currentTransform, maskMode)));
concatenate(Transform2D::identity().translated(Vector2D(-globalRect.x, -globalRect.y)));
concatenate(currentTransform);
return true;
}
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() {

View File

@ -17,6 +17,8 @@
#include "include/effects/SkDashPathEffect.h"
#include "include/effects/SkGradientShader.h"
#include <cfloat>
namespace lottie {
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() {
_canvas->save();
}
@ -105,7 +103,6 @@ void SkiaCanvasImpl::fillPath(CanvasPathEnumerator const &enumeratePath, lottie:
SkPaint paint;
paint.setColor(skColor(color));
paint.setAntiAlias(true);
paint.setBlendMode(_blendMode);
SkPath 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) {
SkPaint paint;
paint.setAntiAlias(true);
paint.setBlendMode(_blendMode);
paint.setDither(false);
paint.setStyle(SkPaint::Style::kFill_Style);
@ -136,7 +132,7 @@ void SkiaCanvasImpl::linearGradientFillPath(CanvasPathEnumerator const &enumerat
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(enumeratePath, nativePath);
@ -145,11 +141,9 @@ void SkiaCanvasImpl::linearGradientFillPath(CanvasPathEnumerator const &enumerat
_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;
paint.setAntiAlias(true);
paint.setBlendMode(_blendMode);
paint.setDither(false);
paint.setStyle(SkPaint::Style::kFill_Style);
std::vector<SkColor> colors;
@ -162,7 +156,7 @@ void SkiaCanvasImpl::radialGradientFillPath(CanvasPathEnumerator const &enumerat
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(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) {
if (lineWidth <= FLT_EPSILON) {
return;
}
SkPaint paint;
paint.setAntiAlias(true);
paint.setBlendMode(_blendMode);
paint.setColor(skColor(color));
paint.setStyle(SkPaint::Style::kStroke_Style);
@ -223,6 +219,9 @@ void SkiaCanvasImpl::strokePath(CanvasPathEnumerator const &enumeratePath, float
for (auto value : dashPattern) {
intervals.push_back(value);
}
if (intervals.size() == 1) {
intervals.push_back(intervals[0]);
}
paint.setPathEffect(SkDashPathEffect::Make(intervals.data(), (int)intervals.size(), dashPhase));
}
@ -240,34 +239,8 @@ void SkiaCanvasImpl::radialGradientStrokePath(CanvasPathEnumerator const &enumer
assert(false);
}
void SkiaCanvasImpl::fill(lottie::CGRect const &rect, lottie::Color const &fillColor) {
SkPaint paint;
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::clip(CGRect const &rect) {
_canvas->clipRect(SkRect::MakeXYWH(rect.x, rect.y, rect.width, rect.height), true);
}
void SkiaCanvasImpl::concatenate(lottie::Transform2D const &transform) {
@ -281,13 +254,32 @@ void SkiaCanvasImpl::concatenate(lottie::Transform2D const &transform) {
_canvas->concat(matrix);
}
void SkiaCanvasImpl::draw(std::shared_ptr<Canvas> const &other, float alpha, lottie::CGRect const &rect) {
SkiaCanvasImpl *impl = (SkiaCanvasImpl *)other.get();
auto image = impl->surface()->makeImageSnapshot();
bool SkiaCanvasImpl::pushLayer(CGRect const &rect, float alpha, std::optional<MaskMode> maskMode) {
SkPaint paint;
paint.setBlendMode(_blendMode);
paint.setAntiAlias(true);
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() {

View File

@ -14,24 +14,21 @@ public:
SkiaCanvasImpl(int width, int height, int bytesPerRow, void *pixelData);
virtual ~SkiaCanvasImpl();
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, 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 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 clip(CGRect const &rect) 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();
sk_sp<SkSurface> surface() const;
@ -41,7 +38,6 @@ private:
bool _ownsPixelData = false;
sk_sp<SkSurface> _surface;
SkCanvas *_canvas = nullptr;
SkBlendMode _blendMode = SkBlendMode::kSrcOver;
};
}

View File

@ -57,98 +57,62 @@ CGRect getPathNativeBoundingBox(CGPathRef _Nonnull path) {
_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();
if (!renderNode) {
return nil;
}
lottie::CanvasRenderer::Configuration configuration;
configuration.canUseMoreMemory = canUseMoreMemory;
//configuration.canUseMoreMemory = true;
//configuration.disableGroupTransparency = true;
if (useReferenceRendering) {
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();
return [[UIImage alloc] initWithCGImage:std::static_pointer_cast<lottie::CoreGraphicsCanvasImpl::Image>(image)->nativeImage()];
} else {
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;
void *pixelData = malloc(bytesPerRow * (int)size.height);
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();
vImage_Buffer src;
src.data = (void *)pixelData;
src.width = (int)size.width;
src.height = (int)size.height;
src.rowBytes = bytesPerRow;
uint8_t permuteMap[4] = {2, 1, 0, 3};
vImagePermuteChannels_ARGB8888(&src, &src, permuteMap, kvImageDoNotTile);
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;
if (skipImageGeneration) {
free(pixelData);
} else {
vImage_Buffer src;
src.data = (void *)pixelData;
src.width = (int)size.width;
src.height = (int)size.height;
src.rowBytes = bytesPerRow;
uint8_t permuteMap[4] = {2, 1, 0, 3};
vImagePermuteChannels_ARGB8888(&src, &src, permuteMap, kvImageDoNotTile);
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;
}
} else if ((int64_t)"" < 0) {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
@ -158,7 +122,7 @@ CGRect getPathNativeBoundingBox(CGPathRef _Nonnull path) {
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));
_canvasRenderer->render(_renderer, context, lottie::Vector2D(size.width, size.height), configuration);
context->flush();
@ -177,7 +141,7 @@ CGRect getPathNativeBoundingBox(CGPathRef _Nonnull path) {
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));
_canvasRenderer->render(_renderer, context, lottie::Vector2D(size.width, size.height), configuration);
return nil;
}

View File

@ -63,10 +63,6 @@ _transform(lottie::Transform2D::identity()) {
ThorVGCanvasImpl::~ThorVGCanvasImpl() {
}
std::shared_ptr<Canvas> ThorVGCanvasImpl::makeLayer(int width, int height) {
return std::make_shared<ThorVGCanvasImpl>(width, height, width * 4);
}
void ThorVGCanvasImpl::saveState() {
_stateStack.push_back(_transform);
}
@ -120,14 +116,14 @@ void ThorVGCanvasImpl::linearGradientFillPath(CanvasPathEnumerator const &enumer
_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();
tvgPath(enumeratePath, shape.get());
shape->transform(tvgTransform(_transform));
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;
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::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) {
_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() {

View File

@ -14,25 +14,18 @@ public:
ThorVGCanvasImpl(int width, int height, int bytesPerRow);
virtual ~ThorVGCanvasImpl();
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, 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 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 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() {
return _backingData;
}

View File

@ -10,17 +10,26 @@ import SoftwareLottieRenderer
import LottieSwift
@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 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
outer: for y in 0 ..< Int(lhs.size.height) {
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 deltaRowPixels = deltaBuffer.data.assumingMemoryBound(to: UInt8.self).advanced(by: y * lhsBuffer.rowBytes)
for x in 0 ..< Int(lhs.size.width) {
let lhs0 = lhsRowPixels.advanced(by: x * 4 + 0).pointee
@ -36,32 +45,30 @@ func areImagesEqual(_ lhs: UIImage, _ rhs: UIImage) -> UIImage? {
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 false {
lhsRowPixels.advanced(by: x * 4 + 0).pointee = 255
lhsRowPixels.advanced(by: x * 4 + 1).pointee = 0
lhsRowPixels.advanced(by: x * 4 + 2).pointee = 0
lhsRowPixels.advanced(by: x * 4 + 3).pointee = 255
}*/
deltaRowPixels.advanced(by: x * 4 + 0).pointee = 255
deltaRowPixels.advanced(by: x * 4 + 1).pointee = 0
deltaRowPixels.advanced(by: x * 4 + 2).pointee = 0
deltaRowPixels.advanced(by: x * 4 + 3).pointee = 255
foundDifferenceCount += 1
}
}
}
lhsBuffer.free()
rhsBuffer.free()
let colorSpace = Unmanaged<CGColorSpace>.passRetained(lhs.cgImage!.colorSpace!)
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 {
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)
return UIImage(cgImage: diffImage)
return (UIImage(cgImage: diffImage), UIImage(cgImage: deltaImage))
} else {
return nil
return (nil, UIImage(cgImage: deltaImage))
}
}
@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 {
print("Could not load \(path)")
return false
@ -85,16 +92,18 @@ func processDrawAnimation(baseCachePath: String, path: String, name: String, siz
let referenceImage = decompressImageFrame(data: referenceImageData)
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) {
updateImage(diffImage, referenceImage)
let (diffImage, deltaImage) = areImagesEqual(image, referenceImage, allowedDifference: allowedDifference)
if !useNonReferenceRendering, let diffImage {
updateImage(diffImage, referenceImage, deltaImage)
print("Mismatch in frame \(frameIndex)")
return false
} else {
if alwaysDraw {
updateImage(image, referenceImage)
updateImage(image, referenceImage, diffImage)
}
return true
}
@ -279,6 +288,43 @@ func decompressImageFrame(data: Data) -> UIImage {
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
func cacheReferenceAnimation(baseCachePath: String, width: Int, path: String, name: String) -> String {
let targetFolderPath = cacheReferenceFolderPath(baseCachePath: baseCachePath, width: width, name: name)
@ -286,34 +332,19 @@ func cacheReferenceAnimation(baseCachePath: String, width: Int, path: String, na
return targetFolderPath
}
guard let referenceAnimation = Animation.filepath(path) else {
preconditionFailure("Could not parse reference animation at \(path)")
guard let referenceItem = ReferenceLottieAnimationItem(path: path) else {
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 _ = try? FileManager.default.createDirectory(atPath: cacheFolderPath, withIntermediateDirectories: true)
let frameCount = Int(referenceAnimation.endFrame - referenceAnimation.startFrame)
let size = CGSize(width: CGFloat(width), height: CGFloat(width))
for i in 0 ..< min(100000, frameCount) {
let frameIndex = i % frameCount
for i in 0 ..< min(100000, referenceItem.frameCount) {
let frameIndex = i % referenceItem.frameCount
referenceLayer.currentFrame = CGFloat(frameIndex)
referenceLayer.displayUpdate()
referenceLayer.position = referenceAnimation.bounds.center
referenceItem.setFrame(index: frameIndex)
referenceLayer.isOpaque = false
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()
let referenceImage = referenceItem.makeImage(width: width, height: width)!
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 imageView = UIImageView()
private let referenceImageView = UIImageView()
private let deltaImageView = UIImageView()
init(view: UIView, testNonReference: Bool) {
lottieSwift_getPathNativeBoundingBox = { path in
@ -37,6 +38,12 @@ private final class ReferenceCompareTest {
self.referenceImageView.backgroundColor = self.view.backgroundColor
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")!
Task.detached {
@ -67,6 +74,9 @@ private final class ReferenceCompareTest {
"1391391008142393350.json": 1024
]
let allowedDifferences: [String: Double] = [
"1258816259754165.json": 0.04
]
let defaultSize = 128
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?
//continueFromName = "5089561049196134821.json"
//continueFromName = "1258816259754165.json"
let _ = await processAnimationFolderAsync(basePath: bundlePath, path: "", stopOnFailure: !testNonReference, process: { path, name, alwaysDraw in
if let continueFromNameValue = continueFromName {
@ -91,10 +101,11 @@ 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, 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 {
self.imageView.image = image
self.referenceImageView.image = referenceImage
self.deltaImageView.image = differenceImage
}
})
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 {
private var link: SharedDisplayLinkDriver.Link?
private var test: AnyObject?
@ -115,14 +259,18 @@ public final class ViewController: UIViewController {
let bundlePath = Bundle.main.path(forResource: "TestDataBundle", ofType: "bundle")!
let filePath = bundlePath + "/fire.json"
let performanceFrameSize = 512
let performanceFrameSize = 128
self.view.layer.addSublayer(MetalEngine.shared.rootLayer)
if "".isEmpty {
if !"".isEmpty {
if #available(iOS 13.0, *) {
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 {
/*let cachedAnimation = cacheLottieMetalAnimation(path: filePath)!
let animation = parseCachedLottieMetalAnimation(data: cachedAnimation)!
@ -161,7 +309,7 @@ public final class ViewController: UIViewController {
var frameIndex = 0
while true {
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
numUpdates += 1
let timestamp = CFAbsoluteTimeGetCurrent()

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