]> git.sesse.net Git - mlt/commitdiff
Add Catmull-Rom spline smooth animation interpolation.
authorDan Dennedy <dan@dennedy.org>
Sun, 19 May 2013 05:45:50 +0000 (22:45 -0700)
committerDan Dennedy <dan@dennedy.org>
Fri, 31 May 2013 23:58:12 +0000 (16:58 -0700)
src/framework/mlt_animation.c
src/framework/mlt_animation.h
src/framework/mlt_property.c
src/framework/mlt_property.h
src/framework/mlt_types.h
src/tests/test_properties/test_properties.cpp

index 449ee2fe0daceb8e4b46476af3dca43b57e4a9ea..b0791bd8dac0e7c8a23d69a7257c92b06b85b75e 100644 (file)
@@ -60,6 +60,8 @@ void mlt_animation_interpolate( mlt_animation self )
                {
                        if ( !current->item.is_key )
                        {
+                               double progress;
+                               mlt_property points[4];
                                animation_node prev = current->prev;
                                animation_node next = current->next;
 
@@ -68,18 +70,14 @@ void mlt_animation_interpolate( mlt_animation self )
 
                                if ( !prev )
                                        current->item.is_key = 1;
-                               if ( current->item.keyframe_type == mlt_keyframe_discrete )
-                               {
-                                       mlt_property_pass( current->item.property, prev->item.property );
-                               }
-                               else
-                               {
-                                       mlt_property_interpolate( current->item.property,
-                                               prev->item.property, next->item.property,
-                                               current->item.frame - prev->item.frame,
-                                               next->item.frame - prev->item.frame,
-                                               self->fps, self->locale );
-                               }
+                               points[0] = prev->prev? prev->prev->item.property : prev->item.property;
+                               points[1] = prev->item.property;
+                               points[2] = next->item.property;
+                               points[3] = next->next? next->next->item.property : next->item.property;
+                               progress = current->item.frame - prev->item.frame;
+                               progress /= next->item.frame - prev->item.frame;
+                               mlt_property_interpolate( current->item.property, points, progress,
+                                       self->fps, self->locale, current->item.keyframe_type );
                        }
 
                        // Move to the next item
@@ -229,8 +227,10 @@ int mlt_animation_parse_item( mlt_animation self, mlt_animation_item item, const
 
                        // The character preceeding the equal sign indicates interpolation method.
                        p = strchr( value, '=' ) - 1;
-                       if ( p[0] == '|' )
+                       if ( p[0] == '|' || p[0] == '!' )
                                item->keyframe_type = mlt_keyframe_discrete;
+                       else if ( p[0] == '~' )
+                               item->keyframe_type = mlt_keyframe_smooth;
                        else
                                item->keyframe_type = mlt_keyframe_linear;
                        value = &p[2];
@@ -240,10 +240,6 @@ int mlt_animation_parse_item( mlt_animation self, mlt_animation_item item, const
                if ( item->frame < 0 )
                        item->frame += self->length;
 
-               // Obtain the current value at this position - this allows new
-               // frames to be created which don't specify all values
-               mlt_animation_get_item( self, item, item->frame );
-
                // Set remainder of string as item value.
                mlt_property_set_string( item->property, value );
                item->is_key = 1;
@@ -292,17 +288,17 @@ int mlt_animation_get_item( mlt_animation self, mlt_animation_item item, int pos
                // Interpolation needed.
                else
                {
+                       double progress;
+                       mlt_property points[4];
+                       points[0] = node->prev? node->prev->item.property : node->item.property;
+                       points[1] = node->item.property;
+                       points[2] = node->next->item.property;
+                       points[3] = node->next->next? node->next->next->item.property : node->next->item.property;
+                       progress = position - node->item.frame;
+                       progress /= node->next->item.frame - node->item.frame;
+                       mlt_property_interpolate( item->property, points, progress,
+                               self->fps, self->locale, item->keyframe_type );
                        item->is_key = 0;
-                       if ( node->item.keyframe_type == mlt_keyframe_discrete )
-                       {
-                               mlt_property_pass( item->property, node->item.property );
-                       }
-                       else
-                       {
-                               mlt_property_interpolate( item->property, node->item.property, node->next->item.property,
-                                       position - node->item.frame, node->next->item.frame - node->item.frame,
-                                       self->fps, self->locale );
-                       }
                }
        }
        else
@@ -505,19 +501,20 @@ char *mlt_animation_serialize_cut( mlt_animation self, int in, int out )
                        if ( ret )
                        {
                                // Append keyframe time and keyframe/value delimiter (=).
-                               if ( item.frame - in != 0 )
-                               {
-                                       const char *s;
-                                       switch (item.keyframe_type) {
-                                       case mlt_keyframe_discrete:
-                                               s = "|";
-                                               break;
-                                       default:
-                                               s = "";
-                                               break;
-                                       }
-                                       sprintf( ret + used, "%d%s=", item.frame - in, s );
+                               const char *s;
+                               switch (item.keyframe_type) {
+                               case mlt_keyframe_discrete:
+                                       s = "|";
+                                       break;
+                               case mlt_keyframe_smooth:
+                                       s = "~";
+                                       break;
+                               default:
+                                       s = "";
+                                       break;
                                }
+                               sprintf( ret + used, "%d%s=", item.frame - in, s );
+
                                // Append item value.
                                if ( item.is_key )
                                        strcat( ret, mlt_property_get_string_l( item.property, self->locale ) );
index a0bb06292e1e625c0ba7ab6d695e1bea01e4c1e7..ef2deae18308299966f0516840d37f0a557f79d3 100644 (file)
 #include "mlt_types.h"
 #include "mlt_property.h"
 
-typedef enum {
-       mlt_keyframe_discrete,
-       mlt_keyframe_linear
-} mlt_keyframe_type;
-
 struct mlt_animation_item_s
 {
        int is_key; /**< = whether this is a key frame or an interpolated item */
index eab9f37e9c669a7b1ffd7b71fd89462dd5ab28ba..e09959f36a88139d1f20558032680c612263fa05 100644 (file)
@@ -942,26 +942,52 @@ static int is_property_numeric( mlt_property self, locale_t locale )
        return result;
 }
 
-static inline double linearstep( double start, double end, double position, int length )
+static inline double linear_interpolate( double y1, double y2, double t )
 {
-       double o = ( end - start ) / length;
-       return start + position * o;
+       return y1 + ( y2 - y1 ) * t;
 }
 
-int mlt_property_interpolate(mlt_property self, mlt_property previous, mlt_property next,
-       double position, int length, double fps, locale_t locale )
+//  For non-closed curves, you need to also supply the tangent vector at the first and last control point.
+// This is commonly done: T(P[0]) = P[1] - P[0] and T(P[n]) = P[n] - P[n-1].
+static inline double catmull_rom_interpolate( double y0, double y1, double y2, double y3, double t )
+{
+       double t2 = t * t;
+       double a0 = -0.5 * y0 + 1.5 * y1 - 1.5 * y2 + 0.5 * y3;
+       double a1 = y0 - 2.5 * y1 + 2 * y2 - 0.5 * y3;
+       double a2 = -0.5 * y0 + 0.5 * y2;
+       double a3 = y1;
+       return a0 * t * t2 + a1 * t2 + a2 * t + a3;
+}
+
+int mlt_property_interpolate( mlt_property self, mlt_property p[],
+       double progress, double fps, locale_t locale, mlt_keyframe_type interp )
 {
        int error = 0;
-       if ( fps > 0 && is_property_numeric( previous, locale ) && is_property_numeric( next, locale ) )
+       if ( interp != mlt_keyframe_discrete && fps > 0 &&
+               is_property_numeric( p[1], locale ) && is_property_numeric( p[2], locale ) )
        {
-               double start = previous? mlt_property_get_double( previous, fps, locale ) : 0;
-               double end = next? mlt_property_get_double( next, fps, locale ) : 0;
-               double value = next? linearstep( start, end, position, length ) : start;
+               double value;
+               if ( interp == mlt_keyframe_linear )
+               {
+                       double points[2];
+                       points[0] = p[1]? mlt_property_get_double( p[1], fps, locale ) : 0;
+                       points[1] = p[2]? mlt_property_get_double( p[2], fps, locale ) : 0;
+                       value = p[2]? linear_interpolate( points[0], points[1], progress ) : points[0];
+               }
+               else if ( interp == mlt_keyframe_smooth )
+               {
+                       double points[4];
+                       points[0] = p[0]? mlt_property_get_double( p[0], fps, locale ) : 0;
+                       points[1] = p[1]? mlt_property_get_double( p[1], fps, locale ) : 0;
+                       points[2] = p[2]? mlt_property_get_double( p[2], fps, locale ) : 0;
+                       points[3] = p[3]? mlt_property_get_double( p[3], fps, locale ) : 0;
+                       value = p[2]? catmull_rom_interpolate( points[0], points[1], points[2], points[3], progress ) : points[1];
+               }
                error = mlt_property_set_double( self, value );
        }
        else
        {
-               mlt_property_pass( self, previous );
+               mlt_property_pass( self, p[1] );
        }
        return error;
 }
index c87f657febcae5918ca0500f24e373f948095251..8df1d16530feadd42df420c38aa43a7d7cbb5273 100644 (file)
@@ -54,8 +54,8 @@ extern void *mlt_property_get_data( mlt_property self, int *length );
 extern void mlt_property_close( mlt_property self );
 extern void mlt_property_pass( mlt_property self, mlt_property that );
 extern char *mlt_property_get_time( mlt_property self, mlt_time_format, double fps, locale_t );
-extern int mlt_property_interpolate(mlt_property self, mlt_property previous, mlt_property next,
-                                     double position, int length, double fps, locale_t locale  );
+extern int mlt_property_interpolate(mlt_property self, mlt_property points[],
+                                     double progress, double fps, locale_t locale  , mlt_keyframe_type interp);
 extern double mlt_property_get_double_pos(mlt_property self, double fps, locale_t locale, int position, int length );
 extern int mlt_property_get_int_pos(mlt_property self, double fps, locale_t locale, int position, int length );
 
index b625f60073d52125bf0ea025f23f36efed5e14f7..5261051f21d0dac048a95354ef84a3e5963d398f 100644 (file)
@@ -75,6 +75,15 @@ typedef enum
 }
 mlt_time_format;
 
+/** Interpolation methods for animation keyframes */
+
+typedef enum {
+       mlt_keyframe_discrete, //< non-interpolated; value changes instantaneously at the key frame
+       mlt_keyframe_linear,   //< simple, constant pace from this key frame to the next
+       mlt_keyframe_smooth    //< eased pacing from this keyframe to the next using a Catmull-Rom spline
+}
+mlt_keyframe_type;
+
 /** The relative time qualifiers */
 
 typedef enum
index b0bd50a9005926becc5fcce9e5af09a75ad8b62b..1aac05cf9133bb476aaaee56f3eb3683f08ae561 100644 (file)
@@ -563,6 +563,54 @@ private Q_SLOTS:
 
         mlt_property_close(p);
     }
+
+    void SmoothIntAnimation()
+    {
+        locale_t locale;
+#if defined(__linux__) || defined(__DARWIN__)
+        locale = newlocale( LC_NUMERIC_MASK, "POSIX", NULL );
+#endif
+        double fps = 25.0;
+        mlt_animation a = mlt_animation_new();
+        struct mlt_animation_item_s item;
+
+        mlt_animation_parse(a, "0=80;10~=80; 20~=30; 30~=40; 40~=28; 50=90; 60=0; 70=60; 80=20", 100, fps, locale);
+        item.property = mlt_property_init();
+        char *a_serialized = mlt_animation_serialize(a);
+        QCOMPARE(a_serialized, "0=80;10~=80;20~=30;30~=40;40~=28;50=90;60=0;70=60;80=20");
+        if (a_serialized) free(a_serialized);
+
+        mlt_animation_get_item(a, &item, 10);
+        QCOMPARE(mlt_property_get_int(item.property, fps, locale), 80);
+        QCOMPARE(item.is_key, 1);
+
+        mlt_animation_get_item(a, &item, 50);
+        QCOMPARE(mlt_property_get_int(item.property, fps, locale), 90);
+        QCOMPARE(item.is_key, 1);
+
+        mlt_animation_get_item(a, &item, 55);
+        QCOMPARE(mlt_property_get_int(item.property, fps, locale), 45);
+        QCOMPARE(item.is_key, 0);
+
+        mlt_animation_get_item(a, &item, 60);
+        QCOMPARE(mlt_property_get_int(item.property, fps, locale), 0);
+        QCOMPARE(item.is_key, 1);
+
+        mlt_animation_get_item(a, &item, 75);
+        QCOMPARE(mlt_property_get_int(item.property, fps, locale), 40);
+        QCOMPARE(item.is_key, 0);
+
+        mlt_animation_get_item(a, &item, 100);
+        QCOMPARE(mlt_property_get_int(item.property, fps, locale), 20);
+        QCOMPARE(item.is_key, 0);
+
+        mlt_animation_get_item(a, &item, 110);
+        QCOMPARE(mlt_property_get_int(item.property, fps, locale), 20);
+        QCOMPARE(item.is_key, 0);
+
+        mlt_property_close(item.property);
+        mlt_animation_close(a);
+    }
 };
 
 QTEST_APPLESS_MAIN(TestProperties)