up-to-date submodule(rive-cpp)
authorTaehyub Kim <taehyub.kim@samsung.com>
Thu, 20 May 2021 08:26:43 +0000 (17:26 +0900)
committerTaehyub Kim <taehyub.kim@samsung.com>
Thu, 20 May 2021 08:26:43 +0000 (17:26 +0900)
Change-Id: I8ff93588e45e8634ea06fa53a61cddb5623e17dc

14 files changed:
submodule/include/animation/linear_animation_instance.hpp
submodule/skia/recorder/include/extractor.hpp
submodule/skia/recorder/src/extractor.cpp
submodule/skia/recorder/src/main.cpp
submodule/skia/recorder/test/src/extracter.cpp
submodule/skia/recorder/test/static/50x51.riv
submodule/skia/recorder/test/static/51x50.riv
submodule/skia/recorder/test/static/animations.riv [new file with mode: 0644]
submodule/skia/recorder/test/static/no_animation.riv
submodule/skia/recorder/test/static/no_artboard.riv
submodule/src/animation/linear_animation_instance.cpp
submodule/test/assets/stroke_name_test.riv [new file with mode: 0644]
submodule/test/linear_animation_instance_test.cpp [new file with mode: 0644]
submodule/test/stroke_test.cpp [new file with mode: 0644]

index 0fb1fb0..808f6db 100644 (file)
@@ -16,6 +16,7 @@ namespace rive
                float m_SpilledTime;
                int m_Direction;
                bool m_DidLoop;
+               int m_LoopValue = -1;
 
        public:
                LinearAnimationInstance(const LinearAnimation* animation);
@@ -66,6 +67,12 @@ namespace rive
                float totalTime() const { return m_TotalTime; }
                float lastTotalTime() const { return m_LastTotalTime; }
                float spilledTime() const { return m_SpilledTime; }
+
+               // Returns either the animation's default or overridden loop values
+               Loop loop() { return (Loop)loopValue(); }
+               int loopValue();
+               // Override the animation's default loop
+               void loopValue(int value);
        };
 } // namespace rive
 #endif
\ No newline at end of file
index 7fcd7e4..1f855bd 100644 (file)
@@ -31,11 +31,14 @@ public:
                           int max_width = 0,
                           int max_height = 0,
                           int min_duration = 0,
-                          int max_duration = 0);
+                          int max_duration = 0,
+                          float fps = 0);
+       ~RiveFrameExtractor();
+
        int width();
        int height();
-       int fps();
        int totalFrames();
+       float fps();
        void advanceFrame();
        const void* getPixelAddresses();
        sk_sp<SkData> getSkData();
@@ -43,7 +46,7 @@ public:
 private:
        int _width, _height, _min_duration, _max_duration;
        rive::File* riveFile;
-       float ifps;
+       float ifps, _fps;
        sk_sp<SkImage> getWaterMark(const char* watermark_name);
        rive::File* getRiveFile(const char* path);
        rive::Artboard* getArtboard(const char* artboard_name);
index ef73b36..a840a0d 100644 (file)
@@ -1,12 +1,8 @@
 #include "extractor.hpp"
 
-int valueOrDefault(int value, int default_value)
+inline int valueOrDefault(int value, int default_value)
 {
-       if (value == 0)
-       {
-               return default_value;
-       }
-       return value;
+       return value <= 0 ? default_value : value;
 }
 
 void scale(int* value, int targetValue, int* otherValue)
@@ -18,9 +14,50 @@ void scale(int* value, int targetValue, int* otherValue)
        }
 }
 
