Lottie optimization

This commit is contained in:
Isaac 2024-05-15 23:52:24 +04:00
parent 507aeee187
commit 0225f74d30
4 changed files with 121 additions and 154 deletions

View File

@ -19,94 +19,62 @@ static void processRenderContentItem(std::shared_ptr<RenderTreeNodeContentItem>
return; return;
} }
std::optional<CGRect> effectiveLocalBounds; std::optional<CGRect> globalRect;
int drawContentDescendants = 0; int drawContentDescendants = 0;
for (const auto &shadingVariant : contentItem->shadings) { for (const auto &shadingVariant : contentItem->shadings) {
drawContentDescendants += 1;
CGRect shapeBounds = bezierPathsBoundingBoxParallel(bezierPathsBoundingBoxContext, shadingVariant->explicitPath.value()); CGRect shapeBounds = bezierPathsBoundingBoxParallel(bezierPathsBoundingBoxContext, shadingVariant->explicitPath.value());
if (shadingVariant->stroke) { if (shadingVariant->stroke) {
shapeBounds = shapeBounds.insetBy(-shadingVariant->stroke->lineWidth / 2.0, -shadingVariant->stroke->lineWidth / 2.0); 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) { } else if (shadingVariant->fill) {
if (effectiveLocalBounds) {
effectiveLocalBounds = effectiveLocalBounds->unionWith(shapeBounds);
} else { } else {
effectiveLocalBounds = shapeBounds; continue;
}
}
} }
std::optional<CGRect> effectiveLocalRect; drawContentDescendants += 1;
if (effectiveLocalBounds.has_value()) {
effectiveLocalRect = effectiveLocalBounds;
}
std::optional<CGRect> subnodesGlobalRect; CGRect shapeGlobalBounds = shapeBounds.applyingTransform(currentTransform);
if (globalRect) {
globalRect = globalRect->unionWith(shapeGlobalBounds);
} else {
globalRect = shapeGlobalBounds;
}
}
for (const auto &subItem : contentItem->subItems) { for (const auto &subItem : contentItem->subItems) {
processRenderContentItem(subItem, globalSize, currentTransform, bezierPathsBoundingBoxContext); processRenderContentItem(subItem, globalSize, currentTransform, bezierPathsBoundingBoxContext);
if (subItem->renderData.isValid) {
drawContentDescendants += subItem->renderData.drawContentDescendants; drawContentDescendants += subItem->renderData.drawContentDescendants;
if (globalRect) {
if (!subItem->renderData.localRect.empty()) { globalRect = globalRect->unionWith(subItem->renderData.globalRect);
if (effectiveLocalRect.has_value()) {
effectiveLocalRect = effectiveLocalRect->unionWith(subItem->renderData.localRect);
} else { } else {
effectiveLocalRect = subItem->renderData.localRect; globalRect = subItem->renderData.globalRect;
}
} }
} }
if (subnodesGlobalRect) { if (!globalRect) {
subnodesGlobalRect = subnodesGlobalRect->unionWith(subItem->renderData.globalRect);
} else {
subnodesGlobalRect = subItem->renderData.globalRect;
}
}
std::optional<CGRect> fuzzyGlobalRect;
if (effectiveLocalBounds) {
CGRect effectiveGlobalBounds = effectiveLocalBounds->applyingTransform(currentTransform);
if (fuzzyGlobalRect) {
fuzzyGlobalRect = fuzzyGlobalRect->unionWith(effectiveGlobalBounds);
} else {
fuzzyGlobalRect = effectiveGlobalBounds;
}
}
if (subnodesGlobalRect) {
if (fuzzyGlobalRect) {
fuzzyGlobalRect = fuzzyGlobalRect->unionWith(subnodesGlobalRect.value());
} else {
fuzzyGlobalRect = subnodesGlobalRect;
}
}
if (!fuzzyGlobalRect) {
contentItem->renderData.isValid = false; contentItem->renderData.isValid = false;
return; return;
} }
CGRect globalRect( CGRect integralGlobalRect(
std::floor(fuzzyGlobalRect->x), std::floor(globalRect->x),
std::floor(fuzzyGlobalRect->y), std::floor(globalRect->y),
std::ceil(fuzzyGlobalRect->width + fuzzyGlobalRect->x - floor(fuzzyGlobalRect->x)), std::ceil(globalRect->width + globalRect->x - floor(globalRect->x)),
std::ceil(fuzzyGlobalRect->height + fuzzyGlobalRect->y - floor(fuzzyGlobalRect->y)) 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; contentItem->renderData.isValid = false;
return; return;
} }
CGRect localRect = effectiveLocalRect.value_or(CGRect(0.0, 0.0, 0.0, 0.0)).applyingTransform(localTransform);
contentItem->renderData.isValid = true; contentItem->renderData.isValid = true;
@ -117,10 +85,8 @@ static void processRenderContentItem(std::shared_ptr<RenderTreeNodeContentItem>
contentItem->renderData.layer._masksToBounds = false; contentItem->renderData.layer._masksToBounds = false;
contentItem->renderData.layer._isHidden = false; contentItem->renderData.layer._isHidden = false;
contentItem->renderData.globalRect = globalRect; contentItem->renderData.globalRect = integralGlobalRect;
contentItem->renderData.localRect = localRect;
contentItem->renderData.globalTransform = currentTransform; contentItem->renderData.globalTransform = currentTransform;
contentItem->renderData.drawsContent = effectiveLocalBounds.has_value();
contentItem->renderData.drawContentDescendants = drawContentDescendants; contentItem->renderData.drawContentDescendants = drawContentDescendants;
contentItem->renderData.isInvertedMatte = false; contentItem->renderData.isInvertedMatte = false;
} }
@ -151,100 +117,59 @@ static void processRenderTree(std::shared_ptr<RenderTreeNode> const &node, Vecto
return; return;
} }
int drawContentDescendants = 0;
std::optional<CGRect> globalRect;
if (node->_contentItem) { if (node->_contentItem) {
processRenderContentItem(node->_contentItem, globalSize, currentTransform, bezierPathsBoundingBoxContext); processRenderContentItem(node->_contentItem, globalSize, currentTransform, bezierPathsBoundingBoxContext);
if (node->_contentItem->renderData.isValid) {
drawContentDescendants += node->_contentItem->renderData.drawContentDescendants;
globalRect = node->_contentItem->renderData.globalRect;
}
} }
std::optional<CGRect> effectiveLocalBounds;
bool isInvertedMatte = isInvertedMask; bool isInvertedMatte = isInvertedMask;
if (isInvertedMatte) { 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<CGRect> effectiveLocalRect;
if (effectiveLocalBounds.has_value()) {
effectiveLocalRect = effectiveLocalBounds;
}
std::optional<CGRect> subnodesGlobalRect;
bool masksToBounds = node->masksToBounds();
int drawContentDescendants = 0;
for (const auto &item : node->subnodes()) { for (const auto &item : node->subnodes()) {
processRenderTree(item, globalSize, currentTransform, false, bezierPathsBoundingBoxContext); processRenderTree(item, globalSize, currentTransform, false, bezierPathsBoundingBoxContext);
if (item->renderData.isValid) { if (item->renderData.isValid) {
drawContentDescendants += item->renderData.drawContentDescendants; drawContentDescendants += item->renderData.drawContentDescendants;
if (item->_contentItem) { if (globalRect) {
drawContentDescendants += 1; globalRect = globalRect->unionWith(item->renderData.globalRect);
}
if (!item->renderData.localRect.empty()) {
if (effectiveLocalRect.has_value()) {
effectiveLocalRect = effectiveLocalRect->unionWith(item->renderData.localRect);
} else { } else {
effectiveLocalRect = item->renderData.localRect; globalRect = item->renderData.globalRect;
}
}
if (subnodesGlobalRect) {
subnodesGlobalRect = subnodesGlobalRect->unionWith(item->renderData.globalRect);
} else {
subnodesGlobalRect = item->renderData.globalRect;
} }
} }
} }
if (masksToBounds && effectiveLocalRect.has_value()) { if (!globalRect) {
if (node->bounds().contains(effectiveLocalRect.value())) {
masksToBounds = false;
}
}
std::optional<CGRect> fuzzyGlobalRect;
if (effectiveLocalBounds) {
CGRect effectiveGlobalBounds = effectiveLocalBounds->applyingTransform(currentTransform);
if (fuzzyGlobalRect) {
fuzzyGlobalRect = fuzzyGlobalRect->unionWith(effectiveGlobalBounds);
} else {
fuzzyGlobalRect = effectiveGlobalBounds;
}
}
if (subnodesGlobalRect) {
if (fuzzyGlobalRect) {
fuzzyGlobalRect = fuzzyGlobalRect->unionWith(subnodesGlobalRect.value());
} else {
fuzzyGlobalRect = subnodesGlobalRect;
}
}
if (!fuzzyGlobalRect) {
node->renderData.isValid = false; node->renderData.isValid = false;
return; return;
} }
CGRect globalRect( CGRect integralGlobalRect(
std::floor(fuzzyGlobalRect->x), std::floor(globalRect->x),
std::floor(fuzzyGlobalRect->y), std::floor(globalRect->y),
std::ceil(fuzzyGlobalRect->width + fuzzyGlobalRect->x - floor(fuzzyGlobalRect->x)), std::ceil(globalRect->width + globalRect->x - floor(globalRect->x)),
std::ceil(fuzzyGlobalRect->height + fuzzyGlobalRect->y - floor(fuzzyGlobalRect->y)) 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; node->renderData.isValid = false;
return; return;
} }
if (masksToBounds && effectiveLocalBounds) { bool masksToBounds = node->masksToBounds();
CGRect effectiveGlobalBounds = effectiveLocalBounds->applyingTransform(currentTransform); if (masksToBounds) {
CGRect effectiveGlobalBounds = node->bounds().applyingTransform(currentTransform);
if (effectiveGlobalBounds.contains(CGRect(0.0, 0.0, globalSize.x, globalSize.y))) { if (effectiveGlobalBounds.contains(CGRect(0.0, 0.0, globalSize.x, globalSize.y))) {
masksToBounds = false; masksToBounds = false;
} }
@ -253,7 +178,7 @@ static void processRenderTree(std::shared_ptr<RenderTreeNode> const &node, Vecto
if (node->mask()) { if (node->mask()) {
processRenderTree(node->mask(), globalSize, currentTransform, node->invertMask(), bezierPathsBoundingBoxContext); processRenderTree(node->mask(), globalSize, currentTransform, node->invertMask(), bezierPathsBoundingBoxContext);
if (node->mask()->renderData.isValid) { if (node->mask()->renderData.isValid) {
if (!node->mask()->renderData.globalRect.intersects(globalRect)) { if (!node->mask()->renderData.globalRect.intersects(integralGlobalRect)) {
node->renderData.isValid = false; node->renderData.isValid = false;
return; return;
} }
@ -263,7 +188,10 @@ static void processRenderTree(std::shared_ptr<RenderTreeNode> 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.isValid = true;
@ -274,10 +202,8 @@ static void processRenderTree(std::shared_ptr<RenderTreeNode> const &node, Vecto
node->renderData.layer._masksToBounds = masksToBounds; node->renderData.layer._masksToBounds = masksToBounds;
node->renderData.layer._isHidden = node->isHidden(); node->renderData.layer._isHidden = node->isHidden();
node->renderData.globalRect = globalRect; node->renderData.globalRect = integralGlobalRect;
node->renderData.localRect = localRect;
node->renderData.globalTransform = currentTransform; node->renderData.globalTransform = currentTransform;
node->renderData.drawsContent = effectiveLocalBounds.has_value();
node->renderData.drawContentDescendants = drawContentDescendants; node->renderData.drawContentDescendants = drawContentDescendants;
node->renderData.isInvertedMatte = isInvertedMatte; node->renderData.isInvertedMatte = isInvertedMatte;
} }
@ -286,9 +212,49 @@ static void processRenderTree(std::shared_ptr<RenderTreeNode> const &node, Vecto
namespace { namespace {
static void drawLottieContentItem(std::shared_ptr<lottieRendering::Canvas> context, std::shared_ptr<lottie::RenderTreeNodeContentItem> item) { static void drawLottieContentItem(std::shared_ptr<lottieRendering::Canvas> parentContext, std::shared_ptr<lottie::RenderTreeNodeContentItem> item, double parentAlpha) {
context->saveState(); if (!item->renderData.isValid) {
context->concatenate(item->transform); 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<lottieRendering::Canvas> currentContext;
std::shared_ptr<lottieRendering::Canvas> 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) { for (const auto &shading : item->shadings) {
if (shading->explicitPath->empty()) { if (shading->explicitPath->empty()) {
@ -398,7 +364,7 @@ static void drawLottieContentItem(std::shared_ptr<lottieRendering::Canvas> conte
dashPattern = shading->stroke->dashPattern; 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) { } else if (shading->stroke->shading->type() == lottie::RenderTreeNodeContentItem::ShadingType::Gradient) {
//TODO:gradient stroke //TODO:gradient stroke
} }
@ -422,7 +388,7 @@ static void drawLottieContentItem(std::shared_ptr<lottieRendering::Canvas> conte
if (shading->fill->shading->type() == lottie::RenderTreeNodeContentItem::ShadingType::Solid) { if (shading->fill->shading->type() == lottie::RenderTreeNodeContentItem::ShadingType::Solid) {
lottie::RenderTreeNodeContentItem::SolidShading *solidShading = (lottie::RenderTreeNodeContentItem::SolidShading *)shading->fill->shading.get(); lottie::RenderTreeNodeContentItem::SolidShading *solidShading = (lottie::RenderTreeNodeContentItem::SolidShading *)shading->fill->shading.get();
if (solidShading->opacity != 0.0) { 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) { } else if (shading->fill->shading->type() == lottie::RenderTreeNodeContentItem::ShadingType::Gradient) {
lottie::RenderTreeNodeContentItem::GradientShading *gradientShading = (lottie::RenderTreeNodeContentItem::GradientShading *)shading->fill->shading.get(); lottie::RenderTreeNodeContentItem::GradientShading *gradientShading = (lottie::RenderTreeNodeContentItem::GradientShading *)shading->fill->shading.get();
@ -431,7 +397,7 @@ static void drawLottieContentItem(std::shared_ptr<lottieRendering::Canvas> conte
std::vector<lottieRendering::Color> colors; std::vector<lottieRendering::Color> colors;
std::vector<double> locations; std::vector<double> locations;
for (const auto &color : gradientShading->colors) { 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; locations = gradientShading->locations;
@ -441,11 +407,11 @@ static void drawLottieContentItem(std::shared_ptr<lottieRendering::Canvas> conte
switch (gradientShading->gradientType) { switch (gradientShading->gradientType) {
case lottie::GradientType::Linear: { case lottie::GradientType::Linear: {
context->linearGradientFillPath(path, rule, gradient, start, end); currentContext->linearGradientFillPath(path, rule, gradient, start, end);
break; break;
} }
case lottie::GradientType::Radial: { 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; break;
} }
default: { default: {
@ -458,10 +424,19 @@ static void drawLottieContentItem(std::shared_ptr<lottieRendering::Canvas> conte
} }
for (const auto &subItem : item->subItems) { 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<lottie::RenderTreeNode> node, std::shared_ptr<lottieRendering::Canvas> parentContext, lottie::Vector2D const &globalSize, double parentAlpha) { static void renderLottieRenderNode(std::shared_ptr<lottie::RenderTreeNode> node, std::shared_ptr<lottieRendering::Canvas> parentContext, lottie::Vector2D const &globalSize, double parentAlpha) {
@ -528,10 +503,8 @@ static void renderLottieRenderNode(std::shared_ptr<lottie::RenderTreeNode> node,
renderAlpha = layerAlpha; renderAlpha = layerAlpha;
} }
currentContext->setAlpha(renderAlpha);
if (node->_contentItem) { if (node->_contentItem) {
drawLottieContentItem(currentContext, node->_contentItem); drawLottieContentItem(currentContext, node->_contentItem, renderAlpha);
} }
if (node->renderData.isInvertedMatte) { if (node->renderData.isInvertedMatte) {

View File

@ -78,7 +78,7 @@ private final class ReferenceCompareTest {
} }
var continueFromName: String? var continueFromName: String?
//continueFromName = "778160933443732778.json" continueFromName = "1391391008142393362.json"
let _ = await processAnimationFolderAsync(basePath: bundlePath, path: "", stopOnFailure: true, process: { path, name, alwaysDraw in let _ = await processAnimationFolderAsync(basePath: bundlePath, path: "", stopOnFailure: true, process: { path, name, alwaysDraw in
if let continueFromNameValue = continueFromName { if let continueFromNameValue = continueFromName {

View File

@ -75,9 +75,7 @@ public:
false false
), ),
globalRect(CGRect(0.0, 0.0, 0.0, 0.0)), globalRect(CGRect(0.0, 0.0, 0.0, 0.0)),
localRect(CGRect(0.0, 0.0, 0.0, 0.0)),
globalTransform(CATransform3D::identity()), globalTransform(CATransform3D::identity()),
drawsContent(false),
drawContentDescendants(false), drawContentDescendants(false),
isInvertedMatte(false) { isInvertedMatte(false) {
@ -86,9 +84,7 @@ public:
bool isValid = false; bool isValid = false;
LayerParams layer; LayerParams layer;
CGRect globalRect; CGRect globalRect;
CGRect localRect;
CATransform3D globalTransform; CATransform3D globalTransform;
bool drawsContent;
int drawContentDescendants; int drawContentDescendants;
bool isInvertedMatte; bool isInvertedMatte;
}; };

View File

@ -65,9 +65,7 @@
result.layer.isHidden = node->renderData.layer._isHidden; 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.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.globalTransform = lottie::nativeTransform(node->renderData.globalTransform);
result.drawsContent = node->renderData.drawsContent;
result.hasSimpleContents = node->renderData.drawContentDescendants <= 1; result.hasSimpleContents = node->renderData.drawContentDescendants <= 1;
result.drawContentDescendants = node->renderData.drawContentDescendants; result.drawContentDescendants = node->renderData.drawContentDescendants;
result.isInvertedMatte = node->renderData.isInvertedMatte; result.isInvertedMatte = node->renderData.isInvertedMatte;