From 4baa1162a515031156f1ff05fed2329cc2b41fc9 Mon Sep 17 00:00:00 2001 From: Dan Dennedy Date: Sat, 18 May 2013 22:45:50 -0700 Subject: [PATCH] Add Catmull-Rom spline smooth animation interpolation. --- src/framework/mlt_animation.c | 75 +++++++++---------- src/framework/mlt_animation.h | 5 -- src/framework/mlt_property.c | 46 +++++++++--- src/framework/mlt_property.h | 4 +- src/framework/mlt_types.h | 9 +++ src/tests/test_properties/test_properties.cpp | 48 ++++++++++++ 6 files changed, 131 insertions(+), 56 deletions(-) diff --git a/src/framework/mlt_animation.c b/src/framework/mlt_animation.c index 449ee2fe..b0791bd8 100644 --- a/src/framework/mlt_animation.c +++ b/src/framework/mlt_animation.c @@ -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 ) ); diff --git a/src/framework/mlt_animation.h b/src/framework/mlt_animation.h index a0bb0629..ef2deae1 100644 --- a/src/framework/mlt_animation.h +++ b/src/framework/mlt_animation.h @@ -25,11 +25,6 @@ #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 */ diff --git a/src/framework/mlt_property.c b/src/framework/mlt_property.c index eab9f37e..e09959f3 100644 --- a/src/framework/mlt_property.c +++ b/src/framework/mlt_property.c @@ -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; } diff --git a/src/framework/mlt_property.h b/src/framework/mlt_property.h index c87f657f..8df1d165 100644 --- a/src/framework/mlt_property.h +++ b/src/framework/mlt_property.h @@ -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 ); diff --git a/src/framework/mlt_types.h b/src/framework/mlt_types.h index b625f600..5261051f 100644 --- a/src/framework/mlt_types.h +++ b/src/framework/mlt_types.h @@ -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 diff --git a/src/tests/test_properties/test_properties.cpp b/src/tests/test_properties/test_properties.cpp index b0bd50a9..1aac05cf 100644 --- a/src/tests/test_properties/test_properties.cpp +++ b/src/tests/test_properties/test_properties.cpp @@ -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) -- 2.39.2