List Importer and FPS interpolation

Found that ListImporter incorrectly interprets FPS in lst files.

For example, let’s take a list of images where frame number is displayed on each image. According to http://synfig.org/wiki/ListImporter, we can import this image sequence in synfig by listing them in text file with .lst extension. Also we can specify fps in first line. Let’s do it:

echo “FPS 12” > list.lst
ls -1 *.jpg >> list.lst

As you can see, the FPS I specified is 12. If we import it into synfig document with default settings (fps=24) we should get this mapping:

Synfig animation frame: 0   1   2   3   4   5   6   7   8   9   10  11  12  13  14  15  16 ...
Image sequence frame:   0   0   1   1   2   2   3   3   4   4   5   5   6   6   7   7   8  ...

I.e. each frame form image sequence should display for 2 frames in synfig animation. But instead of this we got:

Synfig animation frame: 0   1   2   3   4   5   6   7   8   9   10  11  12  13  14  15  16 ...
Image sequence frame:   0   0   1   2   2   3   3   4   4   5   5   5   6   6   7   8   8  ...

You can check it by yourself – example is here. And this is a significant flaw, because it breaks animation timing.

I started to dig into synfig code.

First of all it was required to find where list importer code resides in. I knew that list importer works with *.lst files. So I fired up “git grep .lst”. Among few lines this one catched my attention:

synfig-core/src/synfig/main.cpp:        Importer::book()[String("lst")]=ListImporter::create;

It gives me a name of list importer class. After “git grep ListImporter” I’ve got

synfig-core/src/synfig/listimporter.cpp:ListImporter::ListImporter(const String &filename)
synfig-core/src/synfig/listimporter.cpp:ListImporter::create(const char *filename)
synfig-core/src/synfig/listimporter.cpp:        return new ListImporter(filename);
synfig-core/src/synfig/listimporter.cpp:ListImporter::~ListImporter()
synfig-core/src/synfig/listimporter.cpp:ListImporter::get_frame(Surface &surface,Time time, ProgressCallbac
synfig-core/src/synfig/listimporter.cpp:ListImporter::is_animated()
synfig-core/src/synfig/listimporter.h:/*!       \class ListImporter
synfig-core/src/synfig/listimporter.h:class ListImporter : public Importer
synfig-core/src/synfig/listimporter.h:  ListImporter(const String &filename);
synfig-core/src/synfig/listimporter.h:  virtual ~ListImporter();
synfig-core/src/synfig/main.cpp:        Importer::book()[String("lst")]=ListImporter::create;

Now it’s obvious that list importer code resides in synfig-core/src/synfig/listimporter.cpp.

Looking through that file I found:

//synfig::warning("FPS=%f",fps);

That’s how I figured out how to throw out debug messages into console. (I don’t know how to use debuggers. Yet.)

I was suspect that FPS param is determined incorrectly. But this code part looks ok,

Then I found this:

bool
ListImporter::get_frame(Surface &surface,Time time, ProgressCallback *cb)
{
int frame=round_to_int(time*fps);

Where “time” is float value measured in seconds.

That was the maths how the current frame calculated. After a thinking for a few hours over it, I come to conclusion that the problem with the usage of round function. It makes some frames appear earlier than they should. I.e. time for frame 5 at fps 24 is 5/24.

round_to_int((5/24) * 12) = round_to_int(5/2) = round_to_int(2.5) = 3

That’s it, 5 maps to 3. While it should map to 2. My first solution to fix was replace round function to floor function. But what’s its name? possibly it’s named similar to round_to_int:

$ git grep _to_int
ETL/ETL/_misc.h:inline int round_to_int(const float x) {
ETL/ETL/_misc.h:inline int round_to_int(const double x) {
ETL/ETL/_misc.h:inline int ceil_to_int(const float x) { return static_cast(ceil(x)); }
ETL/ETL/_misc.h:inline int ceil_to_int(const double x) { return static_cast(ceil(x)); }
ETL/ETL/_misc.h:inline int floor_to_int(const float x) { return static_cast(x); }
ETL/ETL/_misc.h:inline int floor_to_int(const double x) { return static_cast(x); }
...

That’s it – floor_to_int. Unfortunately, that not worked. I’ve got opposite result:

Synfig animation frame: 0   1   2   3   4   5   6   7   8   9   10  11  12  13  14  15  16 ...
Image sequence frame:   0   0   0   1   1   2   3   3   3   4   5   5   6   6   6   7   7  ...

Looks like we fall into precision loss during calculation. I.e. for for frame 2 at fps 24 we got

floor_to_int((2/24) * 12) = floor_to_int(0,083333333 * 12) = floor_to_int(0,999999996) = 0

Then I made a second attempt. We should avoid the loss if we will get convert time to frame number and only after that do the mapping for fps.

Looking into synfig-core/src/synfig/time.cpp I found that time converted to frame the same way:

	if (format <= FORMAT_FRAMES)
	{
		if (fps && fps>0)
			return strprintf("%df", round_to_int(time * fps));
		else
			return strprintf("%ds", round_to_int(time * 1));
	}

Then I did following changes to listimporter.cpp:

diff --git a/synfig-core/src/synfig/listimporter.cpp b/synfig-core/src/synfig/listimporter.cpp
index 02680f0..68542ae 100644
--- a/synfig-core/src/synfig/listimporter.cpp
+++ b/synfig-core/src/synfig/listimporter.cpp
@@ -152,7 +152,9 @@ ListImporter::~ListImporter()
 bool
 ListImporter::get_frame(Surface &surface,Time time, ProgressCallback *cb)
 {
-	int frame=round_to_int(time*fps);
+	float document_fps=24.0;
+	int document_frame=round_to_int(time*document_fps);
+	int frame=floor_to_int(document_frame*fps/document_fps);

 	if(!filename_list.size())
 	{

And everything worked like a charm. It also works fine for image sequences with FPS set to 2, 3, 4 and 6. I haven’t tested for exotic cases like 15 fps, because it’s hard to determine

Notice, I don’t know how to retrieve document FPS and hardcoded the value for now. But that should be enough to send this report and patch draft to synfig bugtracker.

Advertisements

1 Comment »

  1. […] the loop glitches at the end. Again, it breaks animation timing. Look similar like FPS interpolation in List Importer. And looks like we have same precision loss during […]

RSS feed for comments on this post · TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: