mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-11-06 17:00:13 +00:00
Lottie: optimization
This commit is contained in:
parent
99e93aadd1
commit
3123519821
@ -11,7 +11,14 @@ extern "C" {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
CGRect getPathNativeBoundingBox(CGPathRef _Nonnull path);
|
CGRect getPathNativeBoundingBox(CGPathRef _Nonnull path);
|
||||||
UIImage * _Nullable renderLottieAnimationContainer(LottieAnimationContainer * _Nonnull animationContainer, CGSize size, bool useReferenceRendering);
|
|
||||||
|
@interface SoftwareLottieRenderer : NSObject
|
||||||
|
|
||||||
|
- (instancetype _Nonnull)initWithAnimationContainer:(LottieAnimationContainer * _Nonnull)animationContainer;
|
||||||
|
|
||||||
|
- (UIImage * _Nullable)renderForSize:(CGSize)size useReferenceRendering:(bool)useReferenceRendering;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,15 +3,225 @@
|
|||||||
#import "Canvas.h"
|
#import "Canvas.h"
|
||||||
#import "CoreGraphicsCanvasImpl.h"
|
#import "CoreGraphicsCanvasImpl.h"
|
||||||
|
|
||||||
|
#include <LottieCpp/RenderTreeNode.h>
|
||||||
|
|
||||||
|
namespace lottie {
|
||||||
|
|
||||||
|
static void processRenderTree(std::shared_ptr<RenderTreeNode> const &node, Vector2D const &globalSize, CATransform3D const &parentTransform, bool isInvertedMask, BezierPathsBoundingBoxContext &bezierPathsBoundingBoxContext) {
|
||||||
|
if (node->isHidden() || node->alpha() == 0.0f) {
|
||||||
|
node->renderData.isValid = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node->masksToBounds()) {
|
||||||
|
if (node->bounds().empty()) {
|
||||||
|
node->renderData.isValid = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto currentTransform = parentTransform;
|
||||||
|
|
||||||
|
Vector2D localTranslation(node->position().x + -node->bounds().x, node->position().y + -node->bounds().y);
|
||||||
|
CATransform3D localTransform = node->transform();
|
||||||
|
localTransform = localTransform.translated(localTranslation);
|
||||||
|
|
||||||
|
currentTransform = localTransform * currentTransform;
|
||||||
|
|
||||||
|
if (!currentTransform.isInvertible()) {
|
||||||
|
node->renderData.isValid = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<CGRect> effectiveLocalBounds;
|
||||||
|
|
||||||
|
double alpha = node->alpha();
|
||||||
|
|
||||||
|
if (node->content()) {
|
||||||
|
RenderTreeNodeContent *shapeContent = node->content().get();
|
||||||
|
|
||||||
|
CGRect shapeBounds = bezierPathsBoundingBoxParallel(bezierPathsBoundingBoxContext, shapeContent->paths);
|
||||||
|
|
||||||
|
if (shapeContent->stroke) {
|
||||||
|
shapeBounds = shapeBounds.insetBy(-shapeContent->stroke->lineWidth / 2.0, -shapeContent->stroke->lineWidth / 2.0);
|
||||||
|
effectiveLocalBounds = shapeBounds;
|
||||||
|
|
||||||
|
switch (shapeContent->stroke->shading->type()) {
|
||||||
|
case RenderTreeNodeContent::ShadingType::Solid: {
|
||||||
|
RenderTreeNodeContent::SolidShading *solidShading = (RenderTreeNodeContent::SolidShading *)shapeContent->stroke->shading.get();
|
||||||
|
|
||||||
|
alpha *= solidShading->opacity;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case RenderTreeNodeContent::ShadingType::Gradient: {
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else if (shapeContent->fill) {
|
||||||
|
effectiveLocalBounds = shapeBounds;
|
||||||
|
|
||||||
|
switch (shapeContent->fill->shading->type()) {
|
||||||
|
case RenderTreeNodeContent::ShadingType::Solid: {
|
||||||
|
RenderTreeNodeContent::SolidShading *solidShading = (RenderTreeNodeContent::SolidShading *)shapeContent->fill->shading.get();
|
||||||
|
|
||||||
|
alpha *= solidShading->opacity;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case RenderTreeNodeContent::ShadingType::Gradient: {
|
||||||
|
RenderTreeNodeContent::GradientShading *gradientShading = (RenderTreeNodeContent::GradientShading *)shapeContent->fill->shading.get();
|
||||||
|
|
||||||
|
alpha *= gradientShading->opacity;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isInvertedMatte = isInvertedMask;
|
||||||
|
if (isInvertedMatte) {
|
||||||
|
effectiveLocalBounds = node->bounds();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (effectiveLocalBounds && effectiveLocalBounds->empty()) {
|
||||||
|
effectiveLocalBounds = std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<CGRect> effectiveLocalRect;
|
||||||
|
if (effectiveLocalBounds.has_value()) {
|
||||||
|
effectiveLocalRect = effectiveLocalBounds;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<CGRect> subnodesGlobalRect;
|
||||||
|
bool masksToBounds = node->masksToBounds();
|
||||||
|
|
||||||
|
int drawContentDescendants = 0;
|
||||||
|
|
||||||
|
for (const auto &item : node->subnodes()) {
|
||||||
|
processRenderTree(item, globalSize, currentTransform, false, bezierPathsBoundingBoxContext);
|
||||||
|
if (item->renderData.isValid) {
|
||||||
|
drawContentDescendants += item->renderData.drawContentDescendants;
|
||||||
|
|
||||||
|
if (item->content()) {
|
||||||
|
drawContentDescendants += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!item->renderData.localRect.empty()) {
|
||||||
|
if (effectiveLocalRect.has_value()) {
|
||||||
|
effectiveLocalRect = effectiveLocalRect->unionWith(item->renderData.localRect);
|
||||||
|
} else {
|
||||||
|
effectiveLocalRect = item->renderData.localRect;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (subnodesGlobalRect) {
|
||||||
|
subnodesGlobalRect = subnodesGlobalRect->unionWith(item->renderData.globalRect);
|
||||||
|
} else {
|
||||||
|
subnodesGlobalRect = item->renderData.globalRect;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (masksToBounds && effectiveLocalRect.has_value()) {
|
||||||
|
if (node->bounds().contains(effectiveLocalRect.value())) {
|
||||||
|
masksToBounds = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<CGRect> fuzzyGlobalRect;
|
||||||
|
|
||||||
|
if (effectiveLocalBounds) {
|
||||||
|
CGRect effectiveGlobalBounds = effectiveLocalBounds->applyingTransform(currentTransform);
|
||||||
|
if (fuzzyGlobalRect) {
|
||||||
|
fuzzyGlobalRect = fuzzyGlobalRect->unionWith(effectiveGlobalBounds);
|
||||||
|
} else {
|
||||||
|
fuzzyGlobalRect = effectiveGlobalBounds;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (subnodesGlobalRect) {
|
||||||
|
if (fuzzyGlobalRect) {
|
||||||
|
fuzzyGlobalRect = fuzzyGlobalRect->unionWith(subnodesGlobalRect.value());
|
||||||
|
} else {
|
||||||
|
fuzzyGlobalRect = subnodesGlobalRect;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fuzzyGlobalRect) {
|
||||||
|
node->renderData.isValid = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CGRect globalRect(
|
||||||
|
std::floor(fuzzyGlobalRect->x),
|
||||||
|
std::floor(fuzzyGlobalRect->y),
|
||||||
|
std::ceil(fuzzyGlobalRect->width + fuzzyGlobalRect->x - floor(fuzzyGlobalRect->x)),
|
||||||
|
std::ceil(fuzzyGlobalRect->height + fuzzyGlobalRect->y - floor(fuzzyGlobalRect->y))
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!CGRect(0.0, 0.0, globalSize.x, globalSize.y).intersects(globalRect)) {
|
||||||
|
node->renderData.isValid = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (masksToBounds && effectiveLocalBounds) {
|
||||||
|
CGRect effectiveGlobalBounds = effectiveLocalBounds->applyingTransform(currentTransform);
|
||||||
|
if (effectiveGlobalBounds.contains(CGRect(0.0, 0.0, globalSize.x, globalSize.y))) {
|
||||||
|
masksToBounds = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node->mask()) {
|
||||||
|
processRenderTree(node->mask(), globalSize, currentTransform, node->invertMask(), bezierPathsBoundingBoxContext);
|
||||||
|
if (node->mask()->renderData.isValid) {
|
||||||
|
if (!node->mask()->renderData.globalRect.intersects(globalRect)) {
|
||||||
|
node->renderData.isValid = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
node->renderData.isValid = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CGRect localRect = effectiveLocalRect.value_or(CGRect(0.0, 0.0, 0.0, 0.0)).applyingTransform(localTransform);
|
||||||
|
|
||||||
|
node->renderData.isValid = true;
|
||||||
|
|
||||||
|
node->renderData.layer._bounds = node->bounds();
|
||||||
|
node->renderData.layer._position = node->position();
|
||||||
|
node->renderData.layer._transform = node->transform();
|
||||||
|
node->renderData.layer._opacity = alpha;
|
||||||
|
node->renderData.layer._masksToBounds = masksToBounds;
|
||||||
|
node->renderData.layer._isHidden = node->isHidden();
|
||||||
|
|
||||||
|
node->renderData.globalRect = globalRect;
|
||||||
|
node->renderData.localRect = localRect;
|
||||||
|
node->renderData.globalTransform = currentTransform;
|
||||||
|
node->renderData.drawsContent = effectiveLocalBounds.has_value();
|
||||||
|
node->renderData.drawContentDescendants = drawContentDescendants;
|
||||||
|
node->renderData.isInvertedMatte = isInvertedMatte;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
static void drawLottieRenderableItem(std::shared_ptr<lottieRendering::Canvas> context, LottieRenderContent * _Nonnull item) {
|
static void drawLottieRenderableItem(std::shared_ptr<lottieRendering::Canvas> context, std::shared_ptr<lottie::RenderTreeNodeContent> item) {
|
||||||
if (item.path == nil) {
|
if (item->paths.empty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<lottie::CGPath> path = lottie::CGPath::makePath();
|
std::shared_ptr<lottie::CGPath> path = lottie::CGPath::makePath();
|
||||||
[item.path enumerateItems:^(LottiePathItem * _Nonnull pathItem) {
|
|
||||||
|
const auto iterate = [&](LottiePathItem const *pathItem) {
|
||||||
switch (pathItem->type) {
|
switch (pathItem->type) {
|
||||||
case LottiePathItemTypeMoveTo: {
|
case LottiePathItemTypeMoveTo: {
|
||||||
path->moveTo(lottie::Vector2D(pathItem->points[0].x, pathItem->points[0].y));
|
path->moveTo(lottie::Vector2D(pathItem->points[0].x, pathItem->points[0].y));
|
||||||
@ -33,23 +243,52 @@ static void drawLottieRenderableItem(std::shared_ptr<lottieRendering::Canvas> co
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}];
|
};
|
||||||
|
|
||||||
if (item.stroke != nil) {
|
LottiePathItem pathItem;
|
||||||
if ([item.stroke.shading isKindOfClass:[LottieRenderContentSolidShading class]]) {
|
for (const auto &path : item->paths) {
|
||||||
LottieRenderContentSolidShading *solidShading = (LottieRenderContentSolidShading *)item.stroke.shading;
|
std::optional<lottie::PathElement> previousElement;
|
||||||
|
for (const auto &element : path.elements()) {
|
||||||
|
if (previousElement.has_value()) {
|
||||||
|
if (previousElement->vertex.outTangentRelative().isZero() && element.vertex.inTangentRelative().isZero()) {
|
||||||
|
pathItem.type = LottiePathItemTypeLineTo;
|
||||||
|
pathItem.points[0] = CGPointMake(element.vertex.point.x, element.vertex.point.y);
|
||||||
|
iterate(&pathItem);
|
||||||
|
} else {
|
||||||
|
pathItem.type = LottiePathItemTypeCurveTo;
|
||||||
|
pathItem.points[2] = CGPointMake(element.vertex.point.x, element.vertex.point.y);
|
||||||
|
pathItem.points[1] = CGPointMake(element.vertex.inTangent.x, element.vertex.inTangent.y);
|
||||||
|
pathItem.points[0] = CGPointMake(previousElement->vertex.outTangent.x, previousElement->vertex.outTangent.y);
|
||||||
|
iterate(&pathItem);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pathItem.type = LottiePathItemTypeMoveTo;
|
||||||
|
pathItem.points[0] = CGPointMake(element.vertex.point.x, element.vertex.point.y);
|
||||||
|
iterate(&pathItem);
|
||||||
|
}
|
||||||
|
previousElement = element;
|
||||||
|
}
|
||||||
|
if (path.closed().value_or(true)) {
|
||||||
|
pathItem.type = LottiePathItemTypeClose;
|
||||||
|
iterate(&pathItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item->stroke) {
|
||||||
|
if (item->stroke->shading->type() == lottie::RenderTreeNodeContent::ShadingType::Solid) {
|
||||||
|
lottie::RenderTreeNodeContent::SolidShading *solidShading = (lottie::RenderTreeNodeContent::SolidShading *)item->stroke->shading.get();
|
||||||
|
|
||||||
lottieRendering::LineJoin lineJoin = lottieRendering::LineJoin::Bevel;
|
lottieRendering::LineJoin lineJoin = lottieRendering::LineJoin::Bevel;
|
||||||
switch (item.stroke.lineJoin) {
|
switch (item->stroke->lineJoin) {
|
||||||
case kCGLineJoinBevel: {
|
case lottie::LineJoin::Bevel: {
|
||||||
lineJoin = lottieRendering::LineJoin::Bevel;
|
lineJoin = lottieRendering::LineJoin::Bevel;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case kCGLineJoinRound: {
|
case lottie::LineJoin::Round: {
|
||||||
lineJoin = lottieRendering::LineJoin::Round;
|
lineJoin = lottieRendering::LineJoin::Round;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case kCGLineJoinMiter: {
|
case lottie::LineJoin::Miter: {
|
||||||
lineJoin = lottieRendering::LineJoin::Miter;
|
lineJoin = lottieRendering::LineJoin::Miter;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -59,16 +298,16 @@ static void drawLottieRenderableItem(std::shared_ptr<lottieRendering::Canvas> co
|
|||||||
}
|
}
|
||||||
|
|
||||||
lottieRendering::LineCap lineCap = lottieRendering::LineCap::Square;
|
lottieRendering::LineCap lineCap = lottieRendering::LineCap::Square;
|
||||||
switch (item.stroke.lineCap) {
|
switch (item->stroke->lineCap) {
|
||||||
case kCGLineCapButt: {
|
case lottie::LineCap::Butt: {
|
||||||
lineCap = lottieRendering::LineCap::Butt;
|
lineCap = lottieRendering::LineCap::Butt;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case kCGLineCapRound: {
|
case lottie::LineCap::Round: {
|
||||||
lineCap = lottieRendering::LineCap::Round;
|
lineCap = lottieRendering::LineCap::Round;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case kCGLineCapSquare: {
|
case lottie::LineCap::Square: {
|
||||||
lineCap = lottieRendering::LineCap::Square;
|
lineCap = lottieRendering::LineCap::Square;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -78,24 +317,22 @@ static void drawLottieRenderableItem(std::shared_ptr<lottieRendering::Canvas> co
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::vector<double> dashPattern;
|
std::vector<double> dashPattern;
|
||||||
if (item.stroke.dashPattern != nil) {
|
if (!item->stroke->dashPattern.empty()) {
|
||||||
for (NSNumber *value in item.stroke.dashPattern) {
|
dashPattern = item->stroke->dashPattern;
|
||||||
dashPattern.push_back([value doubleValue]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
context->strokePath(path, item.stroke.lineWidth, lineJoin, lineCap, item.stroke.dashPhase, dashPattern, lottieRendering::Color(solidShading.color.r, solidShading.color.g, solidShading.color.b, solidShading.color.a));
|
context->strokePath(path, item->stroke->lineWidth, lineJoin, lineCap, item->stroke->dashPhase, dashPattern, lottieRendering::Color(solidShading->color.r, solidShading->color.g, solidShading->color.b, solidShading->color.a));
|
||||||
} else if ([item.stroke.shading isKindOfClass:[LottieRenderContentGradientShading class]]) {
|
} else if (item->stroke->shading->type() == lottie::RenderTreeNodeContent::ShadingType::Gradient) {
|
||||||
__unused LottieRenderContentGradientShading *gradientShading = (LottieRenderContentGradientShading *)item.stroke.shading;
|
//TODO:gradient stroke
|
||||||
}
|
}
|
||||||
} else if (item.fill != nil) {
|
} else if (item->fill) {
|
||||||
lottieRendering::FillRule rule = lottieRendering::FillRule::NonZeroWinding;
|
lottieRendering::FillRule rule = lottieRendering::FillRule::NonZeroWinding;
|
||||||
switch (item.fill.fillRule) {
|
switch (item->fill->rule) {
|
||||||
case LottieFillRuleEvenOdd: {
|
case lottie::FillRule::EvenOdd: {
|
||||||
rule = lottieRendering::FillRule::EvenOdd;
|
rule = lottieRendering::FillRule::EvenOdd;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case LottieFillRuleWinding: {
|
case lottie::FillRule::NonZeroWinding: {
|
||||||
rule = lottieRendering::FillRule::NonZeroWinding;
|
rule = lottieRendering::FillRule::NonZeroWinding;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -104,30 +341,29 @@ static void drawLottieRenderableItem(std::shared_ptr<lottieRendering::Canvas> co
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ([item.fill.shading isKindOfClass:[LottieRenderContentSolidShading class]]) {
|
if (item->fill->shading->type() == lottie::RenderTreeNodeContent::ShadingType::Solid) {
|
||||||
LottieRenderContentSolidShading *solidShading = (LottieRenderContentSolidShading *)item.fill.shading;
|
lottie::RenderTreeNodeContent::SolidShading *solidShading = (lottie::RenderTreeNodeContent::SolidShading *)item->fill->shading.get();
|
||||||
|
context->fillPath(path, rule, lottieRendering::Color(solidShading->color.r, solidShading->color.g, solidShading->color.b, solidShading->color.a));
|
||||||
context->fillPath(path, rule, lottieRendering::Color(solidShading.color.r, solidShading.color.g, solidShading.color.b, solidShading.color.a));
|
} else if (item->fill->shading->type() == lottie::RenderTreeNodeContent::ShadingType::Gradient) {
|
||||||
} else if ([item.fill.shading isKindOfClass:[LottieRenderContentGradientShading class]]) {
|
lottie::RenderTreeNodeContent::GradientShading *gradientShading = (lottie::RenderTreeNodeContent::GradientShading *)item->fill->shading.get();
|
||||||
LottieRenderContentGradientShading *gradientShading = (LottieRenderContentGradientShading *)item.fill.shading;
|
|
||||||
|
|
||||||
std::vector<lottieRendering::Color> colors;
|
std::vector<lottieRendering::Color> colors;
|
||||||
std::vector<double> locations;
|
std::vector<double> locations;
|
||||||
for (LottieColorStop *colorStop in gradientShading.colorStops) {
|
for (const auto &color : gradientShading->colors) {
|
||||||
colors.push_back(lottieRendering::Color(colorStop.color.r, colorStop.color.g, colorStop.color.b, colorStop.color.a));
|
colors.push_back(lottieRendering::Color(color.r, color.g, color.b, color.a));
|
||||||
locations.push_back(colorStop.location);
|
|
||||||
}
|
}
|
||||||
|
locations = gradientShading->locations;
|
||||||
|
|
||||||
lottieRendering::Gradient gradient(colors, locations);
|
lottieRendering::Gradient gradient(colors, locations);
|
||||||
lottie::Vector2D start(gradientShading.start.x, gradientShading.start.y);
|
lottie::Vector2D start(gradientShading->start.x, gradientShading->start.y);
|
||||||
lottie::Vector2D end(gradientShading.end.x, gradientShading.end.y);
|
lottie::Vector2D end(gradientShading->end.x, gradientShading->end.y);
|
||||||
|
|
||||||
switch (gradientShading.gradientType) {
|
switch (gradientShading->gradientType) {
|
||||||
case LottieGradientTypeLinear: {
|
case lottie::GradientType::Linear: {
|
||||||
context->linearGradientFillPath(path, rule, gradient, start, end);
|
context->linearGradientFillPath(path, rule, gradient, start, end);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case LottieGradientTypeRadial: {
|
case lottie::GradientType::Radial: {
|
||||||
context->radialGradientFillPath(path, rule, gradient, start, 0.0, start, start.distanceTo(end));
|
context->radialGradientFillPath(path, rule, gradient, start, 0.0, start, start.distanceTo(end));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -139,11 +375,14 @@ static void drawLottieRenderableItem(std::shared_ptr<lottieRendering::Canvas> co
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void renderLottieRenderNode(LottieRenderNode * _Nonnull node, std::shared_ptr<lottieRendering::Canvas> parentContext, lottie::Vector2D const &globalSize, double parentAlpha) {
|
static void renderLottieRenderNode(std::shared_ptr<lottie::RenderTreeNode> node, std::shared_ptr<lottieRendering::Canvas> parentContext, lottie::Vector2D const &globalSize, double parentAlpha) {
|
||||||
float normalizedOpacity = node.opacity;
|
if (!node->renderData.isValid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
float normalizedOpacity = node->renderData.layer.opacity();
|
||||||
double layerAlpha = ((double)normalizedOpacity) * parentAlpha;
|
double layerAlpha = ((double)normalizedOpacity) * parentAlpha;
|
||||||
|
|
||||||
if (node.isHidden || normalizedOpacity == 0.0f) {
|
if (node->renderData.layer.isHidden() || normalizedOpacity == 0.0f) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,44 +393,44 @@ static void renderLottieRenderNode(LottieRenderNode * _Nonnull node, std::shared
|
|||||||
std::shared_ptr<lottieRendering::Canvas> tempContext;
|
std::shared_ptr<lottieRendering::Canvas> tempContext;
|
||||||
|
|
||||||
bool needsTempContext = false;
|
bool needsTempContext = false;
|
||||||
if (node.mask != nil) {
|
if (node->mask() && node->mask()->renderData.isValid) {
|
||||||
needsTempContext = true;
|
needsTempContext = true;
|
||||||
} else {
|
} else {
|
||||||
needsTempContext = layerAlpha != 1.0 || node.masksToBounds;
|
needsTempContext = layerAlpha != 1.0 || node->renderData.layer.masksToBounds();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (needsTempContext) {
|
if (needsTempContext) {
|
||||||
if (node.mask != nil || node.masksToBounds) {
|
if ((node->mask() && node->mask()->renderData.isValid) || node->renderData.layer.masksToBounds()) {
|
||||||
auto maskBackingStorage = parentContext->makeLayer((int)(node.globalRect.size.width), (int)(node.globalRect.size.height));
|
auto maskBackingStorage = parentContext->makeLayer((int)(node->renderData.globalRect.width), (int)(node->renderData.globalRect.height));
|
||||||
|
|
||||||
maskBackingStorage->concatenate(lottie::CATransform3D::identity().translated(lottie::Vector2D(-node.globalRect.origin.x, -node.globalRect.origin.y)));
|
maskBackingStorage->concatenate(lottie::CATransform3D::identity().translated(lottie::Vector2D(-node->renderData.globalRect.x, -node->renderData.globalRect.y)));
|
||||||
maskBackingStorage->concatenate(lottie::fromNativeTransform(node.globalTransform));
|
maskBackingStorage->concatenate(node->renderData.globalTransform);
|
||||||
|
|
||||||
if (node.masksToBounds) {
|
if (node->renderData.layer.masksToBounds()) {
|
||||||
maskBackingStorage->fill(lottie::CGRect(node.bounds.origin.x, node.bounds.origin.y, node.bounds.size.width, node.bounds.size.height), lottieRendering::Color(1.0, 1.0, 1.0, 1.0));
|
maskBackingStorage->fill(lottie::CGRect(node->renderData.layer.bounds().x, node->renderData.layer.bounds().y, node->renderData.layer.bounds().width, node->renderData.layer.bounds().height), lottieRendering::Color(1.0, 1.0, 1.0, 1.0));
|
||||||
}
|
}
|
||||||
if (node.mask != nil) {
|
if (node->mask() && node->mask()->renderData.isValid) {
|
||||||
renderLottieRenderNode(node.mask, maskBackingStorage, globalSize, 1.0);
|
renderLottieRenderNode(node->mask(), maskBackingStorage, globalSize, 1.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
maskContext = maskBackingStorage;
|
maskContext = maskBackingStorage;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto tempContextValue = parentContext->makeLayer((int)(node.globalRect.size.width), (int)(node.globalRect.size.height));
|
auto tempContextValue = parentContext->makeLayer((int)(node->renderData.globalRect.width), (int)(node->renderData.globalRect.height));
|
||||||
tempContext = tempContextValue;
|
tempContext = tempContextValue;
|
||||||
|
|
||||||
currentContext = tempContextValue;
|
currentContext = tempContextValue;
|
||||||
currentContext->concatenate(lottie::CATransform3D::identity().translated(lottie::Vector2D(-node.globalRect.origin.x, -node.globalRect.origin.y)));
|
currentContext->concatenate(lottie::CATransform3D::identity().translated(lottie::Vector2D(-node->renderData.globalRect.x, -node->renderData.globalRect.y)));
|
||||||
|
|
||||||
currentContext->saveState();
|
currentContext->saveState();
|
||||||
currentContext->concatenate(lottie::fromNativeTransform(node.globalTransform));
|
currentContext->concatenate(node->renderData.globalTransform);
|
||||||
} else {
|
} else {
|
||||||
currentContext = parentContext;
|
currentContext = parentContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
parentContext->concatenate(lottie::CATransform3D::identity().translated(lottie::Vector2D(node.position.x, node.position.y)));
|
parentContext->concatenate(lottie::CATransform3D::identity().translated(lottie::Vector2D(node->renderData.layer.position().x, node->renderData.layer.position().y)));
|
||||||
parentContext->concatenate(lottie::CATransform3D::identity().translated(lottie::Vector2D(-node.bounds.origin.x, -node.bounds.origin.y)));
|
parentContext->concatenate(lottie::CATransform3D::identity().translated(lottie::Vector2D(-node->renderData.layer.bounds().x, -node->renderData.layer.bounds().y)));
|
||||||
parentContext->concatenate(lottie::fromNativeTransform(node.transform));
|
parentContext->concatenate(node->renderData.layer.transform());
|
||||||
|
|
||||||
double renderAlpha = 1.0;
|
double renderAlpha = 1.0;
|
||||||
if (tempContext) {
|
if (tempContext) {
|
||||||
@ -202,30 +441,32 @@ static void renderLottieRenderNode(LottieRenderNode * _Nonnull node, std::shared
|
|||||||
|
|
||||||
currentContext->setAlpha(renderAlpha);
|
currentContext->setAlpha(renderAlpha);
|
||||||
|
|
||||||
if (node.renderContent != nil) {
|
if (node->content()) {
|
||||||
drawLottieRenderableItem(currentContext, node.renderContent);
|
drawLottieRenderableItem(currentContext, node->content());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (node.isInvertedMatte) {
|
if (node->renderData.isInvertedMatte) {
|
||||||
currentContext->fill(lottie::CGRect(node.bounds.origin.x, node.bounds.origin.y, node.bounds.size.width, node.bounds.size.height), lottieRendering::Color(0.0, 0.0, 0.0, 1.0));
|
currentContext->fill(lottie::CGRect(node->renderData.layer.bounds().x, node->renderData.layer.bounds().y, node->renderData.layer.bounds().width, node->renderData.layer.bounds().height), lottieRendering::Color(0.0, 0.0, 0.0, 1.0));
|
||||||
currentContext->setBlendMode(lottieRendering::BlendMode::DestinationOut);
|
currentContext->setBlendMode(lottieRendering::BlendMode::DestinationOut);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (LottieRenderNode *subnode in node.subnodes) {
|
for (const auto &subnode : node->subnodes()) {
|
||||||
|
if (subnode->renderData.isValid) {
|
||||||
renderLottieRenderNode(subnode, currentContext, globalSize, renderAlpha);
|
renderLottieRenderNode(subnode, currentContext, globalSize, renderAlpha);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (tempContext) {
|
if (tempContext) {
|
||||||
tempContext->restoreState();
|
tempContext->restoreState();
|
||||||
|
|
||||||
if (maskContext) {
|
if (maskContext) {
|
||||||
tempContext->setBlendMode(lottieRendering::BlendMode::DestinationIn);
|
tempContext->setBlendMode(lottieRendering::BlendMode::DestinationIn);
|
||||||
tempContext->draw(maskContext, lottie::CGRect(node.globalRect.origin.x, node.globalRect.origin.y, node.globalRect.size.width, node.globalRect.size.height));
|
tempContext->draw(maskContext, lottie::CGRect(node->renderData.globalRect.x, node->renderData.globalRect.y, node->renderData.globalRect.width, node->renderData.globalRect.height));
|
||||||
}
|
}
|
||||||
|
|
||||||
parentContext->concatenate(lottie::fromNativeTransform(node.globalTransform).inverted());
|
parentContext->concatenate(node->renderData.globalTransform.inverted());
|
||||||
parentContext->setAlpha(layerAlpha);
|
parentContext->setAlpha(layerAlpha);
|
||||||
parentContext->draw(tempContext, lottie::CGRect(node.globalRect.origin.x, node.globalRect.origin.y, node.globalRect.size.width, node.globalRect.size.height));
|
parentContext->draw(tempContext, node->renderData.globalRect);
|
||||||
}
|
}
|
||||||
|
|
||||||
parentContext->restoreState();
|
parentContext->restoreState();
|
||||||
@ -238,19 +479,42 @@ CGRect getPathNativeBoundingBox(CGPathRef _Nonnull path) {
|
|||||||
return CGRectMake(rect.origin.x, rect.origin.y, rect.size.width, rect.size.height);
|
return CGRectMake(rect.origin.x, rect.origin.y, rect.size.width, rect.size.height);
|
||||||
}
|
}
|
||||||
|
|
||||||
UIImage * _Nullable renderLottieAnimationContainer(LottieAnimationContainer * _Nonnull animationContainer, CGSize size, bool useReferenceRendering) {
|
@interface SoftwareLottieRenderer() {
|
||||||
LottieAnimation *animation = animationContainer.animation;
|
LottieAnimationContainer *_animationContainer;
|
||||||
LottieRenderNode *lottieNode = [animationContainer getCurrentRenderTreeForSize:size];
|
std::shared_ptr<lottie::BezierPathsBoundingBoxContext> _bezierPathsBoundingBoxContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation SoftwareLottieRenderer
|
||||||
|
|
||||||
|
- (instancetype _Nonnull)initWithAnimationContainer:(LottieAnimationContainer * _Nonnull)animationContainer {
|
||||||
|
self = [super init];
|
||||||
|
if (self != nil) {
|
||||||
|
_animationContainer = animationContainer;
|
||||||
|
_bezierPathsBoundingBoxContext = std::make_shared<lottie::BezierPathsBoundingBoxContext>();
|
||||||
|
}
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (UIImage * _Nullable)renderForSize:(CGSize)size useReferenceRendering:(bool)useReferenceRendering {
|
||||||
|
LottieAnimation *animation = _animationContainer.animation;
|
||||||
|
std::shared_ptr<lottie::RenderTreeNode> renderNode = [_animationContainer internalGetRootRenderTreeNode];
|
||||||
|
if (!renderNode) {
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
processRenderTree(renderNode, lottie::Vector2D((int)size.width, (int)size.height), lottie::CATransform3D::identity().scaled(lottie::Vector2D(size.width / (double)animation.size.width, size.height / (double)animation.size.height)), false, *_bezierPathsBoundingBoxContext.get());
|
||||||
|
|
||||||
|
//LottieRenderNode *lottieNode = [_animationContainer getCurrentRenderTreeForSize:size];
|
||||||
|
|
||||||
if (useReferenceRendering) {
|
if (useReferenceRendering) {
|
||||||
auto context = std::make_shared<lottieRendering::CanvasImpl>((int)size.width, (int)size.height);
|
auto context = std::make_shared<lottieRendering::CanvasImpl>((int)size.width, (int)size.height);
|
||||||
|
|
||||||
if (lottieNode) {
|
|
||||||
CGPoint scale = CGPointMake(size.width / (CGFloat)animation.size.width, size.height / (CGFloat)animation.size.height);
|
CGPoint scale = CGPointMake(size.width / (CGFloat)animation.size.width, size.height / (CGFloat)animation.size.height);
|
||||||
context->concatenate(lottie::CATransform3D::makeScale(scale.x, scale.y, 1.0));
|
context->concatenate(lottie::CATransform3D::makeScale(scale.x, scale.y, 1.0));
|
||||||
|
|
||||||
renderLottieRenderNode(lottieNode, context, lottie::Vector2D(context->width(), context->height()), 1.0);
|
renderLottieRenderNode(renderNode, context, lottie::Vector2D(context->width(), context->height()), 1.0);
|
||||||
}
|
|
||||||
|
|
||||||
auto image = context->makeImage();
|
auto image = context->makeImage();
|
||||||
|
|
||||||
@ -259,3 +523,5 @@ UIImage * _Nullable renderLottieAnimationContainer(LottieAnimationContainer * _N
|
|||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|||||||
@ -79,6 +79,8 @@ func processDrawAnimation(baseCachePath: String, path: String, name: String, siz
|
|||||||
let _ = await cacheReferenceAnimation(baseCachePath: baseCachePath, width: Int(size.width), path: path, name: name)
|
let _ = await cacheReferenceAnimation(baseCachePath: baseCachePath, width: Int(size.width), path: path, name: name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let renderer = SoftwareLottieRenderer(animationContainer: layer)
|
||||||
|
|
||||||
for i in 0 ..< min(100000, animation.frameCount) {
|
for i in 0 ..< min(100000, animation.frameCount) {
|
||||||
let frameResult = autoreleasepool {
|
let frameResult = autoreleasepool {
|
||||||
let frameIndex = i % animation.frameCount
|
let frameIndex = i % animation.frameCount
|
||||||
@ -87,7 +89,7 @@ func processDrawAnimation(baseCachePath: String, path: String, name: String, siz
|
|||||||
let referenceImage = decompressImageFrame(data: referenceImageData)
|
let referenceImage = decompressImageFrame(data: referenceImageData)
|
||||||
|
|
||||||
layer.update(frameIndex)
|
layer.update(frameIndex)
|
||||||
let image = renderLottieAnimationContainer(layer, size, true)!
|
let image = renderer.render(for: size, useReferenceRendering: true)!
|
||||||
|
|
||||||
if let diffImage = areImagesEqual(image, referenceImage) {
|
if let diffImage = areImagesEqual(image, referenceImage) {
|
||||||
updateImage(diffImage, referenceImage)
|
updateImage(diffImage, referenceImage)
|
||||||
|
|||||||
@ -0,0 +1,151 @@
|
|||||||
|
#ifndef BezierPath_h
|
||||||
|
#define BezierPath_h
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
|
||||||
|
#include <LottieCpp/CurveVertex.h>
|
||||||
|
#include <LottieCpp/PathElement.h>
|
||||||
|
#include <LottieCpp/CGPath.h>
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace lottie {
|
||||||
|
|
||||||
|
struct BezierTrimPathPosition {
|
||||||
|
double start;
|
||||||
|
double end;
|
||||||
|
|
||||||
|
explicit BezierTrimPathPosition(double start_, double end_);
|
||||||
|
};
|
||||||
|
|
||||||
|
class BezierPathContents: public std::enable_shared_from_this<BezierPathContents> {
|
||||||
|
public:
|
||||||
|
explicit BezierPathContents(CurveVertex const &startPoint);
|
||||||
|
|
||||||
|
BezierPathContents();
|
||||||
|
|
||||||
|
explicit BezierPathContents(lottiejson11::Json const &jsonAny) noexcept(false);
|
||||||
|
|
||||||
|
BezierPathContents(const BezierPathContents&) = delete;
|
||||||
|
BezierPathContents& operator=(BezierPathContents&) = delete;
|
||||||
|
|
||||||
|
lottiejson11::Json toJson() const;
|
||||||
|
|
||||||
|
std::shared_ptr<CGPath> cgPath() const;
|
||||||
|
|
||||||
|
public:
|
||||||
|
std::vector<PathElement> elements;
|
||||||
|
std::optional<bool> closed;
|
||||||
|
|
||||||
|
double length();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::optional<double> _length;
|
||||||
|
|
||||||
|
public:
|
||||||
|
void moveToStartPoint(CurveVertex const &vertex);
|
||||||
|
void addVertex(CurveVertex const &vertex);
|
||||||
|
|
||||||
|
void reserveCapacity(size_t capacity);
|
||||||
|
void setElementCount(size_t count);
|
||||||
|
void invalidateLength();
|
||||||
|
|
||||||
|
void addCurve(Vector2D const &toPoint, Vector2D const &outTangent, Vector2D const &inTangent);
|
||||||
|
void addLine(Vector2D const &toPoint);
|
||||||
|
void close();
|
||||||
|
void addElement(PathElement const &pathElement);
|
||||||
|
void updateVertex(CurveVertex const &vertex, int atIndex, bool remeasure);
|
||||||
|
|
||||||
|
/// Trims a path fromLength toLength with an offset.
|
||||||
|
///
|
||||||
|
/// Length and offset are defined in the length coordinate space.
|
||||||
|
/// If any argument is outside the range of this path, then it will be looped over the path from finish to start.
|
||||||
|
///
|
||||||
|
/// Cutting the curve when fromLength is less than toLength
|
||||||
|
/// x x x x
|
||||||
|
/// ~~~~~~~~~~~~~~~ooooooooooooooooooooooooooooooooooooooooooooooooo-------------------
|
||||||
|
/// |Offset |fromLength toLength| |
|
||||||
|
///
|
||||||
|
/// Cutting the curve when from Length is greater than toLength
|
||||||
|
/// x x x x x
|
||||||
|
/// oooooooooooooooooo--------------------~~~~~~~~~~~~~~~~ooooooooooooooooooooooooooooo
|
||||||
|
/// | toLength| |Offset |fromLength |
|
||||||
|
///
|
||||||
|
std::vector<std::shared_ptr<BezierPathContents>> trim(double fromLength, double toLength, double offsetLength);
|
||||||
|
|
||||||
|
// MARK: Private
|
||||||
|
|
||||||
|
std::vector<std::shared_ptr<BezierPathContents>> trimPathAtLengths(std::vector<BezierTrimPathPosition> const &positions);
|
||||||
|
};
|
||||||
|
|
||||||
|
class BezierPath {
|
||||||
|
public:
|
||||||
|
explicit BezierPath(CurveVertex const &startPoint);
|
||||||
|
BezierPath();
|
||||||
|
explicit BezierPath(lottiejson11::Json const &jsonAny) noexcept(false);
|
||||||
|
|
||||||
|
lottiejson11::Json toJson() const;
|
||||||
|
|
||||||
|
double length();
|
||||||
|
|
||||||
|
void moveToStartPoint(CurveVertex const &vertex);
|
||||||
|
void addVertex(CurveVertex const &vertex);
|
||||||
|
void reserveCapacity(size_t capacity);
|
||||||
|
void setElementCount(size_t count);
|
||||||
|
void invalidateLength();
|
||||||
|
void addCurve(Vector2D const &toPoint, Vector2D const &outTangent, Vector2D const &inTangent);
|
||||||
|
void addLine(Vector2D const &toPoint);
|
||||||
|
void close();
|
||||||
|
void addElement(PathElement const &pathElement);
|
||||||
|
void updateVertex(CurveVertex const &vertex, int atIndex, bool remeasure);
|
||||||
|
|
||||||
|
/// Trims a path fromLength toLength with an offset.
|
||||||
|
///
|
||||||
|
/// Length and offset are defined in the length coordinate space.
|
||||||
|
/// If any argument is outside the range of this path, then it will be looped over the path from finish to start.
|
||||||
|
///
|
||||||
|
/// Cutting the curve when fromLength is less than toLength
|
||||||
|
/// x x x x
|
||||||
|
/// ~~~~~~~~~~~~~~~ooooooooooooooooooooooooooooooooooooooooooooooooo-------------------
|
||||||
|
/// |Offset |fromLength toLength| |
|
||||||
|
///
|
||||||
|
/// Cutting the curve when from Length is greater than toLength
|
||||||
|
/// x x x x x
|
||||||
|
/// oooooooooooooooooo--------------------~~~~~~~~~~~~~~~~ooooooooooooooooooooooooooooo
|
||||||
|
/// | toLength| |Offset |fromLength |
|
||||||
|
///
|
||||||
|
std::vector<BezierPath> trim(double fromLength, double toLength, double offsetLength);
|
||||||
|
|
||||||
|
std::vector<PathElement> const &elements() const;
|
||||||
|
std::vector<PathElement> &mutableElements();
|
||||||
|
std::optional<bool> const &closed() const;
|
||||||
|
void setClosed(std::optional<bool> const &closed);
|
||||||
|
std::shared_ptr<CGPath> cgPath() const;
|
||||||
|
BezierPath copyUsingTransform(CATransform3D const &transform) const;
|
||||||
|
|
||||||
|
public:
|
||||||
|
BezierPath(std::shared_ptr<BezierPathContents> contents);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::shared_ptr<BezierPathContents> _contents;
|
||||||
|
};
|
||||||
|
|
||||||
|
class BezierPathsBoundingBoxContext {
|
||||||
|
public:
|
||||||
|
BezierPathsBoundingBoxContext();
|
||||||
|
~BezierPathsBoundingBoxContext();
|
||||||
|
|
||||||
|
public:
|
||||||
|
float *pointsX = nullptr;
|
||||||
|
float *pointsY = nullptr;
|
||||||
|
int pointsSize = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
CGRect bezierPathsBoundingBox(std::vector<BezierPath> const &paths);
|
||||||
|
CGRect bezierPathsBoundingBoxParallel(BezierPathsBoundingBoxContext &context, std::vector<BezierPath> const &paths);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* BezierPath_h */
|
||||||
@ -0,0 +1,55 @@
|
|||||||
|
#ifndef LottieColor_h
|
||||||
|
#define LottieColor_h
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
|
||||||
|
#include <LottieCpp/lottiejson11.hpp>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace lottie {
|
||||||
|
|
||||||
|
enum class ColorFormatDenominator {
|
||||||
|
One,
|
||||||
|
OneHundred,
|
||||||
|
TwoFiftyFive
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Color {
|
||||||
|
double r;
|
||||||
|
double g;
|
||||||
|
double b;
|
||||||
|
double a;
|
||||||
|
|
||||||
|
bool operator==(Color const &rhs) const {
|
||||||
|
if (r != rhs.r) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (g != rhs.g) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (b != rhs.b) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (a != rhs.a) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator!=(Color const &rhs) const {
|
||||||
|
return !(*this == rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
explicit Color(double r_, double g_, double b_, double a_, ColorFormatDenominator denominator = ColorFormatDenominator::One);
|
||||||
|
explicit Color(lottiejson11::Json const &jsonAny) noexcept(false);
|
||||||
|
|
||||||
|
lottiejson11::Json toJson() const;
|
||||||
|
|
||||||
|
static Color fromString(std::string const &string);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* LottieColor_h */
|
||||||
@ -1,5 +1,7 @@
|
|||||||
#ifndef CurveVertex_hpp
|
#ifndef CurveVertex_h
|
||||||
#define CurveVertex_hpp
|
#define CurveVertex_h
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
|
||||||
#include <LottieCpp/Vectors.h>
|
#include <LottieCpp/Vectors.h>
|
||||||
#include <LottieCpp/CGPath.h>
|
#include <LottieCpp/CGPath.h>
|
||||||
@ -194,4 +196,6 @@ public:
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif /* CurveVertex_hpp */
|
#endif /* CurveVertex_hpp */
|
||||||
@ -1,10 +1,22 @@
|
|||||||
#ifndef LottieAnimationContainer_h
|
#ifndef LottieAnimationContainer_h
|
||||||
#define LottieAnimationContainer_h
|
#define LottieAnimationContainer_h
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
|
||||||
#import "LottieAnimation.h"
|
#import "LottieAnimation.h"
|
||||||
#import "LottieRenderTree.h"
|
#import "LottieRenderTree.h"
|
||||||
#import "LottieAnimationContainer.h"
|
#import "LottieAnimationContainer.h"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace lottie {
|
||||||
|
|
||||||
|
class RenderTreeNode;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
@ -18,6 +30,10 @@ extern "C" {
|
|||||||
- (void)update:(NSInteger)frame;
|
- (void)update:(NSInteger)frame;
|
||||||
- (LottieRenderNode * _Nullable)getCurrentRenderTreeForSize:(CGSize)size;
|
- (LottieRenderNode * _Nullable)getCurrentRenderTreeForSize:(CGSize)size;
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
- (std::shared_ptr<lottie::RenderTreeNode>)internalGetRootRenderTreeNode;
|
||||||
|
#endif
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
|
|||||||
@ -6,9 +6,15 @@
|
|||||||
#import <LottieCpp/LottieAnimation.h>
|
#import <LottieCpp/LottieAnimation.h>
|
||||||
#import <LottieCpp/LottieAnimationContainer.h>
|
#import <LottieCpp/LottieAnimationContainer.h>
|
||||||
#import <LottieCpp/LottieRenderTree.h>
|
#import <LottieCpp/LottieRenderTree.h>
|
||||||
|
#import <LottieCpp/RenderTreeNode.h>
|
||||||
#import <LottieCpp/CGPath.h>
|
#import <LottieCpp/CGPath.h>
|
||||||
#import <LottieCpp/CGPathCocoa.h>
|
#import <LottieCpp/CGPathCocoa.h>
|
||||||
#import <LottieCpp/Vectors.h>
|
#import <LottieCpp/Vectors.h>
|
||||||
#import <LottieCpp/VectorsCocoa.h>
|
#import <LottieCpp/VectorsCocoa.h>
|
||||||
|
#import <LottieCpp/Color.h>
|
||||||
|
#import <LottieCpp/ShapeAttributes.h>
|
||||||
|
#import <LottieCpp/PathElement.h>
|
||||||
|
#import <LottieCpp/CurveVertex.h>
|
||||||
|
#import <LottieCpp/BezierPath.h>
|
||||||
|
|
||||||
#endif /* LottieCpp_h */
|
#endif /* LottieCpp_h */
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
#ifndef PathElement_hpp
|
#ifndef PathElement_h
|
||||||
#define PathElement_hpp
|
#define PathElement_h
|
||||||
|
|
||||||
#include "Lottie/Private/Utility/Primitives/CurveVertex.hpp"
|
#ifdef __cplusplus
|
||||||
|
|
||||||
|
#include <LottieCpp/CurveVertex.h>
|
||||||
|
|
||||||
namespace lottie {
|
namespace lottie {
|
||||||
|
|
||||||
@ -87,4 +89,6 @@ struct PathElement {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif /* PathElement_hpp */
|
#endif
|
||||||
|
|
||||||
|
#endif /* PathElement_h */
|
||||||
@ -0,0 +1,523 @@
|
|||||||
|
#ifndef RenderTreeNode_hpp
|
||||||
|
#define RenderTreeNode_hpp
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
|
||||||
|
#include <LottieCpp/Vectors.h>
|
||||||
|
#include <LottieCpp/CGPath.h>
|
||||||
|
#include <LottieCpp/Color.h>
|
||||||
|
#include <LottieCpp/ShapeAttributes.h>
|
||||||
|
#include <LottieCpp/BezierPath.h>
|
||||||
|
|
||||||
|
namespace lottie {
|
||||||
|
|
||||||
|
class RenderableItem {
|
||||||
|
public:
|
||||||
|
enum class Type {
|
||||||
|
Shape,
|
||||||
|
GradientFill
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
RenderableItem() {
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual ~RenderableItem() = default;
|
||||||
|
|
||||||
|
virtual Type type() const = 0;
|
||||||
|
virtual CGRect boundingRect() const = 0;
|
||||||
|
|
||||||
|
virtual bool isEqual(std::shared_ptr<RenderableItem> rhs) const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ShapeRenderableItem: public RenderableItem {
|
||||||
|
public:
|
||||||
|
struct Fill {
|
||||||
|
Color color;
|
||||||
|
FillRule rule;
|
||||||
|
|
||||||
|
Fill(Color color_, FillRule rule_) :
|
||||||
|
color(color_), rule(rule_) {
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator==(Fill const &rhs) const {
|
||||||
|
if (color != rhs.color) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (rule != rhs.rule) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator!=(Fill const &rhs) const {
|
||||||
|
return !(*this == rhs);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Stroke {
|
||||||
|
Color color;
|
||||||
|
double lineWidth = 0.0;
|
||||||
|
LineJoin lineJoin = LineJoin::Round;
|
||||||
|
LineCap lineCap = LineCap::Square;
|
||||||
|
double dashPhase = 0.0;
|
||||||
|
std::vector<double> dashPattern;
|
||||||
|
|
||||||
|
Stroke(
|
||||||
|
Color color_,
|
||||||
|
double lineWidth_,
|
||||||
|
LineJoin lineJoin_,
|
||||||
|
LineCap lineCap_,
|
||||||
|
double dashPhase_,
|
||||||
|
std::vector<double> dashPattern_
|
||||||
|
) :
|
||||||
|
color(color_),
|
||||||
|
lineWidth(lineWidth_),
|
||||||
|
lineJoin(lineJoin_),
|
||||||
|
lineCap(lineCap_),
|
||||||
|
dashPhase(dashPhase_),
|
||||||
|
dashPattern(dashPattern_) {
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator==(Stroke const &rhs) const {
|
||||||
|
if (color != rhs.color) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (lineWidth != rhs.lineWidth) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (lineJoin != rhs.lineJoin) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (lineCap != rhs.lineCap) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (dashPhase != rhs.dashPhase) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (dashPattern != rhs.dashPattern) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator!=(Stroke const &rhs) const {
|
||||||
|
return !(*this == rhs);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
ShapeRenderableItem(
|
||||||
|
std::shared_ptr<CGPath> path_,
|
||||||
|
std::optional<Fill> const &fill_,
|
||||||
|
std::optional<Stroke> const &stroke_
|
||||||
|
) :
|
||||||
|
path(path_),
|
||||||
|
fill(fill_),
|
||||||
|
stroke(stroke_) {
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual Type type() const override {
|
||||||
|
return Type::Shape;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual CGRect boundingRect() const override {
|
||||||
|
if (path) {
|
||||||
|
CGRect shapeBounds = path->boundingBox();
|
||||||
|
if (stroke) {
|
||||||
|
shapeBounds = shapeBounds.insetBy(-stroke->lineWidth / 2.0, -stroke->lineWidth / 2.0);
|
||||||
|
}
|
||||||
|
return shapeBounds;
|
||||||
|
} else {
|
||||||
|
return CGRect(0.0, 0.0, 0.0, 0.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual bool isEqual(std::shared_ptr<RenderableItem> rhs) const override {
|
||||||
|
if (rhs->type() != type()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ShapeRenderableItem *other = (ShapeRenderableItem *)rhs.get();
|
||||||
|
if ((path == nullptr) != (other->path == nullptr)) {
|
||||||
|
return false;
|
||||||
|
} else if (path) {
|
||||||
|
if (!path->isEqual(other->path.get())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (fill != other->fill) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (stroke != other->stroke) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
std::shared_ptr<CGPath> path;
|
||||||
|
std::optional<Fill> fill;
|
||||||
|
std::optional<Stroke> stroke;
|
||||||
|
};
|
||||||
|
|
||||||
|
class GradientFillRenderableItem: public RenderableItem {
|
||||||
|
public:
|
||||||
|
GradientFillRenderableItem(
|
||||||
|
std::shared_ptr<CGPath> path_,
|
||||||
|
FillRule pathFillRule_,
|
||||||
|
GradientType gradientType_,
|
||||||
|
std::vector<Color> const &colors_,
|
||||||
|
std::vector<double> const &locations_,
|
||||||
|
Vector2D const &start_,
|
||||||
|
Vector2D const &end_,
|
||||||
|
CGRect bounds_
|
||||||
|
) :
|
||||||
|
path(path_),
|
||||||
|
pathFillRule(pathFillRule_),
|
||||||
|
gradientType(gradientType_),
|
||||||
|
colors(colors_),
|
||||||
|
locations(locations_),
|
||||||
|
start(start_),
|
||||||
|
end(end_),
|
||||||
|
bounds(bounds_) {
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual Type type() const override {
|
||||||
|
return Type::GradientFill;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual CGRect boundingRect() const override {
|
||||||
|
return bounds;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual bool isEqual(std::shared_ptr<RenderableItem> rhs) const override {
|
||||||
|
if (rhs->type() != type()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
GradientFillRenderableItem *other = (GradientFillRenderableItem *)rhs.get();
|
||||||
|
|
||||||
|
if (gradientType != other->gradientType) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (colors != other->colors) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (locations != other->locations) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (start != other->start) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (end != other->end) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (bounds != other->bounds) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
std::shared_ptr<CGPath> path;
|
||||||
|
FillRule pathFillRule;
|
||||||
|
GradientType gradientType;
|
||||||
|
std::vector<Color> colors;
|
||||||
|
std::vector<double> locations;
|
||||||
|
Vector2D start;
|
||||||
|
Vector2D end;
|
||||||
|
CGRect bounds;
|
||||||
|
};
|
||||||
|
|
||||||
|
class RenderTreeNodeContent {
|
||||||
|
public:
|
||||||
|
enum class ShadingType {
|
||||||
|
Solid,
|
||||||
|
Gradient
|
||||||
|
};
|
||||||
|
|
||||||
|
class Shading {
|
||||||
|
public:
|
||||||
|
Shading() {
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual ~Shading() = default;
|
||||||
|
|
||||||
|
virtual ShadingType type() const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class SolidShading: public Shading {
|
||||||
|
public:
|
||||||
|
SolidShading(Color const &color_, double opacity_) :
|
||||||
|
color(color_),
|
||||||
|
opacity(opacity_) {
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual ShadingType type() const override {
|
||||||
|
return ShadingType::Solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
Color color;
|
||||||
|
double opacity = 0.0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class GradientShading: public Shading {
|
||||||
|
public:
|
||||||
|
GradientShading(
|
||||||
|
double opacity_,
|
||||||
|
GradientType gradientType_,
|
||||||
|
std::vector<Color> const &colors_,
|
||||||
|
std::vector<double> const &locations_,
|
||||||
|
Vector2D const &start_,
|
||||||
|
Vector2D const &end_
|
||||||
|
) :
|
||||||
|
opacity(opacity_),
|
||||||
|
gradientType(gradientType_),
|
||||||
|
colors(colors_),
|
||||||
|
locations(locations_),
|
||||||
|
start(start_),
|
||||||
|
end(end_) {
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual ShadingType type() const override {
|
||||||
|
return ShadingType::Gradient;
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
double opacity = 0.0;
|
||||||
|
GradientType gradientType;
|
||||||
|
std::vector<Color> colors;
|
||||||
|
std::vector<double> locations;
|
||||||
|
Vector2D start;
|
||||||
|
Vector2D end;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Stroke {
|
||||||
|
std::shared_ptr<Shading> shading;
|
||||||
|
double lineWidth = 0.0;
|
||||||
|
LineJoin lineJoin = LineJoin::Round;
|
||||||
|
LineCap lineCap = LineCap::Square;
|
||||||
|
double miterLimit = 4.0;
|
||||||
|
double dashPhase = 0.0;
|
||||||
|
std::vector<double> dashPattern;
|
||||||
|
|
||||||
|
Stroke(
|
||||||
|
std::shared_ptr<Shading> shading_,
|
||||||
|
double lineWidth_,
|
||||||
|
LineJoin lineJoin_,
|
||||||
|
LineCap lineCap_,
|
||||||
|
double miterLimit_,
|
||||||
|
double dashPhase_,
|
||||||
|
std::vector<double> dashPattern_
|
||||||
|
) :
|
||||||
|
shading(shading_),
|
||||||
|
lineWidth(lineWidth_),
|
||||||
|
lineJoin(lineJoin_),
|
||||||
|
lineCap(lineCap_),
|
||||||
|
miterLimit(miterLimit_),
|
||||||
|
dashPhase(dashPhase_),
|
||||||
|
dashPattern(dashPattern_) {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Fill {
|
||||||
|
std::shared_ptr<Shading> shading;
|
||||||
|
FillRule rule;
|
||||||
|
|
||||||
|
Fill(
|
||||||
|
std::shared_ptr<Shading> shading_,
|
||||||
|
FillRule rule_
|
||||||
|
) :
|
||||||
|
shading(shading_),
|
||||||
|
rule(rule_) {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
RenderTreeNodeContent(
|
||||||
|
std::vector<BezierPath> paths_,
|
||||||
|
std::shared_ptr<Stroke> stroke_,
|
||||||
|
std::shared_ptr<Fill> fill_
|
||||||
|
) :
|
||||||
|
paths(paths_),
|
||||||
|
stroke(stroke_),
|
||||||
|
fill(fill_) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
std::vector<BezierPath> paths;
|
||||||
|
std::shared_ptr<Stroke> stroke;
|
||||||
|
std::shared_ptr<Fill> fill;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ProcessedRenderTreeNodeData {
|
||||||
|
public:
|
||||||
|
struct LayerParams {
|
||||||
|
CGRect _bounds;
|
||||||
|
Vector2D _position;
|
||||||
|
CATransform3D _transform;
|
||||||
|
double _opacity;
|
||||||
|
bool _masksToBounds;
|
||||||
|
bool _isHidden;
|
||||||
|
|
||||||
|
LayerParams(
|
||||||
|
CGRect bounds_,
|
||||||
|
Vector2D position_,
|
||||||
|
CATransform3D transform_,
|
||||||
|
double opacity_,
|
||||||
|
bool masksToBounds_,
|
||||||
|
bool isHidden_
|
||||||
|
) :
|
||||||
|
_bounds(bounds_),
|
||||||
|
_position(position_),
|
||||||
|
_transform(transform_),
|
||||||
|
_opacity(opacity_),
|
||||||
|
_masksToBounds(masksToBounds_),
|
||||||
|
_isHidden(isHidden_) {
|
||||||
|
}
|
||||||
|
|
||||||
|
CGRect bounds() const {
|
||||||
|
return _bounds;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector2D position() const {
|
||||||
|
return _position;
|
||||||
|
}
|
||||||
|
|
||||||
|
CATransform3D transform() const {
|
||||||
|
return _transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
double opacity() const {
|
||||||
|
return _opacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool masksToBounds() const {
|
||||||
|
return _masksToBounds;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isHidden() const {
|
||||||
|
return _isHidden;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ProcessedRenderTreeNodeData() :
|
||||||
|
isValid(false),
|
||||||
|
layer(
|
||||||
|
CGRect(0.0, 0.0, 0.0, 0.0),
|
||||||
|
Vector2D(0.0, 0.0),
|
||||||
|
CATransform3D::identity(),
|
||||||
|
1.0,
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
),
|
||||||
|
globalRect(CGRect(0.0, 0.0, 0.0, 0.0)),
|
||||||
|
localRect(CGRect(0.0, 0.0, 0.0, 0.0)),
|
||||||
|
globalTransform(CATransform3D::identity()),
|
||||||
|
drawsContent(false),
|
||||||
|
drawContentDescendants(false),
|
||||||
|
isInvertedMatte(false) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isValid = false;
|
||||||
|
LayerParams layer;
|
||||||
|
CGRect globalRect;
|
||||||
|
CGRect localRect;
|
||||||
|
CATransform3D globalTransform;
|
||||||
|
bool drawsContent;
|
||||||
|
int drawContentDescendants;
|
||||||
|
bool isInvertedMatte;
|
||||||
|
};
|
||||||
|
|
||||||
|
class RenderTreeNode {
|
||||||
|
public:
|
||||||
|
RenderTreeNode(
|
||||||
|
CGRect bounds_,
|
||||||
|
Vector2D position_,
|
||||||
|
CATransform3D transform_,
|
||||||
|
double alpha_,
|
||||||
|
bool masksToBounds_,
|
||||||
|
bool isHidden_,
|
||||||
|
std::shared_ptr<RenderTreeNodeContent> content_,
|
||||||
|
std::vector<std::shared_ptr<RenderTreeNode>> subnodes_,
|
||||||
|
std::shared_ptr<RenderTreeNode> mask_,
|
||||||
|
bool invertMask_
|
||||||
|
) :
|
||||||
|
_bounds(bounds_),
|
||||||
|
_position(position_),
|
||||||
|
_transform(transform_),
|
||||||
|
_alpha(alpha_),
|
||||||
|
_masksToBounds(masksToBounds_),
|
||||||
|
_isHidden(isHidden_),
|
||||||
|
_content(content_),
|
||||||
|
_subnodes(subnodes_),
|
||||||
|
_mask(mask_),
|
||||||
|
_invertMask(invertMask_) {
|
||||||
|
}
|
||||||
|
|
||||||
|
~RenderTreeNode() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
CGRect const &bounds() const {
|
||||||
|
return _bounds;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector2D const &position() const {
|
||||||
|
return _position;
|
||||||
|
}
|
||||||
|
|
||||||
|
CATransform3D const &transform() const {
|
||||||
|
return _transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
double alpha() const {
|
||||||
|
return _alpha;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool masksToBounds() const {
|
||||||
|
return _masksToBounds;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isHidden() const {
|
||||||
|
return _isHidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<RenderTreeNodeContent> const &content() const {
|
||||||
|
return _content;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::shared_ptr<RenderTreeNode>> const &subnodes() const {
|
||||||
|
return _subnodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<RenderTreeNode> const &mask() const {
|
||||||
|
return _mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool invertMask() const {
|
||||||
|
return _invertMask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
CGRect _bounds;
|
||||||
|
Vector2D _position;
|
||||||
|
CATransform3D _transform = CATransform3D::identity();
|
||||||
|
double _alpha = 1.0;
|
||||||
|
bool _masksToBounds = false;
|
||||||
|
bool _isHidden = false;
|
||||||
|
std::shared_ptr<RenderTreeNodeContent> _content;
|
||||||
|
std::vector<std::shared_ptr<RenderTreeNode>> _subnodes;
|
||||||
|
std::shared_ptr<RenderTreeNode> _mask;
|
||||||
|
bool _invertMask = false;
|
||||||
|
|
||||||
|
ProcessedRenderTreeNodeData renderData;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* RenderTreeNode_h */
|
||||||
@ -0,0 +1,38 @@
|
|||||||
|
#ifndef ShapeAttributes_h
|
||||||
|
#define ShapeAttributes_h
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
|
||||||
|
namespace lottie {
|
||||||
|
|
||||||
|
enum class FillRule: int {
|
||||||
|
None = 0,
|
||||||
|
NonZeroWinding = 1,
|
||||||
|
EvenOdd = 2
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class LineCap: int {
|
||||||
|
None = 0,
|
||||||
|
Butt = 1,
|
||||||
|
Round = 2,
|
||||||
|
Square = 3
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class LineJoin: int {
|
||||||
|
None = 0,
|
||||||
|
Miter = 1,
|
||||||
|
Round = 2,
|
||||||
|
Bevel = 3
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class GradientType: int {
|
||||||
|
None = 0,
|
||||||
|
Linear = 1,
|
||||||
|
Radial = 2
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* LottieColor_h */
|
||||||
@ -50,6 +50,8 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <map>
|
#include <map>
|
||||||
@ -230,3 +232,5 @@ protected:
|
|||||||
};
|
};
|
||||||
|
|
||||||
} // namespace lottiejson11
|
} // namespace lottiejson11
|
||||||
|
|
||||||
|
#endif
|
||||||
@ -2,7 +2,7 @@
|
|||||||
#define BezierPaths_h
|
#define BezierPaths_h
|
||||||
|
|
||||||
#include "Lottie/Private/Model/ShapeItems/Ellipse.hpp"
|
#include "Lottie/Private/Model/ShapeItems/Ellipse.hpp"
|
||||||
#include "Lottie/Private/Utility/Primitives/BezierPath.hpp"
|
#include <LottieCpp/BezierPath.h>
|
||||||
#include "Lottie/Private/Utility/Primitives/CompoundBezierPath.hpp"
|
#include "Lottie/Private/Utility/Primitives/CompoundBezierPath.hpp"
|
||||||
#include "Lottie/Private/Model/ShapeItems/Trim.hpp"
|
#include "Lottie/Private/Model/ShapeItems/Trim.hpp"
|
||||||
|
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
#define Mask_hpp
|
#define Mask_hpp
|
||||||
|
|
||||||
#include "Lottie/Private/Model/Keyframes/KeyframeGroup.hpp"
|
#include "Lottie/Private/Model/Keyframes/KeyframeGroup.hpp"
|
||||||
#include "Lottie/Private/Utility/Primitives/BezierPath.hpp"
|
#include <LottieCpp/BezierPath.h>
|
||||||
#include "Lottie/Private/Parsing/JsonParsing.hpp"
|
#include "Lottie/Private/Parsing/JsonParsing.hpp"
|
||||||
|
|
||||||
namespace lottie {
|
namespace lottie {
|
||||||
|
|||||||
@ -3,18 +3,13 @@
|
|||||||
|
|
||||||
#include "Lottie/Private/Model/Keyframes/KeyframeGroup.hpp"
|
#include "Lottie/Private/Model/Keyframes/KeyframeGroup.hpp"
|
||||||
#include "Lottie/Private/Model/ShapeItems/ShapeItem.hpp"
|
#include "Lottie/Private/Model/ShapeItems/ShapeItem.hpp"
|
||||||
#include "Lottie/Public/Primitives/Color.hpp"
|
#import <LottieCpp/Color.h>
|
||||||
#include <LottieCpp/Vectors.h>
|
#include <LottieCpp/Vectors.h>
|
||||||
#include "Lottie/Private/Parsing/JsonParsing.hpp"
|
#include "Lottie/Private/Parsing/JsonParsing.hpp"
|
||||||
|
#include <LottieCpp/ShapeAttributes.h>
|
||||||
|
|
||||||
namespace lottie {
|
namespace lottie {
|
||||||
|
|
||||||
enum class FillRule: int {
|
|
||||||
None = 0,
|
|
||||||
NonZeroWinding = 1,
|
|
||||||
EvenOdd = 2
|
|
||||||
};
|
|
||||||
|
|
||||||
class Fill: public ShapeItem {
|
class Fill: public ShapeItem {
|
||||||
public:
|
public:
|
||||||
explicit Fill(lottiejson11::Json::object const &json) noexcept(false) :
|
explicit Fill(lottiejson11::Json::object const &json) noexcept(false) :
|
||||||
|
|||||||
@ -5,15 +5,10 @@
|
|||||||
#include "Lottie/Private/Model/Keyframes/KeyframeGroup.hpp"
|
#include "Lottie/Private/Model/Keyframes/KeyframeGroup.hpp"
|
||||||
#include "Lottie/Private/Parsing/JsonParsing.hpp"
|
#include "Lottie/Private/Parsing/JsonParsing.hpp"
|
||||||
#include "Lottie/Public/Primitives/GradientColorSet.hpp"
|
#include "Lottie/Public/Primitives/GradientColorSet.hpp"
|
||||||
|
#include <LottieCpp/ShapeAttributes.h>
|
||||||
|
|
||||||
namespace lottie {
|
namespace lottie {
|
||||||
|
|
||||||
enum class GradientType: int {
|
|
||||||
None = 0,
|
|
||||||
Linear = 1,
|
|
||||||
Radial = 2
|
|
||||||
};
|
|
||||||
|
|
||||||
/// An item that define a gradient fill
|
/// An item that define a gradient fill
|
||||||
class GradientFill: public ShapeItem {
|
class GradientFill: public ShapeItem {
|
||||||
public:
|
public:
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
#include "Lottie/Private/Model/Keyframes/KeyframeGroup.hpp"
|
#include "Lottie/Private/Model/Keyframes/KeyframeGroup.hpp"
|
||||||
#include "Lottie/Private/Model/Objects/DashElement.hpp"
|
#include "Lottie/Private/Model/Objects/DashElement.hpp"
|
||||||
#include "Lottie/Private/Parsing/JsonParsing.hpp"
|
#include "Lottie/Private/Parsing/JsonParsing.hpp"
|
||||||
#include "Lottie/Public/Primitives/DrawingAttributes.hpp"
|
#include <LottieCpp/ShapeAttributes.h>
|
||||||
|
|
||||||
namespace lottie {
|
namespace lottie {
|
||||||
|
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
#include "Lottie/Private/Model/ShapeItems/ShapeItem.hpp"
|
#include "Lottie/Private/Model/ShapeItems/ShapeItem.hpp"
|
||||||
#include "Lottie/Private/Model/ShapeItems/Ellipse.hpp"
|
#include "Lottie/Private/Model/ShapeItems/Ellipse.hpp"
|
||||||
#include "Lottie/Private/Utility/Primitives/BezierPath.hpp"
|
#include <LottieCpp/BezierPath.h>
|
||||||
#include "Lottie/Private/Parsing/JsonParsing.hpp"
|
#include "Lottie/Private/Parsing/JsonParsing.hpp"
|
||||||
|
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
#include "Lottie/Private/Model/ShapeItems/ShapeItem.hpp"
|
#include "Lottie/Private/Model/ShapeItems/ShapeItem.hpp"
|
||||||
#include "Lottie/Private/Model/ShapeItems/GradientStroke.hpp"
|
#include "Lottie/Private/Model/ShapeItems/GradientStroke.hpp"
|
||||||
#include "Lottie/Public/Primitives/Color.hpp"
|
#import <LottieCpp/Color.h>
|
||||||
#include "Lottie/Private/Model/Keyframes/KeyframeGroup.hpp"
|
#include "Lottie/Private/Model/Keyframes/KeyframeGroup.hpp"
|
||||||
#include "Lottie/Private/Model/Objects/DashElement.hpp"
|
#include "Lottie/Private/Model/Objects/DashElement.hpp"
|
||||||
#include "Lottie/Private/Parsing/JsonParsing.hpp"
|
#include "Lottie/Private/Parsing/JsonParsing.hpp"
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
#define TextAnimator_hpp
|
#define TextAnimator_hpp
|
||||||
|
|
||||||
#include <LottieCpp/Vectors.h>
|
#include <LottieCpp/Vectors.h>
|
||||||
#include "Lottie/Public/Primitives/Color.hpp"
|
#import <LottieCpp/Color.h>
|
||||||
#include "Lottie/Private/Model/Keyframes/KeyframeGroup.hpp"
|
#include "Lottie/Private/Model/Keyframes/KeyframeGroup.hpp"
|
||||||
#include "Lottie/Private/Parsing/JsonParsing.hpp"
|
#include "Lottie/Private/Parsing/JsonParsing.hpp"
|
||||||
|
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
#define TextDocument_hpp
|
#define TextDocument_hpp
|
||||||
|
|
||||||
#include <LottieCpp/Vectors.h>
|
#include <LottieCpp/Vectors.h>
|
||||||
#include "Lottie/Public/Primitives/Color.hpp"
|
#import <LottieCpp/Color.h>
|
||||||
#include "Lottie/Private/Parsing/JsonParsing.hpp"
|
#include "Lottie/Private/Parsing/JsonParsing.hpp"
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|||||||
@ -1,10 +1,519 @@
|
|||||||
#include "BezierPath.hpp"
|
#include <LottieCpp/BezierPath.h>
|
||||||
|
|
||||||
#include <simd/simd.h>
|
#include <simd/simd.h>
|
||||||
#include <Accelerate/Accelerate.h>
|
#include <Accelerate/Accelerate.h>
|
||||||
|
|
||||||
|
#include "Lottie/Private/Parsing/JsonParsing.hpp"
|
||||||
|
|
||||||
namespace lottie {
|
namespace lottie {
|
||||||
|
|
||||||
|
BezierTrimPathPosition::BezierTrimPathPosition(double start_, double end_) :
|
||||||
|
start(start_),
|
||||||
|
end(end_) {
|
||||||
|
}
|
||||||
|
|
||||||
|
BezierPathContents::BezierPathContents(CurveVertex const &startPoint) :
|
||||||
|
elements({ PathElement(startPoint) }) {
|
||||||
|
}
|
||||||
|
|
||||||
|
BezierPathContents::BezierPathContents() :
|
||||||
|
elements({}),
|
||||||
|
closed(false) {
|
||||||
|
}
|
||||||
|
|
||||||
|
BezierPathContents::BezierPathContents(lottiejson11::Json const &jsonAny) noexcept(false) :
|
||||||
|
elements({}) {
|
||||||
|
lottiejson11::Json::object const *json = nullptr;
|
||||||
|
if (jsonAny.is_object()) {
|
||||||
|
json = &jsonAny.object_items();
|
||||||
|
} else if (jsonAny.is_array()) {
|
||||||
|
if (jsonAny.array_items().empty()) {
|
||||||
|
throw LottieParsingException();
|
||||||
|
}
|
||||||
|
if (!jsonAny.array_items()[0].is_object()) {
|
||||||
|
throw LottieParsingException();
|
||||||
|
}
|
||||||
|
json = &jsonAny.array_items()[0].object_items();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (const auto closedData = getOptionalBool(*json, "c")) {
|
||||||
|
closed = closedData.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto vertexContainer = getAnyArray(*json, "v");
|
||||||
|
auto inPointsContainer = getAnyArray(*json, "i");
|
||||||
|
auto outPointsContainer = getAnyArray(*json, "o");
|
||||||
|
|
||||||
|
if (vertexContainer.size() != inPointsContainer.size() || inPointsContainer.size() != outPointsContainer.size()) {
|
||||||
|
throw LottieParsingException();
|
||||||
|
}
|
||||||
|
if (vertexContainer.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create first point
|
||||||
|
Vector2D firstPoint = Vector2D(vertexContainer[0]);
|
||||||
|
Vector2D firstInPoint = Vector2D(inPointsContainer[0]);
|
||||||
|
Vector2D firstOutPoint = Vector2D(outPointsContainer[0]);
|
||||||
|
CurveVertex firstVertex = CurveVertex::relative(
|
||||||
|
firstPoint,
|
||||||
|
firstInPoint,
|
||||||
|
firstOutPoint
|
||||||
|
);
|
||||||
|
PathElement previousElement(firstVertex);
|
||||||
|
elements.push_back(previousElement);
|
||||||
|
|
||||||
|
for (size_t i = 1; i < vertexContainer.size(); i++) {
|
||||||
|
Vector2D point = Vector2D(vertexContainer[i]);
|
||||||
|
Vector2D inPoint = Vector2D(inPointsContainer[i]);
|
||||||
|
Vector2D outPoint = Vector2D(outPointsContainer[i]);
|
||||||
|
CurveVertex vertex = CurveVertex::relative(
|
||||||
|
point,
|
||||||
|
inPoint,
|
||||||
|
outPoint
|
||||||
|
);
|
||||||
|
auto pathElement = previousElement.pathElementTo(vertex);
|
||||||
|
elements.push_back(pathElement);
|
||||||
|
previousElement = pathElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (closed.value_or(false)) {
|
||||||
|
auto closeElement = previousElement.pathElementTo(firstVertex);
|
||||||
|
elements.push_back(closeElement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lottiejson11::Json BezierPathContents::toJson() const {
|
||||||
|
lottiejson11::Json::object result;
|
||||||
|
|
||||||
|
lottiejson11::Json::array vertices;
|
||||||
|
lottiejson11::Json::array inPoints;
|
||||||
|
lottiejson11::Json::array outPoints;
|
||||||
|
|
||||||
|
for (const auto &element : elements) {
|
||||||
|
vertices.push_back(element.vertex.point.toJson());
|
||||||
|
inPoints.push_back(element.vertex.inTangentRelative().toJson());
|
||||||
|
outPoints.push_back(element.vertex.outTangentRelative().toJson());
|
||||||
|
}
|
||||||
|
|
||||||
|
result.insert(std::make_pair("v", vertices));
|
||||||
|
result.insert(std::make_pair("i", inPoints));
|
||||||
|
result.insert(std::make_pair("o", outPoints));
|
||||||
|
|
||||||
|
if (closed.has_value()) {
|
||||||
|
result.insert(std::make_pair("c", closed.value()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return lottiejson11::Json(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<CGPath> BezierPathContents::cgPath() const {
|
||||||
|
auto cgPath = CGPath::makePath();
|
||||||
|
|
||||||
|
std::optional<PathElement> previousElement;
|
||||||
|
for (const auto &element : elements) {
|
||||||
|
if (previousElement.has_value()) {
|
||||||
|
if (previousElement->vertex.outTangentRelative().isZero() && element.vertex.inTangentRelative().isZero()) {
|
||||||
|
cgPath->addLineTo(element.vertex.point);
|
||||||
|
} else {
|
||||||
|
cgPath->addCurveTo(element.vertex.point, previousElement->vertex.outTangent, element.vertex.inTangent);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cgPath->moveTo(element.vertex.point);
|
||||||
|
}
|
||||||
|
previousElement = element;
|
||||||
|
}
|
||||||
|
if (closed.value_or(true)) {
|
||||||
|
cgPath->closeSubpath();
|
||||||
|
}
|
||||||
|
return cgPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
double BezierPathContents::length() {
|
||||||
|
if (_length.has_value()) {
|
||||||
|
return _length.value();
|
||||||
|
} else {
|
||||||
|
double result = 0.0;
|
||||||
|
for (size_t i = 1; i < elements.size(); i++) {
|
||||||
|
result += elements[i].length(elements[i - 1]);
|
||||||
|
}
|
||||||
|
_length = result;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BezierPathContents::moveToStartPoint(CurveVertex const &vertex) {
|
||||||
|
elements = { PathElement(vertex) };
|
||||||
|
_length = std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BezierPathContents::addVertex(CurveVertex const &vertex) {
|
||||||
|
addElement(PathElement(vertex));
|
||||||
|
}
|
||||||
|
|
||||||
|
void BezierPathContents::reserveCapacity(size_t capacity) {
|
||||||
|
elements.reserve(capacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BezierPathContents::setElementCount(size_t count) {
|
||||||
|
elements.resize(count, PathElement(CurveVertex::absolute(Vector2D(0.0, 0.0), Vector2D(0.0, 0.0), Vector2D(0.0, 0.0))));
|
||||||
|
}
|
||||||
|
|
||||||
|
void BezierPathContents::invalidateLength() {
|
||||||
|
_length.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BezierPathContents::addCurve(Vector2D const &toPoint, Vector2D const &outTangent, Vector2D const &inTangent) {
|
||||||
|
if (elements.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto previous = elements[elements.size() - 1];
|
||||||
|
auto newVertex = CurveVertex::absolute(toPoint, inTangent, toPoint);
|
||||||
|
updateVertex(
|
||||||
|
CurveVertex::absolute(previous.vertex.point, previous.vertex.inTangent, outTangent),
|
||||||
|
(int)elements.size() - 1,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
addVertex(newVertex);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BezierPathContents::addLine(Vector2D const &toPoint) {
|
||||||
|
if (elements.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto previous = elements[elements.size() - 1];
|
||||||
|
auto newVertex = CurveVertex::relative(toPoint, Vector2D::Zero(), Vector2D::Zero());
|
||||||
|
updateVertex(
|
||||||
|
CurveVertex::absolute(previous.vertex.point, previous.vertex.inTangent, previous.vertex.point),
|
||||||
|
(int)elements.size() - 1,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
addVertex(newVertex);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BezierPathContents::close() {
|
||||||
|
closed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BezierPathContents::addElement(PathElement const &pathElement) {
|
||||||
|
elements.push_back(pathElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BezierPathContents::updateVertex(CurveVertex const &vertex, int atIndex, bool remeasure) {
|
||||||
|
if (remeasure) {
|
||||||
|
PathElement newElement(CurveVertex::absolute(Vector2D::Zero(), Vector2D::Zero(), Vector2D::Zero()));
|
||||||
|
if (atIndex > 0) {
|
||||||
|
auto previousElement = elements[atIndex - 1];
|
||||||
|
newElement = previousElement.pathElementTo(vertex);
|
||||||
|
} else {
|
||||||
|
newElement = PathElement(vertex);
|
||||||
|
}
|
||||||
|
elements[atIndex] = newElement;
|
||||||
|
|
||||||
|
if (atIndex + 1 < elements.size()) {
|
||||||
|
auto nextElement = elements[atIndex + 1];
|
||||||
|
elements[atIndex + 1] = newElement.pathElementTo(nextElement.vertex);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
auto oldElement = elements[atIndex];
|
||||||
|
elements[atIndex] = oldElement.updateVertex(vertex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::shared_ptr<BezierPathContents>> BezierPathContents::trim(double fromLength, double toLength, double offsetLength) {
|
||||||
|
if (elements.size() <= 1) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fromLength == toLength) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
double lengthValue = length();
|
||||||
|
|
||||||
|
/// Normalize lengths to the curve length.
|
||||||
|
auto start = fmod(fromLength + offsetLength, lengthValue);
|
||||||
|
auto end = fmod(toLength + offsetLength, lengthValue);
|
||||||
|
|
||||||
|
if (start < 0.0) {
|
||||||
|
start = lengthValue + start;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (end < 0.0) {
|
||||||
|
end = lengthValue + end;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (start == lengthValue) {
|
||||||
|
start = 0.0;
|
||||||
|
}
|
||||||
|
if (end == 0.0) {
|
||||||
|
end = lengthValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
(start == 0.0 && end == lengthValue) ||
|
||||||
|
start == end ||
|
||||||
|
(start == lengthValue && end == 0.0)
|
||||||
|
) {
|
||||||
|
/// The trim encompasses the entire path. Return.
|
||||||
|
return { shared_from_this() };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (start > end) {
|
||||||
|
// Start is greater than end. Two paths are returned.
|
||||||
|
return trimPathAtLengths({
|
||||||
|
BezierTrimPathPosition(0.0, end),
|
||||||
|
BezierTrimPathPosition(start, lengthValue)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return trimPathAtLengths({ BezierTrimPathPosition(start, end) });
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Private
|
||||||
|
|
||||||
|
std::vector<std::shared_ptr<BezierPathContents>> BezierPathContents::trimPathAtLengths(std::vector<BezierTrimPathPosition> const &positions) {
|
||||||
|
if (positions.empty()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
auto remainingPositions = positions;
|
||||||
|
|
||||||
|
auto trim = remainingPositions[0];
|
||||||
|
remainingPositions.erase(remainingPositions.begin());
|
||||||
|
|
||||||
|
std::vector<std::shared_ptr<BezierPathContents>> paths;
|
||||||
|
|
||||||
|
double runningLength = 0.0;
|
||||||
|
bool finishedTrimming = false;
|
||||||
|
auto pathElements = elements;
|
||||||
|
|
||||||
|
auto currentPath = std::make_shared<BezierPathContents>();
|
||||||
|
int i = 0;
|
||||||
|
|
||||||
|
while (!finishedTrimming) {
|
||||||
|
if (pathElements.size() <= i) {
|
||||||
|
/// Do this for rounding errors
|
||||||
|
paths.push_back(currentPath);
|
||||||
|
finishedTrimming = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
/// Loop through and add elements within start->end range.
|
||||||
|
/// Get current element
|
||||||
|
auto element = pathElements[i];
|
||||||
|
double elementLength = 0.0;
|
||||||
|
if (i != 0) {
|
||||||
|
elementLength = element.length(pathElements[i - 1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calculate new running length.
|
||||||
|
auto newLength = runningLength + elementLength;
|
||||||
|
|
||||||
|
if (newLength < trim.start) {
|
||||||
|
/// Element is not included in the trim, continue.
|
||||||
|
runningLength = newLength;
|
||||||
|
i = i + 1;
|
||||||
|
/// Increment index, we are done with this element.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newLength == trim.start) {
|
||||||
|
/// Current element IS the start element.
|
||||||
|
/// For start we want to add a zero length element.
|
||||||
|
currentPath->moveToStartPoint(element.vertex);
|
||||||
|
runningLength = newLength;
|
||||||
|
i = i + 1;
|
||||||
|
/// Increment index, we are done with this element.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (runningLength < trim.start && trim.start < newLength && currentPath->elements.size() == 0) {
|
||||||
|
/// The start of the trim is between this element and the previous, trim.
|
||||||
|
/// Get previous element.
|
||||||
|
auto previousElement = pathElements[i - 1];
|
||||||
|
/// Trim it
|
||||||
|
auto trimLength = trim.start - runningLength;
|
||||||
|
auto trimResults = element.splitElementAtPosition(previousElement, trimLength);
|
||||||
|
/// Add the right span start.
|
||||||
|
currentPath->moveToStartPoint(trimResults.rightSpan.start.vertex);
|
||||||
|
|
||||||
|
pathElements[i] = trimResults.rightSpan.end;
|
||||||
|
pathElements[i - 1] = trimResults.rightSpan.start;
|
||||||
|
runningLength = runningLength + trimResults.leftSpan.end.length(trimResults.leftSpan.start);
|
||||||
|
/// Dont increment index or the current length, the end of this path can be within this span.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (trim.start < newLength && newLength < trim.end) {
|
||||||
|
/// Element lies within the trim span.
|
||||||
|
currentPath->addElement(element);
|
||||||
|
runningLength = newLength;
|
||||||
|
i = i + 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newLength == trim.end) {
|
||||||
|
/// Element is the end element.
|
||||||
|
/// The element could have a new length if it's added right after the start node.
|
||||||
|
currentPath->addElement(element);
|
||||||
|
/// We are done with this span.
|
||||||
|
runningLength = newLength;
|
||||||
|
i = i + 1;
|
||||||
|
/// Allow the path to be finalized.
|
||||||
|
/// Fall through to finalize path and move to next position
|
||||||
|
}
|
||||||
|
|
||||||
|
if (runningLength < trim.end && trim.end < newLength) {
|
||||||
|
/// New element must be cut for end.
|
||||||
|
/// Get previous element.
|
||||||
|
auto previousElement = pathElements[i - 1];
|
||||||
|
/// Trim it
|
||||||
|
auto trimLength = trim.end - runningLength;
|
||||||
|
auto trimResults = element.splitElementAtPosition(previousElement, trimLength);
|
||||||
|
/// Add the left span end.
|
||||||
|
|
||||||
|
currentPath->updateVertex(trimResults.leftSpan.start.vertex, (int)currentPath->elements.size() - 1, false);
|
||||||
|
currentPath->addElement(trimResults.leftSpan.end);
|
||||||
|
|
||||||
|
pathElements[i] = trimResults.rightSpan.end;
|
||||||
|
pathElements[i - 1] = trimResults.rightSpan.start;
|
||||||
|
runningLength = runningLength + trimResults.leftSpan.end.length(trimResults.leftSpan.start);
|
||||||
|
/// Dont increment index or the current length, the start of the next path can be within this span.
|
||||||
|
/// We are done with this span.
|
||||||
|
/// Allow the path to be finalized.
|
||||||
|
/// Fall through to finalize path and move to next position
|
||||||
|
}
|
||||||
|
|
||||||
|
paths.push_back(currentPath);
|
||||||
|
currentPath = std::make_shared<BezierPathContents>();
|
||||||
|
if (remainingPositions.size() > 0) {
|
||||||
|
trim = remainingPositions[0];
|
||||||
|
remainingPositions.erase(remainingPositions.begin());
|
||||||
|
} else {
|
||||||
|
finishedTrimming = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return paths;
|
||||||
|
}
|
||||||
|
|
||||||
|
BezierPath::BezierPath(CurveVertex const &startPoint) :
|
||||||
|
_contents(std::make_shared<BezierPathContents>(startPoint)) {
|
||||||
|
}
|
||||||
|
|
||||||
|
BezierPath::BezierPath() :
|
||||||
|
_contents(std::make_shared<BezierPathContents>()) {
|
||||||
|
}
|
||||||
|
|
||||||
|
BezierPath::BezierPath(lottiejson11::Json const &jsonAny) noexcept(false) :
|
||||||
|
_contents(std::make_shared<BezierPathContents>(jsonAny)) {
|
||||||
|
}
|
||||||
|
|
||||||
|
lottiejson11::Json BezierPath::toJson() const {
|
||||||
|
return _contents->toJson();
|
||||||
|
}
|
||||||
|
|
||||||
|
double BezierPath::length() {
|
||||||
|
return _contents->length();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BezierPath::moveToStartPoint(CurveVertex const &vertex) {
|
||||||
|
_contents->moveToStartPoint(vertex);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BezierPath::addVertex(CurveVertex const &vertex) {
|
||||||
|
_contents->addVertex(vertex);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BezierPath::reserveCapacity(size_t capacity) {
|
||||||
|
_contents->reserveCapacity(capacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BezierPath::setElementCount(size_t count) {
|
||||||
|
_contents->setElementCount(count);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BezierPath::invalidateLength() {
|
||||||
|
_contents->invalidateLength();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BezierPath::addCurve(Vector2D const &toPoint, Vector2D const &outTangent, Vector2D const &inTangent) {
|
||||||
|
_contents->addCurve(toPoint, outTangent, inTangent);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BezierPath::addLine(Vector2D const &toPoint) {
|
||||||
|
_contents->addLine(toPoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BezierPath::close() {
|
||||||
|
_contents->close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BezierPath::addElement(PathElement const &pathElement) {
|
||||||
|
_contents->addElement(pathElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BezierPath::updateVertex(CurveVertex const &vertex, int atIndex, bool remeasure) {
|
||||||
|
_contents->updateVertex(vertex, atIndex, remeasure);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<BezierPath> BezierPath::trim(double fromLength, double toLength, double offsetLength) {
|
||||||
|
std::vector<BezierPath> result;
|
||||||
|
|
||||||
|
auto resultContents = _contents->trim(fromLength, toLength, offsetLength);
|
||||||
|
for (const auto &resultContent : resultContents) {
|
||||||
|
result.emplace_back(resultContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<PathElement> const &BezierPath::elements() const {
|
||||||
|
return _contents->elements;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<PathElement> &BezierPath::mutableElements() {
|
||||||
|
return _contents->elements;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<bool> const &BezierPath::closed() const {
|
||||||
|
return _contents->closed;
|
||||||
|
}
|
||||||
|
void BezierPath::setClosed(std::optional<bool> const &closed) {
|
||||||
|
_contents->closed = closed;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<CGPath> BezierPath::cgPath() const {
|
||||||
|
return _contents->cgPath();
|
||||||
|
}
|
||||||
|
|
||||||
|
BezierPath BezierPath::copyUsingTransform(CATransform3D const &transform) const {
|
||||||
|
if (transform == CATransform3D::identity()) {
|
||||||
|
return (*this);
|
||||||
|
}
|
||||||
|
BezierPath result;
|
||||||
|
result._contents->closed = _contents->closed;
|
||||||
|
result.reserveCapacity(_contents->elements.size());
|
||||||
|
for (const auto &element : _contents->elements) {
|
||||||
|
result._contents->elements.emplace_back(element.vertex.transformed(transform));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
BezierPath::BezierPath(std::shared_ptr<BezierPathContents> contents) :
|
||||||
|
_contents(contents) {
|
||||||
|
}
|
||||||
|
|
||||||
|
BezierPathsBoundingBoxContext::BezierPathsBoundingBoxContext() :
|
||||||
|
pointsX((float *)malloc(1024 * 4)),
|
||||||
|
pointsY((float *)malloc(1024 * 4)),
|
||||||
|
pointsSize(1024) {
|
||||||
|
}
|
||||||
|
|
||||||
|
BezierPathsBoundingBoxContext::~BezierPathsBoundingBoxContext() {
|
||||||
|
free(pointsX);
|
||||||
|
free(pointsY);
|
||||||
|
}
|
||||||
|
|
||||||
static CGRect calculateBoundingRectOpt(float const *pointsX, float const *pointsY, int count) {
|
static CGRect calculateBoundingRectOpt(float const *pointsX, float const *pointsY, int count) {
|
||||||
float minX = 0.0;
|
float minX = 0.0;
|
||||||
float maxX = 0.0;
|
float maxX = 0.0;
|
||||||
|
|||||||
@ -1,593 +0,0 @@
|
|||||||
#ifndef BezierPath_hpp
|
|
||||||
#define BezierPath_hpp
|
|
||||||
|
|
||||||
#include "Lottie/Private/Utility/Primitives/CurveVertex.hpp"
|
|
||||||
#include "Lottie/Private/Utility/Primitives/PathElement.hpp"
|
|
||||||
#include "Lottie/Private/Parsing/JsonParsing.hpp"
|
|
||||||
#include <LottieCpp/CGPath.h>
|
|
||||||
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace lottie {
|
|
||||||
|
|
||||||
struct BezierTrimPathPosition {
|
|
||||||
double start;
|
|
||||||
double end;
|
|
||||||
|
|
||||||
explicit BezierTrimPathPosition(double start_, double end_) :
|
|
||||||
start(start_),
|
|
||||||
end(end_) {
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
class BezierPathContents: public std::enable_shared_from_this<BezierPathContents> {
|
|
||||||
public:
|
|
||||||
/// Initializes a new Bezier Path.
|
|
||||||
explicit BezierPathContents(CurveVertex const &startPoint) :
|
|
||||||
elements({ PathElement(startPoint) }) {
|
|
||||||
}
|
|
||||||
|
|
||||||
BezierPathContents() :
|
|
||||||
elements({}),
|
|
||||||
closed(false) {
|
|
||||||
}
|
|
||||||
|
|
||||||
explicit BezierPathContents(lottiejson11::Json const &jsonAny) noexcept(false) :
|
|
||||||
elements({}) {
|
|
||||||
lottiejson11::Json::object const *json = nullptr;
|
|
||||||
if (jsonAny.is_object()) {
|
|
||||||
json = &jsonAny.object_items();
|
|
||||||
} else if (jsonAny.is_array()) {
|
|
||||||
if (jsonAny.array_items().empty()) {
|
|
||||||
throw LottieParsingException();
|
|
||||||
}
|
|
||||||
if (!jsonAny.array_items()[0].is_object()) {
|
|
||||||
throw LottieParsingException();
|
|
||||||
}
|
|
||||||
json = &jsonAny.array_items()[0].object_items();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (const auto closedData = getOptionalBool(*json, "c")) {
|
|
||||||
closed = closedData.value();
|
|
||||||
}
|
|
||||||
|
|
||||||
auto vertexContainer = getAnyArray(*json, "v");
|
|
||||||
auto inPointsContainer = getAnyArray(*json, "i");
|
|
||||||
auto outPointsContainer = getAnyArray(*json, "o");
|
|
||||||
|
|
||||||
if (vertexContainer.size() != inPointsContainer.size() || inPointsContainer.size() != outPointsContainer.size()) {
|
|
||||||
throw LottieParsingException();
|
|
||||||
}
|
|
||||||
if (vertexContainer.empty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create first point
|
|
||||||
Vector2D firstPoint = Vector2D(vertexContainer[0]);
|
|
||||||
Vector2D firstInPoint = Vector2D(inPointsContainer[0]);
|
|
||||||
Vector2D firstOutPoint = Vector2D(outPointsContainer[0]);
|
|
||||||
CurveVertex firstVertex = CurveVertex::relative(
|
|
||||||
firstPoint,
|
|
||||||
firstInPoint,
|
|
||||||
firstOutPoint
|
|
||||||
);
|
|
||||||
PathElement previousElement(firstVertex);
|
|
||||||
elements.push_back(previousElement);
|
|
||||||
|
|
||||||
for (size_t i = 1; i < vertexContainer.size(); i++) {
|
|
||||||
Vector2D point = Vector2D(vertexContainer[i]);
|
|
||||||
Vector2D inPoint = Vector2D(inPointsContainer[i]);
|
|
||||||
Vector2D outPoint = Vector2D(outPointsContainer[i]);
|
|
||||||
CurveVertex vertex = CurveVertex::relative(
|
|
||||||
point,
|
|
||||||
inPoint,
|
|
||||||
outPoint
|
|
||||||
);
|
|
||||||
auto pathElement = previousElement.pathElementTo(vertex);
|
|
||||||
elements.push_back(pathElement);
|
|
||||||
previousElement = pathElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (closed.value_or(false)) {
|
|
||||||
auto closeElement = previousElement.pathElementTo(firstVertex);
|
|
||||||
elements.push_back(closeElement);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
BezierPathContents(const BezierPathContents&) = delete;
|
|
||||||
BezierPathContents& operator=(BezierPathContents&) = delete;
|
|
||||||
|
|
||||||
lottiejson11::Json toJson() const {
|
|
||||||
lottiejson11::Json::object result;
|
|
||||||
|
|
||||||
lottiejson11::Json::array vertices;
|
|
||||||
lottiejson11::Json::array inPoints;
|
|
||||||
lottiejson11::Json::array outPoints;
|
|
||||||
|
|
||||||
for (const auto &element : elements) {
|
|
||||||
vertices.push_back(element.vertex.point.toJson());
|
|
||||||
inPoints.push_back(element.vertex.inTangentRelative().toJson());
|
|
||||||
outPoints.push_back(element.vertex.outTangentRelative().toJson());
|
|
||||||
}
|
|
||||||
|
|
||||||
result.insert(std::make_pair("v", vertices));
|
|
||||||
result.insert(std::make_pair("i", inPoints));
|
|
||||||
result.insert(std::make_pair("o", outPoints));
|
|
||||||
|
|
||||||
if (closed.has_value()) {
|
|
||||||
result.insert(std::make_pair("c", closed.value()));
|
|
||||||
}
|
|
||||||
|
|
||||||
return lottiejson11::Json(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::shared_ptr<CGPath> cgPath() const {
|
|
||||||
auto cgPath = CGPath::makePath();
|
|
||||||
|
|
||||||
std::optional<PathElement> previousElement;
|
|
||||||
for (const auto &element : elements) {
|
|
||||||
if (previousElement.has_value()) {
|
|
||||||
if (previousElement->vertex.outTangentRelative().isZero() && element.vertex.inTangentRelative().isZero()) {
|
|
||||||
cgPath->addLineTo(element.vertex.point);
|
|
||||||
} else {
|
|
||||||
cgPath->addCurveTo(element.vertex.point, previousElement->vertex.outTangent, element.vertex.inTangent);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
cgPath->moveTo(element.vertex.point);
|
|
||||||
}
|
|
||||||
previousElement = element;
|
|
||||||
}
|
|
||||||
if (closed.value_or(true)) {
|
|
||||||
cgPath->closeSubpath();
|
|
||||||
}
|
|
||||||
return cgPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
std::vector<PathElement> elements;
|
|
||||||
std::optional<bool> closed;
|
|
||||||
|
|
||||||
double length() {
|
|
||||||
if (_length.has_value()) {
|
|
||||||
return _length.value();
|
|
||||||
} else {
|
|
||||||
double result = 0.0;
|
|
||||||
for (size_t i = 1; i < elements.size(); i++) {
|
|
||||||
result += elements[i].length(elements[i - 1]);
|
|
||||||
}
|
|
||||||
_length = result;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::optional<double> _length;
|
|
||||||
|
|
||||||
public:
|
|
||||||
void moveToStartPoint(CurveVertex const &vertex) {
|
|
||||||
elements = { PathElement(vertex) };
|
|
||||||
_length = std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
void addVertex(CurveVertex const &vertex) {
|
|
||||||
addElement(PathElement(vertex));
|
|
||||||
}
|
|
||||||
|
|
||||||
void reserveCapacity(size_t capacity) {
|
|
||||||
elements.reserve(capacity);
|
|
||||||
}
|
|
||||||
|
|
||||||
void setElementCount(size_t count) {
|
|
||||||
elements.resize(count, PathElement(CurveVertex::absolute(Vector2D(0.0, 0.0), Vector2D(0.0, 0.0), Vector2D(0.0, 0.0))));
|
|
||||||
}
|
|
||||||
|
|
||||||
void invalidateLength() {
|
|
||||||
_length.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
void addCurve(Vector2D const &toPoint, Vector2D const &outTangent, Vector2D const &inTangent) {
|
|
||||||
if (elements.empty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
auto previous = elements[elements.size() - 1];
|
|
||||||
auto newVertex = CurveVertex::absolute(toPoint, inTangent, toPoint);
|
|
||||||
updateVertex(
|
|
||||||
CurveVertex::absolute(previous.vertex.point, previous.vertex.inTangent, outTangent),
|
|
||||||
(int)elements.size() - 1,
|
|
||||||
false
|
|
||||||
);
|
|
||||||
addVertex(newVertex);
|
|
||||||
}
|
|
||||||
|
|
||||||
void addLine(Vector2D const &toPoint) {
|
|
||||||
if (elements.empty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
auto previous = elements[elements.size() - 1];
|
|
||||||
auto newVertex = CurveVertex::relative(toPoint, Vector2D::Zero(), Vector2D::Zero());
|
|
||||||
updateVertex(
|
|
||||||
CurveVertex::absolute(previous.vertex.point, previous.vertex.inTangent, previous.vertex.point),
|
|
||||||
(int)elements.size() - 1,
|
|
||||||
false
|
|
||||||
);
|
|
||||||
addVertex(newVertex);
|
|
||||||
}
|
|
||||||
|
|
||||||
void close() {
|
|
||||||
closed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void addElement(PathElement const &pathElement) {
|
|
||||||
elements.push_back(pathElement);
|
|
||||||
}
|
|
||||||
|
|
||||||
void updateVertex(CurveVertex const &vertex, int atIndex, bool remeasure) {
|
|
||||||
if (remeasure) {
|
|
||||||
PathElement newElement(CurveVertex::absolute(Vector2D::Zero(), Vector2D::Zero(), Vector2D::Zero()));
|
|
||||||
if (atIndex > 0) {
|
|
||||||
auto previousElement = elements[atIndex - 1];
|
|
||||||
newElement = previousElement.pathElementTo(vertex);
|
|
||||||
} else {
|
|
||||||
newElement = PathElement(vertex);
|
|
||||||
}
|
|
||||||
elements[atIndex] = newElement;
|
|
||||||
|
|
||||||
if (atIndex + 1 < elements.size()) {
|
|
||||||
auto nextElement = elements[atIndex + 1];
|
|
||||||
elements[atIndex + 1] = newElement.pathElementTo(nextElement.vertex);
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
auto oldElement = elements[atIndex];
|
|
||||||
elements[atIndex] = oldElement.updateVertex(vertex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Trims a path fromLength toLength with an offset.
|
|
||||||
///
|
|
||||||
/// Length and offset are defined in the length coordinate space.
|
|
||||||
/// If any argument is outside the range of this path, then it will be looped over the path from finish to start.
|
|
||||||
///
|
|
||||||
/// Cutting the curve when fromLength is less than toLength
|
|
||||||
/// x x x x
|
|
||||||
/// ~~~~~~~~~~~~~~~ooooooooooooooooooooooooooooooooooooooooooooooooo-------------------
|
|
||||||
/// |Offset |fromLength toLength| |
|
|
||||||
///
|
|
||||||
/// Cutting the curve when from Length is greater than toLength
|
|
||||||
/// x x x x x
|
|
||||||
/// oooooooooooooooooo--------------------~~~~~~~~~~~~~~~~ooooooooooooooooooooooooooooo
|
|
||||||
/// | toLength| |Offset |fromLength |
|
|
||||||
///
|
|
||||||
std::vector<std::shared_ptr<BezierPathContents>> trim(double fromLength, double toLength, double offsetLength) {
|
|
||||||
if (elements.size() <= 1) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fromLength == toLength) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
double lengthValue = length();
|
|
||||||
|
|
||||||
/// Normalize lengths to the curve length.
|
|
||||||
auto start = fmod(fromLength + offsetLength, lengthValue);
|
|
||||||
auto end = fmod(toLength + offsetLength, lengthValue);
|
|
||||||
|
|
||||||
if (start < 0.0) {
|
|
||||||
start = lengthValue + start;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (end < 0.0) {
|
|
||||||
end = lengthValue + end;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (start == lengthValue) {
|
|
||||||
start = 0.0;
|
|
||||||
}
|
|
||||||
if (end == 0.0) {
|
|
||||||
end = lengthValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
(start == 0.0 && end == lengthValue) ||
|
|
||||||
start == end ||
|
|
||||||
(start == lengthValue && end == 0.0)
|
|
||||||
) {
|
|
||||||
/// The trim encompasses the entire path. Return.
|
|
||||||
return { shared_from_this() };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (start > end) {
|
|
||||||
// Start is greater than end. Two paths are returned.
|
|
||||||
return trimPathAtLengths({
|
|
||||||
BezierTrimPathPosition(0.0, end),
|
|
||||||
BezierTrimPathPosition(start, lengthValue)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return trimPathAtLengths({ BezierTrimPathPosition(start, end) });
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: Private
|
|
||||||
|
|
||||||
std::vector<std::shared_ptr<BezierPathContents>> trimPathAtLengths(std::vector<BezierTrimPathPosition> const &positions) {
|
|
||||||
if (positions.empty()) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
auto remainingPositions = positions;
|
|
||||||
|
|
||||||
auto trim = remainingPositions[0];
|
|
||||||
remainingPositions.erase(remainingPositions.begin());
|
|
||||||
|
|
||||||
std::vector<std::shared_ptr<BezierPathContents>> paths;
|
|
||||||
|
|
||||||
double runningLength = 0.0;
|
|
||||||
bool finishedTrimming = false;
|
|
||||||
auto pathElements = elements;
|
|
||||||
|
|
||||||
auto currentPath = std::make_shared<BezierPathContents>();
|
|
||||||
int i = 0;
|
|
||||||
|
|
||||||
while (!finishedTrimming) {
|
|
||||||
if (pathElements.size() <= i) {
|
|
||||||
/// Do this for rounding errors
|
|
||||||
paths.push_back(currentPath);
|
|
||||||
finishedTrimming = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
/// Loop through and add elements within start->end range.
|
|
||||||
/// Get current element
|
|
||||||
auto element = pathElements[i];
|
|
||||||
double elementLength = 0.0;
|
|
||||||
if (i != 0) {
|
|
||||||
elementLength = element.length(pathElements[i - 1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Calculate new running length.
|
|
||||||
auto newLength = runningLength + elementLength;
|
|
||||||
|
|
||||||
if (newLength < trim.start) {
|
|
||||||
/// Element is not included in the trim, continue.
|
|
||||||
runningLength = newLength;
|
|
||||||
i = i + 1;
|
|
||||||
/// Increment index, we are done with this element.
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newLength == trim.start) {
|
|
||||||
/// Current element IS the start element.
|
|
||||||
/// For start we want to add a zero length element.
|
|
||||||
currentPath->moveToStartPoint(element.vertex);
|
|
||||||
runningLength = newLength;
|
|
||||||
i = i + 1;
|
|
||||||
/// Increment index, we are done with this element.
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (runningLength < trim.start && trim.start < newLength && currentPath->elements.size() == 0) {
|
|
||||||
/// The start of the trim is between this element and the previous, trim.
|
|
||||||
/// Get previous element.
|
|
||||||
auto previousElement = pathElements[i - 1];
|
|
||||||
/// Trim it
|
|
||||||
auto trimLength = trim.start - runningLength;
|
|
||||||
auto trimResults = element.splitElementAtPosition(previousElement, trimLength);
|
|
||||||
/// Add the right span start.
|
|
||||||
currentPath->moveToStartPoint(trimResults.rightSpan.start.vertex);
|
|
||||||
|
|
||||||
pathElements[i] = trimResults.rightSpan.end;
|
|
||||||
pathElements[i - 1] = trimResults.rightSpan.start;
|
|
||||||
runningLength = runningLength + trimResults.leftSpan.end.length(trimResults.leftSpan.start);
|
|
||||||
/// Dont increment index or the current length, the end of this path can be within this span.
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (trim.start < newLength && newLength < trim.end) {
|
|
||||||
/// Element lies within the trim span.
|
|
||||||
currentPath->addElement(element);
|
|
||||||
runningLength = newLength;
|
|
||||||
i = i + 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newLength == trim.end) {
|
|
||||||
/// Element is the end element.
|
|
||||||
/// The element could have a new length if it's added right after the start node.
|
|
||||||
currentPath->addElement(element);
|
|
||||||
/// We are done with this span.
|
|
||||||
runningLength = newLength;
|
|
||||||
i = i + 1;
|
|
||||||
/// Allow the path to be finalized.
|
|
||||||
/// Fall through to finalize path and move to next position
|
|
||||||
}
|
|
||||||
|
|
||||||
if (runningLength < trim.end && trim.end < newLength) {
|
|
||||||
/// New element must be cut for end.
|
|
||||||
/// Get previous element.
|
|
||||||
auto previousElement = pathElements[i - 1];
|
|
||||||
/// Trim it
|
|
||||||
auto trimLength = trim.end - runningLength;
|
|
||||||
auto trimResults = element.splitElementAtPosition(previousElement, trimLength);
|
|
||||||
/// Add the left span end.
|
|
||||||
|
|
||||||
currentPath->updateVertex(trimResults.leftSpan.start.vertex, (int)currentPath->elements.size() - 1, false);
|
|
||||||
currentPath->addElement(trimResults.leftSpan.end);
|
|
||||||
|
|
||||||
pathElements[i] = trimResults.rightSpan.end;
|
|
||||||
pathElements[i - 1] = trimResults.rightSpan.start;
|
|
||||||
runningLength = runningLength + trimResults.leftSpan.end.length(trimResults.leftSpan.start);
|
|
||||||
/// Dont increment index or the current length, the start of the next path can be within this span.
|
|
||||||
/// We are done with this span.
|
|
||||||
/// Allow the path to be finalized.
|
|
||||||
/// Fall through to finalize path and move to next position
|
|
||||||
}
|
|
||||||
|
|
||||||
paths.push_back(currentPath);
|
|
||||||
currentPath = std::make_shared<BezierPathContents>();
|
|
||||||
if (remainingPositions.size() > 0) {
|
|
||||||
trim = remainingPositions[0];
|
|
||||||
remainingPositions.erase(remainingPositions.begin());
|
|
||||||
} else {
|
|
||||||
finishedTrimming = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return paths;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
class BezierPath {
|
|
||||||
public:
|
|
||||||
/// Initializes a new Bezier Path.
|
|
||||||
explicit BezierPath(CurveVertex const &startPoint) :
|
|
||||||
_contents(std::make_shared<BezierPathContents>(startPoint)) {
|
|
||||||
}
|
|
||||||
|
|
||||||
BezierPath() :
|
|
||||||
_contents(std::make_shared<BezierPathContents>()) {
|
|
||||||
}
|
|
||||||
|
|
||||||
explicit BezierPath(lottiejson11::Json const &jsonAny) noexcept(false) :
|
|
||||||
_contents(std::make_shared<BezierPathContents>(jsonAny)) {
|
|
||||||
}
|
|
||||||
|
|
||||||
lottiejson11::Json toJson() const {
|
|
||||||
return _contents->toJson();
|
|
||||||
}
|
|
||||||
|
|
||||||
double length() {
|
|
||||||
return _contents->length();
|
|
||||||
}
|
|
||||||
|
|
||||||
void moveToStartPoint(CurveVertex const &vertex) {
|
|
||||||
_contents->moveToStartPoint(vertex);
|
|
||||||
}
|
|
||||||
|
|
||||||
void addVertex(CurveVertex const &vertex) {
|
|
||||||
_contents->addVertex(vertex);
|
|
||||||
}
|
|
||||||
|
|
||||||
void reserveCapacity(size_t capacity) {
|
|
||||||
_contents->reserveCapacity(capacity);
|
|
||||||
}
|
|
||||||
|
|
||||||
void setElementCount(size_t count) {
|
|
||||||
_contents->setElementCount(count);
|
|
||||||
}
|
|
||||||
|
|
||||||
void invalidateLength() {
|
|
||||||
_contents->invalidateLength();
|
|
||||||
}
|
|
||||||
|
|
||||||
void addCurve(Vector2D const &toPoint, Vector2D const &outTangent, Vector2D const &inTangent) {
|
|
||||||
_contents->addCurve(toPoint, outTangent, inTangent);
|
|
||||||
}
|
|
||||||
|
|
||||||
void addLine(Vector2D const &toPoint) {
|
|
||||||
_contents->addLine(toPoint);
|
|
||||||
}
|
|
||||||
|
|
||||||
void close() {
|
|
||||||
_contents->close();
|
|
||||||
}
|
|
||||||
|
|
||||||
void addElement(PathElement const &pathElement) {
|
|
||||||
_contents->addElement(pathElement);
|
|
||||||
}
|
|
||||||
|
|
||||||
void updateVertex(CurveVertex const &vertex, int atIndex, bool remeasure) {
|
|
||||||
_contents->updateVertex(vertex, atIndex, remeasure);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Trims a path fromLength toLength with an offset.
|
|
||||||
///
|
|
||||||
/// Length and offset are defined in the length coordinate space.
|
|
||||||
/// If any argument is outside the range of this path, then it will be looped over the path from finish to start.
|
|
||||||
///
|
|
||||||
/// Cutting the curve when fromLength is less than toLength
|
|
||||||
/// x x x x
|
|
||||||
/// ~~~~~~~~~~~~~~~ooooooooooooooooooooooooooooooooooooooooooooooooo-------------------
|
|
||||||
/// |Offset |fromLength toLength| |
|
|
||||||
///
|
|
||||||
/// Cutting the curve when from Length is greater than toLength
|
|
||||||
/// x x x x x
|
|
||||||
/// oooooooooooooooooo--------------------~~~~~~~~~~~~~~~~ooooooooooooooooooooooooooooo
|
|
||||||
/// | toLength| |Offset |fromLength |
|
|
||||||
///
|
|
||||||
std::vector<BezierPath> trim(double fromLength, double toLength, double offsetLength) {
|
|
||||||
std::vector<BezierPath> result;
|
|
||||||
|
|
||||||
auto resultContents = _contents->trim(fromLength, toLength, offsetLength);
|
|
||||||
for (const auto &resultContent : resultContents) {
|
|
||||||
result.emplace_back(resultContent);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: Private
|
|
||||||
|
|
||||||
std::vector<PathElement> const &elements() const {
|
|
||||||
return _contents->elements;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<PathElement> &mutableElements() {
|
|
||||||
return _contents->elements;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<bool> const &closed() const {
|
|
||||||
return _contents->closed;
|
|
||||||
}
|
|
||||||
void setClosed(std::optional<bool> const &closed) {
|
|
||||||
_contents->closed = closed;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::shared_ptr<CGPath> cgPath() const {
|
|
||||||
return _contents->cgPath();
|
|
||||||
}
|
|
||||||
|
|
||||||
BezierPath copyUsingTransform(CATransform3D const &transform) const {
|
|
||||||
if (transform == CATransform3D::identity()) {
|
|
||||||
return (*this);
|
|
||||||
}
|
|
||||||
BezierPath result;
|
|
||||||
result._contents->closed = _contents->closed;
|
|
||||||
result.reserveCapacity(_contents->elements.size());
|
|
||||||
for (const auto &element : _contents->elements) {
|
|
||||||
result._contents->elements.emplace_back(element.vertex.transformed(transform));
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
BezierPath(std::shared_ptr<BezierPathContents> contents) :
|
|
||||||
_contents(contents) {
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::shared_ptr<BezierPathContents> _contents;
|
|
||||||
};
|
|
||||||
|
|
||||||
class BezierPathsBoundingBoxContext {
|
|
||||||
public:
|
|
||||||
BezierPathsBoundingBoxContext() :
|
|
||||||
pointsX((float *)malloc(1024 * 4)),
|
|
||||||
pointsY((float *)malloc(1024 * 4)),
|
|
||||||
pointsSize(1024) {
|
|
||||||
}
|
|
||||||
|
|
||||||
~BezierPathsBoundingBoxContext() {
|
|
||||||
free(pointsX);
|
|
||||||
free(pointsY);
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
float *pointsX = nullptr;
|
|
||||||
float *pointsY = nullptr;
|
|
||||||
int pointsSize = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
CGRect bezierPathsBoundingBox(std::vector<BezierPath> const &paths);
|
|
||||||
CGRect bezierPathsBoundingBoxParallel(BezierPathsBoundingBoxContext &context, std::vector<BezierPath> const &paths);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif /* BezierPath_hpp */
|
|
||||||
@ -1,7 +1,7 @@
|
|||||||
#ifndef CompoundBezierPath_hpp
|
#ifndef CompoundBezierPath_hpp
|
||||||
#define CompoundBezierPath_hpp
|
#define CompoundBezierPath_hpp
|
||||||
|
|
||||||
#include "Lottie/Private/Utility/Primitives/BezierPath.hpp"
|
#include <LottieCpp/BezierPath.h>
|
||||||
|
|
||||||
namespace lottie {
|
namespace lottie {
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
#include "CurveVertex.hpp"
|
#include <LottieCpp/CurveVertex.h>
|
||||||
|
|
||||||
namespace lottie {
|
namespace lottie {
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
#include "PathElement.hpp"
|
#include <LottieCpp/PathElement.h>
|
||||||
|
|
||||||
namespace lottie {
|
namespace lottie {
|
||||||
|
|
||||||
|
|||||||
@ -2,8 +2,8 @@
|
|||||||
#define ValueInterpolators_hpp
|
#define ValueInterpolators_hpp
|
||||||
|
|
||||||
#include <LottieCpp/Vectors.h>
|
#include <LottieCpp/Vectors.h>
|
||||||
#include "Lottie/Public/Primitives/Color.hpp"
|
#import <LottieCpp/Color.h>
|
||||||
#include "Lottie/Private/Utility/Primitives/BezierPath.hpp"
|
#include <LottieCpp/BezierPath.h>
|
||||||
#include "Lottie/Private/Model/Text/TextDocument.hpp"
|
#include "Lottie/Private/Model/Text/TextDocument.hpp"
|
||||||
#include "Lottie/Public/Primitives/GradientColorSet.hpp"
|
#include "Lottie/Public/Primitives/GradientColorSet.hpp"
|
||||||
#include "Lottie/Public/Primitives/DashPattern.hpp"
|
#include "Lottie/Public/Primitives/DashPattern.hpp"
|
||||||
|
|||||||
@ -2,8 +2,8 @@
|
|||||||
#define AnyValue_hpp
|
#define AnyValue_hpp
|
||||||
|
|
||||||
#include <LottieCpp/Vectors.h>
|
#include <LottieCpp/Vectors.h>
|
||||||
#include "Lottie/Public/Primitives/Color.hpp"
|
#import <LottieCpp/Color.h>
|
||||||
#include "Lottie/Private/Utility/Primitives/BezierPath.hpp"
|
#include <LottieCpp/BezierPath.h>
|
||||||
#include "Lottie/Private/Model/Text/TextDocument.hpp"
|
#include "Lottie/Private/Model/Text/TextDocument.hpp"
|
||||||
#include "Lottie/Private/Model/ShapeItems/GradientFill.hpp"
|
#include "Lottie/Private/Model/ShapeItems/GradientFill.hpp"
|
||||||
#include "Lottie/Private/Model/Objects/DashElement.hpp"
|
#include "Lottie/Private/Model/Objects/DashElement.hpp"
|
||||||
|
|||||||
@ -1,12 +1,13 @@
|
|||||||
#ifndef CALayer_hpp
|
#ifndef CALayer_hpp
|
||||||
#define CALayer_hpp
|
#define CALayer_hpp
|
||||||
|
|
||||||
#include "Lottie/Public/Primitives/Color.hpp"
|
#import <LottieCpp/Color.h>
|
||||||
#include <LottieCpp/Vectors.h>
|
#include <LottieCpp/Vectors.h>
|
||||||
#include <LottieCpp/CGPath.h>
|
#include <LottieCpp/CGPath.h>
|
||||||
|
#include <LottieCpp/RenderTreeNode.h>
|
||||||
#include "Lottie/Private/Model/ShapeItems/Fill.hpp"
|
#include "Lottie/Private/Model/ShapeItems/Fill.hpp"
|
||||||
#include "Lottie/Private/Model/Layers/LayerModel.hpp"
|
#include "Lottie/Private/Model/Layers/LayerModel.hpp"
|
||||||
#include "Lottie/Public/Primitives/DrawingAttributes.hpp"
|
#include <LottieCpp/ShapeAttributes.h>
|
||||||
#include "Lottie/Private/Model/ShapeItems/GradientFill.hpp"
|
#include "Lottie/Private/Model/ShapeItems/GradientFill.hpp"
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
@ -15,429 +16,6 @@
|
|||||||
|
|
||||||
namespace lottie {
|
namespace lottie {
|
||||||
|
|
||||||
class RenderableItem {
|
|
||||||
public:
|
|
||||||
enum class Type {
|
|
||||||
Shape,
|
|
||||||
GradientFill
|
|
||||||
};
|
|
||||||
|
|
||||||
public:
|
|
||||||
RenderableItem() {
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual ~RenderableItem() = default;
|
|
||||||
|
|
||||||
virtual Type type() const = 0;
|
|
||||||
virtual CGRect boundingRect() const = 0;
|
|
||||||
|
|
||||||
virtual bool isEqual(std::shared_ptr<RenderableItem> rhs) const = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
class ShapeRenderableItem: public RenderableItem {
|
|
||||||
public:
|
|
||||||
struct Fill {
|
|
||||||
Color color;
|
|
||||||
FillRule rule;
|
|
||||||
|
|
||||||
Fill(Color color_, FillRule rule_) :
|
|
||||||
color(color_), rule(rule_) {
|
|
||||||
}
|
|
||||||
|
|
||||||
bool operator==(Fill const &rhs) const {
|
|
||||||
if (color != rhs.color) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (rule != rhs.rule) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool operator!=(Fill const &rhs) const {
|
|
||||||
return !(*this == rhs);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Stroke {
|
|
||||||
Color color;
|
|
||||||
double lineWidth = 0.0;
|
|
||||||
LineJoin lineJoin = LineJoin::Round;
|
|
||||||
LineCap lineCap = LineCap::Square;
|
|
||||||
double dashPhase = 0.0;
|
|
||||||
std::vector<double> dashPattern;
|
|
||||||
|
|
||||||
Stroke(
|
|
||||||
Color color_,
|
|
||||||
double lineWidth_,
|
|
||||||
LineJoin lineJoin_,
|
|
||||||
LineCap lineCap_,
|
|
||||||
double dashPhase_,
|
|
||||||
std::vector<double> dashPattern_
|
|
||||||
) :
|
|
||||||
color(color_),
|
|
||||||
lineWidth(lineWidth_),
|
|
||||||
lineJoin(lineJoin_),
|
|
||||||
lineCap(lineCap_),
|
|
||||||
dashPhase(dashPhase_),
|
|
||||||
dashPattern(dashPattern_) {
|
|
||||||
}
|
|
||||||
|
|
||||||
bool operator==(Stroke const &rhs) const {
|
|
||||||
if (color != rhs.color) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (lineWidth != rhs.lineWidth) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (lineJoin != rhs.lineJoin) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (lineCap != rhs.lineCap) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (dashPhase != rhs.dashPhase) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (dashPattern != rhs.dashPattern) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool operator!=(Stroke const &rhs) const {
|
|
||||||
return !(*this == rhs);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
public:
|
|
||||||
ShapeRenderableItem(
|
|
||||||
std::shared_ptr<CGPath> path_,
|
|
||||||
std::optional<Fill> const &fill_,
|
|
||||||
std::optional<Stroke> const &stroke_
|
|
||||||
) :
|
|
||||||
path(path_),
|
|
||||||
fill(fill_),
|
|
||||||
stroke(stroke_) {
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual Type type() const override {
|
|
||||||
return Type::Shape;
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual CGRect boundingRect() const override {
|
|
||||||
if (path) {
|
|
||||||
CGRect shapeBounds = path->boundingBox();
|
|
||||||
if (stroke) {
|
|
||||||
shapeBounds = shapeBounds.insetBy(-stroke->lineWidth / 2.0, -stroke->lineWidth / 2.0);
|
|
||||||
}
|
|
||||||
return shapeBounds;
|
|
||||||
} else {
|
|
||||||
return CGRect(0.0, 0.0, 0.0, 0.0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual bool isEqual(std::shared_ptr<RenderableItem> rhs) const override {
|
|
||||||
if (rhs->type() != type()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
ShapeRenderableItem *other = (ShapeRenderableItem *)rhs.get();
|
|
||||||
if ((path == nullptr) != (other->path == nullptr)) {
|
|
||||||
return false;
|
|
||||||
} else if (path) {
|
|
||||||
if (!path->isEqual(other->path.get())) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (fill != other->fill) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (stroke != other->stroke) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
std::shared_ptr<CGPath> path;
|
|
||||||
std::optional<Fill> fill;
|
|
||||||
std::optional<Stroke> stroke;
|
|
||||||
};
|
|
||||||
|
|
||||||
class GradientFillRenderableItem: public RenderableItem {
|
|
||||||
public:
|
|
||||||
GradientFillRenderableItem(
|
|
||||||
std::shared_ptr<CGPath> path_,
|
|
||||||
FillRule pathFillRule_,
|
|
||||||
GradientType gradientType_,
|
|
||||||
std::vector<Color> const &colors_,
|
|
||||||
std::vector<double> const &locations_,
|
|
||||||
Vector2D const &start_,
|
|
||||||
Vector2D const &end_,
|
|
||||||
CGRect bounds_
|
|
||||||
) :
|
|
||||||
path(path_),
|
|
||||||
pathFillRule(pathFillRule_),
|
|
||||||
gradientType(gradientType_),
|
|
||||||
colors(colors_),
|
|
||||||
locations(locations_),
|
|
||||||
start(start_),
|
|
||||||
end(end_),
|
|
||||||
bounds(bounds_) {
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual Type type() const override {
|
|
||||||
return Type::GradientFill;
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual CGRect boundingRect() const override {
|
|
||||||
return bounds;
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual bool isEqual(std::shared_ptr<RenderableItem> rhs) const override {
|
|
||||||
if (rhs->type() != type()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
GradientFillRenderableItem *other = (GradientFillRenderableItem *)rhs.get();
|
|
||||||
|
|
||||||
if (gradientType != other->gradientType) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (colors != other->colors) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (locations != other->locations) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (start != other->start) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (end != other->end) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (bounds != other->bounds) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
std::shared_ptr<CGPath> path;
|
|
||||||
FillRule pathFillRule;
|
|
||||||
GradientType gradientType;
|
|
||||||
std::vector<Color> colors;
|
|
||||||
std::vector<double> locations;
|
|
||||||
Vector2D start;
|
|
||||||
Vector2D end;
|
|
||||||
CGRect bounds;
|
|
||||||
};
|
|
||||||
|
|
||||||
class RenderTreeNodeContent {
|
|
||||||
public:
|
|
||||||
enum class ShadingType {
|
|
||||||
Solid,
|
|
||||||
Gradient
|
|
||||||
};
|
|
||||||
|
|
||||||
class Shading {
|
|
||||||
public:
|
|
||||||
Shading() {
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual ~Shading() = default;
|
|
||||||
|
|
||||||
virtual ShadingType type() const = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
class SolidShading: public Shading {
|
|
||||||
public:
|
|
||||||
SolidShading(Color const &color_, double opacity_) :
|
|
||||||
color(color_),
|
|
||||||
opacity(opacity_) {
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual ShadingType type() const override {
|
|
||||||
return ShadingType::Solid;
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
Color color;
|
|
||||||
double opacity = 0.0;
|
|
||||||
};
|
|
||||||
|
|
||||||
class GradientShading: public Shading {
|
|
||||||
public:
|
|
||||||
GradientShading(
|
|
||||||
double opacity_,
|
|
||||||
GradientType gradientType_,
|
|
||||||
std::vector<Color> const &colors_,
|
|
||||||
std::vector<double> const &locations_,
|
|
||||||
Vector2D const &start_,
|
|
||||||
Vector2D const &end_
|
|
||||||
) :
|
|
||||||
opacity(opacity_),
|
|
||||||
gradientType(gradientType_),
|
|
||||||
colors(colors_),
|
|
||||||
locations(locations_),
|
|
||||||
start(start_),
|
|
||||||
end(end_) {
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual ShadingType type() const override {
|
|
||||||
return ShadingType::Gradient;
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
double opacity = 0.0;
|
|
||||||
GradientType gradientType;
|
|
||||||
std::vector<Color> colors;
|
|
||||||
std::vector<double> locations;
|
|
||||||
Vector2D start;
|
|
||||||
Vector2D end;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Stroke {
|
|
||||||
std::shared_ptr<Shading> shading;
|
|
||||||
double lineWidth = 0.0;
|
|
||||||
LineJoin lineJoin = LineJoin::Round;
|
|
||||||
LineCap lineCap = LineCap::Square;
|
|
||||||
double miterLimit = 4.0;
|
|
||||||
double dashPhase = 0.0;
|
|
||||||
std::vector<double> dashPattern;
|
|
||||||
|
|
||||||
Stroke(
|
|
||||||
std::shared_ptr<Shading> shading_,
|
|
||||||
double lineWidth_,
|
|
||||||
LineJoin lineJoin_,
|
|
||||||
LineCap lineCap_,
|
|
||||||
double miterLimit_,
|
|
||||||
double dashPhase_,
|
|
||||||
std::vector<double> dashPattern_
|
|
||||||
) :
|
|
||||||
shading(shading_),
|
|
||||||
lineWidth(lineWidth_),
|
|
||||||
lineJoin(lineJoin_),
|
|
||||||
lineCap(lineCap_),
|
|
||||||
miterLimit(miterLimit_),
|
|
||||||
dashPhase(dashPhase_),
|
|
||||||
dashPattern(dashPattern_) {
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Fill {
|
|
||||||
std::shared_ptr<Shading> shading;
|
|
||||||
FillRule rule;
|
|
||||||
|
|
||||||
Fill(
|
|
||||||
std::shared_ptr<Shading> shading_,
|
|
||||||
FillRule rule_
|
|
||||||
) :
|
|
||||||
shading(shading_),
|
|
||||||
rule(rule_) {
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
public:
|
|
||||||
RenderTreeNodeContent(
|
|
||||||
std::vector<BezierPath> paths_,
|
|
||||||
std::shared_ptr<Stroke> stroke_,
|
|
||||||
std::shared_ptr<Fill> fill_
|
|
||||||
) :
|
|
||||||
paths(paths_),
|
|
||||||
stroke(stroke_),
|
|
||||||
fill(fill_) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
std::vector<BezierPath> paths;
|
|
||||||
std::shared_ptr<Stroke> stroke;
|
|
||||||
std::shared_ptr<Fill> fill;
|
|
||||||
};
|
|
||||||
|
|
||||||
class RenderTreeNode {
|
|
||||||
public:
|
|
||||||
RenderTreeNode(
|
|
||||||
CGRect bounds_,
|
|
||||||
Vector2D position_,
|
|
||||||
CATransform3D transform_,
|
|
||||||
double alpha_,
|
|
||||||
bool masksToBounds_,
|
|
||||||
bool isHidden_,
|
|
||||||
std::shared_ptr<RenderTreeNodeContent> content_,
|
|
||||||
std::vector<std::shared_ptr<RenderTreeNode>> subnodes_,
|
|
||||||
std::shared_ptr<RenderTreeNode> mask_,
|
|
||||||
bool invertMask_
|
|
||||||
) :
|
|
||||||
_bounds(bounds_),
|
|
||||||
_position(position_),
|
|
||||||
_transform(transform_),
|
|
||||||
_alpha(alpha_),
|
|
||||||
_masksToBounds(masksToBounds_),
|
|
||||||
_isHidden(isHidden_),
|
|
||||||
_content(content_),
|
|
||||||
_subnodes(subnodes_),
|
|
||||||
_mask(mask_),
|
|
||||||
_invertMask(invertMask_) {
|
|
||||||
}
|
|
||||||
|
|
||||||
~RenderTreeNode() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
CGRect const &bounds() const {
|
|
||||||
return _bounds;
|
|
||||||
}
|
|
||||||
|
|
||||||
Vector2D const &position() const {
|
|
||||||
return _position;
|
|
||||||
}
|
|
||||||
|
|
||||||
CATransform3D const &transform() const {
|
|
||||||
return _transform;
|
|
||||||
}
|
|
||||||
|
|
||||||
double alpha() const {
|
|
||||||
return _alpha;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool masksToBounds() const {
|
|
||||||
return _masksToBounds;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isHidden() const {
|
|
||||||
return _isHidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::shared_ptr<RenderTreeNodeContent> const &content() const {
|
|
||||||
return _content;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<std::shared_ptr<RenderTreeNode>> const &subnodes() const {
|
|
||||||
return _subnodes;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::shared_ptr<RenderTreeNode> const &mask() const {
|
|
||||||
return _mask;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool invertMask() const {
|
|
||||||
return _invertMask;
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
CGRect _bounds;
|
|
||||||
Vector2D _position;
|
|
||||||
CATransform3D _transform = CATransform3D::identity();
|
|
||||||
double _alpha = 1.0;
|
|
||||||
bool _masksToBounds = false;
|
|
||||||
bool _isHidden = false;
|
|
||||||
std::shared_ptr<RenderTreeNodeContent> _content;
|
|
||||||
std::vector<std::shared_ptr<RenderTreeNode>> _subnodes;
|
|
||||||
std::shared_ptr<RenderTreeNode> _mask;
|
|
||||||
bool _invertMask = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
class CALayer: public std::enable_shared_from_this<CALayer> {
|
class CALayer: public std::enable_shared_from_this<CALayer> {
|
||||||
public:
|
public:
|
||||||
CALayer() {
|
CALayer() {
|
||||||
|
|||||||
@ -1,9 +1,108 @@
|
|||||||
#include "Color.hpp"
|
#include <LottieCpp/Color.h>
|
||||||
|
|
||||||
|
#include "Lottie/Private/Parsing/JsonParsing.hpp"
|
||||||
|
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
||||||
namespace lottie {
|
namespace lottie {
|
||||||
|
|
||||||
|
Color::Color(double r_, double g_, double b_, double a_, ColorFormatDenominator denominator) {
|
||||||
|
double denominatorValue = 1.0;
|
||||||
|
switch (denominator) {
|
||||||
|
case ColorFormatDenominator::One: {
|
||||||
|
denominatorValue = 1.0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ColorFormatDenominator::OneHundred: {
|
||||||
|
denominatorValue = 100.0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ColorFormatDenominator::TwoFiftyFive: {
|
||||||
|
denominatorValue = 255.0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
r = r_ / denominatorValue;
|
||||||
|
g = g_ / denominatorValue;
|
||||||
|
b = b_ / denominatorValue;
|
||||||
|
a = a_ / denominatorValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Color::Color(lottiejson11::Json const &jsonAny) noexcept(false) :
|
||||||
|
r(0.0), g(0.0), b(0.0), a(0.0) {
|
||||||
|
if (!jsonAny.is_array()) {
|
||||||
|
throw LottieParsingException();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto &item : jsonAny.array_items()) {
|
||||||
|
if (!item.is_number()) {
|
||||||
|
throw LottieParsingException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t index = 0;
|
||||||
|
|
||||||
|
double r1 = 0.0;
|
||||||
|
if (index < jsonAny.array_items().size()) {
|
||||||
|
if (!jsonAny.array_items()[index].is_number()) {
|
||||||
|
throw LottieParsingException();
|
||||||
|
}
|
||||||
|
r1 = jsonAny.array_items()[index].number_value();
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
double g1 = 0.0;
|
||||||
|
if (index < jsonAny.array_items().size()) {
|
||||||
|
if (!jsonAny.array_items()[index].is_number()) {
|
||||||
|
throw LottieParsingException();
|
||||||
|
}
|
||||||
|
g1 = jsonAny.array_items()[index].number_value();
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
double b1 = 0.0;
|
||||||
|
if (index < jsonAny.array_items().size()) {
|
||||||
|
if (!jsonAny.array_items()[index].is_number()) {
|
||||||
|
throw LottieParsingException();
|
||||||
|
}
|
||||||
|
b1 = jsonAny.array_items()[index].number_value();
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
double a1 = 0.0;
|
||||||
|
if (index < jsonAny.array_items().size()) {
|
||||||
|
if (!jsonAny.array_items()[index].is_number()) {
|
||||||
|
throw LottieParsingException();
|
||||||
|
}
|
||||||
|
a1 = jsonAny.array_items()[index].number_value();
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (r1 > 1.0 && r1 > 1.0 && b1 > 1.0 && a1 > 1.0) {
|
||||||
|
r1 = r1 / 255.0;
|
||||||
|
g1 = g1 / 255.0;
|
||||||
|
b1 = b1 / 255.0;
|
||||||
|
a1 = a1 / 255.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
r = r1;
|
||||||
|
g = g1;
|
||||||
|
b = b1;
|
||||||
|
a = a1;
|
||||||
|
}
|
||||||
|
|
||||||
|
lottiejson11::Json Color::toJson() const {
|
||||||
|
lottiejson11::Json::array result;
|
||||||
|
|
||||||
|
result.push_back(lottiejson11::Json(r));
|
||||||
|
result.push_back(lottiejson11::Json(g));
|
||||||
|
result.push_back(lottiejson11::Json(b));
|
||||||
|
result.push_back(lottiejson11::Json(a));
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
Color Color::fromString(std::string const &string) {
|
Color Color::fromString(std::string const &string) {
|
||||||
if (string.empty()) {
|
if (string.empty()) {
|
||||||
return Color(0.0, 0.0, 0.0, 0.0);
|
return Color(0.0, 0.0, 0.0, 0.0);
|
||||||
|
|||||||
@ -1,144 +0,0 @@
|
|||||||
#ifndef Color_hpp
|
|
||||||
#define Color_hpp
|
|
||||||
|
|
||||||
#include "Lottie/Private/Parsing/JsonParsing.hpp"
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
namespace lottie {
|
|
||||||
|
|
||||||
enum class ColorFormatDenominator {
|
|
||||||
One,
|
|
||||||
OneHundred,
|
|
||||||
TwoFiftyFive
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Color {
|
|
||||||
double r;
|
|
||||||
double g;
|
|
||||||
double b;
|
|
||||||
double a;
|
|
||||||
|
|
||||||
bool operator==(Color const &rhs) const {
|
|
||||||
if (r != rhs.r) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (g != rhs.g) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (b != rhs.b) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (a != rhs.a) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool operator!=(Color const &rhs) const {
|
|
||||||
return !(*this == rhs);
|
|
||||||
}
|
|
||||||
|
|
||||||
explicit Color(double r_, double g_, double b_, double a_, ColorFormatDenominator denominator = ColorFormatDenominator::One) {
|
|
||||||
double denominatorValue = 1.0;
|
|
||||||
switch (denominator) {
|
|
||||||
case ColorFormatDenominator::One: {
|
|
||||||
denominatorValue = 1.0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case ColorFormatDenominator::OneHundred: {
|
|
||||||
denominatorValue = 100.0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case ColorFormatDenominator::TwoFiftyFive: {
|
|
||||||
denominatorValue = 255.0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
r = r_ / denominatorValue;
|
|
||||||
g = g_ / denominatorValue;
|
|
||||||
b = b_ / denominatorValue;
|
|
||||||
a = a_ / denominatorValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
explicit Color(lottiejson11::Json const &jsonAny) noexcept(false) :
|
|
||||||
r(0.0), g(0.0), b(0.0), a(0.0) {
|
|
||||||
if (!jsonAny.is_array()) {
|
|
||||||
throw LottieParsingException();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const auto &item : jsonAny.array_items()) {
|
|
||||||
if (!item.is_number()) {
|
|
||||||
throw LottieParsingException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t index = 0;
|
|
||||||
|
|
||||||
double r1 = 0.0;
|
|
||||||
if (index < jsonAny.array_items().size()) {
|
|
||||||
if (!jsonAny.array_items()[index].is_number()) {
|
|
||||||
throw LottieParsingException();
|
|
||||||
}
|
|
||||||
r1 = jsonAny.array_items()[index].number_value();
|
|
||||||
index++;
|
|
||||||
}
|
|
||||||
|
|
||||||
double g1 = 0.0;
|
|
||||||
if (index < jsonAny.array_items().size()) {
|
|
||||||
if (!jsonAny.array_items()[index].is_number()) {
|
|
||||||
throw LottieParsingException();
|
|
||||||
}
|
|
||||||
g1 = jsonAny.array_items()[index].number_value();
|
|
||||||
index++;
|
|
||||||
}
|
|
||||||
|
|
||||||
double b1 = 0.0;
|
|
||||||
if (index < jsonAny.array_items().size()) {
|
|
||||||
if (!jsonAny.array_items()[index].is_number()) {
|
|
||||||
throw LottieParsingException();
|
|
||||||
}
|
|
||||||
b1 = jsonAny.array_items()[index].number_value();
|
|
||||||
index++;
|
|
||||||
}
|
|
||||||
|
|
||||||
double a1 = 0.0;
|
|
||||||
if (index < jsonAny.array_items().size()) {
|
|
||||||
if (!jsonAny.array_items()[index].is_number()) {
|
|
||||||
throw LottieParsingException();
|
|
||||||
}
|
|
||||||
a1 = jsonAny.array_items()[index].number_value();
|
|
||||||
index++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (r1 > 1.0 && r1 > 1.0 && b1 > 1.0 && a1 > 1.0) {
|
|
||||||
r1 = r1 / 255.0;
|
|
||||||
g1 = g1 / 255.0;
|
|
||||||
b1 = b1 / 255.0;
|
|
||||||
a1 = a1 / 255.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
r = r1;
|
|
||||||
g = g1;
|
|
||||||
b = b1;
|
|
||||||
a = a1;
|
|
||||||
}
|
|
||||||
|
|
||||||
lottiejson11::Json toJson() const {
|
|
||||||
lottiejson11::Json::array result;
|
|
||||||
|
|
||||||
result.push_back(lottiejson11::Json(r));
|
|
||||||
result.push_back(lottiejson11::Json(g));
|
|
||||||
result.push_back(lottiejson11::Json(b));
|
|
||||||
result.push_back(lottiejson11::Json(a));
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
static Color fromString(std::string const &string);
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif /* Color_hpp */
|
|
||||||
@ -1,22 +0,0 @@
|
|||||||
#ifndef DrawingAttributes_hpp
|
|
||||||
#define DrawingAttributes_hpp
|
|
||||||
|
|
||||||
namespace lottie {
|
|
||||||
|
|
||||||
enum class LineCap: int {
|
|
||||||
None = 0,
|
|
||||||
Butt = 1,
|
|
||||||
Round = 2,
|
|
||||||
Square = 3
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class LineJoin: int {
|
|
||||||
None = 0,
|
|
||||||
Miter = 1,
|
|
||||||
Round = 2,
|
|
||||||
Bevel = 3
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif /* DrawingAttributes_hpp */
|
|
||||||
@ -32,15 +32,6 @@ struct RenderNodeDesc {
|
|||||||
_isHidden(isHidden_) {
|
_isHidden(isHidden_) {
|
||||||
}
|
}
|
||||||
|
|
||||||
LayerParams(std::shared_ptr<CALayer> const &layer) :
|
|
||||||
_bounds(layer->bounds()),
|
|
||||||
_position(layer->position()),
|
|
||||||
_transform(layer->transform()),
|
|
||||||
_opacity(layer->opacity()),
|
|
||||||
_masksToBounds(layer->masksToBounds()),
|
|
||||||
_isHidden(layer->isHidden()) {
|
|
||||||
}
|
|
||||||
|
|
||||||
CGRect bounds() const {
|
CGRect bounds() const {
|
||||||
return _bounds;
|
return _bounds;
|
||||||
}
|
}
|
||||||
@ -300,209 +291,6 @@ static std::shared_ptr<OutputRenderNode> convertRenderTree(std::shared_ptr<Rende
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*static void visitRenderTree(std::shared_ptr<RenderTreeNode> const &node, Vector2D const &globalSize, CATransform3D const &parentTransform, bool isInvertedMask, BezierPathsBoundingBoxContext &bezierPathsBoundingBoxContext) {
|
|
||||||
if (node->isHidden() || node->alpha() == 0.0f) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (node->masksToBounds()) {
|
|
||||||
if (node->bounds().empty()) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto currentTransform = parentTransform;
|
|
||||||
|
|
||||||
Vector2D localTranslation(node->position().x - node->bounds().x, node->position().y - node->bounds().y);
|
|
||||||
CATransform3D localTransform = node->transform();
|
|
||||||
localTransform = localTransform.translated(localTranslation);
|
|
||||||
|
|
||||||
currentTransform = localTransform * currentTransform;
|
|
||||||
|
|
||||||
if (!currentTransform.isInvertible()) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<CGRect> effectiveLocalBounds;
|
|
||||||
|
|
||||||
double alpha = node->alpha();
|
|
||||||
|
|
||||||
if (node->content()) {
|
|
||||||
RenderTreeNodeContent *shapeContent = node->content().get();
|
|
||||||
|
|
||||||
CGRect shapeBounds = bezierPathsBoundingBoxParallel(bezierPathsBoundingBoxContext, shapeContent->paths);
|
|
||||||
|
|
||||||
if (shapeContent->stroke) {
|
|
||||||
shapeBounds = shapeBounds.insetBy(-shapeContent->stroke->lineWidth / 2.0, -shapeContent->stroke->lineWidth / 2.0);
|
|
||||||
effectiveLocalBounds = shapeBounds;
|
|
||||||
|
|
||||||
switch (shapeContent->stroke->shading->type()) {
|
|
||||||
case RenderTreeNodeContent::ShadingType::Solid: {
|
|
||||||
RenderTreeNodeContent::SolidShading *solidShading = (RenderTreeNodeContent::SolidShading *)shapeContent->stroke->shading.get();
|
|
||||||
|
|
||||||
alpha *= solidShading->opacity;
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case RenderTreeNodeContent::ShadingType::Gradient: {
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else if (shapeContent->fill) {
|
|
||||||
effectiveLocalBounds = shapeBounds;
|
|
||||||
|
|
||||||
switch (shapeContent->fill->shading->type()) {
|
|
||||||
case RenderTreeNodeContent::ShadingType::Solid: {
|
|
||||||
RenderTreeNodeContent::SolidShading *solidShading = (RenderTreeNodeContent::SolidShading *)shapeContent->fill->shading.get();
|
|
||||||
|
|
||||||
alpha *= solidShading->opacity;
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case RenderTreeNodeContent::ShadingType::Gradient: {
|
|
||||||
RenderTreeNodeContent::GradientShading *gradientShading = (RenderTreeNodeContent::GradientShading *)shapeContent->fill->shading.get();
|
|
||||||
|
|
||||||
alpha *= gradientShading->opacity;
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isInvertedMatte = isInvertedMask;
|
|
||||||
if (isInvertedMatte) {
|
|
||||||
effectiveLocalBounds = node->bounds();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (effectiveLocalBounds && effectiveLocalBounds->empty()) {
|
|
||||||
effectiveLocalBounds = std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<CGRect> effectiveLocalRect;
|
|
||||||
if (effectiveLocalBounds.has_value()) {
|
|
||||||
effectiveLocalRect = effectiveLocalBounds;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<std::shared_ptr<OutputRenderNode>> subnodes;
|
|
||||||
std::optional<CGRect> subnodesGlobalRect;
|
|
||||||
bool masksToBounds = node->masksToBounds();
|
|
||||||
|
|
||||||
int drawContentDescendants = 0;
|
|
||||||
|
|
||||||
for (const auto &item : node->subnodes()) {
|
|
||||||
if (const auto subnode = convertRenderTree(item, globalSize, currentTransform, false, bezierPathsBoundingBoxContext)) {
|
|
||||||
subnodes.push_back(subnode);
|
|
||||||
|
|
||||||
drawContentDescendants += subnode->drawContentDescendants;
|
|
||||||
|
|
||||||
if (subnode->renderContent) {
|
|
||||||
drawContentDescendants += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!subnode->localRect.empty()) {
|
|
||||||
if (effectiveLocalRect.has_value()) {
|
|
||||||
effectiveLocalRect = effectiveLocalRect->unionWith(subnode->localRect);
|
|
||||||
} else {
|
|
||||||
effectiveLocalRect = subnode->localRect;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (subnodesGlobalRect) {
|
|
||||||
subnodesGlobalRect = subnodesGlobalRect->unionWith(subnode->globalRect);
|
|
||||||
} else {
|
|
||||||
subnodesGlobalRect = subnode->globalRect;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (masksToBounds && effectiveLocalRect.has_value()) {
|
|
||||||
if (node->bounds().contains(effectiveLocalRect.value())) {
|
|
||||||
masksToBounds = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<CGRect> fuzzyGlobalRect;
|
|
||||||
|
|
||||||
if (effectiveLocalBounds) {
|
|
||||||
CGRect effectiveGlobalBounds = effectiveLocalBounds->applyingTransform(currentTransform);
|
|
||||||
if (fuzzyGlobalRect) {
|
|
||||||
fuzzyGlobalRect = fuzzyGlobalRect->unionWith(effectiveGlobalBounds);
|
|
||||||
} else {
|
|
||||||
fuzzyGlobalRect = effectiveGlobalBounds;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (subnodesGlobalRect) {
|
|
||||||
if (fuzzyGlobalRect) {
|
|
||||||
fuzzyGlobalRect = fuzzyGlobalRect->unionWith(subnodesGlobalRect.value());
|
|
||||||
} else {
|
|
||||||
fuzzyGlobalRect = subnodesGlobalRect;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!fuzzyGlobalRect) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
CGRect globalRect(
|
|
||||||
std::floor(fuzzyGlobalRect->x),
|
|
||||||
std::floor(fuzzyGlobalRect->y),
|
|
||||||
std::ceil(fuzzyGlobalRect->width + fuzzyGlobalRect->x - floor(fuzzyGlobalRect->x)),
|
|
||||||
std::ceil(fuzzyGlobalRect->height + fuzzyGlobalRect->y - floor(fuzzyGlobalRect->y))
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!CGRect(0.0, 0.0, globalSize.x, globalSize.y).intersects(globalRect)) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (masksToBounds && effectiveLocalBounds) {
|
|
||||||
CGRect effectiveGlobalBounds = effectiveLocalBounds->applyingTransform(currentTransform);
|
|
||||||
if (effectiveGlobalBounds.contains(CGRect(0.0, 0.0, globalSize.x, globalSize.y))) {
|
|
||||||
masksToBounds = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::shared_ptr<OutputRenderNode> maskNode;
|
|
||||||
if (node->mask()) {
|
|
||||||
if (const auto maskNodeValue = convertRenderTree(node->mask(), globalSize, currentTransform, node->invertMask(), bezierPathsBoundingBoxContext)) {
|
|
||||||
if (!maskNodeValue->globalRect.intersects(globalRect)) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
maskNode = maskNodeValue;
|
|
||||||
} else {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
CGRect localRect = effectiveLocalRect.value_or(CGRect(0.0, 0.0, 0.0, 0.0)).applyingTransform(localTransform);
|
|
||||||
|
|
||||||
return std::make_shared<OutputRenderNode>(
|
|
||||||
OutputRenderNode::LayerParams(
|
|
||||||
node->bounds(),
|
|
||||||
node->position(),
|
|
||||||
node->transform(),
|
|
||||||
alpha,
|
|
||||||
masksToBounds,
|
|
||||||
node->isHidden()
|
|
||||||
),
|
|
||||||
globalRect,
|
|
||||||
localRect,
|
|
||||||
currentTransform,
|
|
||||||
effectiveLocalBounds.has_value(),
|
|
||||||
node->content(),
|
|
||||||
drawContentDescendants,
|
|
||||||
isInvertedMatte,
|
|
||||||
subnodes,
|
|
||||||
maskNode
|
|
||||||
);
|
|
||||||
}*/
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@interface LottieAnimationContainer () {
|
@interface LottieAnimationContainer () {
|
||||||
@ -542,7 +330,7 @@ static std::shared_ptr<OutputRenderNode> convertRenderTree(std::shared_ptr<Rende
|
|||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (size.width < 0.0) {
|
if (size.width <= 0.0 || size.height <= 0.0) {
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -574,6 +362,11 @@ static std::shared_ptr<OutputRenderNode> convertRenderTree(std::shared_ptr<Rende
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (std::shared_ptr<lottie::RenderTreeNode>)internalGetRootRenderTreeNode {
|
||||||
|
auto renderNode = _layer->renderTreeNode();
|
||||||
|
return renderNode;
|
||||||
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation LottieAnimationContainer (Internal)
|
@implementation LottieAnimationContainer (Internal)
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
#include <LottieCpp/CGPath.h>
|
#include <LottieCpp/CGPath.h>
|
||||||
#include <LottieCpp/CGPathCocoa.h>
|
#include <LottieCpp/CGPathCocoa.h>
|
||||||
#include "Lottie/Public/Primitives/Color.hpp"
|
#import <LottieCpp/Color.h>
|
||||||
#include "Lottie/Public/Primitives/CALayer.hpp"
|
#include "Lottie/Public/Primitives/CALayer.hpp"
|
||||||
#include <LottieCpp/VectorsCocoa.h>
|
#include <LottieCpp/VectorsCocoa.h>
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user