diff --git a/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/SoftwareLottieRenderer.mm b/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/SoftwareLottieRenderer.mm index a0145c5d4a..789c95c35a 100644 --- a/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/SoftwareLottieRenderer.mm +++ b/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/SoftwareLottieRenderer.mm @@ -6,6 +6,48 @@ #include +namespace { + +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); + } + } + + return mappedPaths; +} + +} + namespace lottie { static void processRenderContentItem(std::shared_ptr const &contentItem, Vector2D const &globalSize, CATransform3D const &parentTransform, BezierPathsBoundingBoxContext &bezierPathsBoundingBoxContext) { @@ -24,7 +66,17 @@ static void processRenderContentItem(std::shared_ptr int drawContentDescendants = 0; for (const auto &shadingVariant : contentItem->shadings) { - CGRect shapeBounds = bezierPathsBoundingBoxParallel(bezierPathsBoundingBoxContext, shadingVariant->explicitPath.value()); + 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) { @@ -42,17 +94,23 @@ static void processRenderContentItem(std::shared_ptr } } - 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; + 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) { @@ -257,7 +315,17 @@ static void drawLottieContentItem(std::shared_ptr paren } 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; } @@ -288,7 +356,7 @@ static void drawLottieContentItem(std::shared_ptr paren }; 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()) { diff --git a/Tests/LottieMetalTest/Sources/ViewController.swift b/Tests/LottieMetalTest/Sources/ViewController.swift index 0c757c03a1..599c888d16 100644 --- a/Tests/LottieMetalTest/Sources/ViewController.swift +++ b/Tests/LottieMetalTest/Sources/ViewController.swift @@ -78,7 +78,7 @@ private final class ReferenceCompareTest { } var continueFromName: String? - //continueFromName = "1391391008142393362.json" + //continueFromName = "778160933443732778.json" let _ = await processAnimationFolderAsync(basePath: bundlePath, path: "", stopOnFailure: true, process: { path, name, alwaysDraw in if let continueFromNameValue = continueFromName { 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 48ce9dc944..3565fd8212 100644 --- a/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/RenderTreeNode.h +++ b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/RenderTreeNode.h @@ -422,6 +422,8 @@ public: bool isGroup = false; CATransform3D transform = CATransform3D::identity(); double alpha = 0.0; + std::optional trimParams; + std::optional path; std::vector> shadings; std::vector> subItems; diff --git a/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/ShapeAttributes.h b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/ShapeAttributes.h index 287f7fe491..2b707061a5 100644 --- a/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/ShapeAttributes.h +++ b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/ShapeAttributes.h @@ -31,6 +31,27 @@ 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; + 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_) { + } +}; + } #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 b7eefed9b8..e269f7f74c 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,22 +437,6 @@ 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) : @@ -597,7 +581,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 +629,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 +716,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; @@ -916,15 +900,19 @@ public: std::vector shadings; std::vector> trims; - std::vector> subItems; - public: + std::vector> subItems; 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; @@ -932,8 +920,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++) { @@ -944,9 +932,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)); @@ -993,6 +982,10 @@ public: _contentItem = std::make_shared(); _contentItem->isGroup = isGroup; + if (path) { + _contentItem->path = *path->currentPath(); + } + if (!shadings.empty()) { for (int i = 0; i < shadings.size(); i++) { auto &shadingVariant = shadings[i]; @@ -1031,6 +1024,8 @@ public: if (path) { path->update(frameTime); + } else { + _contentItem->path = std::nullopt; } for (const auto &trim : trims) { trim->update(frameTime); @@ -1067,28 +1062,46 @@ 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); - } - - _contentItem->shadings[i]->explicitPath = resultPaths; } if (isGroup && !subItems.empty()) { @@ -1199,7 +1212,16 @@ 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); + + //itemTree->addTrim(trim); break; } 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: