Lottie optimization

This commit is contained in:
Isaac 2024-05-14 22:34:34 +04:00
parent 2cf5bd724a
commit 920de21020
6 changed files with 349 additions and 34 deletions

View File

@ -38,6 +38,54 @@ static void processRenderTree(std::shared_ptr<RenderTreeNode> const &node, Vecto
double alpha = node->alpha();
/*if (node->_contentItem) {
RenderTreeNodeContentItem *contentItem = node->_contentItem.get();
for (const auto &shadingVariant : contentItem->shadings) {
if (shadingVariant->stroke) {
CGRect shapeBounds = bezierPathsBoundingBoxParallel(bezierPathsBoundingBoxContext, shadingVariant->explicitPath.value());
shapeBounds = shapeBounds.insetBy(-shadingVariant->stroke->lineWidth / 2.0, -shadingVariant->stroke->lineWidth / 2.0);
effectiveLocalBounds = shapeBounds;
switch (shadingVariant->stroke->shading->type()) {
case RenderTreeNodeContent::ShadingType::Solid: {
RenderTreeNodeContent::SolidShading *solidShading = (RenderTreeNodeContent::SolidShading *)shadingVariant->stroke->shading.get();
alpha *= solidShading->opacity;
break;
}
case RenderTreeNodeContent::ShadingType::Gradient: {
break;
}
default:
break;
}
} else if (shadingVariant->fill) {
CGRect shapeBounds = bezierPathsBoundingBoxParallel(bezierPathsBoundingBoxContext, shadingVariant->explicitPath.value());
effectiveLocalBounds = shapeBounds;
switch (shadingVariant->fill->shading->type()) {
case RenderTreeNodeContent::ShadingType::Solid: {
RenderTreeNodeContent::SolidShading *solidShading = (RenderTreeNodeContent::SolidShading *)shadingVariant->fill->shading.get();
alpha *= solidShading->opacity;
break;
}
case RenderTreeNodeContent::ShadingType::Gradient: {
RenderTreeNodeContent::GradientShading *gradientShading = (RenderTreeNodeContent::GradientShading *)shadingVariant->fill->shading.get();
alpha *= gradientShading->opacity;
break;
}
default:
break;
}
}
}
}*/
if (node->content()) {
RenderTreeNodeContent *shapeContent = node->content().get();
@ -47,7 +95,7 @@ static void processRenderTree(std::shared_ptr<RenderTreeNode> const &node, Vecto
shapeBounds = shapeBounds.insetBy(-shapeContent->stroke->lineWidth / 2.0, -shapeContent->stroke->lineWidth / 2.0);
effectiveLocalBounds = shapeBounds;
switch (shapeContent->stroke->shading->type()) {
/*switch (shapeContent->stroke->shading->type()) {
case RenderTreeNodeContent::ShadingType::Solid: {
RenderTreeNodeContent::SolidShading *solidShading = (RenderTreeNodeContent::SolidShading *)shapeContent->stroke->shading.get();
@ -61,11 +109,11 @@ static void processRenderTree(std::shared_ptr<RenderTreeNode> const &node, Vecto
}
default:
break;
}
}*/
} else if (shapeContent->fill) {
effectiveLocalBounds = shapeBounds;
switch (shapeContent->fill->shading->type()) {
/*switch (shapeContent->fill->shading->type()) {
case RenderTreeNodeContent::ShadingType::Solid: {
RenderTreeNodeContent::SolidShading *solidShading = (RenderTreeNodeContent::SolidShading *)shapeContent->fill->shading.get();
@ -82,7 +130,7 @@ static void processRenderTree(std::shared_ptr<RenderTreeNode> const &node, Vecto
}
default:
break;
}
}*/
}
}
@ -110,7 +158,7 @@ static void processRenderTree(std::shared_ptr<RenderTreeNode> const &node, Vecto
if (item->renderData.isValid) {
drawContentDescendants += item->renderData.drawContentDescendants;
if (item->content()) {
if (item->content() || item->_contentItem) {
drawContentDescendants += 1;
}
@ -322,7 +370,7 @@ static void drawLottieRenderableItem(std::shared_ptr<lottieRendering::Canvas> co
dashPattern = item->stroke->dashPattern;
}
context->strokePath(path, item->stroke->lineWidth, lineJoin, lineCap, item->stroke->dashPhase, dashPattern, lottieRendering::Color(solidShading->color.r, solidShading->color.g, solidShading->color.b, solidShading->color.a));
context->strokePath(path, item->stroke->lineWidth, lineJoin, lineCap, item->stroke->dashPhase, dashPattern, lottieRendering::Color(solidShading->color.r, solidShading->color.g, solidShading->color.b, solidShading->color.a * solidShading->opacity));
} else if (item->stroke->shading->type() == lottie::RenderTreeNodeContent::ShadingType::Gradient) {
//TODO:gradient stroke
}
@ -344,14 +392,14 @@ static void drawLottieRenderableItem(std::shared_ptr<lottieRendering::Canvas> co
if (item->fill->shading->type() == lottie::RenderTreeNodeContent::ShadingType::Solid) {
lottie::RenderTreeNodeContent::SolidShading *solidShading = (lottie::RenderTreeNodeContent::SolidShading *)item->fill->shading.get();
context->fillPath(path, rule, lottieRendering::Color(solidShading->color.r, solidShading->color.g, solidShading->color.b, solidShading->color.a));
context->fillPath(path, rule, lottieRendering::Color(solidShading->color.r, solidShading->color.g, solidShading->color.b, solidShading->color.a * solidShading->opacity));
} else if (item->fill->shading->type() == lottie::RenderTreeNodeContent::ShadingType::Gradient) {
lottie::RenderTreeNodeContent::GradientShading *gradientShading = (lottie::RenderTreeNodeContent::GradientShading *)item->fill->shading.get();
std::vector<lottieRendering::Color> colors;
std::vector<double> locations;
for (const auto &color : gradientShading->colors) {
colors.push_back(lottieRendering::Color(color.r, color.g, color.b, color.a));
colors.push_back(lottieRendering::Color(color.r, color.g, color.b, color.a * gradientShading->opacity));
}
locations = gradientShading->locations;
@ -376,6 +424,179 @@ static void drawLottieRenderableItem(std::shared_ptr<lottieRendering::Canvas> co
}
}
static void drawLottieContentItem(std::shared_ptr<lottieRendering::Canvas> context, std::shared_ptr<lottie::RenderTreeNodeContentItem> item) {
if (item->shadings.empty()) {
return;
}
for (const auto &shading : item->shadings) {
if (shading->explicitPath->empty()) {
continue;
}
std::shared_ptr<lottie::CGPath> path = lottie::CGPath::makePath();
const auto iterate = [&](LottiePathItem const *pathItem) {
switch (pathItem->type) {
case LottiePathItemTypeMoveTo: {
path->moveTo(lottie::Vector2D(pathItem->points[0].x, pathItem->points[0].y));
break;
}
case LottiePathItemTypeLineTo: {
path->addLineTo(lottie::Vector2D(pathItem->points[0].x, pathItem->points[0].y));
break;
}
case LottiePathItemTypeCurveTo: {
path->addCurveTo(lottie::Vector2D(pathItem->points[2].x, pathItem->points[2].y), lottie::Vector2D(pathItem->points[0].x, pathItem->points[0].y), lottie::Vector2D(pathItem->points[1].x, pathItem->points[1].y));
break;
}
case LottiePathItemTypeClose: {
path->closeSubpath();
break;
}
default: {
break;
}
}
};
LottiePathItem pathItem;
for (const auto &path : shading->explicitPath.value()) {
std::optional<lottie::PathElement> previousElement;
for (const auto &element : path.elements()) {
if (previousElement.has_value()) {
if (previousElement->vertex.outTangentRelative().isZero() && element.vertex.inTangentRelative().isZero()) {
pathItem.type = LottiePathItemTypeLineTo;
pathItem.points[0] = CGPointMake(element.vertex.point.x, element.vertex.point.y);
iterate(&pathItem);
} else {
pathItem.type = LottiePathItemTypeCurveTo;
pathItem.points[2] = CGPointMake(element.vertex.point.x, element.vertex.point.y);
pathItem.points[1] = CGPointMake(element.vertex.inTangent.x, element.vertex.inTangent.y);
pathItem.points[0] = CGPointMake(previousElement->vertex.outTangent.x, previousElement->vertex.outTangent.y);
iterate(&pathItem);
}
} else {
pathItem.type = LottiePathItemTypeMoveTo;
pathItem.points[0] = CGPointMake(element.vertex.point.x, element.vertex.point.y);
iterate(&pathItem);
}
previousElement = element;
}
if (path.closed().value_or(true)) {
pathItem.type = LottiePathItemTypeClose;
iterate(&pathItem);
}
}
if (shading->stroke) {
if (shading->stroke->shading->type() == lottie::RenderTreeNodeContent::ShadingType::Solid) {
lottie::RenderTreeNodeContent::SolidShading *solidShading = (lottie::RenderTreeNodeContent::SolidShading *)shading->stroke->shading.get();
if (solidShading->opacity != 0.0) {
lottieRendering::LineJoin lineJoin = lottieRendering::LineJoin::Bevel;
switch (shading->stroke->lineJoin) {
case lottie::LineJoin::Bevel: {
lineJoin = lottieRendering::LineJoin::Bevel;
break;
}
case lottie::LineJoin::Round: {
lineJoin = lottieRendering::LineJoin::Round;
break;
}
case lottie::LineJoin::Miter: {
lineJoin = lottieRendering::LineJoin::Miter;
break;
}
default: {
break;
}
}
lottieRendering::LineCap lineCap = lottieRendering::LineCap::Square;
switch (shading->stroke->lineCap) {
case lottie::LineCap::Butt: {
lineCap = lottieRendering::LineCap::Butt;
break;
}
case lottie::LineCap::Round: {
lineCap = lottieRendering::LineCap::Round;
break;
}
case lottie::LineCap::Square: {
lineCap = lottieRendering::LineCap::Square;
break;
}
default: {
break;
}
}
std::vector<double> dashPattern;
if (!shading->stroke->dashPattern.empty()) {
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));
} else if (shading->stroke->shading->type() == lottie::RenderTreeNodeContent::ShadingType::Gradient) {
//TODO:gradient stroke
}
}
} else if (shading->fill) {
lottieRendering::FillRule rule = lottieRendering::FillRule::NonZeroWinding;
switch (shading->fill->rule) {
case lottie::FillRule::EvenOdd: {
rule = lottieRendering::FillRule::EvenOdd;
break;
}
case lottie::FillRule::NonZeroWinding: {
rule = lottieRendering::FillRule::NonZeroWinding;
break;
}
default: {
break;
}
}
if (shading->fill->shading->type() == lottie::RenderTreeNodeContent::ShadingType::Solid) {
lottie::RenderTreeNodeContent::SolidShading *solidShading = (lottie::RenderTreeNodeContent::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));
}
} else if (shading->fill->shading->type() == lottie::RenderTreeNodeContent::ShadingType::Gradient) {
lottie::RenderTreeNodeContent::GradientShading *gradientShading = (lottie::RenderTreeNodeContent::GradientShading *)shading->fill->shading.get();
if (gradientShading->opacity != 0.0) {
std::vector<lottieRendering::Color> colors;
std::vector<double> locations;
for (const auto &color : gradientShading->colors) {
colors.push_back(lottieRendering::Color(color.r, color.g, color.b, color.a * gradientShading->opacity));
}
locations = gradientShading->locations;
lottieRendering::Gradient gradient(colors, locations);
lottie::Vector2D start(gradientShading->start.x, gradientShading->start.y);
lottie::Vector2D end(gradientShading->end.x, gradientShading->end.y);
switch (gradientShading->gradientType) {
case lottie::GradientType::Linear: {
context->linearGradientFillPath(path, rule, gradient, start, end);
break;
}
case lottie::GradientType::Radial: {
context->radialGradientFillPath(path, rule, gradient, start, 0.0, start, start.distanceTo(end));
break;
}
default: {
break;
}
}
}
}
}
}
}
static void renderLottieRenderNode(std::shared_ptr<lottie::RenderTreeNode> node, std::shared_ptr<lottieRendering::Canvas> parentContext, lottie::Vector2D const &globalSize, double parentAlpha) {
if (!node->renderData.isValid) {
return;
@ -442,9 +663,12 @@ static void renderLottieRenderNode(std::shared_ptr<lottie::RenderTreeNode> node,
currentContext->setAlpha(renderAlpha);
if (node->content()) {
if (node->content() && (int64_t)"" < 0) {
drawLottieRenderableItem(currentContext, node->content());
}
if (node->_contentItem) {//} && (int64_t)"" < 0) {
drawLottieContentItem(currentContext, node->_contentItem);
}
if (node->renderData.isInvertedMatte) {
currentContext->fill(lottie::CGRect(node->renderData.layer.bounds().x, node->renderData.layer.bounds().y, node->renderData.layer.bounds().width, node->renderData.layer.bounds().height), lottieRendering::Color(0.0, 0.0, 0.0, 1.0));

View File

@ -103,6 +103,11 @@ func processDrawAnimation(baseCachePath: String, path: String, name: String, siz
return true
}
}
/*if #available(iOS 16.0, *) {
try? await Task.sleep(for: .seconds(0.1))
}*/
if !frameResult {
return false
}

View File

@ -78,7 +78,7 @@ private final class ReferenceCompareTest {
}
var continueFromName: String?
//continueFromName = "1258816259754282.json"
//continueFromName = "35707580709863498.json"
let _ = await processAnimationFolderAsync(basePath: bundlePath, path: "", stopOnFailure: true, process: { path, name, alwaysDraw in
if let continueFromNameValue = continueFromName {
@ -119,7 +119,7 @@ public final class ViewController: UIViewController {
self.view.layer.addSublayer(MetalEngine.shared.rootLayer)
if !"".isEmpty {
if "".isEmpty {
if #available(iOS 13.0, *) {
self.test = ReferenceCompareTest(view: self.view)
}

View File

@ -1072,6 +1072,8 @@ public class ChatSendMessageContextScreen: ViewControllerComponentContainer, Cha
private var processedDidAppear: Bool = false
private var processedDidDisappear: Bool = false
private var isActiveDisposable: Disposable?
override public var overlayWantsToBeBelowKeyboard: Bool {
if let componentView = self.node.hostView.componentView as? ChatSendMessageContextScreenComponent.View {
return componentView.wantsToBeBelowKeyboard()
@ -1136,6 +1138,16 @@ public class ChatSendMessageContextScreen: ViewControllerComponentContainer, Cha
self.lockOrientation = true
self.blocksBackgroundWhenInOverlay = true
self.isActiveDisposable = (context.sharedContext.applicationBindings.applicationInForeground
|> filter { !$0 }
|> take(1)
|> deliverOnMainQueue).startStrict(next: { [weak self] _ in
guard let self else {
return
}
self.dismiss()
})
}
required public init(coder aDecoder: NSCoder) {
@ -1143,6 +1155,7 @@ public class ChatSendMessageContextScreen: ViewControllerComponentContainer, Cha
}
deinit {
self.isActiveDisposable?.dispose()
}
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {

View File

@ -9,6 +9,8 @@
#include <LottieCpp/ShapeAttributes.h>
#include <LottieCpp/BezierPath.h>
#include <optional>
namespace lottie {
class RenderableItem {
@ -229,14 +231,7 @@ public:
CGRect bounds;
};
class RenderTreeNodeContentItem {
public:
RenderTreeNodeContentItem() {
}
public:
std::vector<std::shared_ptr<RenderTreeNodeContentItem>> subItems;
};
class RenderTreeNodeContentItem;
class RenderTreeNodeContent {
public:
@ -360,6 +355,30 @@ public:
std::shared_ptr<Fill> fill;
};
class RenderTreeNodeContentShadingVariant {
public:
RenderTreeNodeContentShadingVariant() {
}
public:
std::shared_ptr<RenderTreeNodeContent::Stroke> stroke;
std::shared_ptr<RenderTreeNodeContent::Fill> fill;
std::optional<std::vector<BezierPath>> explicitPath;
size_t subItemLimit = 0;
bool isGroup = false;
};
class RenderTreeNodeContentItem {
public:
RenderTreeNodeContentItem() {
}
public:
bool isGroup = false;
std::vector<std::shared_ptr<RenderTreeNodeContentShadingVariant>> shadings;
};
class ProcessedRenderTreeNodeData {
public:
struct LayerParams {
@ -518,6 +537,7 @@ public:
bool _masksToBounds = false;
bool _isHidden = false;
std::shared_ptr<RenderTreeNodeContent> _content;
std::shared_ptr<RenderTreeNodeContentItem> _contentItem;
std::vector<std::shared_ptr<RenderTreeNode>> _subnodes;
std::shared_ptr<RenderTreeNode> _mask;
bool _invertMask = false;

View File

@ -899,7 +899,7 @@ public:
std::shared_ptr<RenderTreeNode> _renderTree;
private:
std::vector<TransformedPath> collectPaths(AnimationFrameTime frameTime, size_t subItemLimit, CATransform3D parentTransform) {
std::vector<TransformedPath> collectPaths(size_t subItemLimit, CATransform3D parentTransform) {
std::vector<TransformedPath> mappedPaths;
CATransform3D effectiveTransform = parentTransform;
@ -908,7 +908,6 @@ public:
size_t maxSubitem = std::min(subItems.size(), subItemLimit);
if (path) {
path->update(frameTime);
mappedPaths.emplace_back(*(path->currentPath()), effectiveTransform);
}
@ -917,17 +916,19 @@ public:
CATransform3D subItemTransform = effectiveChildTransform;
if (subItem->isGroup && subItem->transform) {
subItem->transform->update(frameTime);
//update?
//subItem->transform->update(frameTime);
subItemTransform = subItem->transform->transform() * subItemTransform;
}
std::optional<TrimParams> currentTrim;
if (!trims.empty()) {
trims[0]->update(frameTime);
//update?
//trims[0]->update(frameTime);
currentTrim = trims[0]->trimParams();
}
auto subItemPaths = subItem->collectPaths(frameTime, INT32_MAX, subItemTransform);
auto subItemPaths = subItem->collectPaths(INT32_MAX, subItemTransform);
if (currentTrim) {
CompoundBezierPath tempPath;
@ -986,6 +987,9 @@ public:
false
);
_renderTree->_contentItem = std::make_shared<RenderTreeNodeContentItem>();
_renderTree->_contentItem->isGroup = isGroup;
if (!shadings.empty()) {
for (int i = 0; i < shadings.size(); i++) {
auto &shadingVariant = shadings[i];
@ -1008,6 +1012,28 @@ public:
);
shadingVariant.renderTree = shadingRenderTree;
_renderTree->_subnodes.push_back(shadingRenderTree);
auto itemShadingVariant = std::make_shared<RenderTreeNodeContentShadingVariant>();
if (shadingVariant.fill) {
itemShadingVariant->fill = std::make_shared<RenderTreeNodeContent::Fill>(
nullptr,
FillRule::NonZeroWinding
);
}
if (shadingVariant.stroke) {
itemShadingVariant->stroke = std::make_shared<RenderTreeNodeContent::Stroke>(
nullptr,
0.0,
LineJoin::Bevel,
LineCap::Round,
0.0,
0.0,
std::vector<double>()
);
}
itemShadingVariant->subItemLimit = shadingVariant.subItemLimit;
_renderTree->_contentItem->shadings.push_back(itemShadingVariant);
}
}
@ -1036,11 +1062,36 @@ public:
}
public:
void updateChildren(AnimationFrameTime frameTime, std::optional<TrimParams> parentTrim) {
void updateFrame(AnimationFrameTime frameTime) {
if (transform) {
transform->update(frameTime);
}
if (path) {
path->update(frameTime);
}
for (const auto &trim : trims) {
trim->update(frameTime);
}
for (const auto &shadingVariant : shadings) {
if (shadingVariant.fill) {
shadingVariant.fill->update(frameTime);
}
if (shadingVariant.stroke) {
shadingVariant.stroke->update(frameTime);
}
}
for (const auto &subItem : subItems) {
subItem->updateFrame(frameTime);
}
}
void updateChildren(std::optional<TrimParams> parentTrim) {
CATransform3D containerTransform = CATransform3D::identity();
double containerOpacity = 1.0;
if (transform) {
transform->update(frameTime);
containerTransform = transform->transform();
containerOpacity = transform->opacity();
}
@ -1055,7 +1106,7 @@ public:
}
CompoundBezierPath compoundPath;
auto paths = collectPaths(frameTime, shadingVariant.subItemLimit, CATransform3D::identity());
auto paths = collectPaths(shadingVariant.subItemLimit, CATransform3D::identity());
for (const auto &path : paths) {
compoundPath.appendPath(path.path.copyUsingTransform(path.transform));
}
@ -1079,13 +1130,11 @@ public:
std::shared_ptr<RenderTreeNodeContent::Fill> fill;
if (shadingVariant.fill) {
shadingVariant.fill->update(frameTime);
fill = shadingVariant.fill->fill();
}
std::shared_ptr<RenderTreeNodeContent::Stroke> stroke;
if (shadingVariant.stroke) {
shadingVariant.stroke->update(frameTime);
stroke = shadingVariant.stroke->stroke();
}
@ -1096,14 +1145,16 @@ public:
);
shadingVariant.renderTree->_content = content;
_renderTree->_contentItem->shadings[i]->stroke = stroke;
_renderTree->_contentItem->shadings[i]->fill = fill;
_renderTree->_contentItem->shadings[i]->explicitPath = resultPaths;
}
if (isGroup && !subItems.empty()) {
for (int i = (int)subItems.size() - 1; i >= 0; i--) {
std::optional<TrimParams> childTrim = parentTrim;
for (const auto &trim : trims) {
trim->update(frameTime);
if (i < (int)trim->trimParams().subItemLimit) {
//TODO:allow combination
//assert(!parentTrim);
@ -1111,7 +1162,7 @@ public:
}
}
subItems[i]->updateChildren(frameTime, childTrim);
subItems[i]->updateChildren(childTrim);
}
}
}
@ -1284,14 +1335,16 @@ CompositionLayer(solidLayer, Vector2D::Zero()) {
void ShapeCompositionLayer::displayContentsWithFrame(double frame, bool forceUpdates) {
_frameTime = frame;
_frameTimeInitialized = true;
_contentTree->itemTree->updateChildren(_frameTime, std::nullopt);
_contentTree->itemTree->updateFrame(_frameTime);
_contentTree->itemTree->updateChildren(std::nullopt);
}
std::shared_ptr<RenderTreeNode> ShapeCompositionLayer::renderTreeNode() {
if (!_frameTimeInitialized) {
_frameTime = 0.0;
_frameTimeInitialized = true;
_contentTree->itemTree->updateChildren(_frameTime, std::nullopt);
_contentTree->itemTree->updateFrame(_frameTime);
_contentTree->itemTree->updateChildren(std::nullopt);
}
if (!_renderTreeNode) {