+RiveFrameExtractor::RiveFrameExtractor(const char* path,
+                                       const char* artboard_name,
+                                       const char* animation_name,
+                                       const char* watermark_name,
+                                       int width,
+                                       int height,
+                                       int small_extent_target,
+                                       int max_width,
+                                       int max_height,
+                                       int min_duration,
+                                       int max_duration,
+                                       float fps)
+{
+       _min_duration = min_duration;
+       _max_duration = max_duration;
+       riveFile = getRiveFile(path);
+       artboard = getArtboard(artboard_name);
+       animation = getAnimation(animation_name);
+       animation_instance = new rive::LinearAnimationInstance(animation);
+       watermarkImage = getWaterMark(watermark_name);
+       initializeDimensions(
+           width, height, small_extent_target, max_width, max_height);
+       rasterSurface = SkSurface::MakeRaster(SkImageInfo::Make(
+           _width, _height, kRGBA_8888_SkColorType, kPremul_SkAlphaType));
+       rasterCanvas = rasterSurface->getCanvas();
+       _fps = valueOrDefault(fps, animation->fps());
+       ifps = 1.0 / fps;
+};
+
+RiveFrameExtractor::~RiveFrameExtractor()
+{
+       if (animation_instance)
+       {
+               delete animation_instance;
+       }
+       if (riveFile)
+       {
+               delete riveFile;
+       }
+}
+
 int RiveFrameExtractor::width() { return _width; };
 int RiveFrameExtractor::height() { return _height; };
-int RiveFrameExtractor::fps() { return animation->fps(); };
+float RiveFrameExtractor::fps() { return _fps; };
 int RiveFrameExtractor::totalFrames()
 {
        int min_frames = _min_duration * fps();
@@ -65,33 +102,6 @@ int RiveFrameExtractor::totalFrames()
        return totalFrames;
 };
 
-RiveFrameExtractor::RiveFrameExtractor(const char* path,
-                                       const char* artboard_name,
-                                       const char* animation_name,
-                                       const char* watermark_name,
-                                       int width,
-                                       int height,
-                                       int small_extent_target,
-                                       int max_width,
-                                       int max_height,
-                                       int min_duration,
-                                       int max_duration)
-{
-       _min_duration = min_duration;
-       _max_duration = max_duration;
-       riveFile = getRiveFile(path);
-       artboard = getArtboard(artboard_name);
-       animation = getAnimation(animation_name);
-       animation_instance = new rive::LinearAnimationInstance(animation);
-       watermarkImage = getWaterMark(watermark_name);
-       initializeDimensions(
-           width, height, small_extent_target, max_width, max_height);
-       rasterSurface = SkSurface::MakeRaster(SkImageInfo::Make(
-           _width, _height, kRGBA_8888_SkColorType, kPremul_SkAlphaType));
-       rasterCanvas = rasterSurface->getCanvas();
-       ifps = 1.0 / animation->fps();
-};
-
 void RiveFrameExtractor::initializeDimensions(int width,
                                               int height,
                                               int small_extent_target,
@@ -119,7 +129,6 @@ void RiveFrameExtractor::initializeDimensions(int width,
        // if we have a max height, lets scale down to that
        if (max_height != 0 && max_height < _height)
        {
-
                scale(&_height, max_height, &_width);
        }
 
@@ -160,6 +169,7 @@ rive::File* RiveFrameExtractor::getRiveFile(const char* path)
 
        if (fp == nullptr)
        {
+               fclose(fp);
                throw std::invalid_argument(
                    string_format("Failed to open file %s", path));
        }
@@ -167,11 +177,12 @@ rive::File* RiveFrameExtractor::getRiveFile(const char* path)
        auto length = ftell(fp);
        fseek(fp, 0, SEEK_SET);
 
-       // TODO: need to clean this up? how?!
        uint8_t* bytes = new uint8_t[length];
 
        if (fread(bytes, 1, length, fp) != length)
        {
+               fclose(fp);
+               delete[] bytes;
                throw std::invalid_argument(
                    string_format("Failed to read file into bytes array %s", path));
        }
@@ -179,6 +190,10 @@ rive::File* RiveFrameExtractor::getRiveFile(const char* path)
        auto reader = rive::BinaryReader(bytes, length);
        rive::File* file = nullptr;
        auto result = rive::File::import(reader, &file);
+
+       fclose(fp);
+       delete[] bytes;
+
        if (result != rive::ImportResult::success)
        {
                throw std::invalid_argument(
index b49ba1a..07f0abe 100644 (file)
@@ -107,6 +107,9 @@ int main(int argc, char* argv[])
        args::ValueFlag<std::string> snapshot_path(
            optional, "path", "destination image filename", {"snapshot-path"});
 
+       args::ValueFlag<float> fps(
+           required, "number", "frame rate", {"f", "fps"}, 60.0);
+
        args::CompletionFlag completion(parser, {"complete"});
 
        try
@@ -149,9 +152,8 @@ int main(int argc, char* argv[])
                                                   args::get(max_width),
                                                   args::get(max_height),
                                                   args::get(min_duration),
-                                                  args::get(max_duration)
-
-               );
+                                                  args::get(max_duration),
+                                                  args::get(fps));
        }
        catch (const std::invalid_argument e)
        {
@@ -170,6 +172,7 @@ int main(int argc, char* argv[])
        }
        catch (const std::invalid_argument e)
        {
+               delete extractor;
                std::cout << e.what();
                return 1;
        }
@@ -194,6 +197,8 @@ int main(int argc, char* argv[])
                writer->writeFrame(i, (const uint8_t* const*)&pixelData);
        }
        writer->finalize();
