diff --git a/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/SoftwareLottieRenderer.mm b/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/SoftwareLottieRenderer.mm index a4903b4bb6..789c95c35a 100644 --- a/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/SoftwareLottieRenderer.mm +++ b/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/SoftwareLottieRenderer.mm @@ -6,30 +6,147 @@ #include -namespace lottie { +namespace { -static void processRenderContentItem(std::shared_ptr const &contentItem, std::optional &effectiveLocalBounds, BezierPathsBoundingBoxContext &bezierPathsBoundingBoxContext) { - for (const auto &shadingVariant : contentItem->shadings) { - CGRect shapeBounds = bezierPathsBoundingBoxParallel(bezierPathsBoundingBoxContext, shadingVariant->explicitPath.value()); - if (shadingVariant->stroke) { - shapeBounds = shapeBounds.insetBy(-shadingVariant->stroke->lineWidth / 2.0, -shadingVariant->stroke->lineWidth / 2.0); - if (effectiveLocalBounds) { - effectiveLocalBounds = effectiveLocalBounds->unionWith(shapeBounds); - } else { - effectiveLocalBounds = shapeBounds; - } - } else if (shadingVariant->fill) { - if (effectiveLocalBounds) { - effectiveLocalBounds = effectiveLocalBounds->unionWith(shapeBounds); - } else { - effectiveLocalBounds = shapeBounds; - } +struct TransformedPath { + lottie::BezierPath path; + lottie::CATransform3D transform; + + TransformedPath(lottie::BezierPath const &path_, lottie::CATransform3D const &transform_) : + path(path_), + transform(transform_) { + } +}; + +static std::vector collectPaths(std::shared_ptr item, size_t subItemLimit, lottie::CATransform3D const &parentTransform, bool skipApplyTransform) { + std::vector mappedPaths; + + //TODO:remove skipApplyTransform + lottie::CATransform3D effectiveTransform = parentTransform; + if (!skipApplyTransform && item->isGroup) { + effectiveTransform = item->transform * effectiveTransform; + } + + size_t maxSubitem = std::min(item->subItems.size(), subItemLimit); + + if (item->path) { + mappedPaths.emplace_back(item->path.value(), effectiveTransform); + } + + for (size_t i = 0; i < maxSubitem; i++) { + auto &subItem = item->subItems[i]; + + auto subItemPaths = collectPaths(subItem, INT32_MAX, effectiveTransform, false); + + for (auto &path : subItemPaths) { + mappedPaths.emplace_back(path.path, path.transform); } } - for (const auto &subItem : contentItem->subItems) { - processRenderContentItem(subItem, effectiveLocalBounds, bezierPathsBoundingBoxContext); + return mappedPaths; +} + +} + +namespace lottie { + +static void processRenderContentItem(std::shared_ptr const &contentItem, Vector2D const &globalSize, CATransform3D const &parentTransform, BezierPathsBoundingBoxContext &bezierPathsBoundingBoxContext) { + auto currentTransform = parentTransform; + + CATransform3D localTransform = contentItem->transform; + currentTransform = localTransform * currentTransform; + + if (!currentTransform.isInvertible()) { + contentItem->renderData.isValid = false; + return; } + + std::optional globalRect; + + int drawContentDescendants = 0; + + for (const auto &shadingVariant : contentItem->shadings) { + std::vector itemPaths; + if (shadingVariant->explicitPath) { + itemPaths = shadingVariant->explicitPath.value(); + } else { + auto rawPaths = collectPaths(contentItem, shadingVariant->subItemLimit, lottie::CATransform3D::identity(), true); + for (const auto &rawPath : rawPaths) { + itemPaths.push_back(rawPath.path.copyUsingTransform(rawPath.transform)); + } + } + + CGRect shapeBounds = bezierPathsBoundingBoxParallel(bezierPathsBoundingBoxContext, itemPaths); + if (shadingVariant->stroke) { + shapeBounds = shapeBounds.insetBy(-shadingVariant->stroke->lineWidth / 2.0, -shadingVariant->stroke->lineWidth / 2.0); + } else if (shadingVariant->fill) { + } else { + continue; + } + + drawContentDescendants += 1; + + CGRect shapeGlobalBounds = shapeBounds.applyingTransform(currentTransform); + if (globalRect) { + globalRect = globalRect->unionWith(shapeGlobalBounds); + } else { + globalRect = shapeGlobalBounds; + } + } + + if (contentItem->isGroup) { + for (const auto &subItem : contentItem->subItems) { + processRenderContentItem(subItem, globalSize, currentTransform, bezierPathsBoundingBoxContext); + + if (subItem->renderData.isValid) { + drawContentDescendants += subItem->renderData.drawContentDescendants; + if (globalRect) { + globalRect = globalRect->unionWith(subItem->renderData.globalRect); + } else { + globalRect = subItem->renderData.globalRect; + } + } + } + } else { + for (const auto &subItem : contentItem->subItems) { + subItem->renderData.isValid = false; + } + } + + if (!globalRect) { + contentItem->renderData.isValid = false; + return; + } + + CGRect integralGlobalRect( + std::floor(globalRect->x), + std::floor(globalRect->y), + std::ceil(globalRect->width + globalRect->x - floor(globalRect->x)), + std::ceil(globalRect->height + globalRect->y - floor(globalRect->y)) + ); + + if (!CGRect(0.0, 0.0, globalSize.x, globalSize.y).intersects(integralGlobalRect)) { + contentItem->renderData.isValid = false; + return; + } + if (integralGlobalRect.width <= 0.0 || integralGlobalRect.height <= 0.0) { + contentItem->renderData.isValid = false; + return; + } + + contentItem->renderData.isValid = true; + + contentItem->renderData.layer._bounds = CGRect(0.0, 0.0, 0.0, 0.0); + contentItem->renderData.layer._position = Vector2D(0.0, 0.0); + contentItem->renderData.layer._transform = contentItem->transform; + contentItem->renderData.layer._opacity = contentItem->alpha; + contentItem->renderData.layer._masksToBounds = false; + contentItem->renderData.layer._isHidden = false; + + contentItem->renderData.globalRect = integralGlobalRect; + contentItem->renderData.globalTransform = currentTransform; + contentItem->renderData.drawContentDescendants = drawContentDescendants; + contentItem->renderData.isInvertedMatte = false; } static void processRenderTree(std::shared_ptr const &node, Vector2D const &globalSize, CATransform3D const &parentTransform, bool isInvertedMask, BezierPathsBoundingBoxContext &bezierPathsBoundingBoxContext) { @@ -58,102 +175,59 @@ static void processRenderTree(std::shared_ptr const &node, Vecto return; } - std::optional effectiveLocalBounds; - - double alpha = node->alpha(); - + int drawContentDescendants = 0; + std::optional globalRect; if (node->_contentItem) { - processRenderContentItem(node->_contentItem, effectiveLocalBounds, bezierPathsBoundingBoxContext); + processRenderContentItem(node->_contentItem, globalSize, currentTransform, bezierPathsBoundingBoxContext); + if (node->_contentItem->renderData.isValid) { + drawContentDescendants += node->_contentItem->renderData.drawContentDescendants; + globalRect = node->_contentItem->renderData.globalRect; + } } bool isInvertedMatte = isInvertedMask; if (isInvertedMatte) { - effectiveLocalBounds = node->bounds(); + CGRect globalBounds = node->bounds().applyingTransform(currentTransform); + if (globalRect) { + globalRect = globalRect->unionWith(globalBounds); + } else { + globalRect = globalBounds; + } } - 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->_contentItem) { - 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); + if (globalRect) { + globalRect = globalRect->unionWith(item->renderData.globalRect); } else { - subnodesGlobalRect = item->renderData.globalRect; + globalRect = 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) { + if (!globalRect) { 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)) + CGRect integralGlobalRect( + std::floor(globalRect->x), + std::floor(globalRect->y), + std::ceil(globalRect->width + globalRect->x - floor(globalRect->x)), + std::ceil(globalRect->height + globalRect->y - floor(globalRect->y)) ); - if (!CGRect(0.0, 0.0, globalSize.x, globalSize.y).intersects(globalRect)) { + if (!CGRect(0.0, 0.0, globalSize.x, globalSize.y).intersects(integralGlobalRect)) { node->renderData.isValid = false; return; } - if (masksToBounds && effectiveLocalBounds) { - CGRect effectiveGlobalBounds = effectiveLocalBounds->applyingTransform(currentTransform); + bool masksToBounds = node->masksToBounds(); + if (masksToBounds) { + CGRect effectiveGlobalBounds = node->bounds().applyingTransform(currentTransform); if (effectiveGlobalBounds.contains(CGRect(0.0, 0.0, globalSize.x, globalSize.y))) { masksToBounds = false; } @@ -162,7 +236,7 @@ static void processRenderTree(std::shared_ptr const &node, Vecto if (node->mask()) { processRenderTree(node->mask(), globalSize, currentTransform, node->invertMask(), bezierPathsBoundingBoxContext); if (node->mask()->renderData.isValid) { - if (!node->mask()->renderData.globalRect.intersects(globalRect)) { + if (!node->mask()->renderData.globalRect.intersects(integralGlobalRect)) { node->renderData.isValid = false; return; } @@ -172,21 +246,22 @@ static void processRenderTree(std::shared_ptr const &node, Vecto } } - CGRect localRect = effectiveLocalRect.value_or(CGRect(0.0, 0.0, 0.0, 0.0)).applyingTransform(localTransform); + if (integralGlobalRect.width <= 0.0 || integralGlobalRect.height <= 0.0) { + node->renderData.isValid = false; + return; + } 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._opacity = node->alpha(); node->renderData.layer._masksToBounds = masksToBounds; node->renderData.layer._isHidden = node->isHidden(); - node->renderData.globalRect = globalRect; - node->renderData.localRect = localRect; + node->renderData.globalRect = integralGlobalRect; node->renderData.globalTransform = currentTransform; - node->renderData.drawsContent = effectiveLocalBounds.has_value(); node->renderData.drawContentDescendants = drawContentDescendants; node->renderData.isInvertedMatte = isInvertedMatte; } @@ -195,9 +270,62 @@ static void processRenderTree(std::shared_ptr const &node, Vecto namespace { -static void drawLottieContentItem(std::shared_ptr context, std::shared_ptr item) { +static void drawLottieContentItem(std::shared_ptr parentContext, std::shared_ptr item, double parentAlpha) { + if (!item->renderData.isValid) { + return; + } + + float normalizedOpacity = item->renderData.layer.opacity(); + double layerAlpha = ((double)normalizedOpacity) * parentAlpha; + + if (item->renderData.layer.isHidden() || normalizedOpacity == 0.0f) { + return; + } + + parentContext->saveState(); + + std::shared_ptr currentContext; + std::shared_ptr tempContext; + + bool needsTempContext = false; + needsTempContext = layerAlpha != 1.0 && item->renderData.drawContentDescendants > 1; + + if (needsTempContext) { + auto tempContextValue = parentContext->makeLayer((int)(item->renderData.globalRect.width), (int)(item->renderData.globalRect.height)); + tempContext = tempContextValue; + + currentContext = tempContextValue; + currentContext->concatenate(lottie::CATransform3D::identity().translated(lottie::Vector2D(-item->renderData.globalRect.x, -item->renderData.globalRect.y))); + + currentContext->saveState(); + currentContext->concatenate(item->renderData.globalTransform); + } else { + currentContext = parentContext; + } + + parentContext->concatenate(lottie::CATransform3D::identity().translated(lottie::Vector2D(item->renderData.layer.position().x, item->renderData.layer.position().y))); + parentContext->concatenate(lottie::CATransform3D::identity().translated(lottie::Vector2D(-item->renderData.layer.bounds().x, -item->renderData.layer.bounds().y))); + parentContext->concatenate(item->renderData.layer.transform()); + + double renderAlpha = 1.0; + if (tempContext) { + renderAlpha = 1.0; + } else { + renderAlpha = layerAlpha; + } + for (const auto &shading : item->shadings) { - if (shading->explicitPath->empty()) { + std::vector itemPaths; + if (shading->explicitPath) { + itemPaths = shading->explicitPath.value(); + } else { + auto rawPaths = collectPaths(item, shading->subItemLimit, lottie::CATransform3D::identity(), true); + for (const auto &rawPath : rawPaths) { + itemPaths.push_back(rawPath.path.copyUsingTransform(rawPath.transform)); + } + } + + if (itemPaths.empty()) { continue; } @@ -228,7 +356,7 @@ static void drawLottieContentItem(std::shared_ptr conte }; LottiePathItem pathItem; - for (const auto &path : shading->explicitPath.value()) { + for (const auto &path : itemPaths) { std::optional previousElement; for (const auto &element : path.elements()) { if (previousElement.has_value()) { @@ -304,7 +432,7 @@ static void drawLottieContentItem(std::shared_ptr conte dashPattern = shading->stroke->dashPattern; } - context->strokePath(path, shading->stroke->lineWidth, lineJoin, lineCap, shading->stroke->dashPhase, dashPattern, lottieRendering::Color(solidShading->color.r, solidShading->color.g, solidShading->color.b, solidShading->color.a * solidShading->opacity)); + currentContext->strokePath(path, shading->stroke->lineWidth, lineJoin, lineCap, shading->stroke->dashPhase, dashPattern, lottieRendering::Color(solidShading->color.r, solidShading->color.g, solidShading->color.b, solidShading->color.a * solidShading->opacity * renderAlpha)); } else if (shading->stroke->shading->type() == lottie::RenderTreeNodeContentItem::ShadingType::Gradient) { //TODO:gradient stroke } @@ -328,7 +456,7 @@ static void drawLottieContentItem(std::shared_ptr conte if (shading->fill->shading->type() == lottie::RenderTreeNodeContentItem::ShadingType::Solid) { lottie::RenderTreeNodeContentItem::SolidShading *solidShading = (lottie::RenderTreeNodeContentItem::SolidShading *)shading->fill->shading.get(); if (solidShading->opacity != 0.0) { - context->fillPath(path, rule, lottieRendering::Color(solidShading->color.r, solidShading->color.g, solidShading->color.b, solidShading->color.a * solidShading->opacity)); + currentContext->fillPath(path, rule, lottieRendering::Color(solidShading->color.r, solidShading->color.g, solidShading->color.b, solidShading->color.a * solidShading->opacity * renderAlpha)); } } else if (shading->fill->shading->type() == lottie::RenderTreeNodeContentItem::ShadingType::Gradient) { lottie::RenderTreeNodeContentItem::GradientShading *gradientShading = (lottie::RenderTreeNodeContentItem::GradientShading *)shading->fill->shading.get(); @@ -337,7 +465,7 @@ static void drawLottieContentItem(std::shared_ptr conte std::vector colors; std::vector locations; for (const auto &color : gradientShading->colors) { - colors.push_back(lottieRendering::Color(color.r, color.g, color.b, color.a * gradientShading->opacity)); + colors.push_back(lottieRendering::Color(color.r, color.g, color.b, color.a * gradientShading->opacity * renderAlpha)); } locations = gradientShading->locations; @@ -347,11 +475,11 @@ static void drawLottieContentItem(std::shared_ptr conte switch (gradientShading->gradientType) { case lottie::GradientType::Linear: { - context->linearGradientFillPath(path, rule, gradient, start, end); + currentContext->linearGradientFillPath(path, rule, gradient, start, end); break; } case lottie::GradientType::Radial: { - context->radialGradientFillPath(path, rule, gradient, start, 0.0, start, start.distanceTo(end)); + currentContext->radialGradientFillPath(path, rule, gradient, start, 0.0, start, start.distanceTo(end)); break; } default: { @@ -364,8 +492,19 @@ static void drawLottieContentItem(std::shared_ptr conte } for (const auto &subItem : item->subItems) { - drawLottieContentItem(context, subItem); + drawLottieContentItem(currentContext, subItem, renderAlpha); } + + if (tempContext) { + tempContext->restoreState(); + + parentContext->concatenate(item->renderData.globalTransform.inverted()); + parentContext->setAlpha(layerAlpha); + parentContext->draw(tempContext, item->renderData.globalRect); + parentContext->setAlpha(1.0); + } + + parentContext->restoreState(); } static void renderLottieRenderNode(std::shared_ptr node, std::shared_ptr parentContext, lottie::Vector2D const &globalSize, double parentAlpha) { @@ -432,10 +571,8 @@ static void renderLottieRenderNode(std::shared_ptr node, renderAlpha = layerAlpha; } - currentContext->setAlpha(renderAlpha); - if (node->_contentItem) { - drawLottieContentItem(currentContext, node->_contentItem); + drawLottieContentItem(currentContext, node->_contentItem, renderAlpha); } if (node->renderData.isInvertedMatte) { diff --git a/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/BezierPath.h b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/BezierPath.h index f3031fa6d3..43b7018442 100644 --- a/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/BezierPath.h +++ b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/BezierPath.h @@ -6,6 +6,7 @@ #include #include #include +#include #include @@ -126,7 +127,7 @@ public: public: BezierPath(std::shared_ptr contents); -private: +public: std::shared_ptr _contents; }; @@ -144,6 +145,8 @@ public: CGRect bezierPathsBoundingBox(std::vector const &paths); CGRect bezierPathsBoundingBoxParallel(BezierPathsBoundingBoxContext &context, std::vector const &paths); +std::vector trimBezierPaths(std::vector &sourcePaths, double start, double end, double offset, TrimType type); + } #endif diff --git a/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/RenderTreeNode.h b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/RenderTreeNode.h index ee22047000..3565fd8212 100644 --- a/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/RenderTreeNode.h +++ b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/RenderTreeNode.h @@ -13,6 +13,82 @@ namespace lottie { +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)), + globalTransform(CATransform3D::identity()), + drawContentDescendants(false), + isInvertedMatte(false) { + + } + + bool isValid = false; + LayerParams layer; + CGRect globalRect; + CATransform3D globalTransform; + int drawContentDescendants; + bool isInvertedMatte; +}; + class RenderableItem { public: enum class Type { @@ -345,8 +421,13 @@ public: public: bool isGroup = false; CATransform3D transform = CATransform3D::identity(); + double alpha = 0.0; + std::optional trimParams; + std::optional path; std::vector> shadings; std::vector> subItems; + + ProcessedRenderTreeNodeData renderData; }; class RenderTreeNodeContentShadingVariant { @@ -362,86 +443,6 @@ public: size_t subItemLimit = 0; }; -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( diff --git a/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/ShapeAttributes.h b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/ShapeAttributes.h index 287f7fe491..c6f11afa4c 100644 --- a/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/ShapeAttributes.h +++ b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/ShapeAttributes.h @@ -31,6 +31,25 @@ enum class GradientType: int { Radial = 2 }; +enum class TrimType: int { + Simultaneously = 1, + Individually = 2 +}; + +struct TrimParams { + double start = 0.0; + double end = 0.0; + double offset = 0.0; + TrimType type = TrimType::Simultaneously; + + TrimParams(double start_, double end_, double offset_, TrimType type_) : + start(start_), + end(end_), + offset(offset_), + type(type_) { + } +}; + } #endif diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/ShapeCompositionLayer.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/ShapeCompositionLayer.cpp index 1ffaff84f8..64f40338d4 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/ShapeCompositionLayer.cpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/ShapeCompositionLayer.cpp @@ -437,27 +437,10 @@ public: std::shared_ptr _stroke; }; - struct TrimParams { - double start = 0.0; - double end = 0.0; - double offset = 0.0; - TrimType type = TrimType::Simultaneously; - size_t subItemLimit = 0; - - TrimParams(double start_, double end_, double offset_, TrimType type_, size_t subItemLimit_) : - start(start_), - end(end_), - offset(offset_), - type(type_), - subItemLimit(subItemLimit_) { - } - }; - class TrimParamsOutput { public: - TrimParamsOutput(Trim const &trim, size_t subItemLimit) : + TrimParamsOutput(Trim const &trim) : type(trim.trimType), - subItemLimit(subItemLimit), start(trim.start.keyframes), end(trim.end.keyframes), offset(trim.offset.keyframes) { @@ -485,12 +468,11 @@ public: double resolvedOffset = fmod(offsetValue, 360.0) / 360.0; - return TrimParams(resolvedStart, resolvedEnd, resolvedOffset, type, subItemLimit); + return TrimParams(resolvedStart, resolvedEnd, resolvedOffset, type); } private: TrimType type; - size_t subItemLimit = 0; KeyframeInterpolator start; double startValue = 0.0; @@ -597,7 +579,7 @@ public: } if (hasUpdates) { - resolvedPath = makeRectangleBezierPath(Vector2D(positionValue.x, positionValue.y), Vector2D(sizeValue.x, sizeValue.y), cornerRadiusValue, direction); + ValueInterpolator::setInplace(makeRectangleBezierPath(Vector2D(positionValue.x, positionValue.y), Vector2D(sizeValue.x, sizeValue.y), cornerRadiusValue, direction), resolvedPath); } hasValidData = true; @@ -645,7 +627,7 @@ public: } if (hasUpdates) { - resolvedPath = makeEllipseBezierPath(Vector2D(sizeValue.x, sizeValue.y), Vector2D(positionValue.x, positionValue.y), direction); + ValueInterpolator::setInplace(makeEllipseBezierPath(Vector2D(sizeValue.x, sizeValue.y), Vector2D(positionValue.x, positionValue.y), direction), resolvedPath); } hasValidData = true; @@ -732,7 +714,7 @@ public: } if (hasUpdates) { - resolvedPath = makeStarBezierPath(Vector2D(positionValue.x, positionValue.y), outerRadiusValue, innerRadiusValue, outerRoundednessValue, innerRoundednessValue, pointsValue, rotationValue, direction); + ValueInterpolator::setInplace(makeStarBezierPath(Vector2D(positionValue.x, positionValue.y), outerRadiusValue, innerRadiusValue, outerRoundednessValue, innerRoundednessValue, pointsValue, rotationValue, direction), resolvedPath); } hasValidData = true; @@ -909,10 +891,6 @@ public: transform = std::move(transform_); } - std::shared_ptr const &renderTree() const { - return _renderTree; - } - private: std::unique_ptr path; std::unique_ptr transform; @@ -920,14 +898,19 @@ public: std::vector shadings; std::vector> trims; + public: std::vector> subItems; - - std::shared_ptr _renderTree; + std::shared_ptr _contentItem; private: - std::vector collectPaths(size_t subItemLimit, CATransform3D const &parentTransform, bool skipApplyTransform) { + bool hasTrims(size_t subItemLimit) { + return false; + } + + std::vector collectPaths(size_t subItemLimit, CATransform3D const &parentTransform, bool skipApplyTransform, bool &hasTrims) { std::vector mappedPaths; + //TODO:remove skipApplyTransform CATransform3D effectiveTransform = parentTransform; if (!skipApplyTransform && isGroup && transform) { effectiveTransform = transform->transform() * effectiveTransform; @@ -935,8 +918,8 @@ public: size_t maxSubitem = std::min(subItems.size(), subItemLimit); - if (path) { - mappedPaths.emplace_back(*(path->currentPath()), effectiveTransform); + if (_contentItem->path) { + mappedPaths.emplace_back(_contentItem->path.value(), effectiveTransform); } for (size_t i = 0; i < maxSubitem; i++) { @@ -947,9 +930,10 @@ public: currentTrim = trims[0]->trimParams(); } - auto subItemPaths = subItem->collectPaths(INT32_MAX, effectiveTransform, false); + auto subItemPaths = subItem->collectPaths(INT32_MAX, effectiveTransform, false, hasTrims); if (currentTrim) { + hasTrims = true; CompoundBezierPath tempPath; for (auto &path : subItemPaths) { tempPath.appendPath(path.path.copyUsingTransform(path.transform)); @@ -988,25 +972,17 @@ public: } void addTrim(Trim const &trim) { - trims.push_back(std::make_shared(trim, subItems.size())); + trims.push_back(std::make_shared(trim)); } public: void initializeRenderChildren() { - _renderTree = std::make_shared( - CGRect(0.0, 0.0, 0.0, 0.0), - Vector2D(0.0, 0.0), - CATransform3D::identity(), - 1.0, - false, - false, - std::vector>(), - nullptr, - false - ); + _contentItem = std::make_shared(); + _contentItem->isGroup = isGroup; - _renderTree->_contentItem = std::make_shared(); - _renderTree->_contentItem->isGroup = isGroup; + if (path) { + _contentItem->path = *path->currentPath(); + } if (!shadings.empty()) { for (int i = 0; i < shadings.size(); i++) { @@ -1025,7 +1001,7 @@ public: } itemShadingVariant->subItemLimit = shadingVariant.subItemLimit; - _renderTree->_contentItem->shadings.push_back(itemShadingVariant); + _contentItem->shadings.push_back(itemShadingVariant); } } @@ -1033,22 +1009,7 @@ public: std::vector> subItemNodes; for (int i = (int)subItems.size() - 1; i >= 0; i--) { subItems[i]->initializeRenderChildren(); - subItemNodes.push_back(subItems[i]->_renderTree); - //_renderTree->_contentItem->subItems.push_back(subItems[i]->_renderTree->_contentItem); - } - - if (!subItemNodes.empty()) { - _renderTree->_subnodes.push_back(std::make_shared( - CGRect(0.0, 0.0, 0.0, 0.0), - Vector2D(0.0, 0.0), - CATransform3D::identity(), - 1.0, - false, - false, - subItemNodes, - nullptr, - false - )); + _contentItem->subItems.push_back(subItems[i]->_contentItem); } } } @@ -1061,6 +1022,8 @@ public: if (path) { path->update(frameTime); + } else { + _contentItem->path = std::nullopt; } for (const auto &trim : trims) { trim->update(frameTime); @@ -1080,15 +1043,15 @@ public: } } - void updateChildren(std::optional parentTrim) { + void updateContents(std::optional parentTrim) { CATransform3D containerTransform = CATransform3D::identity(); double containerOpacity = 1.0; if (transform) { containerTransform = transform->transform(); containerOpacity = transform->opacity(); } - _renderTree->_transform = containerTransform; - _renderTree->_alpha = containerOpacity; + _contentItem->transform = containerTransform; + _contentItem->alpha = containerOpacity; for (int i = 0; i < shadings.size(); i++) { const auto &shadingVariant = shadings[i]; @@ -1097,42 +1060,58 @@ public: continue; } - CompoundBezierPath compoundPath; - auto paths = collectPaths(shadingVariant.subItemLimit, CATransform3D::identity(), true); - for (const auto &path : paths) { - compoundPath.appendPath(path.path.copyUsingTransform(path.transform)); - } - //std::optional currentTrim = parentTrim; //TODO:investigate /*if (!trims.empty()) { currentTrim = trims[0]; }*/ + bool hasTrims = false; if (parentTrim) { + CompoundBezierPath compoundPath; + hasTrims = true; + auto paths = collectPaths(shadingVariant.subItemLimit, CATransform3D::identity(), true, hasTrims); + for (const auto &path : paths) { + compoundPath.appendPath(path.path.copyUsingTransform(path.transform)); + } + compoundPath = trimCompoundPath(compoundPath, parentTrim->start, parentTrim->end, parentTrim->offset, parentTrim->type); + + std::vector resultPaths; + for (const auto &path : compoundPath.paths) { + resultPaths.push_back(path); + } + _contentItem->shadings[i]->explicitPath = resultPaths; + } else { + CompoundBezierPath compoundPath; + auto paths = collectPaths(shadingVariant.subItemLimit, CATransform3D::identity(), true, hasTrims); + for (const auto &path : paths) { + compoundPath.appendPath(path.path.copyUsingTransform(path.transform)); + } + std::vector resultPaths; + for (const auto &path : compoundPath.paths) { + resultPaths.push_back(path); + } + + if (hasTrims) { + _contentItem->shadings[i]->explicitPath = resultPaths; + } else { + _contentItem->shadings[i]->explicitPath = std::nullopt; + _contentItem->shadings[i]->explicitPath = resultPaths; + } } - - std::vector resultPaths; - for (const auto &path : compoundPath.paths) { - resultPaths.push_back(path); - } - - _renderTree->_contentItem->shadings[i]->explicitPath = resultPaths; } if (isGroup && !subItems.empty()) { for (int i = (int)subItems.size() - 1; i >= 0; i--) { std::optional childTrim = parentTrim; for (const auto &trim : trims) { - if (i < (int)trim->trimParams().subItemLimit) { - //TODO:allow combination - //assert(!parentTrim); - childTrim = trim->trimParams(); - } + //TODO:allow combination + //assert(!parentTrim); + childTrim = trim->trimParams(); } - subItems[i]->updateChildren(childTrim); + subItems[i]->updateContents(childTrim); } } } @@ -1229,7 +1208,14 @@ private: case ShapeType::Trim: { Trim const &trim = *((Trim *)item.get()); - itemTree->addTrim(trim); + auto groupItem = std::make_shared(); + groupItem->isGroup = true; + for (const auto &subItem : itemTree->subItems) { + groupItem->addSubItem(subItem); + } + groupItem->addTrim(trim); + itemTree->subItems.clear(); + itemTree->addSubItem(groupItem); break; } @@ -1306,7 +1292,7 @@ void ShapeCompositionLayer::displayContentsWithFrame(double frame, bool forceUpd _frameTime = frame; _frameTimeInitialized = true; _contentTree->itemTree->updateFrame(_frameTime); - _contentTree->itemTree->updateChildren(std::nullopt); + _contentTree->itemTree->updateContents(std::nullopt); } std::shared_ptr ShapeCompositionLayer::renderTreeNode() { @@ -1314,12 +1300,26 @@ std::shared_ptr ShapeCompositionLayer::renderTreeNode() { _frameTime = 0.0; _frameTimeInitialized = true; _contentTree->itemTree->updateFrame(_frameTime); - _contentTree->itemTree->updateChildren(std::nullopt); + _contentTree->itemTree->updateContents(std::nullopt); } if (!_renderTreeNode) { + _contentRenderTreeNode = std::make_shared( + CGRect(0.0, 0.0, 0.0, 0.0), + Vector2D(0.0, 0.0), + CATransform3D::identity(), + 1.0, + false, + false, + std::vector>(), + nullptr, + false + ); + _contentRenderTreeNode->_contentItem = _contentTree->itemTree->_contentItem; + std::vector> subnodes; - subnodes.push_back(_contentTree->itemTree->renderTree()); + //subnodes.push_back(_contentTree->itemTree->renderTree()); + subnodes.push_back(_contentRenderTreeNode); std::shared_ptr maskNode; bool invertMask = false; @@ -1351,12 +1351,12 @@ void ShapeCompositionLayer::updateRenderTree() { _matteLayer->updateRenderTree(); } - _contentTree->itemTree->renderTree()->_bounds = _contentsLayer->bounds(); - _contentTree->itemTree->renderTree()->_position = _contentsLayer->position(); - _contentTree->itemTree->renderTree()->_transform = _contentsLayer->transform(); - _contentTree->itemTree->renderTree()->_alpha = _contentsLayer->opacity(); - _contentTree->itemTree->renderTree()->_masksToBounds = _contentsLayer->masksToBounds(); - _contentTree->itemTree->renderTree()->_isHidden = _contentsLayer->isHidden(); + _contentRenderTreeNode->_bounds = _contentsLayer->bounds(); + _contentRenderTreeNode->_position = _contentsLayer->position(); + _contentRenderTreeNode->_transform = _contentsLayer->transform(); + _contentRenderTreeNode->_alpha = _contentsLayer->opacity(); + _contentRenderTreeNode->_masksToBounds = _contentsLayer->masksToBounds(); + _contentRenderTreeNode->_isHidden = _contentsLayer->isHidden(); assert(position() == Vector2D::Zero()); assert(transform().isIdentity()); diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/ShapeCompositionLayer.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/ShapeCompositionLayer.hpp index 691f926de5..f36e4a241c 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/ShapeCompositionLayer.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/ShapeCompositionLayer.hpp @@ -27,6 +27,7 @@ private: bool _frameTimeInitialized = false; std::shared_ptr _renderTreeNode; + std::shared_ptr _contentRenderTreeNode; }; } diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Trim.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Trim.hpp index e5c59daecd..e4628d604a 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Trim.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Trim.hpp @@ -8,11 +8,6 @@ namespace lottie { -enum class TrimType: int { - Simultaneously = 1, - Individually = 2 -}; - /// An item that defines trim class Trim: public ShapeItem { public: diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/LottieAnimationContainer.mm b/submodules/TelegramUI/Components/LottieCpp/Sources/LottieAnimationContainer.mm index 0b874ee771..d155af122e 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/LottieAnimationContainer.mm +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/LottieAnimationContainer.mm @@ -65,9 +65,7 @@ result.layer.isHidden = node->renderData.layer._isHidden; result.globalRect = CGRectMake(node->renderData.globalRect.x, node->renderData.globalRect.y, node->renderData.globalRect.width, node->renderData.globalRect.height); - result.localRect = CGRectMake(node->renderData.localRect.x, node->renderData.localRect.y, node->renderData.localRect.width, node->renderData.localRect.height); result.globalTransform = lottie::nativeTransform(node->renderData.globalTransform); - result.drawsContent = node->renderData.drawsContent; result.hasSimpleContents = node->renderData.drawContentDescendants <= 1; result.drawContentDescendants = node->renderData.drawContentDescendants; result.isInvertedMatte = node->renderData.isInvertedMatte;