Merge commit '7621e2f8dec938cf48181c8b10afc9b01f444e68' into beta

This commit is contained in:
Ilya Laktyushin
2025-12-06 02:17:48 +04:00
commit 8344b97e03
28070 changed files with 7995182 additions and 0 deletions
@@ -0,0 +1,63 @@
#ifndef CoreGraphicsCoreGraphicsCanvasImpl_h
#define CoreGraphicsCoreGraphicsCanvasImpl_h
#include <LottieCpp/LottieCpp.h>
#include <QuartzCore/QuartzCore.h>
namespace lottie {
class CoreGraphicsCanvasImpl: public Canvas {
class Layer;
public:
class Image {
public:
Image(::CGImageRef image);
virtual ~Image();
::CGImageRef nativeImage() const;
private:
CGImageRef _image = nil;
};
public:
CoreGraphicsCanvasImpl(int width, int height);
virtual ~CoreGraphicsCanvasImpl();
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, 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 clip(CGRect const &rect) override;
virtual bool clipPath(CanvasPathEnumerator const &enumeratePath, FillRule fillRule, Transform2D const &transform) override;
virtual void concatenate(lottie::Transform2D const &transform) override;
virtual std::shared_ptr<Image> makeImage();
virtual bool pushLayer(CGRect const &rect, float alpha, std::optional<MaskMode> maskMode) override;
virtual void popLayer() override;
std::vector<uint8_t> &backingData();
int bytesPerRow();
private:
std::shared_ptr<Layer> &currentLayer();
private:
int _width = 0;
int _height = 0;
CGContextRef _topContext = nil;
std::vector<std::shared_ptr<Layer>> _layerStack;
};
}
#endif
@@ -0,0 +1,604 @@
#include "CoreGraphicsCanvasImpl.h"
#include <LottieCpp/CGPathCocoa.h>
#include <LottieCpp/VectorsCocoa.h>
namespace lottie {
namespace {
int alignUp(int size, int align) {
assert(((align - 1) & align) == 0);
int alignmentMask = align - 1;
return (size + alignmentMask) & ~alignmentMask;
}
bool addEnumeratedPath(CGContextRef context, CanvasPathEnumerator const &enumeratePath) {
bool isEmpty = true;
enumeratePath([&](PathCommand const &command) {
switch (command.type) {
case PathCommandType::MoveTo: {
if (isEmpty) {
isEmpty = false;
CGContextBeginPath(context);
}
CGContextMoveToPoint(context, command.points[0].x, command.points[0].y);
break;
}
case PathCommandType::LineTo: {
if (isEmpty) {
isEmpty = false;
CGContextBeginPath(context);
}
CGContextAddLineToPoint(context, command.points[0].x, command.points[0].y);
break;
}
case PathCommandType::CurveTo: {
if (isEmpty) {
isEmpty = false;
CGContextBeginPath(context);
}
CGContextAddCurveToPoint(context, command.points[0].x, command.points[0].y, command.points[1].x, command.points[1].y, command.points[2].x, command.points[2].y);
break;
}
case PathCommandType::Close: {
if (isEmpty) {
isEmpty = false;
CGContextBeginPath(context);
}
CGContextClosePath(context);
break;
}
default: {
break;
}
}
});
return !isEmpty;
}
}
class CoreGraphicsCanvasImpl::Layer {
public:
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());
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedFirst;
_context = CGBitmapContextCreate(_backingData.data(), _width, _height, 8, _bytesPerRow, colorSpace, bitmapInfo);
CFRelease(colorSpace);
CGContextClearRect(_context, CGRectMake(0.0, 0.0, _width, _height));
}
~Layer() {
CGContextRelease(_context);
}
CGContextRef context() const {
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) {
_image = CGImageRetain(image);
}
CoreGraphicsCanvasImpl::Image::~Image() {
CFRelease(_image);
}
::CGImageRef CoreGraphicsCanvasImpl::Image::nativeImage() const {
return _image;
}
CoreGraphicsCanvasImpl::CoreGraphicsCanvasImpl(int width, int height) :
_width(width),
_height(height) {
_layerStack.push_back(std::make_shared<Layer>(width, height, std::nullopt));
}
CoreGraphicsCanvasImpl::~CoreGraphicsCanvasImpl() {
}
void CoreGraphicsCanvasImpl::saveState() {
CGContextSaveGState(currentLayer()->context());
}
void CoreGraphicsCanvasImpl::restoreState() {
CGContextRestoreGState(currentLayer()->context());
}
void CoreGraphicsCanvasImpl::fillPath(CanvasPathEnumerator const &enumeratePath, lottie::FillRule fillRule, lottie::Color const &color) {
if (!addEnumeratedPath(currentLayer()->context(), enumeratePath)) {
return;
}
CGFloat components[4] = { color.r, color.g, color.b, color.a };
CGColorRef nativeColor = CGColorCreate(CGBitmapContextGetColorSpace(currentLayer()->context()), components);
CGContextSetFillColorWithColor(currentLayer()->context(), nativeColor);
CFRelease(nativeColor);
switch (fillRule) {
case lottie::FillRule::EvenOdd: {
CGContextEOFillPath(currentLayer()->context());
break;
}
default: {
CGContextFillPath(currentLayer()->context());
break;
}
}
}
void CoreGraphicsCanvasImpl::linearGradientFillPath(CanvasPathEnumerator const &enumeratePath, lottie::FillRule fillRule, Gradient const &gradient, lottie::Vector2D const &start, lottie::Vector2D const &end) {
CGContextSaveGState(currentLayer()->context());
if (!addEnumeratedPath(currentLayer()->context(), enumeratePath)) {
CGContextRestoreGState(currentLayer()->context());
return;
}
switch (fillRule) {
case lottie::FillRule::EvenOdd: {
CGContextEOClip(currentLayer()->context());
break;
}
default: {
CGContextClip(currentLayer()->context());
break;
}
}
std::vector<double> components;
components.reserve(gradient.colors().size() + 4);
for (const auto &color : gradient.colors()) {
components.push_back(color.r);
components.push_back(color.g);
components.push_back(color.b);
components.push_back(color.a);
}
assert(gradient.colors().size() == gradient.locations().size());
std::vector<double> locations;
for (const auto location : gradient.locations()) {
locations.push_back(location);
}
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);
}
CGContextResetClip(currentLayer()->context());
CGContextRestoreGState(currentLayer()->context());
}
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)) {
CGContextRestoreGState(currentLayer()->context());
return;
}
switch (fillRule) {
case lottie::FillRule::EvenOdd: {
CGContextEOClip(currentLayer()->context());
break;
}
default: {
CGContextClip(currentLayer()->context());
break;
}
}
std::vector<double> components;
components.reserve(gradient.colors().size() + 4);
for (const auto &color : gradient.colors()) {
components.push_back(color.r);
components.push_back(color.g);
components.push_back(color.b);
components.push_back(color.a);
}
assert(gradient.colors().size() == gradient.locations().size());
std::vector<double> locations;
for (const auto location : gradient.locations()) {
locations.push_back(location);
}
CGGradientRef nativeGradient = CGGradientCreateWithColorComponents(CGBitmapContextGetColorSpace(currentLayer()->context()), components.data(), locations.data(), locations.size());
if (nativeGradient) {
CGContextDrawRadialGradient(currentLayer()->context(), nativeGradient, CGPointMake(center.x, center.y), 0.0, CGPointMake(center.x, center.y), radius, kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation);
CFRelease(nativeGradient);
}
CGContextResetClip(currentLayer()->context());
CGContextRestoreGState(currentLayer()->context());
}
void CoreGraphicsCanvasImpl::strokePath(CanvasPathEnumerator const &enumeratePath, float lineWidth, lottie::LineJoin lineJoin, lottie::LineCap lineCap, float dashPhase, std::vector<float> const &dashPattern, lottie::Color const &color) {
if (!addEnumeratedPath(currentLayer()->context(), enumeratePath)) {
return;
}
CGFloat components[4] = { color.r, color.g, color.b, color.a };
CGColorRef nativeColor = CGColorCreate(CGBitmapContextGetColorSpace(currentLayer()->context()), components);
CGContextSetStrokeColorWithColor(currentLayer()->context(), nativeColor);
CFRelease(nativeColor);
CGContextSetLineWidth(currentLayer()->context(), lineWidth);
switch (lineJoin) {
case lottie::LineJoin::Miter: {
CGContextSetLineJoin(currentLayer()->context(), kCGLineJoinMiter);
break;
}
case lottie::LineJoin::Round: {
CGContextSetLineJoin(currentLayer()->context(), kCGLineJoinRound);
break;
}
case lottie::LineJoin::Bevel: {
CGContextSetLineJoin(currentLayer()->context(), kCGLineJoinBevel);
break;
}
default: {
CGContextSetLineJoin(currentLayer()->context(), kCGLineJoinBevel);
break;
}
}
switch (lineCap) {
case lottie::LineCap::Butt: {
CGContextSetLineCap(currentLayer()->context(), kCGLineCapButt);
break;
}
case lottie::LineCap::Round: {
CGContextSetLineCap(currentLayer()->context(), kCGLineCapRound);
break;
}
case lottie::LineCap::Square: {
CGContextSetLineCap(currentLayer()->context(), kCGLineCapSquare);
break;
}
default: {
CGContextSetLineCap(currentLayer()->context(), kCGLineCapSquare);
break;
}
}
if (!dashPattern.empty()) {
std::vector<double> mappedDashPattern;
for (const auto value : dashPattern) {
mappedDashPattern.push_back(value);
}
CGContextSetLineDash(currentLayer()->context(), dashPhase, mappedDashPattern.data(), mappedDashPattern.size());
}
CGContextStrokePath(currentLayer()->context());
}
void CoreGraphicsCanvasImpl::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) {
CGContextSaveGState(currentLayer()->context());
if (!addEnumeratedPath(currentLayer()->context(), enumeratePath)) {
CGContextRestoreGState(currentLayer()->context());
return;
}
CGContextSetLineWidth(currentLayer()->context(), lineWidth);
switch (lineJoin) {
case lottie::LineJoin::Miter: {
CGContextSetLineJoin(currentLayer()->context(), kCGLineJoinMiter);
break;
}
case lottie::LineJoin::Round: {
CGContextSetLineJoin(currentLayer()->context(), kCGLineJoinRound);
break;
}
case lottie::LineJoin::Bevel: {
CGContextSetLineJoin(currentLayer()->context(), kCGLineJoinBevel);
break;
}
default: {
CGContextSetLineJoin(currentLayer()->context(), kCGLineJoinBevel);
break;
}
}
switch (lineCap) {
case lottie::LineCap::Butt: {
CGContextSetLineCap(currentLayer()->context(), kCGLineCapButt);
break;
}
case lottie::LineCap::Round: {
CGContextSetLineCap(currentLayer()->context(), kCGLineCapRound);
break;
}
case lottie::LineCap::Square: {
CGContextSetLineCap(currentLayer()->context(), kCGLineCapSquare);
break;
}
default: {
CGContextSetLineCap(currentLayer()->context(), kCGLineCapSquare);
break;
}
}
if (!dashPattern.empty()) {
std::vector<double> mappedDashPattern;
for (const auto value : dashPattern) {
mappedDashPattern.push_back(value);
}
CGContextSetLineDash(currentLayer()->context(), dashPhase, mappedDashPattern.data(), mappedDashPattern.size());
}
CGContextReplacePathWithStrokedPath(currentLayer()->context());
CGContextClip(currentLayer()->context());
std::vector<double> components;
components.reserve(gradient.colors().size() + 4);
for (const auto &color : gradient.colors()) {
components.push_back(color.r);
components.push_back(color.g);
components.push_back(color.b);
components.push_back(color.a);
}
assert(gradient.colors().size() == gradient.locations().size());
std::vector<double> locations;
for (const auto location : gradient.locations()) {
locations.push_back(location);
}
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);
}
CGContextResetClip(currentLayer()->context());
CGContextRestoreGState(currentLayer()->context());
}
void CoreGraphicsCanvasImpl::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) {
CGContextSaveGState(currentLayer()->context());
if (!addEnumeratedPath(currentLayer()->context(), enumeratePath)) {
CGContextRestoreGState(currentLayer()->context());
return;
}
CGContextSetLineWidth(currentLayer()->context(), lineWidth);
switch (lineJoin) {
case lottie::LineJoin::Miter: {
CGContextSetLineJoin(currentLayer()->context(), kCGLineJoinMiter);
break;
}
case lottie::LineJoin::Round: {
CGContextSetLineJoin(currentLayer()->context(), kCGLineJoinRound);
break;
}
case lottie::LineJoin::Bevel: {
CGContextSetLineJoin(currentLayer()->context(), kCGLineJoinBevel);
break;
}
default: {
CGContextSetLineJoin(currentLayer()->context(), kCGLineJoinBevel);
break;
}
}
switch (lineCap) {
case lottie::LineCap::Butt: {
CGContextSetLineCap(currentLayer()->context(), kCGLineCapButt);
break;
}
case lottie::LineCap::Round: {
CGContextSetLineCap(currentLayer()->context(), kCGLineCapRound);
break;
}
case lottie::LineCap::Square: {
CGContextSetLineCap(currentLayer()->context(), kCGLineCapSquare);
break;
}
default: {
CGContextSetLineCap(currentLayer()->context(), kCGLineCapSquare);
break;
}
}
if (!dashPattern.empty()) {
std::vector<double> mappedDashPattern;
for (const auto value : dashPattern) {
mappedDashPattern.push_back(value);
}
CGContextSetLineDash(currentLayer()->context(), dashPhase, mappedDashPattern.data(), mappedDashPattern.size());
}
CGContextReplacePathWithStrokedPath(currentLayer()->context());
CGContextClip(currentLayer()->context());
std::vector<double> components;
components.reserve(gradient.colors().size() + 4);
for (const auto &color : gradient.colors()) {
components.push_back(color.r);
components.push_back(color.g);
components.push_back(color.b);
components.push_back(color.a);
}
assert(gradient.colors().size() == gradient.locations().size());
std::vector<double> locations;
for (const auto location : gradient.locations()) {
locations.push_back(location);
}
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);
}
CGContextResetClip(currentLayer()->context());
CGContextRestoreGState(currentLayer()->context());
}
void CoreGraphicsCanvasImpl::clip(CGRect const &rect) {
CGContextClipToRect(currentLayer()->context(), CGRectMake(rect.x, rect.y, rect.width, rect.height));
}
bool CoreGraphicsCanvasImpl::clipPath(CanvasPathEnumerator const &enumeratePath, FillRule fillRule, Transform2D const &transform) {
CGContextSaveGState(currentLayer()->context());
concatenate(transform);
if (!addEnumeratedPath(currentLayer()->context(), enumeratePath)) {
CGContextRestoreGState(currentLayer()->context());
return false;
}
CGContextRestoreGState(currentLayer()->context());
switch (fillRule) {
case lottie::FillRule::EvenOdd: {
CGContextEOClip(currentLayer()->context());
break;
}
default: {
CGContextClip(currentLayer()->context());
break;
}
}
return true;
}
void CoreGraphicsCanvasImpl::concatenate(lottie::Transform2D const &transform) {
CGContextConcatCTM(currentLayer()->context(), CATransform3DGetAffineTransform(nativeTransform(transform)));
}
std::shared_ptr<CoreGraphicsCanvasImpl::Image> CoreGraphicsCanvasImpl::makeImage() {
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 {
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;
}
_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() {
return _layerStack[_layerStack.size() - 1];
}
}
@@ -0,0 +1,308 @@
#include "SkiaCanvasImpl.h"
#include "include/core/SkCanvas.h"
#include "include/core/SkColor.h"
#include "include/core/SkFont.h"
#include "include/core/SkFontTypes.h"
#include "include/core/SkGraphics.h"
#include "include/core/SkPaint.h"
#include "include/core/SkPoint.h"
#include "include/core/SkRect.h"
#include "include/core/SkShader.h"
#include "include/core/SkString.h"
#include "include/core/SkSurface.h"
#include "include/core/SkTileMode.h"
#include "include/core/SkPath.h"
#include "include/core/SkPathEffect.h"
#include "include/effects/SkDashPathEffect.h"
#include "include/effects/SkGradientShader.h"
#include <cfloat>
namespace lottie {
namespace {
SkColor skColor(Color const &color) {
return SkColorSetARGB((uint8_t)(color.a * 255.0), (uint8_t)(color.r * 255.0), (uint8_t)(color.g * 255.0), (uint8_t)(color.b * 255.0));
}
void skPath(CanvasPathEnumerator const &enumeratePath, SkPath &nativePath) {
enumeratePath([&](PathCommand const &command) {
switch (command.type) {
case PathCommandType::MoveTo: {
nativePath.moveTo(command.points[0].x, command.points[0].y);
break;
}
case PathCommandType::LineTo: {
nativePath.lineTo(command.points[0].x, command.points[0].y);
break;
}
case PathCommandType::CurveTo: {
nativePath.cubicTo(command.points[0].x, command.points[0].y, command.points[1].x, command.points[1].y, command.points[2].x, command.points[2].y);
break;
}
case PathCommandType::Close: {
nativePath.close();
break;
}
}
});
}
SkMatrix skMatrix(Transform2D const &transform) {
SkScalar m9[9] = {
transform.rows().columns[0][0], transform.rows().columns[1][0], transform.rows().columns[2][0],
transform.rows().columns[0][1], transform.rows().columns[1][1], transform.rows().columns[2][1],
transform.rows().columns[0][2], transform.rows().columns[1][2], transform.rows().columns[2][2]
};
SkMatrix matrix;
matrix.set9(m9);
return matrix;
}
}
SkiaCanvasImpl::SkiaCanvasImpl(int width, int height) {
int bytesPerRow = width * 4;
_pixelData = malloc(bytesPerRow * height);
_ownsPixelData = true;
_surface = SkSurfaces::WrapPixels(
SkImageInfo::MakeN32Premul(width, height),
_pixelData,
bytesPerRow,
nullptr
);
_canvas = _surface->getCanvas();
_canvas->resetMatrix();
_canvas->clear(SkColorSetARGB(0, 0, 0, 0));
}
SkiaCanvasImpl::SkiaCanvasImpl(int width, int height, int bytesPerRow, void *pixelData) {
_pixelData = pixelData;
_ownsPixelData = false;
_surface = SkSurfaces::WrapPixels(
SkImageInfo::MakeN32Premul(width, height),
_pixelData,
bytesPerRow,
nullptr
);
_canvas = _surface->getCanvas();
_canvas->resetMatrix();
_canvas->clear(SkColorSetARGB(0, 0, 0, 0));
}
SkiaCanvasImpl::~SkiaCanvasImpl() {
if (_ownsPixelData) {
free(_pixelData);
}
}
void SkiaCanvasImpl::saveState() {
_canvas->save();
}
void SkiaCanvasImpl::restoreState() {
_canvas->restore();
}
void SkiaCanvasImpl::fillPath(CanvasPathEnumerator const &enumeratePath, lottie::FillRule fillRule, lottie::Color const &color) {
SkPaint paint;
paint.setColor(skColor(color));
paint.setAntiAlias(true);
SkPath nativePath;
skPath(enumeratePath, nativePath);
nativePath.setFillType(fillRule == FillRule::EvenOdd ? SkPathFillType::kEvenOdd : SkPathFillType::kWinding);
_canvas->drawPath(nativePath, paint);
}
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.setDither(false);
paint.setStyle(SkPaint::Style::kFill_Style);
SkPoint linearPoints[2] = {
SkPoint::Make(start.x, start.y),
SkPoint::Make(end.x, end.y)
};
std::vector<SkColor> colors;
for (const auto &color : gradient.colors()) {
colors.push_back(skColor(Color(color.r, color.g, color.b, color.a)));
}
std::vector<SkScalar> locations;
for (auto location : gradient.locations()) {
locations.push_back(location);
}
paint.setShader(SkGradientShader::MakeLinear(linearPoints, colors.data(), locations.data(), (int)colors.size(), SkTileMode::kClamp));
SkPath nativePath;
skPath(enumeratePath, nativePath);
nativePath.setFillType(fillRule == FillRule::EvenOdd ? SkPathFillType::kEvenOdd : SkPathFillType::kWinding);
_canvas->drawPath(nativePath, paint);
}
void SkiaCanvasImpl::radialGradientFillPath(CanvasPathEnumerator const &enumeratePath, lottie::FillRule fillRule, lottie::Gradient const &gradient, Vector2D const &center, float radius) {
SkPaint paint;
paint.setAntiAlias(true);
paint.setStyle(SkPaint::Style::kFill_Style);
std::vector<SkColor> colors;
for (const auto &color : gradient.colors()) {
colors.push_back(skColor(Color(color.r, color.g, color.b, color.a)));
}
std::vector<SkScalar> locations;
for (auto location : gradient.locations()) {
locations.push_back(location);
}
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);
nativePath.setFillType(fillRule == FillRule::EvenOdd ? SkPathFillType::kEvenOdd : SkPathFillType::kWinding);
_canvas->drawPath(nativePath, paint);
}
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.setColor(skColor(color));
paint.setStyle(SkPaint::Style::kStroke_Style);
paint.setStrokeWidth(lineWidth);
switch (lineJoin) {
case LineJoin::Miter: {
paint.setStrokeJoin(SkPaint::Join::kMiter_Join);
break;
}
case LineJoin::Round: {
paint.setStrokeJoin(SkPaint::Join::kRound_Join);
break;
}
case LineJoin::Bevel: {
paint.setStrokeJoin(SkPaint::Join::kBevel_Join);
break;
}
default: {
paint.setStrokeJoin(SkPaint::Join::kBevel_Join);
break;
}
}
switch (lineCap) {
case LineCap::Butt: {
paint.setStrokeCap(SkPaint::Cap::kButt_Cap);
break;
}
case LineCap::Round: {
paint.setStrokeCap(SkPaint::Cap::kRound_Cap);
break;
}
case LineCap::Square: {
paint.setStrokeCap(SkPaint::Cap::kSquare_Cap);
break;
}
default: {
paint.setStrokeCap(SkPaint::Cap::kSquare_Cap);
break;
}
}
if (!dashPattern.empty()) {
std::vector<SkScalar> intervals;
intervals.reserve(dashPattern.size());
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));
}
SkPath nativePath;
skPath(enumeratePath, nativePath);
_canvas->drawPath(nativePath, paint);
}
void SkiaCanvasImpl::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) {
assert(false);
}
void SkiaCanvasImpl::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) {
assert(false);
}
void SkiaCanvasImpl::clip(CGRect const &rect) {
_canvas->clipRect(SkRect::MakeXYWH(rect.x, rect.y, rect.width, rect.height), true);
}
bool SkiaCanvasImpl::clipPath(CanvasPathEnumerator const &enumeratePath, FillRule fillRule, Transform2D const &transform) {
SkPath nativePath;
skPath(enumeratePath, nativePath);
nativePath.setFillType(fillRule == FillRule::EvenOdd ? SkPathFillType::kEvenOdd : SkPathFillType::kWinding);
if (!transform.isIdentity()) {
nativePath.transform(skMatrix(transform));
}
_canvas->clipPath(nativePath, true);
return true;
}
void SkiaCanvasImpl::concatenate(lottie::Transform2D const &transform) {
_canvas->concat(skMatrix(transform));
}
bool SkiaCanvasImpl::pushLayer(CGRect const &rect, float alpha, std::optional<MaskMode> maskMode) {
SkPaint paint;
paint.setAntiAlias(true);
paint.setAlphaf(alpha);
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() {
}
sk_sp<SkSurface> SkiaCanvasImpl::surface() const {
return _surface;
}
}
@@ -0,0 +1,46 @@
#ifndef SkiaCanvasImpl_h
#define SkiaCanvasImpl_h
#include <LottieCpp/LottieCpp.h>
#include "include/core/SkCanvas.h"
#include "include/core/SkSurface.h"
namespace lottie {
class SkiaCanvasImpl: public Canvas {
public:
SkiaCanvasImpl(int width, int height);
SkiaCanvasImpl(int width, int height, int bytesPerRow, void *pixelData);
virtual ~SkiaCanvasImpl();
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, 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 clip(CGRect const &rect) override;
virtual bool clipPath(CanvasPathEnumerator const &enumeratePath, FillRule fillRule, Transform2D const &transform) override;
virtual void concatenate(lottie::Transform2D const &transform) 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;
private:
void *_pixelData = nullptr;
bool _ownsPixelData = false;
sk_sp<SkSurface> _surface;
SkCanvas *_canvas = nullptr;
};
}
#endif
@@ -0,0 +1,125 @@
#import <SoftwareLottieRenderer/SoftwareLottieRenderer.h>
#import <LottieCpp/LottieCpp.h>
#import <LottieCpp/NullCanvasImpl.h>
#import "CoreGraphicsCanvasImpl.h"
#import "SkiaCanvasImpl.h"
#include <LottieCpp/RenderTreeNode.h>
#include <LottieCpp/CGPathCocoa.h>
#include <LottieCpp/VectorsCocoa.h>
#import <Accelerate/Accelerate.h>
CGRect getPathNativeBoundingBox(CGPathRef _Nonnull path) {
auto rect = calculatePathBoundingBox(path);
return CGRectMake(rect.origin.x, rect.origin.y, rect.size.width, rect.size.height);
}
@interface SoftwareLottieRenderer() {
std::shared_ptr<lottie::Renderer> _renderer;
std::shared_ptr<lottie::CanvasRenderer> _canvasRenderer;
}
@end
@implementation SoftwareLottieRenderer
- (instancetype _Nullable)initWithData:(NSData * _Nonnull)data {
self = [super init];
if (self != nil) {
_renderer = lottie::Renderer::make(std::string((uint8_t const *)data.bytes, ((uint8_t const *)data.bytes) + data.length));
if (!_renderer) {
return nil;
}
_canvasRenderer = std::make_shared<lottie::CanvasRenderer>();
}
return self;
}
- (NSInteger)frameCount {
return (NSInteger)_renderer->frameCount();
}
- (NSInteger)framesPerSecond {
return (NSInteger)_renderer->framesPerSecond();
}
- (CGSize)size {
lottie::Vector2D size = _renderer->size();
return CGSizeMake(size.x, size.y);
}
- (void)setFrame:(CGFloat)index {
_renderer->setFrame((float)index);
}
- (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), configuration);
auto image = context->makeImage();
return [[UIImage alloc] initWithCGImage:std::static_pointer_cast<lottie::CoreGraphicsCanvasImpl::Image>(image)->nativeImage()];
} else {
if ((int64_t)"" > 0) {
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), configuration);
context->flush();
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 {
auto context = std::make_shared<lottie::NullCanvasImpl>((int)size.width, (int)size.height);
_canvasRenderer->render(_renderer, context, lottie::Vector2D(size.width, size.height), configuration);
return nil;
}
}
return nil;
}
@end