#include "ShapeCompositionLayer.hpp" #include "Lottie/Private/Model/ShapeItems/Group.hpp" #include "Lottie/Private/Model/ShapeItems/Ellipse.hpp" #include "Lottie/Private/Model/ShapeItems/Rectangle.hpp" #include "Lottie/Private/Model/ShapeItems/Star.hpp" #include "Lottie/Private/Model/ShapeItems/Shape.hpp" #include "Lottie/Private/Model/ShapeItems/Trim.hpp" #include "Lottie/Private/Model/ShapeItems/Stroke.hpp" #include "Lottie/Private/Model/ShapeItems/GradientStroke.hpp" #include "Lottie/Private/MainThread/NodeRenderSystem/RenderLayers/GetGradientParameters.hpp" #include "Lottie/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/StrokeNode.hpp" #include "Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/DashPatternInterpolator.hpp" #include "Lottie/Private/MainThread/LayerContainers/CompLayers/ShapeUtils/BezierPathUtils.hpp" #include "Lottie/Private/Model/ShapeItems/ShapeTransform.hpp" namespace lottie { class ShapeLayerPresentationTree { public: class FillOutput { public: FillOutput() { } ~FillOutput() = default; virtual void update(AnimationFrameTime frameTime) = 0; virtual std::shared_ptr fill() = 0; }; class SolidFillOutput : public FillOutput { public: explicit SolidFillOutput(Fill const &fill) : rule(fill.fillRule.value_or(FillRule::NonZeroWinding)), color(fill.color.keyframes), opacity(fill.opacity.keyframes) { } virtual void update(AnimationFrameTime frameTime) override { bool hasUpdates = false; if (color.hasUpdate(frameTime)) { hasUpdates = true; colorValue = color.value(frameTime); } if (opacity.hasUpdate(frameTime)) { hasUpdates = true; opacityValue = opacity.value(frameTime).value; } if (!_fill || hasUpdates) { auto solid = std::make_shared(colorValue, opacityValue * 0.01); _fill = std::make_shared( solid, rule ); } } virtual std::shared_ptr fill() override { return _fill; } private: FillRule rule; KeyframeInterpolator color; Color colorValue = Color(0.0, 0.0, 0.0, 0.0); KeyframeInterpolator opacity; double opacityValue = 0.0; std::shared_ptr _fill; }; class GradientFillOutput : public FillOutput { public: explicit GradientFillOutput(GradientFill const &gradientFill) : rule(FillRule::NonZeroWinding), numberOfColors(gradientFill.numberOfColors), gradientType(gradientFill.gradientType), colors(gradientFill.colors.keyframes), startPoint(gradientFill.startPoint.keyframes), endPoint(gradientFill.endPoint.keyframes), opacity(gradientFill.opacity.keyframes) { } virtual void update(AnimationFrameTime frameTime) override { bool hasUpdates = false; if (colors.hasUpdate(frameTime)) { hasUpdates = true; colorsValue = colors.value(frameTime); } if (startPoint.hasUpdate(frameTime)) { hasUpdates = true; startPointValue = startPoint.value(frameTime); } if (endPoint.hasUpdate(frameTime)) { hasUpdates = true; endPointValue = endPoint.value(frameTime); } if (opacity.hasUpdate(frameTime)) { hasUpdates = true; opacityValue = opacity.value(frameTime).value; } if (!_fill || hasUpdates) { std::vector colors; std::vector locations; getGradientParameters(numberOfColors, colorsValue, colors, locations); auto gradient = std::make_shared( opacityValue * 0.01, gradientType, colors, locations, Vector2D(startPointValue.x, startPointValue.y), Vector2D(endPointValue.x, endPointValue.y) ); _fill = std::make_shared( gradient, rule ); } } virtual std::shared_ptr fill() override { return _fill; } private: FillRule rule; int numberOfColors = 0; GradientType gradientType; KeyframeInterpolator colors; GradientColorSet colorsValue; KeyframeInterpolator startPoint; Vector3D startPointValue = Vector3D(0.0, 0.0, 0.0); KeyframeInterpolator endPoint; Vector3D endPointValue = Vector3D(0.0, 0.0, 0.0); KeyframeInterpolator opacity; double opacityValue = 0.0; std::shared_ptr _fill; }; class StrokeOutput { public: StrokeOutput() { } ~StrokeOutput() = default; virtual void update(AnimationFrameTime frameTime) = 0; virtual std::shared_ptr stroke() = 0; }; class SolidStrokeOutput : public StrokeOutput { public: SolidStrokeOutput(Stroke const &stroke) : lineJoin(stroke.lineJoin), lineCap(stroke.lineCap), miterLimit(stroke.miterLimit.value_or(4.0)), color(stroke.color.keyframes), opacity(stroke.opacity.keyframes), width(stroke.width.keyframes) { if (stroke.dashPattern.has_value()) { StrokeShapeDashConfiguration dashConfiguration(stroke.dashPattern.value()); dashPattern = std::make_unique(dashConfiguration.dashPatterns); if (!dashConfiguration.dashPhase.empty()) { dashPhase = std::make_unique>(dashConfiguration.dashPhase); } } } virtual void update(AnimationFrameTime frameTime) override { bool hasUpdates = false; if (color.hasUpdate(frameTime)) { hasUpdates = true; colorValue = color.value(frameTime); } if (opacity.hasUpdate(frameTime)) { hasUpdates = true; opacityValue = opacity.value(frameTime).value; } if (width.hasUpdate(frameTime)) { hasUpdates = true; widthValue = width.value(frameTime).value; } if (dashPattern) { if (dashPattern->hasUpdate(frameTime)) { hasUpdates = true; dashPatternValue = dashPattern->value(frameTime); } } if (dashPhase) { if (dashPhase->hasUpdate(frameTime)) { hasUpdates = true; dashPhaseValue = dashPhase->value(frameTime).value; } } if (!_stroke || hasUpdates) { bool hasNonZeroDashes = false; if (!dashPatternValue.values.empty()) { for (const auto &value : dashPatternValue.values) { if (value != 0) { hasNonZeroDashes = true; break; } } } auto solid = std::make_shared(colorValue, opacityValue * 0.01); _stroke = std::make_shared( solid, widthValue, lineJoin, lineCap, miterLimit, hasNonZeroDashes ? dashPhaseValue : 0.0, hasNonZeroDashes ? dashPatternValue.values : std::vector() ); } } virtual std::shared_ptr stroke() override { return _stroke; } private: LineJoin lineJoin; LineCap lineCap; double miterLimit = 4.0; KeyframeInterpolator color; Color colorValue = Color(0.0, 0.0, 0.0, 0.0); KeyframeInterpolator opacity; double opacityValue = 0.0; KeyframeInterpolator width; double widthValue = 0.0; std::unique_ptr dashPattern; DashPattern dashPatternValue = DashPattern({}); std::unique_ptr> dashPhase; double dashPhaseValue = 0.0; std::shared_ptr _stroke; }; class GradientStrokeOutput : public StrokeOutput { public: GradientStrokeOutput(GradientStroke const &gradientStroke) : lineJoin(gradientStroke.lineJoin), lineCap(gradientStroke.lineCap), miterLimit(gradientStroke.miterLimit.value_or(4.0)), numberOfColors(gradientStroke.numberOfColors), gradientType(gradientStroke.gradientType), colors(gradientStroke.colors.keyframes), startPoint(gradientStroke.startPoint.keyframes), endPoint(gradientStroke.endPoint.keyframes), opacity(gradientStroke.opacity.keyframes), width(gradientStroke.width.keyframes) { if (gradientStroke.dashPattern.has_value()) { StrokeShapeDashConfiguration dashConfiguration(gradientStroke.dashPattern.value()); dashPattern = std::make_unique(dashConfiguration.dashPatterns); if (!dashConfiguration.dashPhase.empty()) { dashPhase = std::make_unique>(dashConfiguration.dashPhase); } } } virtual void update(AnimationFrameTime frameTime) override { bool hasUpdates = false; if (colors.hasUpdate(frameTime)) { hasUpdates = true; colorsValue = colors.value(frameTime); } if (startPoint.hasUpdate(frameTime)) { hasUpdates = true; startPointValue = startPoint.value(frameTime); } if (endPoint.hasUpdate(frameTime)) { hasUpdates = true; endPointValue = endPoint.value(frameTime); } if (opacity.hasUpdate(frameTime)) { hasUpdates = true; opacityValue = opacity.value(frameTime).value; } if (width.hasUpdate(frameTime)) { hasUpdates = true; widthValue = width.value(frameTime).value; } if (dashPattern) { if (dashPattern->hasUpdate(frameTime)) { hasUpdates = true; dashPatternValue = dashPattern->value(frameTime); } } if (dashPhase) { if (dashPhase->hasUpdate(frameTime)) { hasUpdates = true; dashPhaseValue = dashPhase->value(frameTime).value; } } if (!_stroke || hasUpdates) { bool hasNonZeroDashes = false; if (!dashPatternValue.values.empty()) { for (const auto &value : dashPatternValue.values) { if (value != 0) { hasNonZeroDashes = true; break; } } } std::vector colors; std::vector locations; getGradientParameters(numberOfColors, colorsValue, colors, locations); auto gradient = std::make_shared( opacityValue * 0.01, gradientType, colors, locations, Vector2D(startPointValue.x, startPointValue.y), Vector2D(endPointValue.x, endPointValue.y) ); _stroke = std::make_shared( gradient, widthValue, lineJoin, lineCap, miterLimit, hasNonZeroDashes ? dashPhaseValue : 0.0, hasNonZeroDashes ? dashPatternValue.values : std::vector() ); } } virtual std::shared_ptr stroke() override { return _stroke; } private: LineJoin lineJoin; LineCap lineCap; double miterLimit = 4.0; int numberOfColors = 0; GradientType gradientType; KeyframeInterpolator colors; GradientColorSet colorsValue; KeyframeInterpolator startPoint; Vector3D startPointValue = Vector3D(0.0, 0.0, 0.0); KeyframeInterpolator endPoint; Vector3D endPointValue = Vector3D(0.0, 0.0, 0.0); KeyframeInterpolator opacity; double opacityValue = 0.0; KeyframeInterpolator width; double widthValue = 0.0; std::unique_ptr dashPattern; DashPattern dashPatternValue = DashPattern({}); std::unique_ptr> dashPhase; double dashPhaseValue = 0.0; 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) : type(trim.trimType), subItemLimit(subItemLimit), start(trim.start.keyframes), end(trim.end.keyframes), offset(trim.offset.keyframes) { } void update(AnimationFrameTime frameTime) { if (start.hasUpdate(frameTime)) { startValue = start.value(frameTime).value; } if (end.hasUpdate(frameTime)) { endValue = end.value(frameTime).value; } if (offset.hasUpdate(frameTime)) { offsetValue = offset.value(frameTime).value; } } TrimParams trimParams() { double resolvedStartValue = startValue * 0.01; double resolvedEndValue = endValue * 0.01; double resolvedStart = std::min(resolvedStartValue, resolvedEndValue); double resolvedEnd = std::max(resolvedStartValue, resolvedEndValue); double resolvedOffset = fmod(offsetValue, 360.0) / 360.0; return TrimParams(resolvedStart, resolvedEnd, resolvedOffset, type, subItemLimit); } private: TrimType type; size_t subItemLimit = 0; KeyframeInterpolator start; double startValue = 0.0; KeyframeInterpolator end; double endValue = 0.0; KeyframeInterpolator offset; double offsetValue = 0.0; }; struct ShadingVariant { std::shared_ptr fill; std::shared_ptr stroke; size_t subItemLimit = 0; std::shared_ptr renderTree; }; struct TransformedPath { BezierPath path; CATransform3D transform; TransformedPath(BezierPath const &path_, CATransform3D const &transform_) : path(path_), transform(transform_) { } }; class PathOutput { public: PathOutput() { } virtual ~PathOutput() = default; virtual void update(AnimationFrameTime frameTime) = 0; virtual BezierPath const *currentPath() = 0; }; class StaticPathOutput : public PathOutput { public: explicit StaticPathOutput(BezierPath const &path) : resolvedPath(path) { } virtual void update(AnimationFrameTime frameTime) override { } virtual BezierPath const *currentPath() override { return &resolvedPath; } private: BezierPath resolvedPath; }; class ShapePathOutput : public PathOutput { public: explicit ShapePathOutput(Shape const &shape) : path(shape.path.keyframes) { } virtual void update(AnimationFrameTime frameTime) override { if (!hasValidData || path.hasUpdate(frameTime)) { path.update(frameTime, resolvedPath); } hasValidData = true; } virtual BezierPath const *currentPath() override { return &resolvedPath; } private: bool hasValidData = false; BezierPathKeyframeInterpolator path; BezierPath resolvedPath; }; class RectanglePathOutput : public PathOutput { public: explicit RectanglePathOutput(Rectangle const &rectangle) : direction(rectangle.direction.value_or(PathDirection::Clockwise)), position(rectangle.position.keyframes), size(rectangle.size.keyframes), cornerRadius(rectangle.cornerRadius.keyframes) { } virtual void update(AnimationFrameTime frameTime) override { bool hasUpdates = false; if (!hasValidData || position.hasUpdate(frameTime)) { hasUpdates = true; positionValue = position.value(frameTime); } if (!hasValidData || size.hasUpdate(frameTime)) { hasUpdates = true; sizeValue = size.value(frameTime); } if (!hasValidData || cornerRadius.hasUpdate(frameTime)) { hasUpdates = true; cornerRadiusValue = cornerRadius.value(frameTime).value; } if (hasUpdates) { resolvedPath = makeRectangleBezierPath(Vector2D(positionValue.x, positionValue.y), Vector2D(sizeValue.x, sizeValue.y), cornerRadiusValue, direction); } hasValidData = true; } virtual BezierPath const *currentPath() override { return &resolvedPath; } private: bool hasValidData = false; PathDirection direction; KeyframeInterpolator position; Vector3D positionValue = Vector3D(0.0, 0.0, 0.0); KeyframeInterpolator size; Vector3D sizeValue = Vector3D(0.0, 0.0, 0.0); KeyframeInterpolator cornerRadius; double cornerRadiusValue = 0.0; BezierPath resolvedPath; }; class EllipsePathOutput : public PathOutput { public: explicit EllipsePathOutput(Ellipse const &ellipse) : direction(ellipse.direction.value_or(PathDirection::Clockwise)), position(ellipse.position.keyframes), size(ellipse.size.keyframes) { } virtual void update(AnimationFrameTime frameTime) override { bool hasUpdates = false; if (!hasValidData || position.hasUpdate(frameTime)) { hasUpdates = true; positionValue = position.value(frameTime); } if (!hasValidData || size.hasUpdate(frameTime)) { hasUpdates = true; sizeValue = size.value(frameTime); } if (hasUpdates) { resolvedPath = makeEllipseBezierPath(Vector2D(sizeValue.x, sizeValue.y), Vector2D(positionValue.x, positionValue.y), direction); } hasValidData = true; } virtual BezierPath const *currentPath() override { return &resolvedPath; } private: bool hasValidData = false; PathDirection direction; KeyframeInterpolator position; Vector3D positionValue = Vector3D(0.0, 0.0, 0.0); KeyframeInterpolator size; Vector3D sizeValue = Vector3D(0.0, 0.0, 0.0); BezierPath resolvedPath; }; class StarPathOutput : public PathOutput { public: explicit StarPathOutput(Star const &star) : direction(star.direction.value_or(PathDirection::Clockwise)), position(star.position.keyframes), outerRadius(star.outerRadius.keyframes), outerRoundedness(star.outerRoundness.keyframes), rotation(star.rotation.keyframes), points(star.points.keyframes) { if (star.innerRadius.has_value()) { innerRadius = std::make_unique>(std::make_shared>(star.innerRadius->keyframes)); } else { innerRadius = std::make_unique>(std::make_shared>(Vector1D(0.0))); } if (star.innerRoundness.has_value()) { innerRoundedness = std::make_unique>(std::make_shared>(star.innerRoundness->keyframes)); } else { innerRoundedness = std::make_unique>(std::make_shared>(Vector1D(0.0))); } } virtual void update(AnimationFrameTime frameTime) override { bool hasUpdates = false; if (!hasValidData || position.hasUpdate(frameTime)) { hasUpdates = true; positionValue = position.value(frameTime); } if (!hasValidData || outerRadius.hasUpdate(frameTime)) { hasUpdates = true; outerRadiusValue = outerRadius.value(frameTime).value; } innerRadius->update(frameTime); if (!hasValidData || innerRadiusValue != innerRadius->value().value) { hasUpdates = true; innerRadiusValue = innerRadius->value().value; } if (!hasValidData || outerRoundedness.hasUpdate(frameTime)) { hasUpdates = true; outerRoundednessValue = outerRoundedness.value(frameTime).value; } innerRoundedness->update(frameTime); if (!hasValidData || innerRoundednessValue != innerRoundedness->value().value) { hasUpdates = true; innerRoundednessValue = innerRoundedness->value().value; } if (!hasValidData || points.hasUpdate(frameTime)) { hasUpdates = true; pointsValue = points.value(frameTime).value; } if (!hasValidData || rotation.hasUpdate(frameTime)) { hasUpdates = true; rotationValue = rotation.value(frameTime).value; } if (hasUpdates) { resolvedPath = makeStarBezierPath(Vector2D(positionValue.x, positionValue.y), outerRadiusValue, innerRadiusValue, outerRoundednessValue, innerRoundednessValue, pointsValue, rotationValue, direction); } hasValidData = true; } virtual BezierPath const *currentPath() override { return &resolvedPath; } private: bool hasValidData = false; PathDirection direction; KeyframeInterpolator position; Vector3D positionValue = Vector3D(0.0, 0.0, 0.0); KeyframeInterpolator outerRadius; double outerRadiusValue = 0.0; KeyframeInterpolator outerRoundedness; double outerRoundednessValue = 0.0; std::unique_ptr> innerRadius; double innerRadiusValue = 0.0; std::unique_ptr> innerRoundedness; double innerRoundednessValue = 0.0; KeyframeInterpolator rotation; double rotationValue = 0.0; KeyframeInterpolator points; double pointsValue = 0.0; BezierPath resolvedPath; }; class TransformOutput { public: TransformOutput(std::shared_ptr shapeTransform) { if (shapeTransform->anchor) { _anchor = std::make_unique>(shapeTransform->anchor->keyframes); } if (shapeTransform->position) { _position = std::make_unique>(shapeTransform->position->keyframes); } if (shapeTransform->scale) { _scale = std::make_unique>(shapeTransform->scale->keyframes); } if (shapeTransform->rotation) { _rotation = std::make_unique>(shapeTransform->rotation->keyframes); } if (shapeTransform->skew) { _skew = std::make_unique>(shapeTransform->skew->keyframes); } if (shapeTransform->skewAxis) { _skewAxis = std::make_unique>(shapeTransform->skewAxis->keyframes); } if (shapeTransform->opacity) { _opacity = std::make_unique>(shapeTransform->opacity->keyframes); } } void update(AnimationFrameTime frameTime) { bool hasUpdates = false; if (!hasValidData) { hasUpdates = true; } if (_anchor && _anchor->hasUpdate(frameTime)) { hasUpdates = true; } if (_position && _position->hasUpdate(frameTime)) { hasUpdates = true; } if (_scale && _scale->hasUpdate(frameTime)) { hasUpdates = true; } if (_rotation && _rotation->hasUpdate(frameTime)) { hasUpdates = true; } if (_skew && _skew->hasUpdate(frameTime)) { hasUpdates = true; } if (_skewAxis && _skewAxis->hasUpdate(frameTime)) { hasUpdates = true; } if (_opacity && _opacity->hasUpdate(frameTime)) { hasUpdates = true; } if (hasUpdates) { //TODO:optimize by storing components Vector3D anchorValue(0.0, 0.0, 0.0); if (_anchor) { anchorValue = _anchor->value(frameTime); } Vector3D positionValue(0.0, 0.0, 0.0); if (_position) { positionValue = _position->value(frameTime); } Vector3D scaleValue(100.0, 100.0, 100.0); if (_scale) { scaleValue = _scale->value(frameTime); } double rotationValue = 0.0; if (_rotation) { rotationValue = _rotation->value(frameTime).value; } double skewValue = 0.0; if (_skew) { skewValue = _skew->value(frameTime).value; } double skewAxisValue = 0.0; if (_skewAxis) { skewAxisValue = _skewAxis->value(frameTime).value; } if (_opacity) { _opacityValue = _opacity->value(frameTime).value * 0.01; } else { _opacityValue = 1.0; } _transformValue = CATransform3D::identity().translated(Vector2D(positionValue.x, positionValue.y)).rotated(rotationValue).skewed(-skewValue, skewAxisValue).scaled(Vector2D(scaleValue.x * 0.01, scaleValue.y * 0.01)).translated(Vector2D(-anchorValue.x, -anchorValue.y)); hasValidData = true; } } CATransform3D const &transform() { return _transformValue; } double opacity() { return _opacityValue; } private: bool hasValidData = false; std::unique_ptr> _anchor; std::unique_ptr> _position; std::unique_ptr> _scale; std::unique_ptr> _rotation; std::unique_ptr> _skew; std::unique_ptr> _skewAxis; std::unique_ptr> _opacity; CATransform3D _transformValue = CATransform3D::identity(); double _opacityValue = 1.0; }; class ContentItem { public: ContentItem() { } public: bool isGroup = false; void setPath(std::unique_ptr &&path_) { path = std::move(path_); } void setTransform(std::unique_ptr &&transform_) { transform = std::move(transform_); } std::shared_ptr const &renderTree() const { return _renderTree; } private: std::unique_ptr path; std::unique_ptr transform; std::vector shadings; std::vector> trims; std::vector> subItems; std::shared_ptr _renderTree; private: std::vector collectPaths(AnimationFrameTime frameTime, size_t subItemLimit, CATransform3D parentTransform) { std::vector mappedPaths; CATransform3D effectiveTransform = parentTransform; CATransform3D effectiveChildTransform = parentTransform; size_t maxSubitem = std::min(subItems.size(), subItemLimit); if (path) { path->update(frameTime); mappedPaths.emplace_back(*(path->currentPath()), effectiveTransform); } for (size_t i = 0; i < maxSubitem; i++) { auto &subItem = subItems[i]; CATransform3D subItemTransform = effectiveChildTransform; if (subItem->isGroup && subItem->transform) { subItem->transform->update(frameTime); subItemTransform = subItem->transform->transform() * subItemTransform; } std::optional currentTrim; if (!trims.empty()) { trims[0]->update(frameTime); currentTrim = trims[0]->trimParams(); } auto subItemPaths = subItem->collectPaths(frameTime, INT32_MAX, subItemTransform); if (currentTrim) { CompoundBezierPath tempPath; for (auto &path : subItemPaths) { tempPath.appendPath(path.path.copyUsingTransform(path.transform)); } CompoundBezierPath trimmedPath = trimCompoundPath(tempPath, currentTrim->start, currentTrim->end, currentTrim->offset, currentTrim->type); for (auto &path : trimmedPath.paths) { mappedPaths.emplace_back(path, CATransform3D::identity()); } } else { for (auto &path : subItemPaths) { mappedPaths.emplace_back(path.path, path.transform); } } } return mappedPaths; } public: void addSubItem(std::shared_ptr const &subItem) { subItems.push_back(subItem); } void addFill(std::shared_ptr fill) { ShadingVariant shading; shading.subItemLimit = subItems.size(); shading.fill = fill; shadings.insert(shadings.begin(), shading); } void addStroke(std::shared_ptr stroke) { ShadingVariant shading; shading.subItemLimit = subItems.size(); shading.stroke = stroke; shadings.insert(shadings.begin(), shading); } void addTrim(Trim const &trim) { trims.push_back(std::make_shared(trim, subItems.size())); } 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, nullptr, std::vector>(), nullptr, false ); if (!shadings.empty()) { for (int i = 0; i < shadings.size(); i++) { auto &shadingVariant = shadings[i]; if (!(shadingVariant.fill || shadingVariant.stroke)) { continue; } auto shadingRenderTree = std::make_shared( CGRect(0.0, 0.0, 0.0, 0.0), Vector2D(0.0, 0.0), CATransform3D::identity(), 1.0, false, false, nullptr, std::vector>(), nullptr, false ); shadingVariant.renderTree = shadingRenderTree; _renderTree->_subnodes.push_back(shadingRenderTree); } } if (isGroup && !subItems.empty()) { std::vector> subItemNodes; for (int i = (int)subItems.size() - 1; i >= 0; i--) { subItems[i]->initializeRenderChildren(); subItemNodes.push_back(subItems[i]->_renderTree); } 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, nullptr, subItemNodes, nullptr, false )); } } } public: void renderChildren(AnimationFrameTime frameTime, std::optional parentTrim) { CATransform3D containerTransform = CATransform3D::identity(); double containerOpacity = 1.0; if (transform) { transform->update(frameTime); containerTransform = transform->transform(); containerOpacity = transform->opacity(); } _renderTree->_transform = containerTransform; _renderTree->_alpha = containerOpacity; for (int i = 0; i < shadings.size(); i++) { const auto &shadingVariant = shadings[i]; if (!(shadingVariant.fill || shadingVariant.stroke)) { continue; } CompoundBezierPath compoundPath; auto paths = collectPaths(frameTime, shadingVariant.subItemLimit, CATransform3D::identity()); for (const auto &path : paths) { compoundPath.appendPath(path.path.copyUsingTransform(path.transform)); } //std::optional currentTrim = parentTrim; //TODO:investigate /*if (!trims.empty()) { currentTrim = trims[0]; }*/ if (parentTrim) { compoundPath = trimCompoundPath(compoundPath, parentTrim->start, parentTrim->end, parentTrim->offset, parentTrim->type); } std::vector resultPaths; for (const auto &path : compoundPath.paths) { resultPaths.push_back(path); } std::shared_ptr content; std::shared_ptr fill; if (shadingVariant.fill) { shadingVariant.fill->update(frameTime); fill = shadingVariant.fill->fill(); } std::shared_ptr stroke; if (shadingVariant.stroke) { shadingVariant.stroke->update(frameTime); stroke = shadingVariant.stroke->stroke(); } content = std::make_shared( resultPaths, stroke, fill ); shadingVariant.renderTree->_content = content; } if (isGroup && !subItems.empty()) { for (int i = (int)subItems.size() - 1; i >= 0; i--) { std::optional childTrim = parentTrim; for (const auto &trim : trims) { trim->update(frameTime); if (i < (int)trim->trimParams().subItemLimit) { //TODO:allow combination //assert(!parentTrim); childTrim = trim->trimParams(); } } subItems[i]->renderChildren(frameTime, childTrim); } } } }; public: ShapeLayerPresentationTree(std::vector> const &items) { itemTree = std::make_shared(); itemTree->isGroup = true; ShapeLayerPresentationTree::renderTreeContent(items, itemTree); } ShapeLayerPresentationTree(std::shared_ptr const &solidLayer) { itemTree = std::make_shared(); itemTree->isGroup = true; std::vector> items; items.push_back(std::make_shared( std::nullopt, std::nullopt, std::nullopt, std::nullopt, solidLayer->hidden, std::nullopt, std::nullopt, std::nullopt, std::nullopt, KeyframeGroup(Vector3D(0.0, 0.0, 0.0)), KeyframeGroup(Vector3D(solidLayer->width, solidLayer->height, 0.0)), KeyframeGroup(Vector1D(0.0)) )); ShapeLayerPresentationTree::renderTreeContent(items, itemTree); } private: static void renderTreeContent(std::vector> const &items, std::shared_ptr &itemTree) { for (const auto &item : items) { if (item->hidden()) { continue; } switch (item->type) { case ShapeType::Fill: { Fill const &fill = *((Fill *)item.get()); itemTree->addFill(std::make_shared(fill)); break; } case ShapeType::GradientFill: { GradientFill const &gradientFill = *((GradientFill *)item.get()); itemTree->addFill(std::make_shared(gradientFill)); break; } case ShapeType::Stroke: { Stroke const &stroke = *((Stroke *)item.get()); itemTree->addStroke(std::make_shared(stroke)); break; } case ShapeType::GradientStroke: { GradientStroke const &gradientStroke = *((GradientStroke *)item.get()); itemTree->addStroke(std::make_shared(gradientStroke)); break; } case ShapeType::Group: { Group const &group = *((Group *)item.get()); auto groupItem = std::make_shared(); groupItem->isGroup = true; ShapeLayerPresentationTree::renderTreeContent(group.items, groupItem); itemTree->addSubItem(groupItem); break; } case ShapeType::Shape: { Shape const &shape = *((Shape *)item.get()); auto shapeItem = std::make_shared(); shapeItem->setPath(std::make_unique(shape)); itemTree->addSubItem(shapeItem); break; } case ShapeType::Trim: { Trim const &trim = *((Trim *)item.get()); itemTree->addTrim(trim); break; } case ShapeType::Transform: { auto transform = std::static_pointer_cast(item); itemTree->setTransform(std::make_unique(transform)); break; } case ShapeType::Ellipse: { Ellipse const &ellipse = *((Ellipse *)item.get()); auto shapeItem = std::make_shared(); shapeItem->setPath(std::make_unique(ellipse)); itemTree->addSubItem(shapeItem); break; } case ShapeType::Merge: { //assert(false); break; } case ShapeType::Rectangle: { Rectangle const &rectangle = *((Rectangle *)item.get()); auto shapeItem = std::make_shared(); shapeItem->setPath(std::make_unique(rectangle)); itemTree->addSubItem(shapeItem); break; } case ShapeType::Repeater: { assert(false); break; } case ShapeType::Star: { Star const &star = *((Star *)item.get()); auto shapeItem = std::make_shared(); shapeItem->setPath(std::make_unique(star)); itemTree->addSubItem(shapeItem); break; } case ShapeType::RoundedRectangle: { //TODO:restore break; } default: { break; } } } itemTree->initializeRenderChildren(); } public: std::shared_ptr itemTree; }; ShapeCompositionLayer::ShapeCompositionLayer(std::shared_ptr const &shapeLayer) : CompositionLayer(shapeLayer, Vector2D::Zero()) { _contentTree = std::make_shared(shapeLayer->items); } ShapeCompositionLayer::ShapeCompositionLayer(std::shared_ptr const &solidLayer) : CompositionLayer(solidLayer, Vector2D::Zero()) { _contentTree = std::make_shared(solidLayer); } void ShapeCompositionLayer::displayContentsWithFrame(double frame, bool forceUpdates) { _frameTime = frame; _frameTimeInitialized = true; _contentTree->itemTree->renderChildren(_frameTime, std::nullopt); } std::shared_ptr ShapeCompositionLayer::renderTreeNode() { if (_contentsLayer->isHidden()) { return nullptr; } assert(_frameTimeInitialized); std::shared_ptr maskNode; bool invertMask = false; if (_matteLayer) { maskNode = _matteLayer->renderTreeNode(); if (maskNode && _matteType.has_value() && _matteType.value() == MatteType::Invert) { invertMask = true; } } std::vector> renderTreeValue; renderTreeValue.push_back(_contentTree->itemTree->renderTree()); //printf("Name: %s\n", keypathName().c_str()); /*if (!maskNode && keypathName().find("Shape Layer 3") != -1) { return std::make_shared( bounds(), _contentsLayer->position(), _contentsLayer->transform(), _contentsLayer->opacity(), _contentsLayer->masksToBounds(), _contentsLayer->isHidden(), nullptr, renderTreeValue, nullptr, false ); }*/ std::vector> subnodes; subnodes.push_back(std::make_shared( _contentsLayer->bounds(), _contentsLayer->position(), _contentsLayer->transform(), _contentsLayer->opacity(), _contentsLayer->masksToBounds(), _contentsLayer->isHidden(), nullptr, renderTreeValue, nullptr, false )); assert(position() == Vector2D::Zero()); assert(transform().isIdentity()); assert(opacity() == 1.0); assert(!masksToBounds()); assert(!isHidden()); assert(_contentsLayer->bounds() == CGRect(0.0, 0.0, 0.0, 0.0)); assert(_contentsLayer->position() == Vector2D::Zero()); assert(!_contentsLayer->masksToBounds()); return std::make_shared( bounds(), position(), transform(), opacity(), masksToBounds(), isHidden(), nullptr, subnodes, maskNode, invertMask ); } }