diff --git a/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/SoftwareLottieRenderer.mm b/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/SoftwareLottieRenderer.mm index 2f2189b74a..a0145c5d4a 100644 --- a/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/SoftwareLottieRenderer.mm +++ b/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/SoftwareLottieRenderer.mm @@ -19,94 +19,62 @@ static void processRenderContentItem(std::shared_ptr return; } - std::optional effectiveLocalBounds; + std::optional globalRect; int drawContentDescendants = 0; for (const auto &shadingVariant : contentItem->shadings) { - drawContentDescendants += 1; - 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; - } + } else { + continue; + } + + drawContentDescendants += 1; + + CGRect shapeGlobalBounds = shapeBounds.applyingTransform(currentTransform); + if (globalRect) { + globalRect = globalRect->unionWith(shapeGlobalBounds); + } else { + globalRect = shapeGlobalBounds; } } - std::optional effectiveLocalRect; - if (effectiveLocalBounds.has_value()) { - effectiveLocalRect = effectiveLocalBounds; - } - - std::optional subnodesGlobalRect; - for (const auto &subItem : contentItem->subItems) { processRenderContentItem(subItem, globalSize, currentTransform, bezierPathsBoundingBoxContext); - drawContentDescendants += subItem->renderData.drawContentDescendants; - - if (!subItem->renderData.localRect.empty()) { - if (effectiveLocalRect.has_value()) { - effectiveLocalRect = effectiveLocalRect->unionWith(subItem->renderData.localRect); + if (subItem->renderData.isValid) { + drawContentDescendants += subItem->renderData.drawContentDescendants; + if (globalRect) { + globalRect = globalRect->unionWith(subItem->renderData.globalRect); } else { - effectiveLocalRect = subItem->renderData.localRect; + globalRect = subItem->renderData.globalRect; } } - - if (subnodesGlobalRect) { - subnodesGlobalRect = subnodesGlobalRect->unionWith(subItem->renderData.globalRect); - } else { - subnodesGlobalRect = subItem->renderData.globalRect; - } } - 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) { contentItem->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)) { + contentItem->renderData.isValid = false; + return; + } + if (integralGlobalRect.width <= 0.0 || integralGlobalRect.height <= 0.0) { contentItem->renderData.isValid = false; return; } - - CGRect localRect = effectiveLocalRect.value_or(CGRect(0.0, 0.0, 0.0, 0.0)).applyingTransform(localTransform); contentItem->renderData.isValid = true; @@ -117,10 +85,8 @@ static void processRenderContentItem(std::shared_ptr contentItem->renderData.layer._masksToBounds = false; contentItem->renderData.layer._isHidden = false; - contentItem->renderData.globalRect = globalRect; - contentItem->renderData.localRect = localRect; + contentItem->renderData.globalRect = integralGlobalRect; contentItem->renderData.globalTransform = currentTransform; - contentItem->renderData.drawsContent = effectiveLocalBounds.has_value(); contentItem->renderData.drawContentDescendants = drawContentDescendants; contentItem->renderData.isInvertedMatte = false; } @@ -151,100 +117,59 @@ static void processRenderTree(std::shared_ptr const &node, Vecto return; } + int drawContentDescendants = 0; + std::optional globalRect; if (node->_contentItem) { processRenderContentItem(node->_contentItem, globalSize, currentTransform, bezierPathsBoundingBoxContext); + if (node->_contentItem->renderData.isValid) { + drawContentDescendants += node->_contentItem->renderData.drawContentDescendants; + globalRect = node->_contentItem->renderData.globalRect; + } } - std::optional effectiveLocalBounds; - 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; } @@ -253,7 +178,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; } @@ -263,7 +188,10 @@ 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; @@ -274,10 +202,8 @@ static void processRenderTree(std::shared_ptr const &node, Vecto 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; } @@ -286,9 +212,49 @@ static void processRenderTree(std::shared_ptr const &node, Vecto namespace { -static void drawLottieContentItem(std::shared_ptr context, std::shared_ptr item) { - context->saveState(); - context->concatenate(item->transform); +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()) { @@ -398,7 +364,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 } @@ -422,7 +388,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(); @@ -431,7 +397,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; @@ -441,11 +407,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: { @@ -458,10 +424,19 @@ static void drawLottieContentItem(std::shared_ptr conte } for (const auto &subItem : item->subItems) { - drawLottieContentItem(context, subItem); + drawLottieContentItem(currentContext, subItem, renderAlpha); } - context->restoreState(); + 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) { @@ -528,10 +503,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/Tests/LottieMetalTest/Sources/ViewController.swift b/Tests/LottieMetalTest/Sources/ViewController.swift index 599c888d16..24ddbef56f 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 = "778160933443732778.json" + continueFromName = "1391391008142393362.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/RenderTreeNode.h b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/RenderTreeNode.h index 75e4f9d3fa..48ce9dc944 100644 --- a/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/RenderTreeNode.h +++ b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/RenderTreeNode.h @@ -75,9 +75,7 @@ public: 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) { @@ -86,9 +84,7 @@ public: bool isValid = false; LayerParams layer; CGRect globalRect; - CGRect localRect; CATransform3D globalTransform; - bool drawsContent; int drawContentDescendants; bool isInvertedMatte; }; 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;