From d2386e86e98986e553aff0aa4f64631fa719e61c Mon Sep 17 00:00:00 2001 From: kunitoki Date: Sat, 11 Jul 2026 18:18:44 +0200 Subject: [PATCH 1/2] More lottie fixes --- .../core/yup_AnimationTransform.cpp | 13 +++++++++- .../core/yup_AnimationTransform.h | 3 +++ modules/yup_animation/io/yup_LottieReader.cpp | 1 + modules/yup_animation/io/yup_LottieWriter.cpp | 1 + .../renderer/yup_AnimationRenderer.cpp | 15 +++++++---- tests/data/lottie/bell.json | 1 + .../yup_animation/yup_AnimationTransform.cpp | 14 ++++++++++ tests/yup_animation/yup_LottieReader.cpp | 26 +++++++++++++++++++ tests/yup_animation/yup_LottieWriter.cpp | 16 ++++++++++++ 9 files changed, 84 insertions(+), 6 deletions(-) create mode 100644 tests/data/lottie/bell.json diff --git a/modules/yup_animation/core/yup_AnimationTransform.cpp b/modules/yup_animation/core/yup_AnimationTransform.cpp index dec329720..cc3532499 100644 --- a/modules/yup_animation/core/yup_AnimationTransform.cpp +++ b/modules/yup_animation/core/yup_AnimationTransform.cpp @@ -32,12 +32,23 @@ AffineTransform AnimationTransform::toAffineTransform (float frameNo) const const Point a = anchor.getValueAt (frameNo); const Size s = scale.getValueAt (frameNo); - const float r = is3DData ? rotationZ.getValueAt (frameNo) : rotation.getValueAt (frameNo); + float r = is3DData ? rotationZ.getValueAt (frameNo) : rotation.getValueAt (frameNo); const float sk = skew.getValueAt (frameNo); const float sa = skewAxis.getValueAt (frameNo); const Point p = positionAt (frameNo); + if (autoOrient) + { + constexpr float sampleOffset = 0.01f; + const Point before = positionAt (frameNo - sampleOffset); + const Point after = positionAt (frameNo + sampleOffset); + const Point direction = after - before; + + if (direction.getX() * direction.getX() + direction.getY() * direction.getY() > 1.0e-8f) + r += radiansToDegrees (std::atan2 (direction.getY(), direction.getX())); + } + // Compose: translate(p) * rotate(r) * skew * scale(s/100) * translate(-a) AffineTransform t; t = t.translated (-a.getX(), -a.getY()); diff --git a/modules/yup_animation/core/yup_AnimationTransform.h b/modules/yup_animation/core/yup_AnimationTransform.h index f54724e85..5bc0a784e 100644 --- a/modules/yup_animation/core/yup_AnimationTransform.h +++ b/modules/yup_animation/core/yup_AnimationTransform.h @@ -83,6 +83,9 @@ class YUP_API AnimationTransform FloatProperty skew { AnimationProperty::staticValue (0.0f) }; FloatProperty skewAxis { AnimationProperty::staticValue (0.0f) }; + /** When true, aligns the local X axis with the instantaneous position motion path. */ + bool autoOrient = false; + /** 3D rotation channels (Lottie "rx", "ry", "rz"). When is3DData is true, these replace the 2D rotation for 3D layers. */ bool is3DData = false; diff --git a/modules/yup_animation/io/yup_LottieReader.cpp b/modules/yup_animation/io/yup_LottieReader.cpp index ad6a7cadf..0f0f461b7 100644 --- a/modules/yup_animation/io/yup_LottieReader.cpp +++ b/modules/yup_animation/io/yup_LottieReader.cpp @@ -778,6 +778,7 @@ AnimationLayer::Ptr LottieReader::parseLayer (const var& layerObj) layer->matteType = static_cast (matteType); layer->isMatteSource = varInt (layerObj["td"]) != 0; + layer->transform.autoOrient = layer->autoOrient; parseTransform (layerObj["ks"], layer->transform, (bool) layerObj["ddd"]); parseMasks (layerObj["masksProperties"], *layer); parseEffects (layerObj["ef"], *layer); diff --git a/modules/yup_animation/io/yup_LottieWriter.cpp b/modules/yup_animation/io/yup_LottieWriter.cpp index 734e17cfc..36093eac9 100644 --- a/modules/yup_animation/io/yup_LottieWriter.cpp +++ b/modules/yup_animation/io/yup_LottieWriter.cpp @@ -89,6 +89,7 @@ var LottieWriter::serializeLayer (const AnimationLayer& layer) obj->setProperty ("sr", var ((double) layer.timeStretch)); obj->setProperty ("ddd", var (0)); obj->setProperty ("hd", var (layer.hidden)); + obj->setProperty ("ao", var (layer.autoOrient)); obj->setProperty ("bm", var (static_cast (layer.blendMode))); obj->setProperty ("tt", var (static_cast (layer.matteType))); diff --git a/modules/yup_animation/renderer/yup_AnimationRenderer.cpp b/modules/yup_animation/renderer/yup_AnimationRenderer.cpp index 0c8d2ca4a..7ed9ef5db 100644 --- a/modules/yup_animation/renderer/yup_AnimationRenderer.cpp +++ b/modules/yup_animation/renderer/yup_AnimationRenderer.cpp @@ -1023,6 +1023,13 @@ void AnimationRenderer::renderGroup (Graphics& g, // star shapes in pumped_up.json / mughead.json). const bool mergesNestedGeometry = activeMergePaths != nullptr && ! activeMergePaths->hidden; const bool hasModifiers = hasRounded || hasTrim || hasRepeater || hasMergePaths; + const bool hasDirectPaint = std::any_of (group.children.begin(), + group.children.end(), + [] (const AnimationGroup::ChildItem& child) + { + return child.kind == AnimationGroup::ChildKind::Fill + || child.kind == AnimationGroup::ChildKind::Stroke; + }); std::vector currentPaths; std::vector preparedCache; @@ -1184,11 +1191,9 @@ void AnimationRenderer::renderGroup (Graphics& g, { renderGroup (g, *child.group, ctx, opacity, activeRoundedCorner); - // A nested group without its own paint acts as a geometry container - // for the parent's Merge Paths. Without an active Merge Paths modifier - // the group is self-contained — do NOT feed its geometry into the - // parent's paints (avoids filling construction-guide shapes). - if (! mergesNestedGeometry) + // A nested group without its own paint can supply geometry to a + // parent paint or Merge Paths modifier. + if (! mergesNestedGeometry && ! hasDirectPaint) continue; const bool hasOwnPaint = std::any_of (child.group->children.begin(), diff --git a/tests/data/lottie/bell.json b/tests/data/lottie/bell.json new file mode 100644 index 000000000..c70f06bb7 --- /dev/null +++ b/tests/data/lottie/bell.json @@ -0,0 +1 @@ +{"assets":[{"id":"comp_0","layers":[{"ddd":0,"ind":0,"ty":1,"nm":"White Solid 1","td":1,"ks":{"o":{"k":100},"r":{"k":22},"p":{"k":[288.003,547.449,0]},"a":{"k":[25,25,0]},"s":{"k":[412,168,100]}},"ao":0,"sw":50,"sh":50,"sc":"#ffffff","ip":0,"op":50,"st":0,"bm":0,"sr":1},{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 1","tt":2,"ks":{"o":{"k":100},"r":{"k":22},"p":{"k":[273.145,580.253,0]},"a":{"k":[-0.776,155.616,0]},"s":{"k":[110.945,113.273,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"k":[59.648,59.648]},"p":{"k":[0,0]},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse"},{"ty":"fl","fillEnabled":true,"c":{"k":[0,0,0,1]},"o":{"k":100},"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill"},{"ty":"tr","p":{"k":[-1,155.5],"ix":2},"a":{"k":[0,0],"ix":1},"s":{"k":[100,100],"ix":3},"r":{"k":0,"ix":6},"o":{"k":100,"ix":7},"sk":{"k":0,"ix":4},"sa":{"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"mn":"ADBE Vector Group"}],"ip":0,"op":50,"st":0,"bm":0,"sr":1}]}],"layers":[{"ddd":0,"ind":0,"ty":3,"nm":"Null 1","ks":{"o":{"k":0},"r":{"k":[{"i":{"x":[0.833],"y":[1.093]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_1p093_0p167_0p167"],"t":0,"s":[0],"e":[-22]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0.037]},"n":["0p833_1_0p167_0p037"],"t":6.931,"s":[-22],"e":[22]},{"i":{"x":[0.833],"y":[0.966]},"o":{"x":[0.167],"y":[0]},"n":["0p833_0p966_0p167_0"],"t":12.435,"s":[22],"e":[-22]},{"i":{"x":[0.833],"y":[0.857]},"o":{"x":[0.167],"y":[-0.098]},"n":["0p833_0p857_0p167_-0p098"],"t":18,"s":[-22],"e":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0.295]},"n":["0p833_1_0p167_0p295"],"t":26,"s":[0],"e":[4]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0]},"n":["0p667_1_0p167_0"],"t":29,"s":[4],"e":[0]},{"t":33}]},"p":{"k":[221.5,77,0]},"a":{"k":[0,0,0]},"s":{"k":[344,344,100]}},"ao":0,"ip":0,"op":50,"st":0,"bm":0,"sr":1},{"ddd":0,"ind":1,"ty":1,"nm":"Black Solid 1","parent":0,"ks":{"o":{"k":100},"r":{"k":0},"p":{"k":[0,30.959,0]},"a":{"k":[221.5,183.5,0]},"s":{"k":[29.07,29.07,100]}},"ao":0,"hasMask":true,"masksProperties":[{"inv":false,"mode":"a","pt":{"k":{"i":[[3.376,4.804],[0,15.358],[0,0],[40.857,4.809],[0,6.956],[9.556,0],[0,-9.556],[-4.437,-3.072],[-0.007,-0.031],[0,-42.092],[0,0],[8.971,-12.559],[-5.461,0],[0,0]],"o":[[-8.874,-12.628],[0,0],[0,-41.969],[4.427,-3.689],[0,-9.556],[-9.556,0],[0,5.802],[0,0],[-40.638,4.737],[0,0],[0,15.358],[-3.413,4.778],[0,0],[5.461,0]],"v":[[318.087,264.087],[303.411,220.401],[303.411,172.619],[231.019,91.367],[238.565,77.056],[221.5,59.991],[204.435,77.056],[211.944,91.049],[211.971,91.152],[139.589,172.619],[139.589,220.401],[124.913,264.087],[130.374,275.009],[312.626,275.009]],"c":true}},"o":{"k":100},"x":{"k":0},"nm":"Mask 1"}],"ef":[{"ty":21,"nm":"Fill","mn":"ADBE Fill","ix":1,"ef":[{"ty":3,"nm":"Fill Mask","mn":"ADBE Fill-0001","ix":1,"v":{"k":0}},{"ty":7,"nm":"All Masks","mn":"ADBE Fill-0007","ix":2,"v":{"k":0}},{"ty":2,"nm":"Color","mn":"ADBE Fill-0002","ix":3,"v":{"k":[1,1,1,1]}},{"ty":7,"nm":"Invert","mn":"ADBE Fill-0006","ix":4,"v":{"k":0}},{"ty":0,"nm":"Horizontal Feather","mn":"ADBE Fill-0003","ix":5,"v":{"k":0}},{"ty":0,"nm":"Vertical Feather","mn":"ADBE Fill-0004","ix":6,"v":{"k":0}},{"ty":0,"nm":"Opacity","mn":"ADBE Fill-0005","ix":7,"v":{"k":1}}]}],"sw":443,"sh":367,"sc":"#000000","ip":0,"op":50,"st":0,"bm":0,"sr":1},{"ddd":0,"ind":2,"ty":0,"nm":"Pre-comp 1","parent":0,"refId":"comp_0","ks":{"o":{"k":100},"r":{"k":-22},"p":{"k":[{"i":{"x":0.833,"y":0.814},"o":{"x":0.167,"y":0.167},"n":"0p833_0p814_0p167_0p167","t":2,"s":[4.737,11.725,0],"e":[19.737,11.725,0],"to":[2.5,0,0],"ti":[2.83333325386047,0,0]},{"i":{"x":0.833,"y":0.844},"o":{"x":0.167,"y":0.157},"n":"0p833_0p844_0p167_0p157","t":4.572,"s":[19.737,11.725,0],"e":[-12.263,11.725,0],"to":[-2.83333325386047,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.88},"o":{"x":0.167,"y":0.177},"n":"0p833_0p88_0p167_0p177","t":9,"s":[-12.263,11.725,0],"e":[19.737,11.725,0],"to":[0,0,0],"ti":[-2.66666674613953,0,0]},{"i":{"x":0.833,"y":0.886},"o":{"x":0.167,"y":0.22},"n":"0p833_0p886_0p167_0p22","t":14.008,"s":[19.737,11.725,0],"e":[-8.129,11.725,0],"to":[0.7367005944252,0,0],"ti":[4.95352220535278,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.284},"n":"0p833_0p833_0p167_0p284","t":22,"s":[-8.129,11.725,0],"e":[3.737,11.725,0],"to":[-2.79587697982788,0,0],"ti":[0.0821717903018,0,0]},{"t":31}]},"a":{"k":[350,437.5,0]},"s":{"k":[29.07,29.07,100]}},"ao":0,"ef":[{"ty":21,"nm":"Fill","mn":"ADBE Fill","ix":1,"ef":[{"ty":3,"nm":"Fill Mask","mn":"ADBE Fill-0001","ix":1,"v":{"k":0}},{"ty":7,"nm":"All Masks","mn":"ADBE Fill-0007","ix":2,"v":{"k":0}},{"ty":2,"nm":"Color","mn":"ADBE Fill-0002","ix":3,"v":{"k":[1,1,1,1]}},{"ty":7,"nm":"Invert","mn":"ADBE Fill-0006","ix":4,"v":{"k":0}},{"ty":0,"nm":"Horizontal Feather","mn":"ADBE Fill-0003","ix":5,"v":{"k":0}},{"ty":0,"nm":"Vertical Feather","mn":"ADBE Fill-0004","ix":6,"v":{"k":0}},{"ty":0,"nm":"Opacity","mn":"ADBE Fill-0005","ix":7,"v":{"k":1}}]}],"w":700,"h":875,"ip":0,"op":50,"st":0,"bm":0,"sr":1}],"v":"4.5.0","ddd":0,"ip":0,"op":42,"fr":25,"w":443,"h":367} \ No newline at end of file diff --git a/tests/yup_animation/yup_AnimationTransform.cpp b/tests/yup_animation/yup_AnimationTransform.cpp index 4e6be4e93..ddb650361 100644 --- a/tests/yup_animation/yup_AnimationTransform.cpp +++ b/tests/yup_animation/yup_AnimationTransform.cpp @@ -178,6 +178,20 @@ TEST_F (AnimationTransformTests, SpatialPositionFollowsCircularArc) } } +TEST_F (AnimationTransformTests, AutoOrientAlignsWithMotionPath) +{ + AnimationTransform t; + t.autoOrient = true; + t.position = Vec2Property::Builder {} + .keyframe (0.0f, Point (0.0f, 0.0f), AnimationEasing::linear()) + .keyframe (10.0f, Point (0.0f, 100.0f), AnimationEasing::linear()) + .build(); + + const auto transformed = Point (1.0f, 0.0f).transformed (t.toAffineTransform (5.0f)); + EXPECT_NEAR (transformed.getX(), 0.0f, 0.01f); + EXPECT_NEAR (transformed.getY(), 51.0f, 0.01f); +} + // ============================================================================= // Rotation // ============================================================================= diff --git a/tests/yup_animation/yup_LottieReader.cpp b/tests/yup_animation/yup_LottieReader.cpp index fe23f7641..05ac6b662 100644 --- a/tests/yup_animation/yup_LottieReader.cpp +++ b/tests/yup_animation/yup_LottieReader.cpp @@ -144,6 +144,32 @@ TEST_F (LottieReaderTests, ParseDataSetsNoErrorForValidJson) EXPECT_TRUE (errorMsg.isEmpty()); } +TEST_F (LottieReaderTests, ParseFilePreservesBellSolidColor) +{ + const auto file = getLottieTestDataDir().getChildFile ("bell.json"); + auto comp = LottieReader::parseFile (file); + + ASSERT_NE (comp, nullptr); + ASSERT_GE (comp->layers.size(), 2u); + + const auto* solid = dynamic_cast (comp->layers[1].get()); + ASSERT_NE (solid, nullptr); + EXPECT_EQ (solid->solidColor, Color (0xFF000000)); +} + +TEST_F (LottieReaderTests, ParseDataReadsLayerAutoOrient) +{ + auto comp = LottieReader::parseData (R"json({ + "v": "5.5.2", "ip": 0, "op": 10, "fr": 30, "w": 100, "h": 100, + "layers": [{ "ty": 3, "ind": 1, "ao": 1, "ks": {} }] + })json"); + + ASSERT_NE (comp, nullptr); + ASSERT_EQ (comp->layers.size(), 1u); + EXPECT_TRUE (comp->layers[0]->autoOrient); + EXPECT_TRUE (comp->layers[0]->transform.autoOrient); +} + TEST_F (LottieReaderTests, ParseDataParsesCompositionProperties) { auto comp = LottieReader::parseData (kLottieReaderBaseJson); diff --git a/tests/yup_animation/yup_LottieWriter.cpp b/tests/yup_animation/yup_LottieWriter.cpp index 73a285065..3711dc6d6 100644 --- a/tests/yup_animation/yup_LottieWriter.cpp +++ b/tests/yup_animation/yup_LottieWriter.cpp @@ -536,6 +536,22 @@ TEST_F (LottieWriterTests, Roundtrip_PreservesLayerCount) EXPECT_EQ (readback->layers.size(), 2u); } +TEST_F (LottieWriterTests, Roundtrip_PreservesLayerAutoOrient) +{ + auto comp = LottieReader::parseData (kShapeAndNullJson); + ASSERT_NE (comp, nullptr); + ASSERT_FALSE (comp->layers.empty()); + comp->layers[0]->autoOrient = true; + comp->layers[0]->transform.autoOrient = true; + + auto readback = LottieReader::parseData (LottieWriter::toJson (*comp)); + + ASSERT_NE (readback, nullptr); + ASSERT_FALSE (readback->layers.empty()); + EXPECT_TRUE (readback->layers[0]->autoOrient); + EXPECT_TRUE (readback->layers[0]->transform.autoOrient); +} + TEST_F (LottieWriterTests, Roundtrip_PreservesLayerNames) { auto comp = LottieReader::parseData (kShapeAndNullJson); From 4308706de8b99a4874a430fb45f2de5f03fdf1a2 Mon Sep 17 00:00:00 2001 From: kunitoki Date: Mon, 13 Jul 2026 09:05:08 +0200 Subject: [PATCH 2/2] More rewrites --- CHANGELOG.md | 1 + .../yup_animation/animation/yup_Animation.cpp | 12 +- .../io/yup_LottieExpressionEvaluator.h | 42 ++++- modules/yup_animation/io/yup_LottieReader.cpp | 145 +++++---------- modules/yup_animation/io/yup_LottieReader.h | 107 ++++++----- modules/yup_animation/io/yup_LottieWriter.h | 16 +- .../model/yup_AnimationComposition.h | 4 +- .../renderer/yup_AnimationRenderResources.cpp | 86 ++++----- .../renderer/yup_AnimationRenderResources.h | 14 +- .../renderer/yup_AnimationRenderer.h | 76 ++++---- tests/yup_animation/yup_AnimationRenderer.cpp | 92 +++++----- tests/yup_animation/yup_LottieReader.cpp | 143 +++++++-------- tests/yup_animation/yup_LottieRoundtrip.cpp | 32 ++-- tests/yup_animation/yup_LottieWriter.cpp | 166 +++++++++--------- 14 files changed, 448 insertions(+), 488 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 20a3e7199..6c5ef4df8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ### Breaking changes - macOS: OpenGL rendering backend disabled in favor of Metal +- `LottieReader::parseFile()`, `parseData()`, `parseStream()`, and `parseFromZip()` now return `ResultValue` and no longer take a trailing `String* outError` out-parameter; check `wasOk()`/`failed()` and read the message via `getErrorMessage()`. - `AnimationFrameExporter` is now an instance-based class bound to a `GraphicsContext` (construct `AnimationFrameExporter exporter (ctx);` then call `exporter.renderFrame(anim, …)` / `exporter.renderAllFrames(…)` / `exporter.exportToGif(anim, …)`), so it can own and reuse the GPU matte-composite pipeline across frames instead of recompiling it per frame. The `exportToGif(frames, frameRate, …)` frame-sequence encoder remains a static helper. ### Graphics diff --git a/modules/yup_animation/animation/yup_Animation.cpp b/modules/yup_animation/animation/yup_Animation.cpp index 3fe8e73ea..12a630adb 100644 --- a/modules/yup_animation/animation/yup_Animation.cpp +++ b/modules/yup_animation/animation/yup_Animation.cpp @@ -48,10 +48,10 @@ Animation Animation::loadFromFile (const File& file, const LoadOptions& opts) loaderOpts.resourceDirectory = opts.resourceDirectory; auto comp = LottieReader::parseFile (file, loaderOpts); - if (comp == nullptr) + if (comp.failed()) return {}; - return Animation (std::move (comp)); + return Animation (comp.getReference()); } Animation Animation::loadFromData (const String& jsonText, const LoadOptions& opts) @@ -60,10 +60,10 @@ Animation Animation::loadFromData (const String& jsonText, const LoadOptions& op loaderOpts.resourceDirectory = opts.resourceDirectory; auto comp = LottieReader::parseData (jsonText, loaderOpts); - if (comp == nullptr) + if (comp.failed()) return {}; - return Animation (std::move (comp)); + return Animation (comp.getReference()); } Animation Animation::loadFromStream (InputStream& stream, const LoadOptions& opts) @@ -72,10 +72,10 @@ Animation Animation::loadFromStream (InputStream& stream, const LoadOptions& opt loaderOpts.resourceDirectory = opts.resourceDirectory; auto comp = LottieReader::parseStream (stream, loaderOpts); - if (comp == nullptr) + if (comp.failed()) return {}; - return Animation (std::move (comp)); + return Animation (comp.getReference()); } Animation Animation::fromComposition (AnimationComposition::Ptr comp) diff --git a/modules/yup_animation/io/yup_LottieExpressionEvaluator.h b/modules/yup_animation/io/yup_LottieExpressionEvaluator.h index d4ebfaa01..fc87a2535 100644 --- a/modules/yup_animation/io/yup_LottieExpressionEvaluator.h +++ b/modules/yup_animation/io/yup_LottieExpressionEvaluator.h @@ -35,6 +35,7 @@ class LottieExpressionEvaluator { public: //============================================================================== + /** Represents a layer in the composition context. */ struct LayerContext { String name; @@ -42,6 +43,7 @@ class LottieExpressionEvaluator AnimationTransform* transform = nullptr; }; + /** Represents the composition context for evaluating expressions. */ struct CompositionContext { Size size; @@ -50,6 +52,7 @@ class LottieExpressionEvaluator }; //============================================================================== + /** Represents a shape layer's top-level content group. */ struct EvalResult { enum class Kind @@ -60,7 +63,7 @@ class LottieExpressionEvaluator ShapeContentRef ///< content("G").content("P").path or content("G").transform.rotation }; - Kind kind = Kind::Unknown; + Kind kind = Kind::Unknown; ///< The kind of evaluation result var value; ///< Computed frame-0 value (set for StaticValue and as a snapshot for ref kinds) @@ -76,40 +79,65 @@ class LottieExpressionEvaluator }; //============================================================================== + /** Constructs a new LottieExpressionEvaluator. */ LottieExpressionEvaluator(); /** Registers thisComp with the given composition data. - Must be called before evaluating layer transform expressions. */ + + @param ctx The composition context to use for evaluating expressions. + + Must be called before evaluating layer transform expressions. + */ void setupCompositionContext (const CompositionContext& ctx); /** Registers content() pointing into a ShapeLayer's top-level groups. - Call before evaluating expressions inside parseShapeContents. */ + + @param layer The shape layer to use for evaluating expressions. + + Call before evaluating expressions inside parseShapeContents. + */ void setupShapeContext (const ShapeLayer& layer); /** Registers content() pointing into an AnimationGroup's child groups. - Call before evaluating expressions inside parseGroupItems. */ + + @param group The animation group to use for evaluating expressions. + + Call before evaluating expressions inside parseGroupItems. + */ void setupGroupContext (const AnimationGroup& group); - /** Evaluates an AE expression string. - Returns Kind::Unknown for empty input or evaluation failures. */ + /** Evaluates an AfterEffects expression string. + + @param expression The expression string to evaluate. + + @returns An EvalResult describing the result of the evaluation, or + Kind::Unknown if the expression is empty or evaluation failed. + */ [[nodiscard]] EvalResult evaluate (const String& expression); //============================================================================== - // Internal state setters used by proxy objects during evaluation. + /** @internal */ void setLastLayerName (const String& s) { lastLayerName_ = s; } + /** @internal */ void setLastLayerId (int id) { lastLayerId_ = id; } + /** @internal */ void setLastContentGroup (const String& s) { lastContentGroup_ = s; } + /** @internal */ void setLastContentItem (const String& s) { lastContentItem_ = s; } + /** @internal */ void setLastProperty (const String& s) { lastProperty_ = s; } + /** @internal */ String getLastContentGroup() const { return lastContentGroup_; } + /** @internal */ String getLastContentItem() const { return lastContentItem_; } + /** @internal */ String& getLastPropertyRef() { return lastProperty_; } private: diff --git a/modules/yup_animation/io/yup_LottieReader.cpp b/modules/yup_animation/io/yup_LottieReader.cpp index 0f0f461b7..5379f5b2d 100644 --- a/modules/yup_animation/io/yup_LottieReader.cpp +++ b/modules/yup_animation/io/yup_LottieReader.cpp @@ -125,23 +125,17 @@ inline Color parseHexColor (const String& hex) } // namespace //============================================================================== -LottieReader::LottieReader (const LottieLoadOptions& options, String* outError) - : options_ (options) - , errorOut_ (outError) +LottieReader::LottieReader (const LottieLoadOptions& optionsToUse) + : options (optionsToUse) { } //============================================================================== -AnimationComposition::Ptr LottieReader::parseFile (const File& file, - const LottieLoadOptions& options, - String* outError) +ResultValue LottieReader::parseFile (const File& file, + const LottieLoadOptions& options) { if (! file.existsAsFile()) - { - if (outError != nullptr) - *outError = "File not found: " + file.getFullPathName(); - return {}; - } + return makeResultValueFail ("File not found: " + file.getFullPathName()); LottieLoadOptions opts = options; if (opts.resourceDirectory == File()) @@ -149,46 +143,32 @@ AnimationComposition::Ptr LottieReader::parseFile (const File& file, auto stream = file.createInputStream(); if (stream == nullptr) - { - if (outError != nullptr) - *outError = "Failed to open file: " + file.getFullPathName(); - return {}; - } + return makeResultValueFail ("Failed to open file: " + file.getFullPathName()); - return parseStream (*stream, opts, outError); + return parseStream (*stream, opts); } //============================================================================== -AnimationComposition::Ptr LottieReader::parseData (const String& jsonText, - const LottieLoadOptions& options, - String* outError) +ResultValue LottieReader::parseData (const String& jsonText, + const LottieLoadOptions& options) { var root; const Result parseResult = JSON::parse (jsonText, root); if (parseResult.failed()) - { - if (outError != nullptr) - *outError = "JSON parse error: " + parseResult.getErrorMessage(); - return {}; - } + return makeResultValueFail ("JSON parse error: " + parseResult.getErrorMessage()); - LottieReader reader (options, outError); + LottieReader reader (options); return reader.parseRoot (root); } //============================================================================== -AnimationComposition::Ptr LottieReader::parseStream (InputStream& stream, - const LottieLoadOptions& options, - String* outError) +ResultValue LottieReader::parseStream (InputStream& stream, + const LottieLoadOptions& options) { MemoryBlock data; const auto bytesRead = stream.readIntoMemoryBlock (data); if (bytesRead == 0 || data.isEmpty()) - { - if (outError != nullptr) - *outError = "Empty or unreadable stream"; - return {}; - } + return makeResultValueFail ("Empty or unreadable stream"); // .lottie ZIP archives start with "PK" magic bytes if (data.getSize() >= 2 && memcmp (data.getData(), "PK", 2) == 0) @@ -198,31 +178,19 @@ AnimationComposition::Ptr LottieReader::parseStream (InputStream& stream, const auto* manifestEntry = zip.getEntry ("manifest.json", true); if (manifestEntry == nullptr) - { - if (outError != nullptr) - *outError = "manifest.json not found in .lottie stream"; - return {}; - } + return makeResultValueFail ("manifest.json not found in .lottie stream"); std::unique_ptr manifestStream (zip.createStreamForEntry (*manifestEntry)); if (manifestStream == nullptr) - return {}; + return makeResultValueFail ("Failed to open manifest.json in .lottie stream"); var manifest; if (JSON::parse (manifestStream->readEntireStreamAsString(), manifest).failed()) - { - if (outError != nullptr) - *outError = "Failed to parse manifest.json"; - return {}; - } + return makeResultValueFail ("Failed to parse manifest.json"); const auto* anims = safeArray (manifest["animations"]); if (anims == nullptr || anims->isEmpty()) - { - if (outError != nullptr) - *outError = "No animations found in manifest"; - return {}; - } + return makeResultValueFail ("No animations found in manifest"); const auto& anim = (*anims)[0]; const String animId = varString (anim["id"]); @@ -232,20 +200,16 @@ AnimationComposition::Ptr LottieReader::parseStream (InputStream& stream, const auto* jsonEntry = zip.getEntry (jsonPath, true); if (jsonEntry == nullptr) - { - if (outError != nullptr) - *outError = "Animation JSON not found inside archive: " + jsonPath; - return {}; - } + return makeResultValueFail ("Animation JSON not found inside archive: " + jsonPath); std::unique_ptr jsonStream (zip.createStreamForEntry (*jsonEntry)); if (jsonStream == nullptr) - return {}; + return makeResultValueFail ("Failed to open animation JSON inside archive: " + jsonPath); - return parseData (jsonStream->readEntireStreamAsString(), options, outError); + return parseData (jsonStream->readEntireStreamAsString(), options); } - return parseData (data.toString(), options, outError); + return parseData (data.toString(), options); } //============================================================================== @@ -275,43 +239,30 @@ std::vector LottieReader::listAnimationIds (const File& lottieZipFile) } //============================================================================== -AnimationComposition::Ptr LottieReader::parseFromZip (const File& lottieZipFile, - const String& animationId, - const LottieLoadOptions& options, - String* outError) +ResultValue LottieReader::parseFromZip (const File& lottieZipFile, + const String& animationId, + const LottieLoadOptions& options) { ZipFile zip (lottieZipFile); // Read manifest const ZipFile::ZipEntry* manifestEntry = zip.getEntry ("manifest.json", true); if (manifestEntry == nullptr) - { - if (outError != nullptr) - *outError = "manifest.json not found in .lottie file"; - return {}; - } + return makeResultValueFail ("manifest.json not found in .lottie file"); std::unique_ptr manifestStream (zip.createStreamForEntry (*manifestEntry)); if (manifestStream == nullptr) - return {}; + return makeResultValueFail ("Failed to open manifest.json in .lottie file"); var manifest; if (JSON::parse (manifestStream->readEntireStreamAsString(), manifest).failed()) - { - if (outError != nullptr) - *outError = "Failed to parse manifest.json"; - return {}; - } + return makeResultValueFail ("Failed to parse manifest.json"); // Find the target animation path from the manifest String jsonPath; const auto* anims = safeArray (manifest["animations"]); if (anims == nullptr || anims->isEmpty()) - { - if (outError != nullptr) - *outError = "No animations found in manifest"; - return {}; - } + return makeResultValueFail ("No animations found in manifest"); for (const var& anim : *anims) { @@ -326,29 +277,21 @@ AnimationComposition::Ptr LottieReader::parseFromZip (const File& lottieZipFile, } if (jsonPath.isEmpty()) - { - if (outError != nullptr) - *outError = "Animation id not found in manifest: " + animationId; - return {}; - } + return makeResultValueFail ("Animation id not found in manifest: " + animationId); const ZipFile::ZipEntry* jsonEntry = zip.getEntry (jsonPath, true); if (jsonEntry == nullptr) - { - if (outError != nullptr) - *outError = "Animation JSON not found inside archive: " + jsonPath; - return {}; - } + return makeResultValueFail ("Animation JSON not found inside archive: " + jsonPath); std::unique_ptr jsonStream (zip.createStreamForEntry (*jsonEntry)); if (jsonStream == nullptr) - return {}; + return makeResultValueFail ("Failed to open animation JSON inside archive: " + jsonPath); - return parseData (jsonStream->readEntireStreamAsString(), options, outError); + return parseData (jsonStream->readEntireStreamAsString(), options); } //============================================================================== -AnimationComposition::Ptr LottieReader::parseRoot (const var& root) +ResultValue LottieReader::parseRoot (const var& root) { auto comp = AnimationComposition::create ( { varFloat (root["w"], 500.0f), varFloat (root["h"], 500.0f) }, @@ -375,15 +318,13 @@ AnimationComposition::Ptr LottieReader::parseRoot (const var& root) // Validate composition (gap 22) if (comp->version.isEmpty()) { - if (errorOut_ != nullptr) - *errorOut_ = "Invalid Lottie: missing version"; - return {}; + errorMessage = "Invalid Lottie: missing version"; + return makeResultValueFail (errorMessage); } if (comp->startFrame > comp->endFrame) { - if (errorOut_ != nullptr) - *errorOut_ = "Invalid Lottie: startFrame > endFrame"; - return {}; + errorMessage = "Invalid Lottie: startFrame > endFrame"; + return makeResultValueFail (errorMessage); } if (const auto* markersArr = safeArray (root["markers"])) @@ -398,7 +339,7 @@ AnimationComposition::Ptr LottieReader::parseRoot (const var& root) } } - return comp; + return makeResultValueOk (std::move (comp)); } //============================================================================== @@ -451,9 +392,9 @@ void LottieReader::parseAssets (const var& assetsVal, AnimationComposition& comp } } - if (! asset->bitmap.has_value() && options_.imageResolver) + if (! asset->bitmap.has_value() && options.imageResolver) { - auto img = options_.imageResolver (asset->path, options_.resourceDirectory); + auto img = options.imageResolver (asset->path, options.resourceDirectory); if (img.has_value()) asset->bitmap = std::move (img); } @@ -739,11 +680,7 @@ AnimationLayer::Ptr LottieReader::parseLayer (const var& layerObj) // Self-parenting check if (layer->parentId >= 0 && layer->id == layer->parentId) - { - if (errorOut_ != nullptr) - *errorOut_ = "Invalid Lottie: layer references itself as parent"; return {}; - } // Hidden layers - downgrade to Null to save resources (gap 23) if (layer->hidden) diff --git a/modules/yup_animation/io/yup_LottieReader.h b/modules/yup_animation/io/yup_LottieReader.h index 8611b1333..f1144826a 100644 --- a/modules/yup_animation/io/yup_LottieReader.h +++ b/modules/yup_animation/io/yup_LottieReader.h @@ -44,27 +44,27 @@ class YUP_API LottieReader public: //============================================================================== /** Parses a Lottie file (either `.json` or `.lottie` ZIP). - Returns nullptr on failure; writes an error message into @p outError when non-null. + Returns a successful ResultValue holding the composition, or a failed + ResultValue carrying an error message. */ - [[nodiscard]] static AnimationComposition::Ptr parseFile (const File& file, - const LottieLoadOptions& options = {}, - String* outError = nullptr); + [[nodiscard]] static ResultValue parseFile (const File& file, + const LottieLoadOptions& options = {}); /** Parses a Lottie JSON string. - Returns nullptr on failure. + Returns a successful ResultValue holding the composition, or a failed + ResultValue carrying an error message. */ - [[nodiscard]] static AnimationComposition::Ptr parseData (const String& jsonText, - const LottieLoadOptions& options = {}, - String* outError = nullptr); + [[nodiscard]] static ResultValue parseData (const String& jsonText, + const LottieLoadOptions& options = {}); /** Parses a Lottie animation from an InputStream. The stream is fully consumed. Both plain Lottie JSON and .lottie ZIP (binary) streams are supported; the format is auto-detected. - Returns nullptr on failure. + Returns a successful ResultValue holding the composition, or a failed + ResultValue carrying an error message. */ - [[nodiscard]] static AnimationComposition::Ptr parseStream (InputStream& stream, - const LottieLoadOptions& options = {}, - String* outError = nullptr); + [[nodiscard]] static ResultValue parseStream (InputStream& stream, + const LottieLoadOptions& options = {}); /** Lists animation IDs contained inside a `.lottie` ZIP archive. Returns an empty vector if the file is not a valid .lottie file. @@ -73,80 +73,75 @@ class YUP_API LottieReader /** Parses a specific animation from a `.lottie` ZIP archive. If @p animationId is empty the first animation is used. + Returns a successful ResultValue holding the composition, or a failed + ResultValue carrying an error message. */ - [[nodiscard]] static AnimationComposition::Ptr parseFromZip (const File& lottieZipFile, - const String& animationId = {}, - const LottieLoadOptions& options = {}, - String* outError = nullptr); + [[nodiscard]] static ResultValue parseFromZip (const File& lottieZipFile, + const String& animationId = {}, + const LottieLoadOptions& options = {}); private: //============================================================================== - LottieReader (const LottieLoadOptions& options, String* outError); - - AnimationComposition::Ptr parseRoot (const var& root); - - void parseLayers (const var& layersArray, - std::vector& out, - std::vector& parsedIndicesOut); - AnimationLayer::Ptr parseLayer (const var& layerObj); + explicit LottieReader (const LottieLoadOptions& options); + //============================================================================== void resolveLayerExpressions (const AnimationComposition& comp, const Array& layerArray, std::vector& layers, size_t firstLayerIndex, const std::vector& parsedLayerIndices); - static void applyLayerPropertyRef (const String& property, - const AnimationLayer& source, - AnimationTransform& target); - - static void applyStaticTransformValue (const String& propName, - const var& value, - AnimationTransform& transform); - - void parseShapeContents (const var& itemsArray, ShapeLayer& layer); void resolveShapeLayerExpressions (const Array& itemsArray, ShapeLayer& layer); - void parseGroupItems (const var& itemsArray, AnimationGroup& group); void resolveGroupExpressions (const Array& itemsArray, AnimationGroup& group); - void parseSingleItem (const var& itemObj, AnimationGroup& group); + void resolveLayerAssets (AnimationComposition& comp); - void parseTransform (const var& ksObj, AnimationTransform& transform, bool ddd = false); + void resolveLayerAssets (AnimationComposition& comp, + std::vector& layers, + StringArray& resolvingPrecomps); - /** Detects an AfterEffects inertial-bounce expression on the position channel - and, when present, populates transform.positionBounce. */ + //============================================================================== + ResultValue parseRoot (const var& root); + AnimationLayer::Ptr parseLayer (const var& layerObj); + void parseLayers (const var& layersArray, + std::vector& out, + std::vector& parsedIndicesOut); + void parseShapeContents (const var& itemsArray, ShapeLayer& layer); + void parseGroupItems (const var& itemsArray, AnimationGroup& group); + void parseSingleItem (const var& itemObj, AnimationGroup& group); + void parseTransform (const var& ksObj, AnimationTransform& transform, bool ddd = false); void parsePositionBounce (const var& positionObj, AnimationTransform& transform); - - template - AnimationProperty parseProperty (const var& propObj, - std::function extractor); - - AnimationEasing parseEasing (const var& kfObj); - - /** Looks up or creates a cached interpolator by name/string key. - Named interpolators ("n" key) reference previously defined easing curves. */ - AnimationEasing lookupInterpolator (const String& name, float ox, float oy, float ix, float iy); - void parseGradient (const var& gradObj, AnimationGradient& gradient); void parseMasks (const var& masksArray, AnimationLayer& layer); void parseEffects (const var& effectsArray, AnimationLayer& layer); void parseAssets (const var& assetsArray, AnimationComposition& comp); + AnimationEasing parseEasing (const var& kfObj); - void resolveLayerAssets (AnimationComposition& comp); - void resolveLayerAssets (AnimationComposition& comp, - std::vector& layers, - StringArray& resolvingPrecomps); + template + AnimationProperty parseProperty (const var& propObj, std::function extractor); + + //============================================================================== + static void applyLayerPropertyRef (const String& property, + const AnimationLayer& source, + AnimationTransform& target); + + static void applyStaticTransformValue (const String& propName, + const var& value, + AnimationTransform& transform); - // Value extractors + //============================================================================== + AnimationEasing lookupInterpolator (const String& name, float ox, float oy, float ix, float iy); + + //============================================================================== [[nodiscard]] static Color extractColor (const var& v); [[nodiscard]] static Point extractPoint (const var& v); [[nodiscard]] static Size extractSize (const var& v); [[nodiscard]] static float extractFloat (const var& v); [[nodiscard]] static AnimationPathData extractPath (const var& v); - LottieLoadOptions options_; - String* errorOut_ = nullptr; + LottieLoadOptions options; + String errorMessage; HashMap interpolatorCache; float frameRate_ = 60.0f; }; diff --git a/modules/yup_animation/io/yup_LottieWriter.h b/modules/yup_animation/io/yup_LottieWriter.h index b0e8c7346..52db24911 100644 --- a/modules/yup_animation/io/yup_LottieWriter.h +++ b/modules/yup_animation/io/yup_LottieWriter.h @@ -37,12 +37,15 @@ class YUP_API LottieWriter bool prettyPrint = true); /** Writes the composition to a file. Returns Result::ok() on success. */ - static Result toFile (const AnimationComposition& comp, - const File& destination, - bool prettyPrint = true); + [[nodiscard]] static Result toFile (const AnimationComposition& comp, + const File& destination, + bool prettyPrint = true); private: //============================================================================== + template + static var serializeProperty (const AnimationProperty& prop, std::function serializer); + static var serializeComposition (const AnimationComposition& comp); static var serializeLayers (const std::vector& layers); static var serializeLayer (const AnimationLayer& layer); @@ -63,14 +66,7 @@ class YUP_API LottieWriter static var serializeMask (const AnimationMask& mask); static var serializeAssets (const HashMap& assets); static var serializeMarkers (const std::vector& markers); - - template - static var serializeProperty (const AnimationProperty& prop, - std::function serializer); - static var serializeEasing (const AnimationEasing& easing); - - // Value serialisers static var serializeColor (const Color& c); static var serializePoint (const Point& p); static var serializeSize (const Size& s); diff --git a/modules/yup_animation/model/yup_AnimationComposition.h b/modules/yup_animation/model/yup_AnimationComposition.h index a723fb610..dfda16342 100644 --- a/modules/yup_animation/model/yup_AnimationComposition.h +++ b/modules/yup_animation/model/yup_AnimationComposition.h @@ -177,8 +177,6 @@ class YUP_API AnimationComposition : public ReferenceCountedObject private: AnimationComposition() = default; - HashMap propertyOverrides; - static void setPropertyOverrideImpl (PropertyOverrideSet& set, AnimationPropertyID id, AnimationPropertyOverride override) { set.setFloatOverride (id, std::move (override)); @@ -198,6 +196,8 @@ class YUP_API AnimationComposition : public ReferenceCountedObject { set.setSizeOverride (id, std::move (override)); } + + HashMap propertyOverrides; }; } // namespace yup diff --git a/modules/yup_animation/renderer/yup_AnimationRenderResources.cpp b/modules/yup_animation/renderer/yup_AnimationRenderResources.cpp index ee61ef1e4..e118e5167 100644 --- a/modules/yup_animation/renderer/yup_AnimationRenderResources.cpp +++ b/modules/yup_animation/renderer/yup_AnimationRenderResources.cpp @@ -71,14 +71,14 @@ void main() { //============================================================================== AnimationRenderResources::MatteCanvasLease::MatteCanvasLease (AnimationRenderResources& owner, size_t slotIndex) noexcept - : owner_ (std::addressof (owner)) - , slotIndex_ (slotIndex) + : owner (std::addressof (owner)) + , slotIndex (slotIndex) { } AnimationRenderResources::MatteCanvasLease::MatteCanvasLease (MatteCanvasLease&& other) noexcept - : owner_ (std::exchange (other.owner_, nullptr)) - , slotIndex_ (other.slotIndex_) + : owner (std::exchange (other.owner, nullptr)) + , slotIndex (other.slotIndex) { } @@ -87,8 +87,8 @@ AnimationRenderResources::MatteCanvasLease& AnimationRenderResources::MatteCanva if (this != std::addressof (other)) { release(); - owner_ = std::exchange (other.owner_, nullptr); - slotIndex_ = other.slotIndex_; + owner = std::exchange (other.owner, nullptr); + slotIndex = other.slotIndex; } return *this; @@ -101,42 +101,42 @@ AnimationRenderResources::MatteCanvasLease::~MatteCanvasLease() bool AnimationRenderResources::MatteCanvasLease::isValid() const noexcept { - return owner_ != nullptr - && slotIndex_ < owner_->matteCanvasPool_.size(); + return owner != nullptr + && slotIndex < owner->matteCanvasPool.size(); } GpuCanvas& AnimationRenderResources::MatteCanvasLease::getTargetCanvas() const noexcept { jassert (isValid()); - return *owner_->matteCanvasPool_[slotIndex_].targetCanvas; + return *owner->matteCanvasPool[slotIndex].targetCanvas; } GpuCanvas& AnimationRenderResources::MatteCanvasLease::getSourceCanvas() const noexcept { jassert (isValid()); - return *owner_->matteCanvasPool_[slotIndex_].sourceCanvas; + return *owner->matteCanvasPool[slotIndex].sourceCanvas; } GpuCanvas& AnimationRenderResources::MatteCanvasLease::getResultCanvas() const noexcept { jassert (isValid()); - return *owner_->matteCanvasPool_[slotIndex_].resultCanvas; + return *owner->matteCanvasPool[slotIndex].resultCanvas; } void AnimationRenderResources::MatteCanvasLease::release() noexcept { - if (auto* owner = std::exchange (owner_, nullptr)) - owner->releaseMatteCanvasSlot (slotIndex_); + if (auto* oldOwner = std::exchange (owner, nullptr)) + oldOwner->releaseMatteCanvasSlot (slotIndex); } //============================================================================== GpuPipeline::Ptr AnimationRenderResources::getMattePipeline (GraphicsContext& context) { - if (mattePipelineCompiled_) - return mattePipeline_; + if (mattePipelineCompiled) + return mattePipeline; - mattePipelineCompiled_ = true; + mattePipelineCompiled = true; #if YUP_ENABLE_SHADER_TRANSPILER // The fragment shader outputs the final premultiplied-alpha pixel @@ -159,12 +159,12 @@ GpuPipeline::Ptr AnimationRenderResources::getMattePipeline (GraphicsContext& co return nullptr; } - mattePipeline_ = result.getValue(); + mattePipeline = result.getValue(); #else ignoreUnused (context); #endif - return mattePipeline_; + return mattePipeline; } AnimationRenderResources::MatteCanvasLease AnimationRenderResources::acquireMatteCanvases (GraphicsContext& context, int width, int height) @@ -172,24 +172,25 @@ AnimationRenderResources::MatteCanvasLease AnimationRenderResources::acquireMatt if (width <= 0 || height <= 0) return {}; - if (matteCanvasContext_ != nullptr && matteCanvasContext_ != std::addressof (context)) + if (matteCanvasContext != nullptr && matteCanvasContext != std::addressof (context)) { - const bool hasActiveLease = std::any_of (matteCanvasPool_.begin(), matteCanvasPool_.end(), [] (const auto& slot) + const bool hasActiveLease = std::any_of (matteCanvasPool.begin(), matteCanvasPool.end(), [] (const auto& slot) { return slot.inUse; }); + jassert (! hasActiveLease); if (hasActiveLease) return {}; - matteCanvasPool_.clear(); + matteCanvasPool.clear(); } - matteCanvasContext_ = std::addressof (context); + matteCanvasContext = std::addressof (context); - for (size_t i = 0; i < matteCanvasPool_.size(); ++i) + for (size_t i = 0; i < matteCanvasPool.size(); ++i) { - auto& slot = matteCanvasPool_[i]; + auto& slot = matteCanvasPool[i]; if (! slot.inUse && slot.width == width && slot.height == height) { slot.inUse = true; @@ -207,8 +208,8 @@ AnimationRenderResources::MatteCanvasLease AnimationRenderResources::acquireMatt slot.width = width; slot.height = height; slot.inUse = true; - matteCanvasPool_.push_back (std::move (slot)); - return { *this, matteCanvasPool_.size() - 1 }; + matteCanvasPool.push_back (std::move (slot)); + return { *this, matteCanvasPool.size() - 1 }; } GpuCanvas::Ptr AnimationRenderResources::getPrecompCanvas (GraphicsContext& context, const String& key, int width, int height) @@ -216,23 +217,24 @@ GpuCanvas::Ptr AnimationRenderResources::getPrecompCanvas (GraphicsContext& cont if (width <= 0 || height <= 0) return nullptr; - if (matteCanvasContext_ != nullptr && matteCanvasContext_ != std::addressof (context)) + if (matteCanvasContext != nullptr && matteCanvasContext != std::addressof (context)) { - const bool hasActiveLease = std::any_of (matteCanvasPool_.begin(), matteCanvasPool_.end(), [] (const auto& slot) + const bool hasActiveLease = std::any_of (matteCanvasPool.begin(), matteCanvasPool.end(), [] (const auto& slot) { return slot.inUse; }); + jassert (! hasActiveLease); if (hasActiveLease) return nullptr; - matteCanvasPool_.clear(); - precompCanvasPool_.clear(); + matteCanvasPool.clear(); + precompCanvasPool.clear(); } - matteCanvasContext_ = std::addressof (context); + matteCanvasContext = std::addressof (context); - for (auto& slot : precompCanvasPool_) + for (auto& slot : precompCanvasPool) { if (slot.key != key) continue; @@ -254,31 +256,31 @@ GpuCanvas::Ptr AnimationRenderResources::getPrecompCanvas (GraphicsContext& cont if (canvas == nullptr) return nullptr; - precompCanvasPool_.push_back ({ key, canvas, width, height }); + precompCanvasPool.push_back ({ key, canvas, width, height }); return canvas; } void AnimationRenderResources::releaseMatteCanvasSlot (size_t slotIndex) noexcept { - if (slotIndex >= matteCanvasPool_.size()) + if (slotIndex >= matteCanvasPool.size()) return; - jassert (matteCanvasPool_[slotIndex].inUse); - matteCanvasPool_[slotIndex].inUse = false; + jassert (matteCanvasPool[slotIndex].inUse); + matteCanvasPool[slotIndex].inUse = false; } void AnimationRenderResources::reset() { - jassert (std::none_of (matteCanvasPool_.begin(), matteCanvasPool_.end(), [] (const auto& slot) + jassert (std::none_of (matteCanvasPool.begin(), matteCanvasPool.end(), [] (const auto& slot) { return slot.inUse; })); - mattePipeline_ = nullptr; - mattePipelineCompiled_ = false; - matteCanvasContext_ = nullptr; - matteCanvasPool_.clear(); - precompCanvasPool_.clear(); + mattePipeline = nullptr; + mattePipelineCompiled = false; + matteCanvasContext = nullptr; + matteCanvasPool.clear(); + precompCanvasPool.clear(); } } // namespace yup diff --git a/modules/yup_animation/renderer/yup_AnimationRenderResources.h b/modules/yup_animation/renderer/yup_AnimationRenderResources.h index 642810cec..41423e9e6 100644 --- a/modules/yup_animation/renderer/yup_AnimationRenderResources.h +++ b/modules/yup_animation/renderer/yup_AnimationRenderResources.h @@ -81,8 +81,8 @@ class YUP_API AnimationRenderResources MatteCanvasLease (AnimationRenderResources& owner, size_t slotIndex) noexcept; void release() noexcept; - AnimationRenderResources* owner_ = nullptr; - size_t slotIndex_ = 0; + AnimationRenderResources* owner = nullptr; + size_t slotIndex = 0; }; //============================================================================== @@ -132,11 +132,11 @@ class YUP_API AnimationRenderResources void releaseMatteCanvasSlot (size_t slotIndex) noexcept; - GpuPipeline::Ptr mattePipeline_; - bool mattePipelineCompiled_ = false; - GraphicsContext* matteCanvasContext_ = nullptr; - std::vector matteCanvasPool_; - std::vector precompCanvasPool_; + GpuPipeline::Ptr mattePipeline; + bool mattePipelineCompiled = false; + GraphicsContext* matteCanvasContext = nullptr; + std::vector matteCanvasPool; + std::vector precompCanvasPool; YUP_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AnimationRenderResources) }; diff --git a/modules/yup_animation/renderer/yup_AnimationRenderer.h b/modules/yup_animation/renderer/yup_AnimationRenderer.h index ff04cae1b..036adc164 100644 --- a/modules/yup_animation/renderer/yup_AnimationRenderer.h +++ b/modules/yup_animation/renderer/yup_AnimationRenderer.h @@ -56,21 +56,6 @@ class YUP_API AnimationRenderer AnimationRenderResources* renderResources = nullptr); private: - static void renderComposition (Graphics& g, - const AnimationComposition& comp, - float frameNo, - Rectangle bounds, - Fitting fitting, - Justification justification, - float opacity, - std::optional paintOverride, - AnimationRenderResources* renderResources); - - static AffineTransform calculateViewTransform (Size compSize, - Rectangle targetArea, - Fitting fitting, - Justification justification); - //============================================================================== // Shared per-frame scene data (avoided per-layer deep copy) struct SceneContext @@ -106,28 +91,64 @@ class YUP_API AnimationRenderer }; //============================================================================== + struct ClipPathResult + { + Path path; + bool active = false; + }; + + static ClipPathResult buildLayerMaskClipPath (const AnimationLayer& layer, float frameNo, Size compSize); + + //============================================================================== + static AffineTransform calculateViewTransform (Size compSize, + Rectangle targetArea, + Fitting fitting, + Justification justification); + + //============================================================================== + static void renderComposition (Graphics& g, + const AnimationComposition& comp, + float frameNo, + Rectangle bounds, + Fitting fitting, + Justification justification, + float opacity, + std::optional paintOverride, + AnimationRenderResources* renderResources); + + static void renderGroup (Graphics& g, + const AnimationGroup& group, + const RenderContext& ctx, + float opacity, + const AnimationRoundedCorner* parentRoundedCorner = nullptr); + static void renderLayerList (Graphics& g, const std::vector& layers, const RenderContext& ctx); + static void renderLayer (Graphics& g, const AnimationLayer& layer, const RenderContext& ctx, const AnimationLayer* matteSource = nullptr); + static void renderLayerDirect (Graphics& g, const AnimationLayer& layer, const RenderContext& ctx, const AnimationLayer* matteSource, float opacity); + static bool renderLayerIsolated (Graphics& g, const AnimationLayer& layer, const RenderContext& ctx, const AnimationLayer* matteSource, float opacity); + static bool renderLayerWithMatte (Graphics& g, const AnimationLayer& layer, const RenderContext& ctx, const AnimationLayer& matteSource, float opacity); + static void renderDropShadow (Graphics& g, const AnimationLayer& layer, const RenderContext& ctx, float opacity); static void renderLayerContent (Graphics& g, const AnimationLayer& layer, const RenderContext& ctx, float opacity); static void renderShapeLayer (Graphics& g, const ShapeLayer& layer, const RenderContext& ctx, float opacity); @@ -135,32 +156,17 @@ class YUP_API AnimationRenderer static void renderImageLayer (Graphics& g, const ImageLayer& layer, const RenderContext& ctx, float opacity); static void renderPrecompLayer (Graphics& g, const PrecompLayer& layer, const RenderContext& ctx, float opacity); + //============================================================================== + static void applyTrim (Path& path, const AnimationTrim& trim, float frameNo); + static void applyTrimIndividually (std::vector& paths, const AnimationTrim& trim, float frameNo); + static void applyFill (Graphics& g, const Path& path, const FillPaint& fill, const RenderContext& ctx, float opacity); + static void applyStroke (Graphics& g, const Path& path, const StrokePaint& stroke, const RenderContext& ctx, float opacity); static bool applyMasks (Graphics& g, const AnimationLayer& layer, float frameNo, Size compSize); static void applyMatteSourceClip (Graphics& g, const AnimationLayer& layer, const AnimationLayer& matteSource, const RenderContext& ctx, bool inverted); - - struct ClipPathResult - { - Path path; - bool active = false; - }; - - static ClipPathResult buildLayerMaskClipPath (const AnimationLayer& layer, float frameNo, Size compSize); - - static void renderGroup (Graphics& g, - const AnimationGroup& group, - const RenderContext& ctx, - float opacity, - const AnimationRoundedCorner* parentRoundedCorner = nullptr); - - static void applyTrim (Path& path, const AnimationTrim& trim, float frameNo); - static void applyTrimIndividually (std::vector& paths, const AnimationTrim& trim, float frameNo); - - static void applyFill (Graphics& g, const Path& path, const FillPaint& fill, const RenderContext& ctx, float opacity); - static void applyStroke (Graphics& g, const Path& path, const StrokePaint& stroke, const RenderContext& ctx, float opacity); }; } // namespace yup diff --git a/tests/yup_animation/yup_AnimationRenderer.cpp b/tests/yup_animation/yup_AnimationRenderer.cpp index 5f0559857..769899f09 100644 --- a/tests/yup_animation/yup_AnimationRenderer.cpp +++ b/tests/yup_animation/yup_AnimationRenderer.cpp @@ -1320,7 +1320,7 @@ TEST_F (AnimationRendererTests, RenderEmptyCompositionDoesNotCrash) TEST_F (AnimationRendererTests, RenderShapeLayerCompositionDoesNotCrash) { - auto comp = LottieReader::parseData (kShapeLayerJson); + auto comp = LottieReader::parseData (kShapeLayerJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); auto renderer = context->makeRenderer (100, 100); @@ -1333,7 +1333,7 @@ TEST_F (AnimationRendererTests, RenderShapeLayerCompositionDoesNotCrash) TEST_F (AnimationRendererTests, RenderSolidLayerCompositionDoesNotCrash) { - auto comp = LottieReader::parseData (kSolidLayerJson); + auto comp = LottieReader::parseData (kSolidLayerJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); auto renderer = context->makeRenderer (100, 100); @@ -1346,7 +1346,7 @@ TEST_F (AnimationRendererTests, RenderSolidLayerCompositionDoesNotCrash) TEST_F (AnimationRendererTests, RenderNullLayerCompositionDoesNotCrash) { - auto comp = LottieReader::parseData (kNullLayerJson); + auto comp = LottieReader::parseData (kNullLayerJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); auto renderer = context->makeRenderer (100, 100); @@ -1359,7 +1359,7 @@ TEST_F (AnimationRendererTests, RenderNullLayerCompositionDoesNotCrash) TEST_F (AnimationRendererTests, RenderHiddenLayerCompositionDoesNotCrash) { - auto comp = LottieReader::parseData (kHiddenLayerJson); + auto comp = LottieReader::parseData (kHiddenLayerJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); ASSERT_EQ (comp->layers.size(), 1u); EXPECT_TRUE (comp->layers[0]->hidden); @@ -1374,7 +1374,7 @@ TEST_F (AnimationRendererTests, RenderHiddenLayerCompositionDoesNotCrash) TEST_F (AnimationRendererTests, RenderMultiLayerCompositionDoesNotCrash) { - auto comp = LottieReader::parseData (kMultiLayerJson); + auto comp = LottieReader::parseData (kMultiLayerJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); ASSERT_EQ (comp->layers.size(), 2u); @@ -1388,7 +1388,7 @@ TEST_F (AnimationRendererTests, RenderMultiLayerCompositionDoesNotCrash) TEST_F (AnimationRendererTests, RenderAtVariousFrameNumbersDoesNotCrash) { - auto comp = LottieReader::parseData (kShapeLayerJson); + auto comp = LottieReader::parseData (kShapeLayerJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); auto renderer = context->makeRenderer (100, 100); @@ -1406,7 +1406,7 @@ TEST_F (AnimationRendererTests, RenderAtVariousFrameNumbersDoesNotCrash) TEST_F (AnimationRendererTests, RenderWithScaleToFitAndFillDoesNotCrash) { - auto comp = LottieReader::parseData (kShapeLayerJson); + auto comp = LottieReader::parseData (kShapeLayerJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); auto renderer = context->makeRenderer (200, 100); @@ -1425,7 +1425,7 @@ TEST_F (AnimationRendererTests, RenderWithScaleToFitAndFillDoesNotCrash) TEST_F (AnimationRendererTests, RenderIntoSmallBoundsDoesNotCrash) { - auto comp = LottieReader::parseData (kShapeLayerJson); + auto comp = LottieReader::parseData (kShapeLayerJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); auto renderer = context->makeRenderer (1, 1); @@ -1438,7 +1438,7 @@ TEST_F (AnimationRendererTests, RenderIntoSmallBoundsDoesNotCrash) TEST_F (AnimationRendererTests, RenderIntoLargeBoundsDoesNotCrash) { - auto comp = LottieReader::parseData (kShapeLayerJson); + auto comp = LottieReader::parseData (kShapeLayerJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); auto renderer = context->makeRenderer (1000, 1000); @@ -1474,7 +1474,7 @@ TEST_F (AnimationRendererTests, RenderProgrammaticallyBuiltCompositionDoesNotCra TEST_F (AnimationRendererTests, RenderShapeLayerWithPartialOpacityDoesNotCrash) { - auto comp = LottieReader::parseData (kPartialOpacityShapeJson); + auto comp = LottieReader::parseData (kPartialOpacityShapeJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); ASSERT_EQ (comp->layers.size(), 1u); @@ -1492,7 +1492,7 @@ TEST_F (AnimationRendererTests, RenderShapeLayerWithPartialOpacityDoesNotCrash) TEST_F (AnimationRendererTests, RenderShapeLayerWithDropShadowDoesNotCrash) { - auto comp = LottieReader::parseData (kDropShadowShapeJson); + auto comp = LottieReader::parseData (kDropShadowShapeJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); auto renderer = context->makeRenderer (100, 100); @@ -1509,7 +1509,7 @@ TEST_F (AnimationRendererTests, RenderShapeLayerWithDropShadowDoesNotCrash) TEST_F (AnimationRendererTests, RenderShapeLayerWithFillEffectDoesNotCrash) { - auto comp = LottieReader::parseData (kFillEffectJson); + auto comp = LottieReader::parseData (kFillEffectJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); auto renderer = context->makeRenderer (100, 100); @@ -1526,7 +1526,7 @@ TEST_F (AnimationRendererTests, RenderShapeLayerWithFillEffectDoesNotCrash) TEST_F (AnimationRendererTests, RenderShapeLayerWithAddMaskDoesNotCrash) { - auto comp = LottieReader::parseData (kMaskAddJson); + auto comp = LottieReader::parseData (kMaskAddJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); auto renderer = context->makeRenderer (100, 100); @@ -1539,7 +1539,7 @@ TEST_F (AnimationRendererTests, RenderShapeLayerWithAddMaskDoesNotCrash) TEST_F (AnimationRendererTests, RenderShapeLayerWithSubtractMaskDoesNotCrash) { - auto comp = LottieReader::parseData (kMaskSubtractJson); + auto comp = LottieReader::parseData (kMaskSubtractJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); auto renderer = context->makeRenderer (100, 100); @@ -1552,7 +1552,7 @@ TEST_F (AnimationRendererTests, RenderShapeLayerWithSubtractMaskDoesNotCrash) TEST_F (AnimationRendererTests, RenderShapeLayerWithIntersectMaskDoesNotCrash) { - auto comp = LottieReader::parseData (kMaskIntersectJson); + auto comp = LottieReader::parseData (kMaskIntersectJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); auto renderer = context->makeRenderer (100, 100); @@ -1565,7 +1565,7 @@ TEST_F (AnimationRendererTests, RenderShapeLayerWithIntersectMaskDoesNotCrash) TEST_F (AnimationRendererTests, RenderShapeLayerWithInvertedMaskDoesNotCrash) { - auto comp = LottieReader::parseData (kMaskInvertedJson); + auto comp = LottieReader::parseData (kMaskInvertedJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); auto renderer = context->makeRenderer (100, 100); @@ -1582,7 +1582,7 @@ TEST_F (AnimationRendererTests, RenderShapeLayerWithInvertedMaskDoesNotCrash) TEST_F (AnimationRendererTests, RenderShapeLayerWithTrimPathsSimultaneousDoesNotCrash) { - auto comp = LottieReader::parseData (kTrimPathsSimultaneousJson); + auto comp = LottieReader::parseData (kTrimPathsSimultaneousJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); auto renderer = context->makeRenderer (100, 100); @@ -1595,7 +1595,7 @@ TEST_F (AnimationRendererTests, RenderShapeLayerWithTrimPathsSimultaneousDoesNot TEST_F (AnimationRendererTests, RenderShapeLayerWithTrimPathsIndividuallyDoesNotCrash) { - auto comp = LottieReader::parseData (kTrimPathsIndividuallyJson); + auto comp = LottieReader::parseData (kTrimPathsIndividuallyJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); auto renderer = context->makeRenderer (100, 100); @@ -1612,7 +1612,7 @@ TEST_F (AnimationRendererTests, RenderShapeLayerWithTrimPathsIndividuallyDoesNot TEST_F (AnimationRendererTests, RenderShapeLayerWithRepeaterDoesNotCrash) { - auto comp = LottieReader::parseData (kRepeaterJson); + auto comp = LottieReader::parseData (kRepeaterJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); auto renderer = context->makeRenderer (200, 100); @@ -1629,7 +1629,7 @@ TEST_F (AnimationRendererTests, RenderShapeLayerWithRepeaterDoesNotCrash) TEST_F (AnimationRendererTests, RenderLayerWithAlphaMatteDoesNotCrash) { - auto comp = LottieReader::parseData (kAlphaMatteJson); + auto comp = LottieReader::parseData (kAlphaMatteJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); ASSERT_EQ (comp->layers.size(), 2u); @@ -1646,7 +1646,7 @@ TEST_F (AnimationRendererTests, RenderLayerWithPartialOpacityMatteSourceDoesNotC // Matte source fill at 65% opacity: a correct alpha matte multiplies the // target's alpha by the source's rendered alpha. On a headless context this // exercises the geometric-clip fallback path. - auto comp = LottieReader::parseData (kAlphaMatteJson); + auto comp = LottieReader::parseData (kAlphaMatteJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); ASSERT_EQ (comp->layers.size(), 2u); @@ -1676,7 +1676,7 @@ TEST_F (AnimationRendererTests, RenderLayerWithPartialOpacityMatteSourceDoesNotC TEST_F (AnimationRendererTests, RenderLayerWithParentChainDoesNotCrash) { - auto comp = LottieReader::parseData (kParentChainJson); + auto comp = LottieReader::parseData (kParentChainJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); ASSERT_EQ (comp->layers.size(), 2u); @@ -1694,7 +1694,7 @@ TEST_F (AnimationRendererTests, RenderLayerWithParentChainDoesNotCrash) TEST_F (AnimationRendererTests, RenderShapeLayerWithDashStrokeDoesNotCrash) { - auto comp = LottieReader::parseData (kDashStrokeJson); + auto comp = LottieReader::parseData (kDashStrokeJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); auto renderer = context->makeRenderer (100, 100); @@ -1711,7 +1711,7 @@ TEST_F (AnimationRendererTests, RenderShapeLayerWithDashStrokeDoesNotCrash) TEST_F (AnimationRendererTests, RenderLayerNotYetVisibleAtFrameZeroDoesNotCrash) { - auto comp = LottieReader::parseData (kLayerOutOfRangeJson); + auto comp = LottieReader::parseData (kLayerOutOfRangeJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); ASSERT_EQ (comp->layers.size(), 1u); EXPECT_FLOAT_EQ (comp->layers[0]->inFrame, 20.0f); @@ -1734,7 +1734,7 @@ TEST_F (AnimationRendererTests, RenderLayerNotYetVisibleAtFrameZeroDoesNotCrash) TEST_F (AnimationRendererTests, RenderShapeLayerWithGradientFillDoesNotCrash) { - auto comp = LottieReader::parseData (kGradientFillJson); + auto comp = LottieReader::parseData (kGradientFillJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); auto renderer = context->makeRenderer (100, 100); @@ -1747,7 +1747,7 @@ TEST_F (AnimationRendererTests, RenderShapeLayerWithGradientFillDoesNotCrash) TEST_F (AnimationRendererTests, RenderShapeLayerWithGradientStrokeDoesNotCrash) { - auto comp = LottieReader::parseData (kGradientStrokeJson); + auto comp = LottieReader::parseData (kGradientStrokeJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); auto renderer = context->makeRenderer (100, 100); @@ -1764,7 +1764,7 @@ TEST_F (AnimationRendererTests, RenderShapeLayerWithGradientStrokeDoesNotCrash) TEST_F (AnimationRendererTests, RenderShapeLayerWithEllipseAndPolystarDoesNotCrash) { - auto comp = LottieReader::parseData (kEllipseAndPolystarJson); + auto comp = LottieReader::parseData (kEllipseAndPolystarJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); auto renderer = context->makeRenderer (200, 100); @@ -1781,7 +1781,7 @@ TEST_F (AnimationRendererTests, RenderShapeLayerWithEllipseAndPolystarDoesNotCra TEST_F (AnimationRendererTests, RenderShapeLayerWithDifferenceMaskDoesNotCrash) { - auto comp = LottieReader::parseData (kMaskDifferenceJson); + auto comp = LottieReader::parseData (kMaskDifferenceJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); auto renderer = context->makeRenderer (100, 100); @@ -1798,7 +1798,7 @@ TEST_F (AnimationRendererTests, RenderShapeLayerWithDifferenceMaskDoesNotCrash) TEST_F (AnimationRendererTests, RenderShapeLayerWithShadowOnlyDoesNotCrash) { - auto comp = LottieReader::parseData (kShadowOnlyJson); + auto comp = LottieReader::parseData (kShadowOnlyJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); auto renderer = context->makeRenderer (100, 100); @@ -1815,7 +1815,7 @@ TEST_F (AnimationRendererTests, RenderShapeLayerWithShadowOnlyDoesNotCrash) TEST_F (AnimationRendererTests, RenderPrecompLayerDoesNotCrash) { - auto comp = LottieReader::parseData (kPrecompJson); + auto comp = LottieReader::parseData (kPrecompJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); ASSERT_GE (comp->layers.size(), 1u); @@ -1829,7 +1829,7 @@ TEST_F (AnimationRendererTests, RenderPrecompLayerDoesNotCrash) TEST_F (AnimationRendererTests, RenderPrecompLayerAtVariousFramesDoesNotCrash) { - auto comp = LottieReader::parseData (kPrecompJson); + auto comp = LottieReader::parseData (kPrecompJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); auto renderer = context->makeRenderer (100, 100); @@ -1851,7 +1851,7 @@ TEST_F (AnimationRendererTests, RenderPrecompLayerAtVariousFramesDoesNotCrash) TEST_F (AnimationRendererTests, RenderWithFittingNoneDoesNotCrash) { - auto comp = LottieReader::parseData (kShapeLayerJson); + auto comp = LottieReader::parseData (kShapeLayerJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); auto renderer = context->makeRenderer (100, 100); @@ -1864,7 +1864,7 @@ TEST_F (AnimationRendererTests, RenderWithFittingNoneDoesNotCrash) TEST_F (AnimationRendererTests, RenderWithFittingFitWidthDoesNotCrash) { - auto comp = LottieReader::parseData (kShapeLayerJson); + auto comp = LottieReader::parseData (kShapeLayerJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); auto renderer = context->makeRenderer (200, 100); @@ -1877,7 +1877,7 @@ TEST_F (AnimationRendererTests, RenderWithFittingFitWidthDoesNotCrash) TEST_F (AnimationRendererTests, RenderWithFittingFitHeightDoesNotCrash) { - auto comp = LottieReader::parseData (kShapeLayerJson); + auto comp = LottieReader::parseData (kShapeLayerJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); auto renderer = context->makeRenderer (100, 200); @@ -1890,7 +1890,7 @@ TEST_F (AnimationRendererTests, RenderWithFittingFitHeightDoesNotCrash) TEST_F (AnimationRendererTests, RenderWithFittingScaleToFillDoesNotCrash) { - auto comp = LottieReader::parseData (kShapeLayerJson); + auto comp = LottieReader::parseData (kShapeLayerJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); auto renderer = context->makeRenderer (200, 100); @@ -1903,7 +1903,7 @@ TEST_F (AnimationRendererTests, RenderWithFittingScaleToFillDoesNotCrash) TEST_F (AnimationRendererTests, RenderWithFittingCenterCropDoesNotCrash) { - auto comp = LottieReader::parseData (kShapeLayerJson); + auto comp = LottieReader::parseData (kShapeLayerJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); auto renderer = context->makeRenderer (200, 100); @@ -1916,7 +1916,7 @@ TEST_F (AnimationRendererTests, RenderWithFittingCenterCropDoesNotCrash) TEST_F (AnimationRendererTests, RenderWithFittingCenterInsideDoesNotCrash) { - auto comp = LottieReader::parseData (kShapeLayerJson); + auto comp = LottieReader::parseData (kShapeLayerJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); auto renderer = context->makeRenderer (200, 200); @@ -1929,7 +1929,7 @@ TEST_F (AnimationRendererTests, RenderWithFittingCenterInsideDoesNotCrash) TEST_F (AnimationRendererTests, RenderWithFittingStretchWidthDoesNotCrash) { - auto comp = LottieReader::parseData (kShapeLayerJson); + auto comp = LottieReader::parseData (kShapeLayerJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); auto renderer = context->makeRenderer (200, 100); @@ -1942,7 +1942,7 @@ TEST_F (AnimationRendererTests, RenderWithFittingStretchWidthDoesNotCrash) TEST_F (AnimationRendererTests, RenderWithFittingStretchHeightDoesNotCrash) { - auto comp = LottieReader::parseData (kShapeLayerJson); + auto comp = LottieReader::parseData (kShapeLayerJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); auto renderer = context->makeRenderer (100, 200); @@ -1955,7 +1955,7 @@ TEST_F (AnimationRendererTests, RenderWithFittingStretchHeightDoesNotCrash) TEST_F (AnimationRendererTests, RenderWithFittingTileDoesNotCrash) { - auto comp = LottieReader::parseData (kShapeLayerJson); + auto comp = LottieReader::parseData (kShapeLayerJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); auto renderer = context->makeRenderer (100, 100); @@ -1972,7 +1972,7 @@ TEST_F (AnimationRendererTests, RenderWithFittingTileDoesNotCrash) TEST_F (AnimationRendererTests, RenderWithLeftJustificationDoesNotCrash) { - auto comp = LottieReader::parseData (kShapeLayerJson); + auto comp = LottieReader::parseData (kShapeLayerJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); auto renderer = context->makeRenderer (100, 100); @@ -1985,7 +1985,7 @@ TEST_F (AnimationRendererTests, RenderWithLeftJustificationDoesNotCrash) TEST_F (AnimationRendererTests, RenderWithRightJustificationDoesNotCrash) { - auto comp = LottieReader::parseData (kShapeLayerJson); + auto comp = LottieReader::parseData (kShapeLayerJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); auto renderer = context->makeRenderer (100, 100); @@ -1998,7 +1998,7 @@ TEST_F (AnimationRendererTests, RenderWithRightJustificationDoesNotCrash) TEST_F (AnimationRendererTests, RenderWithTopJustificationDoesNotCrash) { - auto comp = LottieReader::parseData (kShapeLayerJson); + auto comp = LottieReader::parseData (kShapeLayerJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); auto renderer = context->makeRenderer (100, 100); @@ -2011,7 +2011,7 @@ TEST_F (AnimationRendererTests, RenderWithTopJustificationDoesNotCrash) TEST_F (AnimationRendererTests, RenderWithBottomJustificationDoesNotCrash) { - auto comp = LottieReader::parseData (kShapeLayerJson); + auto comp = LottieReader::parseData (kShapeLayerJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); auto renderer = context->makeRenderer (100, 100); @@ -2024,7 +2024,7 @@ TEST_F (AnimationRendererTests, RenderWithBottomJustificationDoesNotCrash) TEST_F (AnimationRendererTests, RenderWithCenterJustificationDoesNotCrash) { - auto comp = LottieReader::parseData (kShapeLayerJson); + auto comp = LottieReader::parseData (kShapeLayerJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); auto renderer = context->makeRenderer (100, 100); @@ -2041,7 +2041,7 @@ TEST_F (AnimationRendererTests, RenderWithCenterJustificationDoesNotCrash) TEST_F (AnimationRendererTests, RenderWithRenderResourcesDoesNotCrash) { - auto comp = LottieReader::parseData (kShapeLayerJson); + auto comp = LottieReader::parseData (kShapeLayerJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); auto renderer = context->makeRenderer (100, 100); diff --git a/tests/yup_animation/yup_LottieReader.cpp b/tests/yup_animation/yup_LottieReader.cpp index 05ac6b662..f4da2ebfd 100644 --- a/tests/yup_animation/yup_LottieReader.cpp +++ b/tests/yup_animation/yup_LottieReader.cpp @@ -115,39 +115,38 @@ class LottieReaderTests : public ::testing::Test TEST_F (LottieReaderTests, ParseDataReturnsValidCompositionForValidJson) { - auto comp = LottieReader::parseData (kLottieReaderBaseJson); + auto comp = LottieReader::parseData (kLottieReaderBaseJson).valueOr (nullptr); EXPECT_NE (comp, nullptr); } TEST_F (LottieReaderTests, ParseDataReturnsNullForGarbageJson) { - auto comp = LottieReader::parseData ("{{ not json }}"); + auto comp = LottieReader::parseData ("{{ not json }}").valueOr (nullptr); EXPECT_EQ (comp, nullptr); } TEST_F (LottieReaderTests, ParseDataPopulatesErrorOutputForBadJson) { - String errorMsg; - auto comp = LottieReader::parseData ("{{ bad }", {}, &errorMsg); + auto result = LottieReader::parseData ("{{ bad }"); - EXPECT_EQ (comp, nullptr); - EXPECT_FALSE (errorMsg.isEmpty()); - EXPECT_TRUE (errorMsg.contains ("JSON parse error")); + EXPECT_TRUE (result.failed()); + EXPECT_FALSE (result.getErrorMessage().isEmpty()); + EXPECT_TRUE (result.getErrorMessage().contains ("JSON parse error")); } TEST_F (LottieReaderTests, ParseDataSetsNoErrorForValidJson) { - String errorMsg; - auto comp = LottieReader::parseData (kLottieReaderBaseJson, {}, &errorMsg); + auto result = LottieReader::parseData (kLottieReaderBaseJson); - EXPECT_NE (comp, nullptr); - EXPECT_TRUE (errorMsg.isEmpty()); + EXPECT_TRUE (result.wasOk()); + EXPECT_NE (result.getReference(), nullptr); } +#if ! YUP_WASM TEST_F (LottieReaderTests, ParseFilePreservesBellSolidColor) { const auto file = getLottieTestDataDir().getChildFile ("bell.json"); - auto comp = LottieReader::parseFile (file); + auto comp = LottieReader::parseFile (file).valueOr (nullptr); ASSERT_NE (comp, nullptr); ASSERT_GE (comp->layers.size(), 2u); @@ -156,13 +155,15 @@ TEST_F (LottieReaderTests, ParseFilePreservesBellSolidColor) ASSERT_NE (solid, nullptr); EXPECT_EQ (solid->solidColor, Color (0xFF000000)); } +#endif TEST_F (LottieReaderTests, ParseDataReadsLayerAutoOrient) { auto comp = LottieReader::parseData (R"json({ "v": "5.5.2", "ip": 0, "op": 10, "fr": 30, "w": 100, "h": 100, "layers": [{ "ty": 3, "ind": 1, "ao": 1, "ks": {} }] - })json"); + })json") + .valueOr (nullptr); ASSERT_NE (comp, nullptr); ASSERT_EQ (comp->layers.size(), 1u); @@ -172,7 +173,7 @@ TEST_F (LottieReaderTests, ParseDataReadsLayerAutoOrient) TEST_F (LottieReaderTests, ParseDataParsesCompositionProperties) { - auto comp = LottieReader::parseData (kLottieReaderBaseJson); + auto comp = LottieReader::parseData (kLottieReaderBaseJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); EXPECT_FLOAT_EQ (comp->size.getWidth(), 120.0f); @@ -187,17 +188,16 @@ TEST_F (LottieReaderTests, ParseDataParsesCompositionProperties) TEST_F (LottieReaderTests, ParseDataReturnsNullForReversedFrameRange) { // ip=50 > op=10 — startFrame > endFrame → validation fails - auto comp = LottieReader::parseData (kLottieReaderReversedFrameJson); + auto comp = LottieReader::parseData (kLottieReaderReversedFrameJson).valueOr (nullptr); EXPECT_EQ (comp, nullptr); } TEST_F (LottieReaderTests, ParseDataSetsErrorForReversedFrameRange) { - String errorMsg; - auto comp = LottieReader::parseData (kLottieReaderReversedFrameJson, {}, &errorMsg); + auto result = LottieReader::parseData (kLottieReaderReversedFrameJson); - EXPECT_EQ (comp, nullptr); - EXPECT_FALSE (errorMsg.isEmpty()); + EXPECT_TRUE (result.failed()); + EXPECT_FALSE (result.getErrorMessage().isEmpty()); } // ============================================================================= @@ -206,14 +206,14 @@ TEST_F (LottieReaderTests, ParseDataSetsErrorForReversedFrameRange) TEST_F (LottieReaderTests, ParseDataParsesMarkersField) { - auto comp = LottieReader::parseData (kLottieReaderWithMarkersJson); + auto comp = LottieReader::parseData (kLottieReaderWithMarkersJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); EXPECT_EQ (comp->markers.size(), 3u); } TEST_F (LottieReaderTests, ParseDataMarkersHaveCorrectFields) { - auto comp = LottieReader::parseData (kLottieReaderWithMarkersJson); + auto comp = LottieReader::parseData (kLottieReaderWithMarkersJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); ASSERT_GE (comp->markers.size(), 3u); @@ -230,7 +230,7 @@ TEST_F (LottieReaderTests, ParseDataMarkersHaveCorrectFields) TEST_F (LottieReaderTests, ParseDataWithNoMarkersFieldHasEmptyMarkersVector) { - auto comp = LottieReader::parseData (kLottieReaderBaseJson); + auto comp = LottieReader::parseData (kLottieReaderBaseJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); EXPECT_TRUE (comp->markers.empty()); } @@ -241,7 +241,7 @@ TEST_F (LottieReaderTests, ParseDataWithNoMarkersFieldHasEmptyMarkersVector) TEST_F (LottieReaderTests, FindMarkerReturnsCorrectMarkerByName) { - auto comp = LottieReader::parseData (kLottieReaderWithMarkersJson); + auto comp = LottieReader::parseData (kLottieReaderWithMarkersJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); const AnimationMarker* m = comp->findMarker ("loop"); @@ -253,7 +253,7 @@ TEST_F (LottieReaderTests, FindMarkerReturnsCorrectMarkerByName) TEST_F (LottieReaderTests, FindMarkerReturnsNullForMissingName) { - auto comp = LottieReader::parseData (kLottieReaderWithMarkersJson); + auto comp = LottieReader::parseData (kLottieReaderWithMarkersJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); EXPECT_EQ (comp->findMarker ("nonexistent"), nullptr); @@ -261,7 +261,7 @@ TEST_F (LottieReaderTests, FindMarkerReturnsNullForMissingName) TEST_F (LottieReaderTests, FindMarkerReturnsNullOnEmptyMarkersList) { - auto comp = LottieReader::parseData (kLottieReaderBaseJson); + auto comp = LottieReader::parseData (kLottieReaderBaseJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); EXPECT_EQ (comp->findMarker ("any"), nullptr); @@ -273,18 +273,17 @@ TEST_F (LottieReaderTests, FindMarkerReturnsNullOnEmptyMarkersList) TEST_F (LottieReaderTests, ParseFileReturnsNullForMissingFile) { - auto comp = LottieReader::parseFile (File ("/nonexistent/path/file.json")); + auto comp = LottieReader::parseFile (File ("/nonexistent/path/file.json")).valueOr (nullptr); EXPECT_EQ (comp, nullptr); } TEST_F (LottieReaderTests, ParseFileSetsErrorForMissingFile) { - String errorMsg; - auto comp = LottieReader::parseFile (File ("/nonexistent/path/file.json"), {}, &errorMsg); + auto result = LottieReader::parseFile (File ("/nonexistent/path/file.json")); - EXPECT_EQ (comp, nullptr); - EXPECT_FALSE (errorMsg.isEmpty()); - EXPECT_TRUE (errorMsg.contains ("File not found")); + EXPECT_TRUE (result.failed()); + EXPECT_FALSE (result.getErrorMessage().isEmpty()); + EXPECT_TRUE (result.getErrorMessage().contains ("File not found")); } TEST_F (LottieReaderTests, ParseFileReturnsNullForEmptyFile) @@ -292,11 +291,10 @@ TEST_F (LottieReaderTests, ParseFileReturnsNullForEmptyFile) const File tempFile = File::createTempFile (".json"); tempFile.replaceWithText ({}); - String errorMsg; - auto comp = LottieReader::parseFile (tempFile, {}, &errorMsg); + auto result = LottieReader::parseFile (tempFile); - EXPECT_EQ (comp, nullptr); - EXPECT_FALSE (errorMsg.isEmpty()); + EXPECT_TRUE (result.failed()); + EXPECT_FALSE (result.getErrorMessage().isEmpty()); tempFile.deleteFile(); } @@ -306,7 +304,7 @@ TEST_F (LottieReaderTests, ParseFileReturnsValidCompositionForValidFile) const File tempFile = File::createTempFile (".json"); tempFile.replaceWithText (kLottieReaderBaseJson); - auto comp = LottieReader::parseFile (tempFile); + auto comp = LottieReader::parseFile (tempFile).valueOr (nullptr); EXPECT_NE (comp, nullptr); tempFile.deleteFile(); @@ -318,7 +316,7 @@ TEST_F (LottieReaderTests, ParseFileSetsResourceDirectoryFromFileParent) tempFile.replaceWithText (kLottieReaderBaseJson); LottieLoadOptions opts; - auto comp = LottieReader::parseFile (tempFile, opts); + auto comp = LottieReader::parseFile (tempFile, opts).valueOr (nullptr); EXPECT_NE (comp, nullptr); tempFile.deleteFile(); @@ -351,11 +349,10 @@ TEST_F (LottieReaderTests, ListAnimationIdsReturnsEmptyForRegularJsonFile) TEST_F (LottieReaderTests, ParseFromZipReturnsNullForMissingFile) { - String errorMsg; - auto comp = LottieReader::parseFromZip (File ("/nonexistent/file.lottie"), {}, {}, &errorMsg); + auto result = LottieReader::parseFromZip (File ("/nonexistent/file.lottie")); - EXPECT_EQ (comp, nullptr); - EXPECT_FALSE (errorMsg.isEmpty()); + EXPECT_TRUE (result.failed()); + EXPECT_FALSE (result.getErrorMessage().isEmpty()); } TEST_F (LottieReaderTests, ParseFromZipReturnsNullForNonZipFile) @@ -363,10 +360,9 @@ TEST_F (LottieReaderTests, ParseFromZipReturnsNullForNonZipFile) const File tempFile = File::createTempFile (".lottie"); tempFile.replaceWithText ("not a zip file"); - String errorMsg; - auto comp = LottieReader::parseFromZip (tempFile, {}, {}, &errorMsg); + auto result = LottieReader::parseFromZip (tempFile); - EXPECT_EQ (comp, nullptr); + EXPECT_TRUE (result.failed()); tempFile.deleteFile(); } @@ -388,7 +384,7 @@ TEST_F (LottieReaderTests, ImageResolverIsCalledForExternalImageAssets) return std::nullopt; // don't provide a bitmap }; - auto comp = LottieReader::parseData (kLottieReaderImageAssetJson, opts); + auto comp = LottieReader::parseData (kLottieReaderImageAssetJson, opts).valueOr (nullptr); ASSERT_NE (comp, nullptr); EXPECT_TRUE (resolverCalled); @@ -406,7 +402,7 @@ TEST_F (LottieReaderTests, ImageResolverCanProvideImageForAsset) return providedImage; }; - auto comp = LottieReader::parseData (kLottieReaderImageAssetJson, opts); + auto comp = LottieReader::parseData (kLottieReaderImageAssetJson, opts).valueOr (nullptr); ASSERT_NE (comp, nullptr); // Verify the asset was registered (asset table contains our image) @@ -424,7 +420,7 @@ TEST_F (LottieReaderTests, ImageResolverNotCalledWhenNoImageAssets) return std::nullopt; }; - auto comp = LottieReader::parseData (kLottieReaderBaseJson, opts); + auto comp = LottieReader::parseData (kLottieReaderBaseJson, opts).valueOr (nullptr); ASSERT_NE (comp, nullptr); EXPECT_FALSE (resolverCalled); @@ -436,7 +432,7 @@ TEST_F (LottieReaderTests, ImageResolverNotCalledWhenNoImageAssets) TEST_F (LottieReaderTests, ParseDataWithImageAssetCreatesAssetEntry) { - auto comp = LottieReader::parseData (kLottieReaderImageAssetJson); + auto comp = LottieReader::parseData (kLottieReaderImageAssetJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); EXPECT_EQ (comp->assets.size(), 1); @@ -445,7 +441,7 @@ TEST_F (LottieReaderTests, ParseDataWithImageAssetCreatesAssetEntry) TEST_F (LottieReaderTests, ParseDataImageAssetHasCorrectDimensions) { - auto comp = LottieReader::parseData (kLottieReaderImageAssetJson); + auto comp = LottieReader::parseData (kLottieReaderImageAssetJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); const auto asset = comp->assets["img0"]; @@ -457,7 +453,7 @@ TEST_F (LottieReaderTests, ParseDataImageAssetHasCorrectDimensions) TEST_F (LottieReaderTests, ParseDataImageAssetHasCorrectPath) { - auto comp = LottieReader::parseData (kLottieReaderImageAssetJson); + auto comp = LottieReader::parseData (kLottieReaderImageAssetJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); const auto asset = comp->assets["img0"]; @@ -476,7 +472,7 @@ TEST_F (LottieReaderTests, ParseFileWithGoalLottieReturnsValidComposition) const File file = getLottieTestDataDir().getChildFile ("goal.lottie"); ASSERT_TRUE (file.existsAsFile()) << "Test data file missing: " << file.getFullPathName(); - auto comp = LottieReader::parseFile (file); + auto comp = LottieReader::parseFile (file).valueOr (nullptr); ASSERT_NE (comp, nullptr); EXPECT_EQ (comp->name, String ("Goal")); EXPECT_FLOAT_EQ (comp->frameRate, 30.0f); @@ -491,7 +487,7 @@ TEST_F (LottieReaderTests, ParseFileWithGoalLottieParsesLayers) const File file = getLottieTestDataDir().getChildFile ("goal.lottie"); ASSERT_TRUE (file.existsAsFile()); - auto comp = LottieReader::parseFile (file); + auto comp = LottieReader::parseFile (file).valueOr (nullptr); ASSERT_NE (comp, nullptr); EXPECT_EQ (comp->layers.size(), 3u); EXPECT_GT (comp->assets.size(), 0u); @@ -502,7 +498,7 @@ TEST_F (LottieReaderTests, ParseFileWithJollyWalkerJsonReturnsValidComposition) const File file = getLottieTestDataDir().getChildFile ("jolly_walker.json"); ASSERT_TRUE (file.existsAsFile()) << "Test data file missing: " << file.getFullPathName(); - auto comp = LottieReader::parseFile (file); + auto comp = LottieReader::parseFile (file).valueOr (nullptr); ASSERT_NE (comp, nullptr); EXPECT_EQ (comp->name, String ("Comp 1")); EXPECT_FLOAT_EQ (comp->frameRate, 60.0f); @@ -516,7 +512,7 @@ TEST_F (LottieReaderTests, ParseFileWithImageTestJsonReturnsValidComposition) const File file = getLottieTestDataDir().getChildFile ("image_test.json"); ASSERT_TRUE (file.existsAsFile()) << "Test data file missing: " << file.getFullPathName(); - auto comp = LottieReader::parseFile (file); + auto comp = LottieReader::parseFile (file).valueOr (nullptr); ASSERT_NE (comp, nullptr); EXPECT_EQ (comp->name, String ("test")); EXPECT_EQ (comp->assets.size(), 1u); @@ -527,7 +523,7 @@ TEST_F (LottieReaderTests, ParseFileWithImageEmbeddedJsonReturnsValidComposition const File file = getLottieTestDataDir().getChildFile ("image_embedded.json"); ASSERT_TRUE (file.existsAsFile()) << "Test data file missing: " << file.getFullPathName(); - auto comp = LottieReader::parseFile (file); + auto comp = LottieReader::parseFile (file).valueOr (nullptr); ASSERT_NE (comp, nullptr); EXPECT_EQ (comp->name, String ("Comp 1")); EXPECT_EQ (comp->assets.size(), 1u); @@ -548,7 +544,7 @@ TEST_F (LottieReaderTests, ParseFromZipWithAnimationIdParsesGoalLottie) const File file = getLottieTestDataDir().getChildFile ("goal.lottie"); ASSERT_TRUE (file.existsAsFile()); - auto comp = LottieReader::parseFromZip (file, "goal-celebrate-every-win"); + auto comp = LottieReader::parseFromZip (file, "goal-celebrate-every-win").valueOr (nullptr); ASSERT_NE (comp, nullptr); EXPECT_EQ (comp->name, String ("Goal")); EXPECT_EQ (comp->layers.size(), 3u); @@ -559,7 +555,7 @@ TEST_F (LottieReaderTests, ParseFromZipWithDefaultIdParsesGoalLottie) const File file = getLottieTestDataDir().getChildFile ("goal.lottie"); ASSERT_TRUE (file.existsAsFile()); - auto comp = LottieReader::parseFromZip (file); + auto comp = LottieReader::parseFromZip (file).valueOr (nullptr); ASSERT_NE (comp, nullptr); EXPECT_EQ (comp->name, String ("Goal")); } @@ -573,7 +569,7 @@ TEST_F (LottieReaderTests, ParseStreamReturnsValidCompositionForValidJson) { MemoryInputStream stream (kLottieReaderBaseJson, strlen (kLottieReaderBaseJson), false); - auto comp = LottieReader::parseStream (stream); + auto comp = LottieReader::parseStream (stream).valueOr (nullptr); ASSERT_NE (comp, nullptr); EXPECT_EQ (comp->name, String ("ReaderTest")); EXPECT_FLOAT_EQ (comp->frameRate, 24.0f); @@ -586,7 +582,7 @@ TEST_F (LottieReaderTests, ParseStreamReturnsNullForGarbageInput) const char garbage[] = "not json {{{"; MemoryInputStream stream (garbage, strlen (garbage), false); - auto comp = LottieReader::parseStream (stream); + auto comp = LottieReader::parseStream (stream).valueOr (nullptr); EXPECT_EQ (comp, nullptr); } @@ -595,41 +591,38 @@ TEST_F (LottieReaderTests, ParseStreamSetsErrorForGarbageInput) const char garbage[] = "not json {{{"; MemoryInputStream stream (garbage, strlen (garbage), false); - String errorMsg; - auto comp = LottieReader::parseStream (stream, {}, &errorMsg); + auto result = LottieReader::parseStream (stream); - EXPECT_EQ (comp, nullptr); - EXPECT_FALSE (errorMsg.isEmpty()); + EXPECT_TRUE (result.failed()); + EXPECT_FALSE (result.getErrorMessage().isEmpty()); } TEST_F (LottieReaderTests, ParseStreamSetsNoErrorForValidJson) { MemoryInputStream stream (kLottieReaderBaseJson, strlen (kLottieReaderBaseJson), false); - String errorMsg; - auto comp = LottieReader::parseStream (stream, {}, &errorMsg); + auto result = LottieReader::parseStream (stream); - EXPECT_NE (comp, nullptr); - EXPECT_TRUE (errorMsg.isEmpty()); + EXPECT_TRUE (result.wasOk()); + EXPECT_NE (result.getReference(), nullptr); } TEST_F (LottieReaderTests, ParseStreamWithEmptyStreamReturnsNull) { MemoryInputStream stream (nullptr, 0, false); - String errorMsg; - auto comp = LottieReader::parseStream (stream, {}, &errorMsg); + auto result = LottieReader::parseStream (stream); - EXPECT_EQ (comp, nullptr); - EXPECT_FALSE (errorMsg.isEmpty()); - EXPECT_TRUE (errorMsg.contains ("Empty")); + EXPECT_TRUE (result.failed()); + EXPECT_FALSE (result.getErrorMessage().isEmpty()); + EXPECT_TRUE (result.getErrorMessage().contains ("Empty")); } TEST_F (LottieReaderTests, ParseStreamParsesMarkers) { MemoryInputStream stream (kLottieReaderWithMarkersJson, strlen (kLottieReaderWithMarkersJson), false); - auto comp = LottieReader::parseStream (stream); + auto comp = LottieReader::parseStream (stream).valueOr (nullptr); ASSERT_NE (comp, nullptr); EXPECT_EQ (comp->markers.size(), 3u); } @@ -642,7 +635,7 @@ TEST_F (LottieReaderTests, ParseStreamWithFileInputStreamParsesJson) auto fis = tempFile.createInputStream(); ASSERT_NE (fis, nullptr); - auto comp = LottieReader::parseStream (*fis); + auto comp = LottieReader::parseStream (*fis).valueOr (nullptr); ASSERT_NE (comp, nullptr); EXPECT_EQ (comp->name, String ("ReaderTest")); @@ -658,7 +651,7 @@ TEST_F (LottieReaderTests, ParseStreamWithGoalLottieAsZipStream) auto fis = file.createInputStream(); ASSERT_NE (fis, nullptr); - auto comp = LottieReader::parseStream (*fis); + auto comp = LottieReader::parseStream (*fis).valueOr (nullptr); ASSERT_NE (comp, nullptr); EXPECT_EQ (comp->name, String ("Goal")); EXPECT_EQ (comp->layers.size(), 3u); diff --git a/tests/yup_animation/yup_LottieRoundtrip.cpp b/tests/yup_animation/yup_LottieRoundtrip.cpp index 812a752ec..64d24cf5f 100644 --- a/tests/yup_animation/yup_LottieRoundtrip.cpp +++ b/tests/yup_animation/yup_LottieRoundtrip.cpp @@ -814,7 +814,7 @@ class LottieRoundtripTests : public ::testing::Test TEST_F (LottieRoundtripTests, ParseMinimalJson) { - auto comp = LottieReader::parseData (kMinimalLottieJson); + auto comp = LottieReader::parseData (kMinimalLottieJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); EXPECT_EQ (comp->name, "Test"); @@ -827,7 +827,7 @@ TEST_F (LottieRoundtripTests, ParseMinimalJson) TEST_F (LottieRoundtripTests, ParsedLayerCountMatchesInput) { - auto comp = LottieReader::parseData (kMinimalLottieJson); + auto comp = LottieReader::parseData (kMinimalLottieJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); EXPECT_EQ (comp->layers.size(), 1u); @@ -837,7 +837,7 @@ TEST_F (LottieRoundtripTests, ParsedLayerCountMatchesInput) TEST_F (LottieRoundtripTests, ResolvesLayerPositionExpressionReferences) { - auto comp = LottieReader::parseData (kLayerPositionExpressionJson); + auto comp = LottieReader::parseData (kLayerPositionExpressionJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); ASSERT_EQ (comp->layers.size(), 2u); @@ -855,7 +855,7 @@ TEST_F (LottieRoundtripTests, ResolvesLayerPositionExpressionReferences) TEST_F (LottieRoundtripTests, ResolvesMultipleLayersWithSamePositionExpression) { - auto comp = LottieReader::parseData (kMultiLayerPositionExpressionJson); + auto comp = LottieReader::parseData (kMultiLayerPositionExpressionJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); ASSERT_EQ (comp->layers.size(), 3u); @@ -877,7 +877,7 @@ TEST_F (LottieRoundtripTests, ResolvesMultipleLayersWithSamePositionExpression) TEST_F (LottieRoundtripTests, ResolvesPrecompAssetLayerPositionExpressionReferences) { - auto comp = LottieReader::parseData (kPrecompAssetLayerExpressionJson); + auto comp = LottieReader::parseData (kPrecompAssetLayerExpressionJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); auto asset = comp->assets["pre_0"]; @@ -894,7 +894,7 @@ TEST_F (LottieRoundtripTests, ResolvesPrecompAssetLayerPositionExpressionReferen TEST_F (LottieRoundtripTests, ResolvesShapeContentExpressionReferences) { - auto comp = LottieReader::parseData (kShapeContentExpressionJson); + auto comp = LottieReader::parseData (kShapeContentExpressionJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); ASSERT_EQ (comp->layers.size(), 1u); @@ -917,7 +917,7 @@ TEST_F (LottieRoundtripTests, ResolvesShapeContentExpressionReferences) TEST_F (LottieRoundtripTests, ParsesShapeTransformSkew) { - auto comp = LottieReader::parseData (kShapeTransformSkewJson); + auto comp = LottieReader::parseData (kShapeTransformSkewJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); ASSERT_EQ (comp->layers.size(), 1u); @@ -931,7 +931,7 @@ TEST_F (LottieRoundtripTests, ParsesShapeTransformSkew) TEST_F (LottieRoundtripTests, ParsesRepeaterOffset) { - auto comp = LottieReader::parseData (kShapeRepeaterOffsetJson); + auto comp = LottieReader::parseData (kShapeRepeaterOffsetJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); ASSERT_EQ (comp->layers.size(), 1u); @@ -950,13 +950,13 @@ TEST_F (LottieRoundtripTests, ParsesRepeaterOffset) TEST_F (LottieRoundtripTests, WriteAndReparse) { - auto original = LottieReader::parseData (kMinimalLottieJson); + auto original = LottieReader::parseData (kMinimalLottieJson).valueOr (nullptr); ASSERT_NE (original, nullptr); const String json = LottieWriter::toJson (*original); EXPECT_FALSE (json.isEmpty()); - auto reparsed = LottieReader::parseData (json); + auto reparsed = LottieReader::parseData (json).valueOr (nullptr); ASSERT_NE (reparsed, nullptr); EXPECT_EQ (reparsed->name, original->name); @@ -966,7 +966,7 @@ TEST_F (LottieRoundtripTests, WriteAndReparse) TEST_F (LottieRoundtripTests, ParsesDropShadowEffect) { - auto comp = LottieReader::parseData (kDropShadowJson); + auto comp = LottieReader::parseData (kDropShadowJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); ASSERT_EQ (comp->layers.size(), 1u); @@ -984,7 +984,7 @@ TEST_F (LottieRoundtripTests, ParsesDropShadowEffect) TEST_F (LottieRoundtripTests, ParsesFillEffect) { - auto comp = LottieReader::parseData (kFillEffectExampleJson); + auto comp = LottieReader::parseData (kFillEffectExampleJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); ASSERT_EQ (comp->layers.size(), 1u); @@ -1004,7 +1004,7 @@ TEST_F (LottieRoundtripTests, ParsesFillEffect) TEST_F (LottieRoundtripTests, ParsesNoneMaskMode) { - auto comp = LottieReader::parseData (kNoneMaskJson); + auto comp = LottieReader::parseData (kNoneMaskJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); ASSERT_EQ (comp->layers.size(), 1u); @@ -1054,7 +1054,7 @@ TEST_F (LottieRoundtripTests, CompositionCanAddShapeLayer) TEST_F (LottieRoundtripTests, AnimatedPathKeyframesUnwrapShapeValues) { - auto comp = LottieReader::parseData (kAnimatedPathJson); + auto comp = LottieReader::parseData (kAnimatedPathJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); ASSERT_EQ (comp->layers.size(), 1u); @@ -1077,7 +1077,7 @@ TEST_F (LottieRoundtripTests, AnimatedPathKeyframesUnwrapShapeValues) TEST_F (LottieRoundtripTests, TerminalKeyframeUsesPreviousEndValue) { - auto comp = LottieReader::parseData (kTerminalKeyframeJson); + auto comp = LottieReader::parseData (kTerminalKeyframeJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); ASSERT_EQ (comp->layers.size(), 1u); @@ -1100,7 +1100,7 @@ TEST_F (LottieRoundtripTests, ResolvesImageLayersInsideNestedPrecomps) return std::nullopt; }; - auto comp = LottieReader::parseData (kNestedPrecompImageJson, options); + auto comp = LottieReader::parseData (kNestedPrecompImageJson, options).valueOr (nullptr); ASSERT_NE (comp, nullptr); auto compB = comp->assets["comp_b"]; diff --git a/tests/yup_animation/yup_LottieWriter.cpp b/tests/yup_animation/yup_LottieWriter.cpp index 3711dc6d6..cd83ef020 100644 --- a/tests/yup_animation/yup_LottieWriter.cpp +++ b/tests/yup_animation/yup_LottieWriter.cpp @@ -462,11 +462,11 @@ TEST_F (LottieWriterTests, ToJson_ContainsRequiredTopLevelFields) TEST_F (LottieWriterTests, Roundtrip_PreservesCompositionName) { - auto comp = LottieReader::parseData (kMinimalJson); + auto comp = LottieReader::parseData (kMinimalJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); const String json = LottieWriter::toJson (*comp); - auto readback = LottieReader::parseData (json); + auto readback = LottieReader::parseData (json).valueOr (nullptr); ASSERT_NE (readback, nullptr); EXPECT_EQ (readback->name, "MinimalComp"); @@ -474,11 +474,11 @@ TEST_F (LottieWriterTests, Roundtrip_PreservesCompositionName) TEST_F (LottieWriterTests, Roundtrip_PreservesFrameRate) { - auto comp = LottieReader::parseData (kMinimalJson); + auto comp = LottieReader::parseData (kMinimalJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); const String json = LottieWriter::toJson (*comp); - auto readback = LottieReader::parseData (json); + auto readback = LottieReader::parseData (json).valueOr (nullptr); ASSERT_NE (readback, nullptr); EXPECT_FLOAT_EQ (readback->frameRate, 25.0f); @@ -486,11 +486,11 @@ TEST_F (LottieWriterTests, Roundtrip_PreservesFrameRate) TEST_F (LottieWriterTests, Roundtrip_PreservesSize) { - auto comp = LottieReader::parseData (kMinimalJson); + auto comp = LottieReader::parseData (kMinimalJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); const String json = LottieWriter::toJson (*comp); - auto readback = LottieReader::parseData (json); + auto readback = LottieReader::parseData (json).valueOr (nullptr); ASSERT_NE (readback, nullptr); EXPECT_FLOAT_EQ (readback->size.getWidth(), 200.0f); @@ -499,11 +499,11 @@ TEST_F (LottieWriterTests, Roundtrip_PreservesSize) TEST_F (LottieWriterTests, Roundtrip_PreservesStartAndEndFrames) { - auto comp = LottieReader::parseData (kMinimalJson); + auto comp = LottieReader::parseData (kMinimalJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); const String json = LottieWriter::toJson (*comp); - auto readback = LottieReader::parseData (json); + auto readback = LottieReader::parseData (json).valueOr (nullptr); ASSERT_NE (readback, nullptr); EXPECT_FLOAT_EQ (readback->startFrame, 0.0f); @@ -512,12 +512,12 @@ TEST_F (LottieWriterTests, Roundtrip_PreservesStartAndEndFrames) TEST_F (LottieWriterTests, Roundtrip_EmptyLayerList) { - auto comp = LottieReader::parseData (kMinimalJson); + auto comp = LottieReader::parseData (kMinimalJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); EXPECT_EQ (comp->layers.size(), 0u); const String json = LottieWriter::toJson (*comp); - auto readback = LottieReader::parseData (json); + auto readback = LottieReader::parseData (json).valueOr (nullptr); ASSERT_NE (readback, nullptr); EXPECT_EQ (readback->layers.size(), 0u); @@ -525,12 +525,12 @@ TEST_F (LottieWriterTests, Roundtrip_EmptyLayerList) TEST_F (LottieWriterTests, Roundtrip_PreservesLayerCount) { - auto comp = LottieReader::parseData (kShapeAndNullJson); + auto comp = LottieReader::parseData (kShapeAndNullJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); ASSERT_EQ (comp->layers.size(), 2u); const String json = LottieWriter::toJson (*comp); - auto readback = LottieReader::parseData (json); + auto readback = LottieReader::parseData (json).valueOr (nullptr); ASSERT_NE (readback, nullptr); EXPECT_EQ (readback->layers.size(), 2u); @@ -538,13 +538,13 @@ TEST_F (LottieWriterTests, Roundtrip_PreservesLayerCount) TEST_F (LottieWriterTests, Roundtrip_PreservesLayerAutoOrient) { - auto comp = LottieReader::parseData (kShapeAndNullJson); + auto comp = LottieReader::parseData (kShapeAndNullJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); ASSERT_FALSE (comp->layers.empty()); comp->layers[0]->autoOrient = true; comp->layers[0]->transform.autoOrient = true; - auto readback = LottieReader::parseData (LottieWriter::toJson (*comp)); + auto readback = LottieReader::parseData (LottieWriter::toJson (*comp)).valueOr (nullptr); ASSERT_NE (readback, nullptr); ASSERT_FALSE (readback->layers.empty()); @@ -554,11 +554,11 @@ TEST_F (LottieWriterTests, Roundtrip_PreservesLayerAutoOrient) TEST_F (LottieWriterTests, Roundtrip_PreservesLayerNames) { - auto comp = LottieReader::parseData (kShapeAndNullJson); + auto comp = LottieReader::parseData (kShapeAndNullJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); const String json = LottieWriter::toJson (*comp); - auto readback = LottieReader::parseData (json); + auto readback = LottieReader::parseData (json).valueOr (nullptr); ASSERT_NE (readback, nullptr); ASSERT_EQ (readback->layers.size(), 2u); @@ -568,11 +568,11 @@ TEST_F (LottieWriterTests, Roundtrip_PreservesLayerNames) TEST_F (LottieWriterTests, Roundtrip_PreservesLayerTypes) { - auto comp = LottieReader::parseData (kShapeAndNullJson); + auto comp = LottieReader::parseData (kShapeAndNullJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); const String json = LottieWriter::toJson (*comp); - auto readback = LottieReader::parseData (json); + auto readback = LottieReader::parseData (json).valueOr (nullptr); ASSERT_NE (readback, nullptr); ASSERT_EQ (readback->layers.size(), 2u); @@ -593,7 +593,7 @@ TEST_F (LottieWriterTests, Roundtrip_ProgrammaticCompositionMetadata) comp->endFrame = 65.0f; const String json = LottieWriter::toJson (*comp); - auto readback = LottieReader::parseData (json); + auto readback = LottieReader::parseData (json).valueOr (nullptr); ASSERT_NE (readback, nullptr); EXPECT_EQ (readback->name, String ("MyAnimation")); @@ -614,7 +614,7 @@ TEST_F (LottieWriterTests, Roundtrip_ProgrammaticLayers) ASSERT_EQ (comp->layers.size(), 2u); const String json = LottieWriter::toJson (*comp); - auto readback = LottieReader::parseData (json); + auto readback = LottieReader::parseData (json).valueOr (nullptr); ASSERT_NE (readback, nullptr); ASSERT_EQ (readback->layers.size(), 2u); @@ -630,12 +630,12 @@ TEST_F (LottieWriterTests, Roundtrip_ProgrammaticLayers) TEST_F (LottieWriterTests, Roundtrip_PreservesMarkers) { - auto comp = LottieReader::parseData (kMarkerJson); + auto comp = LottieReader::parseData (kMarkerJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); ASSERT_EQ (comp->markers.size(), 2u); const String json = LottieWriter::toJson (*comp); - auto readback = LottieReader::parseData (json); + auto readback = LottieReader::parseData (json).valueOr (nullptr); ASSERT_NE (readback, nullptr); ASSERT_EQ (readback->markers.size(), 2u); @@ -645,11 +645,11 @@ TEST_F (LottieWriterTests, Roundtrip_PreservesMarkers) TEST_F (LottieWriterTests, Roundtrip_PreservesMarkerTiming) { - auto comp = LottieReader::parseData (kMarkerJson); + auto comp = LottieReader::parseData (kMarkerJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); const String json = LottieWriter::toJson (*comp); - auto readback = LottieReader::parseData (json); + auto readback = LottieReader::parseData (json).valueOr (nullptr); ASSERT_NE (readback, nullptr); ASSERT_EQ (readback->markers.size(), 2u); @@ -676,10 +676,11 @@ TEST_F (LottieWriterTests, ToFile_WritesToTemporaryFileAndCanBeReadBack) EXPECT_TRUE (writeResult.wasOk()); EXPECT_TRUE (tempFile.exists()); - String outError; - auto readback = LottieReader::parseFile (tempFile, {}, &outError); + auto readbackResult = LottieReader::parseFile (tempFile); - ASSERT_NE (readback, nullptr) << outError; + ASSERT_TRUE (readbackResult.wasOk()) << readbackResult.getErrorMessage(); + auto readback = readbackResult.getReference(); + ASSERT_NE (readback, nullptr); EXPECT_EQ (readback->name, String ("FileRoundtripTest")); EXPECT_EQ (readback->layers.size(), 1u); EXPECT_FLOAT_EQ (readback->frameRate, 25.0f); @@ -693,13 +694,13 @@ TEST_F (LottieWriterTests, ToFile_WritesToTemporaryFileAndCanBeReadBack) TEST_F (LottieWriterTests, Roundtrip_StatsAreConsistentAfterWriteAndRead) { - auto comp = LottieReader::parseData (kShapeAndNullJson); + auto comp = LottieReader::parseData (kShapeAndNullJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); const auto originalStats = comp->computeStats(); const String json = LottieWriter::toJson (*comp); - auto readback = LottieReader::parseData (json); + auto readback = LottieReader::parseData (json).valueOr (nullptr); ASSERT_NE (readback, nullptr); const auto readbackStats = readback->computeStats(); @@ -715,13 +716,13 @@ TEST_F (LottieWriterTests, Roundtrip_StatsAreConsistentAfterWriteAndRead) TEST_F (LottieWriterTests, Roundtrip_SolidLayerPreservesType) { - auto comp = LottieReader::parseData (kSolidLayerExampleJson); + auto comp = LottieReader::parseData (kSolidLayerExampleJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); ASSERT_EQ (comp->layers.size(), 1u); EXPECT_EQ (comp->layers[0]->getType(), AnimationLayer::Type::Solid); const String json = LottieWriter::toJson (*comp); - auto readback = LottieReader::parseData (json); + auto readback = LottieReader::parseData (json).valueOr (nullptr); ASSERT_NE (readback, nullptr); ASSERT_EQ (readback->layers.size(), 1u); @@ -730,11 +731,11 @@ TEST_F (LottieWriterTests, Roundtrip_SolidLayerPreservesType) TEST_F (LottieWriterTests, Roundtrip_SolidLayerPreservesSize) { - auto comp = LottieReader::parseData (kSolidLayerExampleJson); + auto comp = LottieReader::parseData (kSolidLayerExampleJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); const String json = LottieWriter::toJson (*comp); - auto readback = LottieReader::parseData (json); + auto readback = LottieReader::parseData (json).valueOr (nullptr); ASSERT_NE (readback, nullptr); ASSERT_EQ (readback->layers.size(), 1u); @@ -746,7 +747,7 @@ TEST_F (LottieWriterTests, Roundtrip_SolidLayerPreservesSize) TEST_F (LottieWriterTests, ToJson_SolidLayerContainsSolidColorField) { - auto comp = LottieReader::parseData (kSolidLayerExampleJson); + auto comp = LottieReader::parseData (kSolidLayerExampleJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); const std::string json = LottieWriter::toJson (*comp).toStdString(); @@ -761,11 +762,11 @@ TEST_F (LottieWriterTests, ToJson_SolidLayerContainsSolidColorField) TEST_F (LottieWriterTests, Roundtrip_ShapeLayerWithFillPreservesLayerType) { - auto comp = LottieReader::parseData (kShapeWithFillJson); + auto comp = LottieReader::parseData (kShapeWithFillJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); const String json = LottieWriter::toJson (*comp); - auto readback = LottieReader::parseData (json); + auto readback = LottieReader::parseData (json).valueOr (nullptr); ASSERT_NE (readback, nullptr); ASSERT_EQ (readback->layers.size(), 1u); @@ -774,7 +775,7 @@ TEST_F (LottieWriterTests, Roundtrip_ShapeLayerWithFillPreservesLayerType) TEST_F (LottieWriterTests, ToJson_ShapeLayerWithFillContainsShapesField) { - auto comp = LottieReader::parseData (kShapeWithFillJson); + auto comp = LottieReader::parseData (kShapeWithFillJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); const std::string json = LottieWriter::toJson (*comp).toStdString(); @@ -784,7 +785,7 @@ TEST_F (LottieWriterTests, ToJson_ShapeLayerWithFillContainsShapesField) TEST_F (LottieWriterTests, Roundtrip_ShapeLayerWithFillPreservesGroupCount) { - auto comp = LottieReader::parseData (kShapeWithFillJson); + auto comp = LottieReader::parseData (kShapeWithFillJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); ASSERT_EQ (comp->layers.size(), 1u); const auto* sl = dynamic_cast (comp->layers[0].get()); @@ -792,7 +793,7 @@ TEST_F (LottieWriterTests, Roundtrip_ShapeLayerWithFillPreservesGroupCount) const auto originalGroupCount = sl->groups.size(); const String json = LottieWriter::toJson (*comp); - auto readback = LottieReader::parseData (json); + auto readback = LottieReader::parseData (json).valueOr (nullptr); ASSERT_NE (readback, nullptr); ASSERT_EQ (readback->layers.size(), 1u); @@ -807,11 +808,11 @@ TEST_F (LottieWriterTests, Roundtrip_ShapeLayerWithFillPreservesGroupCount) TEST_F (LottieWriterTests, Roundtrip_ShapeLayerWithStrokePreservesType) { - auto comp = LottieReader::parseData (kShapeWithStrokeJson); + auto comp = LottieReader::parseData (kShapeWithStrokeJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); const String json = LottieWriter::toJson (*comp); - auto readback = LottieReader::parseData (json); + auto readback = LottieReader::parseData (json).valueOr (nullptr); ASSERT_NE (readback, nullptr); ASSERT_EQ (readback->layers.size(), 1u); @@ -820,7 +821,7 @@ TEST_F (LottieWriterTests, Roundtrip_ShapeLayerWithStrokePreservesType) TEST_F (LottieWriterTests, ToJson_ShapeLayerWithStrokeContainsStrokeType) { - auto comp = LottieReader::parseData (kShapeWithStrokeJson); + auto comp = LottieReader::parseData (kShapeWithStrokeJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); const std::string json = LottieWriter::toJson (*comp).toStdString(); @@ -833,11 +834,11 @@ TEST_F (LottieWriterTests, ToJson_ShapeLayerWithStrokeContainsStrokeType) TEST_F (LottieWriterTests, Roundtrip_ShapeLayerWithEllipsePreservesType) { - auto comp = LottieReader::parseData (kShapeWithEllipseJson); + auto comp = LottieReader::parseData (kShapeWithEllipseJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); const String json = LottieWriter::toJson (*comp); - auto readback = LottieReader::parseData (json); + auto readback = LottieReader::parseData (json).valueOr (nullptr); ASSERT_NE (readback, nullptr); ASSERT_EQ (readback->layers.size(), 1u); @@ -846,7 +847,7 @@ TEST_F (LottieWriterTests, Roundtrip_ShapeLayerWithEllipsePreservesType) TEST_F (LottieWriterTests, ToJson_ShapeLayerWithEllipseContainsEllipseType) { - auto comp = LottieReader::parseData (kShapeWithEllipseJson); + auto comp = LottieReader::parseData (kShapeWithEllipseJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); const std::string json = LottieWriter::toJson (*comp).toStdString(); @@ -859,13 +860,13 @@ TEST_F (LottieWriterTests, ToJson_ShapeLayerWithEllipseContainsEllipseType) TEST_F (LottieWriterTests, Roundtrip_LayerWithMaskPreservesMaskCount) { - auto comp = LottieReader::parseData (kShapeWithMaskJson); + auto comp = LottieReader::parseData (kShapeWithMaskJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); ASSERT_EQ (comp->layers.size(), 1u); EXPECT_EQ (comp->layers[0]->masks.size(), 1u); const String json = LottieWriter::toJson (*comp); - auto readback = LottieReader::parseData (json); + auto readback = LottieReader::parseData (json).valueOr (nullptr); ASSERT_NE (readback, nullptr); ASSERT_EQ (readback->layers.size(), 1u); @@ -874,7 +875,7 @@ TEST_F (LottieWriterTests, Roundtrip_LayerWithMaskPreservesMaskCount) TEST_F (LottieWriterTests, ToJson_LayerWithMaskContainsMasksPropertiesField) { - auto comp = LottieReader::parseData (kShapeWithMaskJson); + auto comp = LottieReader::parseData (kShapeWithMaskJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); const std::string json = LottieWriter::toJson (*comp).toStdString(); @@ -887,13 +888,13 @@ TEST_F (LottieWriterTests, ToJson_LayerWithMaskContainsMasksPropertiesField) TEST_F (LottieWriterTests, Roundtrip_HiddenLayerPreservesHiddenState) { - auto comp = LottieReader::parseData (kHiddenShapeLayerJson); + auto comp = LottieReader::parseData (kHiddenShapeLayerJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); ASSERT_EQ (comp->layers.size(), 1u); EXPECT_TRUE (comp->layers[0]->hidden); const String json = LottieWriter::toJson (*comp); - auto readback = LottieReader::parseData (json); + auto readback = LottieReader::parseData (json).valueOr (nullptr); ASSERT_NE (readback, nullptr); ASSERT_EQ (readback->layers.size(), 1u); @@ -906,14 +907,14 @@ TEST_F (LottieWriterTests, Roundtrip_HiddenLayerPreservesHiddenState) TEST_F (LottieWriterTests, Roundtrip_LayerInOutFramesArePreserved) { - auto comp = LottieReader::parseData (kLayerWithInOutJson); + auto comp = LottieReader::parseData (kLayerWithInOutJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); ASSERT_EQ (comp->layers.size(), 1u); EXPECT_FLOAT_EQ (comp->layers[0]->inFrame, 10.0f); EXPECT_FLOAT_EQ (comp->layers[0]->outFrame, 80.0f); const String json = LottieWriter::toJson (*comp); - auto readback = LottieReader::parseData (json); + auto readback = LottieReader::parseData (json).valueOr (nullptr); ASSERT_NE (readback, nullptr); ASSERT_EQ (readback->layers.size(), 1u); @@ -923,13 +924,13 @@ TEST_F (LottieWriterTests, Roundtrip_LayerInOutFramesArePreserved) TEST_F (LottieWriterTests, Roundtrip_LayerStartFrameIsPreserved) { - auto comp = LottieReader::parseData (kLayerWithInOutJson); + auto comp = LottieReader::parseData (kLayerWithInOutJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); ASSERT_EQ (comp->layers.size(), 1u); EXPECT_FLOAT_EQ (comp->layers[0]->startFrame, 5.0f); const String json = LottieWriter::toJson (*comp); - auto readback = LottieReader::parseData (json); + auto readback = LottieReader::parseData (json).valueOr (nullptr); ASSERT_NE (readback, nullptr); ASSERT_EQ (readback->layers.size(), 1u); @@ -947,7 +948,7 @@ TEST_F (LottieWriterTests, Roundtrip_ProgrammaticSolidLayer) comp->addSolidLayer ("RedSolid", Color (0xFFFF0000), { 80.0f, 60.0f }); const String json = LottieWriter::toJson (*comp); - auto readback = LottieReader::parseData (json); + auto readback = LottieReader::parseData (json).valueOr (nullptr); ASSERT_NE (readback, nullptr); ASSERT_EQ (readback->layers.size(), 1u); @@ -966,7 +967,7 @@ TEST_F (LottieWriterTests, Roundtrip_ProgrammaticSolidLayer) TEST_F (LottieWriterTests, ToJson_LayerContainsTransformField) { - auto comp = LottieReader::parseData (kShapeWithFillJson); + auto comp = LottieReader::parseData (kShapeWithFillJson).valueOr (nullptr); ASSERT_NE (comp, nullptr); const std::string json = LottieWriter::toJson (*comp).toStdString(); @@ -984,11 +985,11 @@ TEST_F (LottieWriterTests, Roundtrip_GoalLottiePreservesName) const File file = getLottieTestDataDir().getChildFile ("goal.lottie"); ASSERT_TRUE (file.existsAsFile()); - auto comp = LottieReader::parseFile (file); + auto comp = LottieReader::parseFile (file).valueOr (nullptr); ASSERT_NE (comp, nullptr); const String json = LottieWriter::toJson (*comp); - auto readback = LottieReader::parseData (json); + auto readback = LottieReader::parseData (json).valueOr (nullptr); ASSERT_NE (readback, nullptr); EXPECT_EQ (readback->name, String ("Goal")); @@ -999,11 +1000,11 @@ TEST_F (LottieWriterTests, Roundtrip_GoalLottiePreservesFrameRate) const File file = getLottieTestDataDir().getChildFile ("goal.lottie"); ASSERT_TRUE (file.existsAsFile()); - auto comp = LottieReader::parseFile (file); + auto comp = LottieReader::parseFile (file).valueOr (nullptr); ASSERT_NE (comp, nullptr); const String json = LottieWriter::toJson (*comp); - auto readback = LottieReader::parseData (json); + auto readback = LottieReader::parseData (json).valueOr (nullptr); ASSERT_NE (readback, nullptr); EXPECT_FLOAT_EQ (readback->frameRate, 30.0f); @@ -1014,11 +1015,11 @@ TEST_F (LottieWriterTests, Roundtrip_GoalLottiePreservesSize) const File file = getLottieTestDataDir().getChildFile ("goal.lottie"); ASSERT_TRUE (file.existsAsFile()); - auto comp = LottieReader::parseFile (file); + auto comp = LottieReader::parseFile (file).valueOr (nullptr); ASSERT_NE (comp, nullptr); const String json = LottieWriter::toJson (*comp); - auto readback = LottieReader::parseData (json); + auto readback = LottieReader::parseData (json).valueOr (nullptr); ASSERT_NE (readback, nullptr); EXPECT_FLOAT_EQ (readback->size.getWidth(), 1080.0f); @@ -1030,11 +1031,11 @@ TEST_F (LottieWriterTests, Roundtrip_GoalLottiePreservesLayerCount) const File file = getLottieTestDataDir().getChildFile ("goal.lottie"); ASSERT_TRUE (file.existsAsFile()); - auto comp = LottieReader::parseFile (file); + auto comp = LottieReader::parseFile (file).valueOr (nullptr); ASSERT_NE (comp, nullptr); const String json = LottieWriter::toJson (*comp); - auto readback = LottieReader::parseData (json); + auto readback = LottieReader::parseData (json).valueOr (nullptr); ASSERT_NE (readback, nullptr); EXPECT_EQ (readback->layers.size(), 3u); @@ -1045,13 +1046,13 @@ TEST_F (LottieWriterTests, Roundtrip_GoalLottiePreservesMarkers) const File file = getLottieTestDataDir().getChildFile ("goal.lottie"); ASSERT_TRUE (file.existsAsFile()); - auto comp = LottieReader::parseFile (file); + auto comp = LottieReader::parseFile (file).valueOr (nullptr); ASSERT_NE (comp, nullptr); const auto originalMarkerCount = comp->markers.size(); const String json = LottieWriter::toJson (*comp); - auto readback = LottieReader::parseData (json); + auto readback = LottieReader::parseData (json).valueOr (nullptr); ASSERT_NE (readback, nullptr); EXPECT_EQ (readback->markers.size(), originalMarkerCount); @@ -1062,12 +1063,12 @@ TEST_F (LottieWriterTests, Roundtrip_JollyWalkerPreservesLayerCount) const File file = getLottieTestDataDir().getChildFile ("jolly_walker.json"); ASSERT_TRUE (file.existsAsFile()); - auto comp = LottieReader::parseFile (file); + auto comp = LottieReader::parseFile (file).valueOr (nullptr); ASSERT_NE (comp, nullptr); ASSERT_EQ (comp->layers.size(), 21u); const String json = LottieWriter::toJson (*comp); - auto readback = LottieReader::parseData (json); + auto readback = LottieReader::parseData (json).valueOr (nullptr); ASSERT_NE (readback, nullptr); EXPECT_EQ (readback->layers.size(), 21u); @@ -1078,11 +1079,11 @@ TEST_F (LottieWriterTests, Roundtrip_JollyWalkerPreservesName) const File file = getLottieTestDataDir().getChildFile ("jolly_walker.json"); ASSERT_TRUE (file.existsAsFile()); - auto comp = LottieReader::parseFile (file); + auto comp = LottieReader::parseFile (file).valueOr (nullptr); ASSERT_NE (comp, nullptr); const String json = LottieWriter::toJson (*comp); - auto readback = LottieReader::parseData (json); + auto readback = LottieReader::parseData (json).valueOr (nullptr); ASSERT_NE (readback, nullptr); EXPECT_EQ (readback->name, String ("Comp 1")); @@ -1093,11 +1094,11 @@ TEST_F (LottieWriterTests, Roundtrip_JollyWalkerPreservesFrameRate) const File file = getLottieTestDataDir().getChildFile ("jolly_walker.json"); ASSERT_TRUE (file.existsAsFile()); - auto comp = LottieReader::parseFile (file); + auto comp = LottieReader::parseFile (file).valueOr (nullptr); ASSERT_NE (comp, nullptr); const String json = LottieWriter::toJson (*comp); - auto readback = LottieReader::parseData (json); + auto readback = LottieReader::parseData (json).valueOr (nullptr); ASSERT_NE (readback, nullptr); EXPECT_FLOAT_EQ (readback->frameRate, 60.0f); @@ -1108,11 +1109,11 @@ TEST_F (LottieWriterTests, Roundtrip_ImageTestPreservesAssetCount) const File file = getLottieTestDataDir().getChildFile ("image_test.json"); ASSERT_TRUE (file.existsAsFile()); - auto comp = LottieReader::parseFile (file); + auto comp = LottieReader::parseFile (file).valueOr (nullptr); ASSERT_NE (comp, nullptr); const String json = LottieWriter::toJson (*comp); - auto readback = LottieReader::parseData (json); + auto readback = LottieReader::parseData (json).valueOr (nullptr); ASSERT_NE (readback, nullptr); EXPECT_EQ (readback->assets.size(), 1u); @@ -1123,11 +1124,11 @@ TEST_F (LottieWriterTests, Roundtrip_ImageEmbeddedPreservesAssetCount) const File file = getLottieTestDataDir().getChildFile ("image_embedded.json"); ASSERT_TRUE (file.existsAsFile()); - auto comp = LottieReader::parseFile (file); + auto comp = LottieReader::parseFile (file).valueOr (nullptr); ASSERT_NE (comp, nullptr); const String json = LottieWriter::toJson (*comp); - auto readback = LottieReader::parseData (json); + auto readback = LottieReader::parseData (json).valueOr (nullptr); ASSERT_NE (readback, nullptr); EXPECT_EQ (readback->assets.size(), 1u); @@ -1138,7 +1139,7 @@ TEST_F (LottieWriterTests, ToFile_GoalLottieWriteAndReadBack) const File inputFile = getLottieTestDataDir().getChildFile ("goal.lottie"); ASSERT_TRUE (inputFile.existsAsFile()); - auto comp = LottieReader::parseFile (inputFile); + auto comp = LottieReader::parseFile (inputFile).valueOr (nullptr); ASSERT_NE (comp, nullptr); const File tempFile = File::createTempFile ("lottie_writer_goal.json"); @@ -1146,10 +1147,11 @@ TEST_F (LottieWriterTests, ToFile_GoalLottieWriteAndReadBack) EXPECT_TRUE (writeResult.wasOk()); EXPECT_TRUE (tempFile.exists()); - String outError; - auto readback = LottieReader::parseFile (tempFile, {}, &outError); + auto readbackResult = LottieReader::parseFile (tempFile); - ASSERT_NE (readback, nullptr) << outError; + ASSERT_TRUE (readbackResult.wasOk()) << readbackResult.getErrorMessage(); + auto readback = readbackResult.getReference(); + ASSERT_NE (readback, nullptr); EXPECT_EQ (readback->name, String ("Goal")); EXPECT_FLOAT_EQ (readback->frameRate, 30.0f); EXPECT_FLOAT_EQ (readback->size.getWidth(), 1080.0f); @@ -1164,14 +1166,14 @@ TEST_F (LottieWriterTests, ToFile_JollyWalkerWriteAndReadBack) const File inputFile = getLottieTestDataDir().getChildFile ("jolly_walker.json"); ASSERT_TRUE (inputFile.existsAsFile()); - auto comp = LottieReader::parseFile (inputFile); + auto comp = LottieReader::parseFile (inputFile).valueOr (nullptr); ASSERT_NE (comp, nullptr); const File tempFile = File::createTempFile ("lottie_writer_jolly.json"); const Result writeResult = LottieWriter::toFile (*comp, tempFile); EXPECT_TRUE (writeResult.wasOk()); - auto readback = LottieReader::parseFile (tempFile); + auto readback = LottieReader::parseFile (tempFile).valueOr (nullptr); ASSERT_NE (readback, nullptr); EXPECT_EQ (readback->layers.size(), 21u);