diff --git a/Tests/LottieMetalTest/SoftwareLottieRenderer/PublicHeaders/SoftwareLottieRenderer/SoftwareLottieRenderer.h b/Tests/LottieMetalTest/SoftwareLottieRenderer/PublicHeaders/SoftwareLottieRenderer/SoftwareLottieRenderer.h index 7a3dded9ce..4293902250 100644 --- a/Tests/LottieMetalTest/SoftwareLottieRenderer/PublicHeaders/SoftwareLottieRenderer/SoftwareLottieRenderer.h +++ b/Tests/LottieMetalTest/SoftwareLottieRenderer/PublicHeaders/SoftwareLottieRenderer/SoftwareLottieRenderer.h @@ -11,7 +11,14 @@ extern "C" { #endif 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 } diff --git a/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/SoftwareLottieRenderer.mm b/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/SoftwareLottieRenderer.mm index 3d5b3ce137..b5e1964dd1 100644 --- a/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/SoftwareLottieRenderer.mm +++ b/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/SoftwareLottieRenderer.mm @@ -3,15 +3,225 @@ #import "Canvas.h" #import "CoreGraphicsCanvasImpl.h" +#include + +namespace lottie { + +static void processRenderTree(std::shared_ptr 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 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 effectiveLocalRect; + if (effectiveLocalBounds.has_value()) { + effectiveLocalRect = effectiveLocalBounds; + } + + std::optional 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 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 { -static void drawLottieRenderableItem(std::shared_ptr context, LottieRenderContent * _Nonnull item) { - if (item.path == nil) { +static void drawLottieRenderableItem(std::shared_ptr context, std::shared_ptr item) { + if (item->paths.empty()) { return; } std::shared_ptr path = lottie::CGPath::makePath(); - [item.path enumerateItems:^(LottiePathItem * _Nonnull pathItem) { + + const auto iterate = [&](LottiePathItem const *pathItem) { switch (pathItem->type) { case LottiePathItemTypeMoveTo: { path->moveTo(lottie::Vector2D(pathItem->points[0].x, pathItem->points[0].y)); @@ -33,23 +243,52 @@ static void drawLottieRenderableItem(std::shared_ptr co break; } } - }]; + }; - if (item.stroke != nil) { - if ([item.stroke.shading isKindOfClass:[LottieRenderContentSolidShading class]]) { - LottieRenderContentSolidShading *solidShading = (LottieRenderContentSolidShading *)item.stroke.shading; + LottiePathItem pathItem; + for (const auto &path : item->paths) { + std::optional 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; - switch (item.stroke.lineJoin) { - case kCGLineJoinBevel: { + switch (item->stroke->lineJoin) { + case lottie::LineJoin::Bevel: { lineJoin = lottieRendering::LineJoin::Bevel; break; } - case kCGLineJoinRound: { + case lottie::LineJoin::Round: { lineJoin = lottieRendering::LineJoin::Round; break; } - case kCGLineJoinMiter: { + case lottie::LineJoin::Miter: { lineJoin = lottieRendering::LineJoin::Miter; break; } @@ -59,16 +298,16 @@ static void drawLottieRenderableItem(std::shared_ptr co } lottieRendering::LineCap lineCap = lottieRendering::LineCap::Square; - switch (item.stroke.lineCap) { - case kCGLineCapButt: { + switch (item->stroke->lineCap) { + case lottie::LineCap::Butt: { lineCap = lottieRendering::LineCap::Butt; break; } - case kCGLineCapRound: { + case lottie::LineCap::Round: { lineCap = lottieRendering::LineCap::Round; break; } - case kCGLineCapSquare: { + case lottie::LineCap::Square: { lineCap = lottieRendering::LineCap::Square; break; } @@ -78,24 +317,22 @@ static void drawLottieRenderableItem(std::shared_ptr co } std::vector dashPattern; - if (item.stroke.dashPattern != nil) { - for (NSNumber *value in item.stroke.dashPattern) { - dashPattern.push_back([value doubleValue]); - } + if (!item->stroke->dashPattern.empty()) { + dashPattern = item->stroke->dashPattern; } - 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]]) { - __unused LottieRenderContentGradientShading *gradientShading = (LottieRenderContentGradientShading *)item.stroke.shading; + 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->type() == lottie::RenderTreeNodeContent::ShadingType::Gradient) { + //TODO:gradient stroke } - } else if (item.fill != nil) { + } else if (item->fill) { lottieRendering::FillRule rule = lottieRendering::FillRule::NonZeroWinding; - switch (item.fill.fillRule) { - case LottieFillRuleEvenOdd: { + switch (item->fill->rule) { + case lottie::FillRule::EvenOdd: { rule = lottieRendering::FillRule::EvenOdd; break; } - case LottieFillRuleWinding: { + case lottie::FillRule::NonZeroWinding: { rule = lottieRendering::FillRule::NonZeroWinding; break; } @@ -104,30 +341,29 @@ static void drawLottieRenderableItem(std::shared_ptr co } } - if ([item.fill.shading isKindOfClass:[LottieRenderContentSolidShading class]]) { - LottieRenderContentSolidShading *solidShading = (LottieRenderContentSolidShading *)item.fill.shading; - - context->fillPath(path, rule, lottieRendering::Color(solidShading.color.r, solidShading.color.g, solidShading.color.b, solidShading.color.a)); - } else if ([item.fill.shading isKindOfClass:[LottieRenderContentGradientShading class]]) { - LottieRenderContentGradientShading *gradientShading = (LottieRenderContentGradientShading *)item.fill.shading; + if (item->fill->shading->type() == lottie::RenderTreeNodeContent::ShadingType::Solid) { + 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)); + } else if (item->fill->shading->type() == lottie::RenderTreeNodeContent::ShadingType::Gradient) { + lottie::RenderTreeNodeContent::GradientShading *gradientShading = (lottie::RenderTreeNodeContent::GradientShading *)item->fill->shading.get(); std::vector colors; std::vector locations; - for (LottieColorStop *colorStop in gradientShading.colorStops) { - colors.push_back(lottieRendering::Color(colorStop.color.r, colorStop.color.g, colorStop.color.b, colorStop.color.a)); - locations.push_back(colorStop.location); + for (const auto &color : gradientShading->colors) { + colors.push_back(lottieRendering::Color(color.r, color.g, color.b, color.a)); } + locations = gradientShading->locations; lottieRendering::Gradient gradient(colors, locations); - lottie::Vector2D start(gradientShading.start.x, gradientShading.start.y); - lottie::Vector2D end(gradientShading.end.x, gradientShading.end.y); + lottie::Vector2D start(gradientShading->start.x, gradientShading->start.y); + lottie::Vector2D end(gradientShading->end.x, gradientShading->end.y); - switch (gradientShading.gradientType) { - case LottieGradientTypeLinear: { + switch (gradientShading->gradientType) { + case lottie::GradientType::Linear: { context->linearGradientFillPath(path, rule, gradient, start, end); break; } - case LottieGradientTypeRadial: { + case lottie::GradientType::Radial: { context->radialGradientFillPath(path, rule, gradient, start, 0.0, start, start.distanceTo(end)); break; } @@ -139,11 +375,14 @@ static void drawLottieRenderableItem(std::shared_ptr co } } -static void renderLottieRenderNode(LottieRenderNode * _Nonnull node, std::shared_ptr parentContext, lottie::Vector2D const &globalSize, double parentAlpha) { - float normalizedOpacity = node.opacity; +static void renderLottieRenderNode(std::shared_ptr node, std::shared_ptr parentContext, lottie::Vector2D const &globalSize, double parentAlpha) { + if (!node->renderData.isValid) { + return; + } + float normalizedOpacity = node->renderData.layer.opacity(); double layerAlpha = ((double)normalizedOpacity) * parentAlpha; - if (node.isHidden || normalizedOpacity == 0.0f) { + if (node->renderData.layer.isHidden() || normalizedOpacity == 0.0f) { return; } @@ -154,44 +393,44 @@ static void renderLottieRenderNode(LottieRenderNode * _Nonnull node, std::shared std::shared_ptr tempContext; bool needsTempContext = false; - if (node.mask != nil) { + if (node->mask() && node->mask()->renderData.isValid) { needsTempContext = true; } else { - needsTempContext = layerAlpha != 1.0 || node.masksToBounds; + needsTempContext = layerAlpha != 1.0 || node->renderData.layer.masksToBounds(); } if (needsTempContext) { - if (node.mask != nil || node.masksToBounds) { - auto maskBackingStorage = parentContext->makeLayer((int)(node.globalRect.size.width), (int)(node.globalRect.size.height)); + if ((node->mask() && node->mask()->renderData.isValid) || node->renderData.layer.masksToBounds()) { + 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::fromNativeTransform(node.globalTransform)); + maskBackingStorage->concatenate(lottie::CATransform3D::identity().translated(lottie::Vector2D(-node->renderData.globalRect.x, -node->renderData.globalRect.y))); + maskBackingStorage->concatenate(node->renderData.globalTransform); - if (node.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)); + if (node->renderData.layer.masksToBounds()) { + 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) { - renderLottieRenderNode(node.mask, maskBackingStorage, globalSize, 1.0); + if (node->mask() && node->mask()->renderData.isValid) { + renderLottieRenderNode(node->mask(), maskBackingStorage, globalSize, 1.0); } 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; 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->concatenate(lottie::fromNativeTransform(node.globalTransform)); + currentContext->concatenate(node->renderData.globalTransform); } else { 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.bounds.origin.x, -node.bounds.origin.y))); - parentContext->concatenate(lottie::fromNativeTransform(node.transform)); + 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->renderData.layer.bounds().x, -node->renderData.layer.bounds().y))); + parentContext->concatenate(node->renderData.layer.transform()); double renderAlpha = 1.0; if (tempContext) { @@ -202,17 +441,19 @@ static void renderLottieRenderNode(LottieRenderNode * _Nonnull node, std::shared currentContext->setAlpha(renderAlpha); - if (node.renderContent != nil) { - drawLottieRenderableItem(currentContext, node.renderContent); + if (node->content()) { + drawLottieRenderableItem(currentContext, node->content()); } - if (node.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)); + if (node->renderData.isInvertedMatte) { + 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); } - for (LottieRenderNode *subnode in node.subnodes) { - renderLottieRenderNode(subnode, currentContext, globalSize, renderAlpha); + for (const auto &subnode : node->subnodes()) { + if (subnode->renderData.isValid) { + renderLottieRenderNode(subnode, currentContext, globalSize, renderAlpha); + } } if (tempContext) { @@ -220,12 +461,12 @@ static void renderLottieRenderNode(LottieRenderNode * _Nonnull node, std::shared if (maskContext) { 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->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(); @@ -238,19 +479,42 @@ CGRect getPathNativeBoundingBox(CGPathRef _Nonnull path) { return CGRectMake(rect.origin.x, rect.origin.y, rect.size.width, rect.size.height); } -UIImage * _Nullable renderLottieAnimationContainer(LottieAnimationContainer * _Nonnull animationContainer, CGSize size, bool useReferenceRendering) { - LottieAnimation *animation = animationContainer.animation; - LottieRenderNode *lottieNode = [animationContainer getCurrentRenderTreeForSize:size]; +@interface SoftwareLottieRenderer() { + LottieAnimationContainer *_animationContainer; + std::shared_ptr _bezierPathsBoundingBoxContext; +} + +@end + +@implementation SoftwareLottieRenderer + +- (instancetype _Nonnull)initWithAnimationContainer:(LottieAnimationContainer * _Nonnull)animationContainer { + self = [super init]; + if (self != nil) { + _animationContainer = animationContainer; + _bezierPathsBoundingBoxContext = std::make_shared(); + } + return self; +} + +- (UIImage * _Nullable)renderForSize:(CGSize)size useReferenceRendering:(bool)useReferenceRendering { + LottieAnimation *animation = _animationContainer.animation; + std::shared_ptr 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) { auto context = std::make_shared((int)size.width, (int)size.height); - if (lottieNode) { - 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)); - - renderLottieRenderNode(lottieNode, context, lottie::Vector2D(context->width(), context->height()), 1.0); - } + 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)); + + renderLottieRenderNode(renderNode, context, lottie::Vector2D(context->width(), context->height()), 1.0); auto image = context->makeImage(); @@ -259,3 +523,5 @@ UIImage * _Nullable renderLottieAnimationContainer(LottieAnimationContainer * _N return nil; } } + +@end diff --git a/Tests/LottieMetalTest/Sources/CompareToReferenceRendering.swift b/Tests/LottieMetalTest/Sources/CompareToReferenceRendering.swift index 3889a5e3d3..f80c8cc1a5 100644 --- a/Tests/LottieMetalTest/Sources/CompareToReferenceRendering.swift +++ b/Tests/LottieMetalTest/Sources/CompareToReferenceRendering.swift @@ -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 renderer = SoftwareLottieRenderer(animationContainer: layer) + for i in 0 ..< min(100000, animation.frameCount) { let frameResult = autoreleasepool { let frameIndex = i % animation.frameCount @@ -87,7 +89,7 @@ func processDrawAnimation(baseCachePath: String, path: String, name: String, siz let referenceImage = decompressImageFrame(data: referenceImageData) layer.update(frameIndex) - let image = renderLottieAnimationContainer(layer, size, true)! + let image = renderer.render(for: size, useReferenceRendering: true)! if let diffImage = areImagesEqual(image, referenceImage) { updateImage(diffImage, referenceImage) diff --git a/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/BezierPath.h b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/BezierPath.h new file mode 100644 index 0000000000..f3031fa6d3 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/BezierPath.h @@ -0,0 +1,151 @@ +#ifndef BezierPath_h +#define BezierPath_h + +#ifdef __cplusplus + +#include +#include +#include + +#include + +namespace lottie { + +struct BezierTrimPathPosition { + double start; + double end; + + explicit BezierTrimPathPosition(double start_, double end_); +}; + +class BezierPathContents: public std::enable_shared_from_this { +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() const; + +public: + std::vector elements; + std::optional closed; + + double length(); + +private: + std::optional _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> trim(double fromLength, double toLength, double offsetLength); + + // MARK: Private + + std::vector> trimPathAtLengths(std::vector 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 trim(double fromLength, double toLength, double offsetLength); + + std::vector const &elements() const; + std::vector &mutableElements(); + std::optional const &closed() const; + void setClosed(std::optional const &closed); + std::shared_ptr cgPath() const; + BezierPath copyUsingTransform(CATransform3D const &transform) const; + +public: + BezierPath(std::shared_ptr contents); + +private: + std::shared_ptr _contents; +}; + +class BezierPathsBoundingBoxContext { +public: + BezierPathsBoundingBoxContext(); + ~BezierPathsBoundingBoxContext(); + +public: + float *pointsX = nullptr; + float *pointsY = nullptr; + int pointsSize = 0; +}; + +CGRect bezierPathsBoundingBox(std::vector const &paths); +CGRect bezierPathsBoundingBoxParallel(BezierPathsBoundingBoxContext &context, std::vector const &paths); + +} + +#endif + +#endif /* BezierPath_h */ diff --git a/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/Color.h b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/Color.h new file mode 100644 index 0000000000..9f20d05a3e --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/Color.h @@ -0,0 +1,55 @@ +#ifndef LottieColor_h +#define LottieColor_h + +#ifdef __cplusplus + +#include +#include + +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 */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/CurveVertex.hpp b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/CurveVertex.h similarity index 99% rename from submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/CurveVertex.hpp rename to submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/CurveVertex.h index 93897f0288..1734cd3be9 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/CurveVertex.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/CurveVertex.h @@ -1,5 +1,7 @@ -#ifndef CurveVertex_hpp -#define CurveVertex_hpp +#ifndef CurveVertex_h +#define CurveVertex_h + +#ifdef __cplusplus #include #include @@ -194,4 +196,6 @@ public: } +#endif + #endif /* CurveVertex_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/LottieAnimationContainer.h b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/LottieAnimationContainer.h index f2bd278fd6..7d99bda20e 100644 --- a/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/LottieAnimationContainer.h +++ b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/LottieAnimationContainer.h @@ -1,10 +1,22 @@ #ifndef LottieAnimationContainer_h #define LottieAnimationContainer_h +#ifdef __cplusplus + #import "LottieAnimation.h" #import "LottieRenderTree.h" #import "LottieAnimationContainer.h" +#include + +namespace lottie { + +class RenderTreeNode; + +} + +#endif + #ifdef __cplusplus extern "C" { #endif @@ -18,6 +30,10 @@ extern "C" { - (void)update:(NSInteger)frame; - (LottieRenderNode * _Nullable)getCurrentRenderTreeForSize:(CGSize)size; +#ifdef __cplusplus +- (std::shared_ptr)internalGetRootRenderTreeNode; +#endif + @end #ifdef __cplusplus diff --git a/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/LottieCpp.h b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/LottieCpp.h index 93d87a0569..22091e064b 100644 --- a/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/LottieCpp.h +++ b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/LottieCpp.h @@ -6,9 +6,15 @@ #import #import #import +#import #import #import #import #import +#import +#import +#import +#import +#import #endif /* LottieCpp_h */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/PathElement.hpp b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/PathElement.h similarity index 95% rename from submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/PathElement.hpp rename to submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/PathElement.h index 16af3c82ee..46478ceeed 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/PathElement.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/PathElement.h @@ -1,7 +1,9 @@ -#ifndef PathElement_hpp -#define PathElement_hpp +#ifndef PathElement_h +#define PathElement_h -#include "Lottie/Private/Utility/Primitives/CurveVertex.hpp" +#ifdef __cplusplus + +#include namespace lottie { @@ -87,4 +89,6 @@ struct PathElement { } -#endif /* PathElement_hpp */ +#endif + +#endif /* PathElement_h */ diff --git a/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/RenderTreeNode.h b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/RenderTreeNode.h new file mode 100644 index 0000000000..daa07c3472 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/RenderTreeNode.h @@ -0,0 +1,523 @@ +#ifndef RenderTreeNode_hpp +#define RenderTreeNode_hpp + +#ifdef __cplusplus + +#include +#include +#include +#include +#include + +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 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 dashPattern; + + Stroke( + Color color_, + double lineWidth_, + LineJoin lineJoin_, + LineCap lineCap_, + double dashPhase_, + std::vector 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 path_, + std::optional const &fill_, + std::optional 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 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 path; + std::optional fill; + std::optional stroke; +}; + +class GradientFillRenderableItem: public RenderableItem { +public: + GradientFillRenderableItem( + std::shared_ptr path_, + FillRule pathFillRule_, + GradientType gradientType_, + std::vector const &colors_, + std::vector 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 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 path; + FillRule pathFillRule; + GradientType gradientType; + std::vector colors; + std::vector 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 const &colors_, + std::vector 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 colors; + std::vector locations; + Vector2D start; + Vector2D end; + }; + + struct Stroke { + std::shared_ptr shading; + double lineWidth = 0.0; + LineJoin lineJoin = LineJoin::Round; + LineCap lineCap = LineCap::Square; + double miterLimit = 4.0; + double dashPhase = 0.0; + std::vector dashPattern; + + Stroke( + std::shared_ptr shading_, + double lineWidth_, + LineJoin lineJoin_, + LineCap lineCap_, + double miterLimit_, + double dashPhase_, + std::vector dashPattern_ + ) : + shading(shading_), + lineWidth(lineWidth_), + lineJoin(lineJoin_), + lineCap(lineCap_), + miterLimit(miterLimit_), + dashPhase(dashPhase_), + dashPattern(dashPattern_) { + } + }; + + struct Fill { + std::shared_ptr shading; + FillRule rule; + + Fill( + std::shared_ptr shading_, + FillRule rule_ + ) : + shading(shading_), + rule(rule_) { + } + }; + +public: + RenderTreeNodeContent( + std::vector paths_, + std::shared_ptr stroke_, + std::shared_ptr fill_ + ) : + paths(paths_), + stroke(stroke_), + fill(fill_) { + } + +public: + std::vector paths; + std::shared_ptr stroke; + std::shared_ptr 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 content_, + std::vector> subnodes_, + std::shared_ptr 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 const &content() const { + return _content; + } + + std::vector> const &subnodes() const { + return _subnodes; + } + + std::shared_ptr 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 _content; + std::vector> _subnodes; + std::shared_ptr _mask; + bool _invertMask = false; + + ProcessedRenderTreeNodeData renderData; +}; + +} + +#endif + +#endif /* RenderTreeNode_h */ diff --git a/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/ShapeAttributes.h b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/ShapeAttributes.h new file mode 100644 index 0000000000..287f7fe491 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/ShapeAttributes.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 */ diff --git a/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/lottiejson11.hpp b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/lottiejson11.hpp index 7761b93d49..aa188f366b 100644 --- a/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/lottiejson11.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/lottiejson11.hpp @@ -50,6 +50,8 @@ #pragma once +#ifdef __cplusplus + #include #include #include @@ -230,3 +232,5 @@ protected: }; } // namespace lottiejson11 + +#endif \ No newline at end of file diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/ShapeUtils/BezierPathUtils.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/ShapeUtils/BezierPathUtils.hpp index 9d9d27b612..6b223adc3d 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/ShapeUtils/BezierPathUtils.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/ShapeUtils/BezierPathUtils.hpp @@ -2,7 +2,7 @@ #define BezierPaths_h #include "Lottie/Private/Model/ShapeItems/Ellipse.hpp" -#include "Lottie/Private/Utility/Primitives/BezierPath.hpp" +#include #include "Lottie/Private/Utility/Primitives/CompoundBezierPath.hpp" #include "Lottie/Private/Model/ShapeItems/Trim.hpp" diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/Mask.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/Mask.hpp index fd3c2cba7b..0a4d790684 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/Mask.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/Mask.hpp @@ -2,7 +2,7 @@ #define Mask_hpp #include "Lottie/Private/Model/Keyframes/KeyframeGroup.hpp" -#include "Lottie/Private/Utility/Primitives/BezierPath.hpp" +#include #include "Lottie/Private/Parsing/JsonParsing.hpp" namespace lottie { diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Fill.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Fill.hpp index 78c9432f44..f8139628a6 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Fill.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Fill.hpp @@ -3,18 +3,13 @@ #include "Lottie/Private/Model/Keyframes/KeyframeGroup.hpp" #include "Lottie/Private/Model/ShapeItems/ShapeItem.hpp" -#include "Lottie/Public/Primitives/Color.hpp" +#import #include #include "Lottie/Private/Parsing/JsonParsing.hpp" +#include namespace lottie { -enum class FillRule: int { - None = 0, - NonZeroWinding = 1, - EvenOdd = 2 -}; - class Fill: public ShapeItem { public: explicit Fill(lottiejson11::Json::object const &json) noexcept(false) : diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/GradientFill.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/GradientFill.hpp index fbe8bdbe16..c0fecb7baf 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/GradientFill.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/GradientFill.hpp @@ -5,15 +5,10 @@ #include "Lottie/Private/Model/Keyframes/KeyframeGroup.hpp" #include "Lottie/Private/Parsing/JsonParsing.hpp" #include "Lottie/Public/Primitives/GradientColorSet.hpp" +#include namespace lottie { -enum class GradientType: int { - None = 0, - Linear = 1, - Radial = 2 -}; - /// An item that define a gradient fill class GradientFill: public ShapeItem { public: diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/GradientStroke.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/GradientStroke.hpp index ff60fc1b20..4110e7cf78 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/GradientStroke.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/GradientStroke.hpp @@ -6,7 +6,7 @@ #include "Lottie/Private/Model/Keyframes/KeyframeGroup.hpp" #include "Lottie/Private/Model/Objects/DashElement.hpp" #include "Lottie/Private/Parsing/JsonParsing.hpp" -#include "Lottie/Public/Primitives/DrawingAttributes.hpp" +#include namespace lottie { diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Shape.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Shape.hpp index 0fd4a2dd76..66a5a36f85 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Shape.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Shape.hpp @@ -3,7 +3,7 @@ #include "Lottie/Private/Model/ShapeItems/ShapeItem.hpp" #include "Lottie/Private/Model/ShapeItems/Ellipse.hpp" -#include "Lottie/Private/Utility/Primitives/BezierPath.hpp" +#include #include "Lottie/Private/Parsing/JsonParsing.hpp" #include diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Stroke.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Stroke.hpp index 0131f7e69f..b3d8ff3066 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Stroke.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Stroke.hpp @@ -3,7 +3,7 @@ #include "Lottie/Private/Model/ShapeItems/ShapeItem.hpp" #include "Lottie/Private/Model/ShapeItems/GradientStroke.hpp" -#include "Lottie/Public/Primitives/Color.hpp" +#import #include "Lottie/Private/Model/Keyframes/KeyframeGroup.hpp" #include "Lottie/Private/Model/Objects/DashElement.hpp" #include "Lottie/Private/Parsing/JsonParsing.hpp" diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Text/TextAnimator.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Text/TextAnimator.hpp index 838cf8aab3..6724c7e3fa 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Text/TextAnimator.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Text/TextAnimator.hpp @@ -2,7 +2,7 @@ #define TextAnimator_hpp #include -#include "Lottie/Public/Primitives/Color.hpp" +#import #include "Lottie/Private/Model/Keyframes/KeyframeGroup.hpp" #include "Lottie/Private/Parsing/JsonParsing.hpp" diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Text/TextDocument.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Text/TextDocument.hpp index 5c7ef247dc..d692dd0d77 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Text/TextDocument.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Text/TextDocument.hpp @@ -2,7 +2,7 @@ #define TextDocument_hpp #include -#include "Lottie/Public/Primitives/Color.hpp" +#import #include "Lottie/Private/Parsing/JsonParsing.hpp" #include diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/BezierPath.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/BezierPath.cpp index 95b6e1fc68..5985865ef2 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/BezierPath.cpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/BezierPath.cpp @@ -1,10 +1,519 @@ -#include "BezierPath.hpp" +#include #include #include +#include "Lottie/Private/Parsing/JsonParsing.hpp" + 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 BezierPathContents::cgPath() const { + auto cgPath = CGPath::makePath(); + + std::optional 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> 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> BezierPathContents::trimPathAtLengths(std::vector const &positions) { + if (positions.empty()) { + return {}; + } + auto remainingPositions = positions; + + auto trim = remainingPositions[0]; + remainingPositions.erase(remainingPositions.begin()); + + std::vector> paths; + + double runningLength = 0.0; + bool finishedTrimming = false; + auto pathElements = elements; + + auto currentPath = std::make_shared(); + 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(); + 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(startPoint)) { +} + +BezierPath::BezierPath() : +_contents(std::make_shared()) { +} + +BezierPath::BezierPath(lottiejson11::Json const &jsonAny) noexcept(false) : +_contents(std::make_shared(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::trim(double fromLength, double toLength, double offsetLength) { + std::vector result; + + auto resultContents = _contents->trim(fromLength, toLength, offsetLength); + for (const auto &resultContent : resultContents) { + result.emplace_back(resultContent); + } + + return result; +} + +std::vector const &BezierPath::elements() const { + return _contents->elements; +} + +std::vector &BezierPath::mutableElements() { + return _contents->elements; +} + +std::optional const &BezierPath::closed() const { + return _contents->closed; +} +void BezierPath::setClosed(std::optional const &closed) { + _contents->closed = closed; +} + +std::shared_ptr 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 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) { float minX = 0.0; float maxX = 0.0; diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/BezierPath.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/BezierPath.hpp deleted file mode 100644 index 5b1d404c53..0000000000 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/BezierPath.hpp +++ /dev/null @@ -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 - -#include - -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 { -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() const { - auto cgPath = CGPath::makePath(); - - std::optional 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 elements; - std::optional 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 _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> 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> trimPathAtLengths(std::vector const &positions) { - if (positions.empty()) { - return {}; - } - auto remainingPositions = positions; - - auto trim = remainingPositions[0]; - remainingPositions.erase(remainingPositions.begin()); - - std::vector> paths; - - double runningLength = 0.0; - bool finishedTrimming = false; - auto pathElements = elements; - - auto currentPath = std::make_shared(); - 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(); - 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(startPoint)) { - } - - BezierPath() : - _contents(std::make_shared()) { - } - - explicit BezierPath(lottiejson11::Json const &jsonAny) noexcept(false) : - _contents(std::make_shared(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 trim(double fromLength, double toLength, double offsetLength) { - std::vector result; - - auto resultContents = _contents->trim(fromLength, toLength, offsetLength); - for (const auto &resultContent : resultContents) { - result.emplace_back(resultContent); - } - - return result; - } - - // MARK: Private - - std::vector const &elements() const { - return _contents->elements; - } - - std::vector &mutableElements() { - return _contents->elements; - } - - std::optional const &closed() const { - return _contents->closed; - } - void setClosed(std::optional const &closed) { - _contents->closed = closed; - } - - std::shared_ptr 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 contents) : - _contents(contents) { - } - -private: - std::shared_ptr _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 const &paths); -CGRect bezierPathsBoundingBoxParallel(BezierPathsBoundingBoxContext &context, std::vector const &paths); - -} - -#endif /* BezierPath_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/CompoundBezierPath.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/CompoundBezierPath.hpp index c9766c93ae..5a95f4f02d 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/CompoundBezierPath.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/CompoundBezierPath.hpp @@ -1,7 +1,7 @@ #ifndef CompoundBezierPath_hpp #define CompoundBezierPath_hpp -#include "Lottie/Private/Utility/Primitives/BezierPath.hpp" +#include namespace lottie { diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/CurveVertex.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/CurveVertex.cpp index 5c1d4a0cb0..cbdd11762a 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/CurveVertex.cpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/CurveVertex.cpp @@ -1,4 +1,4 @@ -#include "CurveVertex.hpp" +#include namespace lottie { diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/PathElement.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/PathElement.cpp index 24b73221a0..af059629cd 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/PathElement.cpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/PathElement.cpp @@ -1,4 +1,4 @@ -#include "PathElement.hpp" +#include namespace lottie { diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Keyframes/ValueInterpolators.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Keyframes/ValueInterpolators.hpp index 0e89a6a27a..2f830d23ab 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Keyframes/ValueInterpolators.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Keyframes/ValueInterpolators.hpp @@ -2,8 +2,8 @@ #define ValueInterpolators_hpp #include -#include "Lottie/Public/Primitives/Color.hpp" -#include "Lottie/Private/Utility/Primitives/BezierPath.hpp" +#import +#include #include "Lottie/Private/Model/Text/TextDocument.hpp" #include "Lottie/Public/Primitives/GradientColorSet.hpp" #include "Lottie/Public/Primitives/DashPattern.hpp" diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/AnyValue.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/AnyValue.hpp index 72cdaf674a..0cd69ea851 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/AnyValue.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/AnyValue.hpp @@ -2,8 +2,8 @@ #define AnyValue_hpp #include -#include "Lottie/Public/Primitives/Color.hpp" -#include "Lottie/Private/Utility/Primitives/BezierPath.hpp" +#import +#include #include "Lottie/Private/Model/Text/TextDocument.hpp" #include "Lottie/Private/Model/ShapeItems/GradientFill.hpp" #include "Lottie/Private/Model/Objects/DashElement.hpp" diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/CALayer.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/CALayer.hpp index b3928779c6..9f9fd4641f 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/CALayer.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/CALayer.hpp @@ -1,12 +1,13 @@ #ifndef CALayer_hpp #define CALayer_hpp -#include "Lottie/Public/Primitives/Color.hpp" +#import #include #include +#include #include "Lottie/Private/Model/ShapeItems/Fill.hpp" #include "Lottie/Private/Model/Layers/LayerModel.hpp" -#include "Lottie/Public/Primitives/DrawingAttributes.hpp" +#include #include "Lottie/Private/Model/ShapeItems/GradientFill.hpp" #include @@ -15,429 +16,6 @@ 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 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 dashPattern; - - Stroke( - Color color_, - double lineWidth_, - LineJoin lineJoin_, - LineCap lineCap_, - double dashPhase_, - std::vector 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 path_, - std::optional const &fill_, - std::optional 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 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 path; - std::optional fill; - std::optional stroke; -}; - -class GradientFillRenderableItem: public RenderableItem { -public: - GradientFillRenderableItem( - std::shared_ptr path_, - FillRule pathFillRule_, - GradientType gradientType_, - std::vector const &colors_, - std::vector 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 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 path; - FillRule pathFillRule; - GradientType gradientType; - std::vector colors; - std::vector 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 const &colors_, - std::vector 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 colors; - std::vector locations; - Vector2D start; - Vector2D end; - }; - - struct Stroke { - std::shared_ptr shading; - double lineWidth = 0.0; - LineJoin lineJoin = LineJoin::Round; - LineCap lineCap = LineCap::Square; - double miterLimit = 4.0; - double dashPhase = 0.0; - std::vector dashPattern; - - Stroke( - std::shared_ptr shading_, - double lineWidth_, - LineJoin lineJoin_, - LineCap lineCap_, - double miterLimit_, - double dashPhase_, - std::vector dashPattern_ - ) : - shading(shading_), - lineWidth(lineWidth_), - lineJoin(lineJoin_), - lineCap(lineCap_), - miterLimit(miterLimit_), - dashPhase(dashPhase_), - dashPattern(dashPattern_) { - } - }; - - struct Fill { - std::shared_ptr shading; - FillRule rule; - - Fill( - std::shared_ptr shading_, - FillRule rule_ - ) : - shading(shading_), - rule(rule_) { - } - }; - -public: - RenderTreeNodeContent( - std::vector paths_, - std::shared_ptr stroke_, - std::shared_ptr fill_ - ) : - paths(paths_), - stroke(stroke_), - fill(fill_) { - } - -public: - std::vector paths; - std::shared_ptr stroke; - std::shared_ptr fill; -}; - -class RenderTreeNode { -public: - RenderTreeNode( - CGRect bounds_, - Vector2D position_, - CATransform3D transform_, - double alpha_, - bool masksToBounds_, - bool isHidden_, - std::shared_ptr content_, - std::vector> subnodes_, - std::shared_ptr 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 const &content() const { - return _content; - } - - std::vector> const &subnodes() const { - return _subnodes; - } - - std::shared_ptr 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 _content; - std::vector> _subnodes; - std::shared_ptr _mask; - bool _invertMask = false; -}; - class CALayer: public std::enable_shared_from_this { public: CALayer() { diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/Color.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/Color.cpp index c1ecb86f45..e7c6b9996e 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/Color.cpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/Color.cpp @@ -1,9 +1,108 @@ -#include "Color.hpp" +#include + +#include "Lottie/Private/Parsing/JsonParsing.hpp" #include 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) { if (string.empty()) { return Color(0.0, 0.0, 0.0, 0.0); diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/Color.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/Color.hpp deleted file mode 100644 index df436cc932..0000000000 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/Color.hpp +++ /dev/null @@ -1,144 +0,0 @@ -#ifndef Color_hpp -#define Color_hpp - -#include "Lottie/Private/Parsing/JsonParsing.hpp" - -#include - -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 */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/DrawingAttributes.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/DrawingAttributes.hpp deleted file mode 100644 index 7b43581e65..0000000000 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/DrawingAttributes.hpp +++ /dev/null @@ -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 */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/LottieAnimationContainer.mm b/submodules/TelegramUI/Components/LottieCpp/Sources/LottieAnimationContainer.mm index b2fe181e1e..14c6a227cb 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/LottieAnimationContainer.mm +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/LottieAnimationContainer.mm @@ -32,15 +32,6 @@ struct RenderNodeDesc { _isHidden(isHidden_) { } - LayerParams(std::shared_ptr const &layer) : - _bounds(layer->bounds()), - _position(layer->position()), - _transform(layer->transform()), - _opacity(layer->opacity()), - _masksToBounds(layer->masksToBounds()), - _isHidden(layer->isHidden()) { - } - CGRect bounds() const { return _bounds; } @@ -300,209 +291,6 @@ static std::shared_ptr convertRenderTree(std::shared_ptr 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 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 effectiveLocalRect; - if (effectiveLocalBounds.has_value()) { - effectiveLocalRect = effectiveLocalBounds; - } - - std::vector> subnodes; - std::optional 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 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 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::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 () { @@ -542,7 +330,7 @@ static std::shared_ptr convertRenderTree(std::shared_ptr convertRenderTree(std::shared_ptr)internalGetRootRenderTreeNode { + auto renderNode = _layer->renderTreeNode(); + return renderNode; +} + @end @implementation LottieAnimationContainer (Internal) diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/LottieRenderTree.mm b/submodules/TelegramUI/Components/LottieCpp/Sources/LottieRenderTree.mm index b053b3b19e..c92606839d 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/LottieRenderTree.mm +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/LottieRenderTree.mm @@ -3,7 +3,7 @@ #include #include -#include "Lottie/Public/Primitives/Color.hpp" +#import #include "Lottie/Public/Primitives/CALayer.hpp" #include