+       delete writer;
+       delete extractor;
 }
 
 #endif
index 1aa03c2..a911557 100644 (file)
@@ -112,7 +112,7 @@ TEST_CASE("Test 2s_pingpong min 5s")
        REQUIRE(rive->totalFrames() == 480);
 }
 
-TEST_CASE("Test 100s_oneshot animation min duration 10s")
+TEST_CASE("Test 100s_oneShot animation min duration 10s")
 {
        auto rive = new RiveFrameExtractor("./static/animations.riv",
                                           "",
@@ -150,3 +150,20 @@ TEST_CASE("Test 100s_pingpong animation min duration 10s")
                                           10);
        REQUIRE(rive->totalFrames() == 600);
 }
+
+TEST_CASE("Test 1s_oneShot animation custom fps 120")
+{
+       auto rive = new RiveFrameExtractor("./static/animations.riv",
+                                          "",
+                                          "100s_oneShot",
+                                          "",
+                                          0,
+                                          0,
+                                          0,
+                                          0,
+                                          0,
+                                          0,
+                                          10,
+                                          120);
+       REQUIRE(rive->totalFrames() == 1200);
+}
index 04a5f99..c993cd4 100644 (file)
Binary files a/submodule/skia/recorder/test/static/50x51.riv and b/submodule/skia/recorder/test/static/50x51.riv differ
index 48c6eb4..27186ec 100644 (file)
Binary files a/submodule/skia/recorder/test/static/51x50.riv and b/submodule/skia/recorder/test/static/51x50.riv differ
diff --git a/submodule/skia/recorder/test/static/animations.riv b/submodule/skia/recorder/test/static/animations.riv
new file mode 100644 (file)
index 0000000..842acf7
Binary files /dev/null and b/submodule/skia/recorder/test/static/animations.riv differ
index 814d263..ea10b1d 100644 (file)
Binary files a/submodule/skia/recorder/test/static/no_animation.riv and b/submodule/skia/recorder/test/static/no_animation.riv differ
index dd491c9..fc05ce0 100644 (file)
Binary files a/submodule/skia/recorder/test/static/no_artboard.riv and b/submodule/skia/recorder/test/static/no_artboard.riv differ
index 705986c..b58e006 100644 (file)
@@ -38,7 +38,7 @@ bool LinearAnimationInstance::advance(float elapsedSeconds)
        bool didLoop = false;
        m_SpilledTime = 0.0f;
 
-       switch (animation.loop())
+       switch (loop())
        {
                case Loop::oneShot:
                        if (m_Direction == 1 && frames > end)
@@ -134,3 +134,27 @@ void LinearAnimationInstance::time(float value)
        // playing things backwards and seeking. what purpose does it solve?
        m_Direction = 1;
 }
+
+// Returns either the animation's default or overridden loop values
+int LinearAnimationInstance::loopValue()
+{
+       if (m_LoopValue != -1)
+       {
+               return m_LoopValue;
+       }
+       return m_Animation->loopValue();
+}
+
+// Override the animation's loop value
+void LinearAnimationInstance::loopValue(int value)
+{
+       if (m_LoopValue == value)
+       {
+               return;
+       }
+       if (m_LoopValue == -1 && m_Animation->loopValue() == value)
+       {
+               return;
+       }
+       m_LoopValue = value;
+}
\ No newline at end of file
diff --git a/submodule/test/assets/stroke_name_test.riv b/submodule/test/assets/stroke_name_test.riv
new file mode 100644 (file)
index 0000000..b1ca076
Binary files /dev/null and b/submodule/test/assets/stroke_name_test.riv differ
diff --git a/submodule/test/linear_animation_instance_test.cpp b/submodule/test/linear_animation_instance_test.cpp
new file mode 100644 (file)
index 0000000..1f85934
--- /dev/null
@@ -0,0 +1,295 @@
+#include "catch.hpp"
+#include "animation/loop.hpp"
+#include "animation/linear_animation.hpp"
+#include "animation/linear_animation_instance.hpp"
+#include <cstdio>
+
+TEST_CASE("LinearAnimationInstance oneShot", "[animation]")
+{
+       rive::LinearAnimation* linearAnimation = new rive::LinearAnimation();
+       // duration in seconds is 5
+       linearAnimation->duration(10);
+       linearAnimation->fps(2);
+       linearAnimation->loopValue(static_cast<int>(rive::Loop::oneShot));
+
+       rive::LinearAnimationInstance* linearAnimationInstance =
+           new rive::LinearAnimationInstance(linearAnimation);
+
+       // play from beginning.
+       bool continuePlaying = linearAnimationInstance->advance(2.0);
+       REQUIRE(continuePlaying == true);
+       REQUIRE(linearAnimationInstance->time() == 2.0);
+       REQUIRE(linearAnimationInstance->totalTime() == 2.0);
+       REQUIRE(linearAnimationInstance->didLoop() == false);
+
+       // get stuck at end
+       continuePlaying = linearAnimationInstance->advance(10.0);
+       REQUIRE(linearAnimationInstance->time() == 5.0);
+       REQUIRE(linearAnimationInstance->totalTime() == 12.0);
+       REQUIRE(linearAnimationInstance->didLoop() == true);
+       REQUIRE(continuePlaying == false);
+
+       delete linearAnimationInstance;
+       delete linearAnimation;
+}
+
+TEST_CASE("LinearAnimationInstance oneShot <-", "[animation]")
+{
+       rive::LinearAnimation* linearAnimation = new rive::LinearAnimation();
+       // duration in seconds is 5
+       linearAnimation->duration(10);
+       linearAnimation->fps(2);
+       linearAnimation->loopValue(static_cast<int>(rive::Loop::oneShot));
+
+       rive::LinearAnimationInstance* linearAnimationInstance =
+           new rive::LinearAnimationInstance(linearAnimation);
+       linearAnimationInstance->direction(-1);
+       REQUIRE(linearAnimationInstance->time() == 0.0);
+
+       // Advancing 2 seconds backwards will keep the "animation" time at 0
+       bool continuePlaying = linearAnimationInstance->advance(2.0);
+       REQUIRE(continuePlaying == false);
+       REQUIRE(linearAnimationInstance->time() == 0.0);
+       REQUIRE(linearAnimationInstance->totalTime() == 2.0);
+       REQUIRE(linearAnimationInstance->didLoop() == true);
+
+       // set time to end..
+       // TODO: this also "resets" the total time. is that sensible?
+       linearAnimationInstance->time(5.0);
+       REQUIRE(linearAnimationInstance->totalTime() == 5.0);
+       // TODO: get rid if we stop killing m_Direction
+       linearAnimationInstance->direction(-1);
+
+       // play from end to beginning
+       continuePlaying = linearAnimationInstance->advance(2.0);
+       REQUIRE(continuePlaying == true);
+       REQUIRE(linearAnimationInstance->time() == 3.0);
+       REQUIRE(linearAnimationInstance->totalTime() == 7.0);
+       REQUIRE(linearAnimationInstance->didLoop() == false);
+
+       // get stuck at beginning
+       continuePlaying = linearAnimationInstance->advance(4.0);
+       REQUIRE(continuePlaying == false);
+       REQUIRE(linearAnimationInstance->time() == 0.0);
+       REQUIRE(linearAnimationInstance->totalTime() == 11.0);
+       REQUIRE(linearAnimationInstance->didLoop() == true);
+
+       delete linearAnimationInstance;
+       delete linearAnimation;
+}
+
+TEST_CASE("LinearAnimationInstance loop ->", "[animation]")
+{
+       rive::LinearAnimation* linearAnimation = new rive::LinearAnimation();
+       // duration in seconds is 5
+       linearAnimation->duration(10);
+       linearAnimation->fps(2);
+       linearAnimation->loopValue(static_cast<int>(rive::Loop::loop));
+
+       rive::LinearAnimationInstance* linearAnimationInstance =
+           new rive::LinearAnimationInstance(linearAnimation);
+
+       // play from beginning.
+       bool continuePlaying = linearAnimationInstance->advance(2.0);
+       REQUIRE(continuePlaying == true);
+       REQUIRE(linearAnimationInstance->time() == 2.0);
+       REQUIRE(linearAnimationInstance->totalTime() == 2.0);
+       REQUIRE(linearAnimationInstance->didLoop() == false);
+
+       // loop around a couple of times, back to the same spot
+       continuePlaying = linearAnimationInstance->advance(10.0);
+       REQUIRE(linearAnimationInstance->time() == 2.0);
+       REQUIRE(linearAnimationInstance->totalTime() == 12.0);
+       REQUIRE(linearAnimationInstance->didLoop() == true);
+       REQUIRE(continuePlaying == true);
+
+       delete linearAnimationInstance;
+       delete linearAnimation;
+}
+
+TEST_CASE("LinearAnimationInstance loop <-", "[animation]")
+{
+       rive::LinearAnimation* linearAnimation = new rive::LinearAnimation();
+       // duration in seconds is 5
+       linearAnimation->duration(10);
+       linearAnimation->fps(2);
+       linearAnimation->loopValue(static_cast<int>(rive::Loop::loop));
+
+       rive::LinearAnimationInstance* linearAnimationInstance =
+           new rive::LinearAnimationInstance(linearAnimation);
+       linearAnimationInstance->direction(-1);
+       REQUIRE(linearAnimationInstance->time() == 0.0);
+
+       // Advancing 2 seconds backwards will get the "animation" time to 3
+       bool continuePlaying = linearAnimationInstance->advance(2.0);
+       REQUIRE(continuePlaying == true);
+       REQUIRE(linearAnimationInstance->direction() == -1);
+       REQUIRE(linearAnimationInstance->time() == 3.0);
+       REQUIRE(linearAnimationInstance->totalTime() == 2.0);
+       REQUIRE(linearAnimationInstance->didLoop() == true);
+
+       // play without looping past the beginning.
+       continuePlaying = linearAnimationInstance->advance(2.0);
+       REQUIRE(continuePlaying == true);
+       REQUIRE(linearAnimationInstance->time() == 1.0);
+       REQUIRE(linearAnimationInstance->totalTime() == 4.0);
+       REQUIRE(linearAnimationInstance->didLoop() == false);
+
+       // loop past beginning again
+       continuePlaying = linearAnimationInstance->advance(4.0);
+       REQUIRE(continuePlaying == true);
+       REQUIRE(linearAnimationInstance->time() == 2.0);
+       REQUIRE(linearAnimationInstance->totalTime() == 8.0);
+       REQUIRE(linearAnimationInstance->didLoop() == true);
+
+       delete linearAnimationInstance;
+       delete linearAnimation;
+}
+
+TEST_CASE("LinearAnimationInstance loop <- work area", "[animation]")
+{
+       rive::LinearAnimation* linearAnimation = new rive::LinearAnimation();
+       // duration in seconds is 50
+       linearAnimation->workStart(4);
+       linearAnimation->enableWorkArea(true);
+       linearAnimation->workEnd(10);
+       linearAnimation->duration(100);
+       linearAnimation->fps(2);
+       linearAnimation->loopValue(static_cast<int>(rive::Loop::loop));
+
+       rive::LinearAnimationInstance* linearAnimationInstance =
+           new rive::LinearAnimationInstance(linearAnimation);
+       linearAnimationInstance->direction(-1);
+       REQUIRE(linearAnimationInstance->time() == 2.0);
+
+       // kick off, we're at the lower bound, will move to 5s.
+       bool continuePlaying = linearAnimationInstance->advance(0.0);
+
+       REQUIRE(continuePlaying == true);
+       REQUIRE(linearAnimationInstance->direction() == -1);
+       REQUIRE(linearAnimationInstance->time() == 5.0);
+       REQUIRE(linearAnimationInstance->totalTime() == 0.0);
+       REQUIRE(linearAnimationInstance->didLoop() == true);
+
+       // 2 more secs , 5s -> 3s
+       continuePlaying = linearAnimationInstance->advance(2.0);
+       REQUIRE(continuePlaying == true);
+       REQUIRE(linearAnimationInstance->direction() == -1);
+       REQUIRE(linearAnimationInstance->time() == 3.0);
+       REQUIRE(linearAnimationInstance->totalTime() == 2.0);
+       REQUIRE(linearAnimationInstance->didLoop() == false);
+
+       // 2 more secs , 3s -> 1s, thats before start, so loops to 4s
+       continuePlaying = linearAnimationInstance->advance(2.0);
+       REQUIRE(continuePlaying == true);
+       REQUIRE(linearAnimationInstance->direction() == -1);
+       REQUIRE(linearAnimationInstance->time() == 4.0);
+       REQUIRE(linearAnimationInstance->totalTime() == 4.0);
+       REQUIRE(linearAnimationInstance->didLoop() == true);
+
+       // another hit, 4->2s, thats at the start, loops to 5s
+       continuePlaying = linearAnimationInstance->advance(2.0);
+       REQUIRE(continuePlaying == true);
+       REQUIRE(linearAnimationInstance->time() == 5.0);
+       REQUIRE(linearAnimationInstance->totalTime() == 6.0);
+       REQUIRE(linearAnimationInstance->didLoop() == true);
+
+       delete linearAnimationInstance;
+       delete linearAnimation;
+}
+
+TEST_CASE("LinearAnimationInstance pingpong ->", "[animation]")
+{
+       rive::LinearAnimation* linearAnimation = new rive::LinearAnimation();
+       // duration in seconds is 5
+       linearAnimation->duration(10);
+       linearAnimation->fps(2);
+       linearAnimation->loopValue(static_cast<int>(rive::Loop::pingPong));
+
+       rive::LinearAnimationInstance* linearAnimationInstance =
+           new rive::LinearAnimationInstance(linearAnimation);
+
+       // play from beginning.
+       bool continuePlaying = linearAnimationInstance->advance(2.0);
+       REQUIRE(continuePlaying == true);
+       REQUIRE(linearAnimationInstance->time() == 2.0);
+       REQUIRE(linearAnimationInstance->totalTime() == 2.0);
+       REQUIRE(linearAnimationInstance->didLoop() == false);
+
+       // pingpong at the end and come back to 3.
+       continuePlaying = linearAnimationInstance->advance(5.0);
+       REQUIRE(linearAnimationInstance->time() == 3.0);
+       REQUIRE(linearAnimationInstance->totalTime() == 7.0);
+       REQUIRE(linearAnimationInstance->direction() == -1);
+       REQUIRE(linearAnimationInstance->didLoop() == true);
+       REQUIRE(continuePlaying == true);
+
+       delete linearAnimationInstance;
+       delete linearAnimation;
+}
+
+TEST_CASE("LinearAnimationInstance pingpong <-", "[animation]")
+{
+       rive::LinearAnimation* linearAnimation = new rive::LinearAnimation();
+       // duration in seconds is 5
+       linearAnimation->duration(10);
+       linearAnimation->fps(2);
+       linearAnimation->loopValue(static_cast<int>(rive::Loop::pingPong));
+
+       rive::LinearAnimationInstance* linearAnimationInstance =
+           new rive::LinearAnimationInstance(linearAnimation);
+       linearAnimationInstance->direction(-1);
+       REQUIRE(linearAnimationInstance->time() == 0.0);
+
+       // Advancing 2 seconds backwards, pongs immediately (and is acutally just
+       // like playing forwards)
+       bool continuePlaying = linearAnimationInstance->advance(2.0);
+       REQUIRE(continuePlaying == true);
+       REQUIRE(linearAnimationInstance->direction() == 1);
+       REQUIRE(linearAnimationInstance->time() == 2.0);
+       REQUIRE(linearAnimationInstance->totalTime() == 2.0);
+       REQUIRE(linearAnimationInstance->didLoop() == true);
+
+       // pingpong at the end
+       continuePlaying = linearAnimationInstance->advance(4.0);
+       REQUIRE(continuePlaying == true);
+       REQUIRE(linearAnimationInstance->direction() == -1);
+       REQUIRE(linearAnimationInstance->time() == 4.0);
+       REQUIRE(linearAnimationInstance->totalTime() == 6.0);
+       REQUIRE(linearAnimationInstance->didLoop() == true);
+
+       // just a normal advance, no loop
+       continuePlaying = linearAnimationInstance->advance(2.0);
+       REQUIRE(continuePlaying == true);
+       REQUIRE(linearAnimationInstance->direction() == -1);
+       REQUIRE(linearAnimationInstance->time() == 2.0);
+       REQUIRE(linearAnimationInstance->totalTime() == 8.0);
+       REQUIRE(linearAnimationInstance->didLoop() == false);
+
+       delete linearAnimationInstance;
+       delete linearAnimation;
+}
+
+TEST_CASE("LinearAnimationInstance override loop", "[animation]")
+{
+       rive::LinearAnimation* linearAnimation = new rive::LinearAnimation();
+       // duration in seconds is 5
+       linearAnimation->duration(10);
+       linearAnimation->fps(2);
+       linearAnimation->loopValue(static_cast<int>(rive::Loop::oneShot));
+
+       rive::LinearAnimationInstance* linearAnimationInstance =
+           new rive::LinearAnimationInstance(linearAnimation);
+
+       // Check the loop value is same as the animation's
+       REQUIRE(linearAnimationInstance->loopValue() == linearAnimation->loopValue());
+
+       // Override the loop type
+       linearAnimationInstance->loopValue(static_cast<int>(rive::Loop::pingPong));
+       REQUIRE(linearAnimationInstance->loopValue() != linearAnimation->loopValue());
+       REQUIRE(linearAnimationInstance->loopValue() == static_cast<int>(rive::Loop::pingPong));
+       REQUIRE(linearAnimationInstance->loop() == rive::Loop::pingPong);
+
+       delete linearAnimationInstance;
+       delete linearAnimation;
+}
\ No newline at end of file
diff --git a/submodule/test/stroke_test.cpp b/submodule/test/stroke_test.cpp
new file mode 100644 (file)
index 0000000..346d9a9
--- /dev/null
@@ -0,0 +1,40 @@
+#include "catch.hpp"
+#include "core/binary_reader.hpp"
+#include "file.hpp"
+#include "no_op_renderer.hpp"
+#include "node.hpp"
+#include "shapes/rectangle.hpp"
+#include "shapes/shape.hpp"
+#include "shapes/paint/stroke.hpp"
+#include "shapes/paint/solid_color.hpp"
+#include "shapes/paint/color.hpp"
+#include <cstdio>
+
+TEST_CASE("stroke can be looked up at runtime", "[file]")
+{
+       FILE* fp = fopen("../../test/assets/stroke_name_test.riv", "r");
+       REQUIRE(fp != nullptr);
+
+       fseek(fp, 0, SEEK_END);
+       auto length = ftell(fp);
+       fseek(fp, 0, SEEK_SET);
+       uint8_t* bytes = new uint8_t[length];
+       REQUIRE(fread(bytes, 1, length, fp) == length);
+       auto reader = rive::BinaryReader(bytes, length);
+       rive::File* file = nullptr;
+       auto result = rive::File::import(reader, &file);
+
+       REQUIRE(result == rive::ImportResult::success);
+       REQUIRE(file != nullptr);
+       REQUIRE(file->artboard() != nullptr);
+
+       auto artboard = file->artboard();
+       REQUIRE(artboard->find<rive::Stroke>("white_stroke") != nullptr);
+       auto stroke = artboard->find<rive::Stroke>("white_stroke");
+       REQUIRE(stroke->paint()->is<rive::SolidColor>());
+       stroke->paint()->as<rive::SolidColor>()->colorValue(
+           rive::colorARGB(255, 0, 255, 255));
+
+       delete file;
+       delete[] bytes;
+}
\ No newline at end of file