]> git.sesse.net Git - mlt/blobdiff - src/modules/rotoscoping/filter_rotoscoping.c
Add gpl flag file to rotoscoping filter.
[mlt] / src / modules / rotoscoping / filter_rotoscoping.c
index d7459e298701199a202c6229c6f9a3828cd06751..c054f6ae1d9c5707e1ef6f2cfabbb8254661669c 100644 (file)
@@ -27,8 +27,8 @@
 #include <math.h>
 #include <string.h>
 
-#define MAX( x, y ) x > y ? x : y
-#define MIN( x, y ) x < y ? x : y
+#define MAX( x, y ) ( ( x ) > ( y ) ? ( x ) : ( y ) )
+#define MIN( x, y ) ( ( x ) < ( y ) ? ( x ) : ( y ) )
 #define SQR( x ) ( x ) * ( x )
 
 /** x, y tuple with double precision */
@@ -45,8 +45,8 @@ typedef struct BPointF
     struct PointF h2;
 } BPointF;
 
-enum MODES { MODE_RGB, MODE_ALPHA, MODE_MATTE };
-const char *MODESTR[3] = { "rgb", "alpha", "matte" };
+enum MODES { MODE_RGB, MODE_ALPHA, MODE_LUMA };
+const char *MODESTR[3] = { "rgb", "alpha", "luma" };
 
 enum ALPHAOPERATIONS { ALPHA_CLEAR, ALPHA_MAX, ALPHA_MIN, ALPHA_ADD, ALPHA_SUB };
 const char *ALPHAOPERATIONSTR[5] = { "clear", "max", "min", "add", "sub" };
@@ -63,8 +63,16 @@ int stringValue( const char *string, const char **stringList, int max )
     return 0;
 }
 
+/** Sets "spline_is_dirty" to 1 if property "spline" was changed.
+ * We then know when to parse the json stored in "spline" */
+static void rotoPropertyChanged( mlt_service owner, mlt_filter this, char *name )
+{
+    if ( !strcmp( name, "spline" ) )
+        mlt_properties_set_int( MLT_FILTER_PROPERTIES( this ), "_spline_is_dirty", 1 );
+}
+
 /** Linear interp */
-void lerp( const PointF *a, const PointF *b, PointF *result, double t )
+inline void lerp( const PointF *a, const PointF *b, PointF *result, double t )
 {
     result->x = a->x + ( b->x - a->x ) * t;
     result->y = a->y + ( b->y - a->y ) * t;
@@ -72,10 +80,10 @@ void lerp( const PointF *a, const PointF *b, PointF *result, double t )
 
 /** Linear interp. with t = 0.5
  * Speed gain? */
-void lerpHalf( const PointF *a, const PointF *b, PointF *result )
+inline void lerpHalf( const PointF *a, const PointF *b, PointF *result )
 {
-    result->x = a->x + ( b->x - a->x ) * .5;
-    result->y = a->y + ( b->y - a->y ) * .5;
+    result->x = ( a->x + b->x ) * .5;
+    result->y = ( a->y + b->y ) * .5;
 }
 
 /** Helper for using qsort with an array of integers. */
@@ -110,14 +118,14 @@ int json2BCurves( cJSON *array, BPointF **points )
     int i = 0;
     do
     {
-        if ( cJSON_GetArraySize( child ) == 3 )
+        if ( child && cJSON_GetArraySize( child ) == 3 )
         {
             jsonGetPoint( child->child , &(*points)[i].h1 );
             jsonGetPoint( child->child->next, &(*points)[i].p );
             jsonGetPoint( child->child->next->next, &(*points)[i].h2 );
             i++;
         }
-    } while ( ( child = child->next ) );
+    } while ( child && ( child = child->next ) );
 
     if ( i < count )
         *points = mlt_pool_realloc( *points, i * sizeof( BPointF ) );
@@ -125,6 +133,92 @@ int json2BCurves( cJSON *array, BPointF **points )
     return i;
 }
 
+/** Blurs \param src horizontally. \See funtion blur. */
+void blurHorizontal( uint8_t *src, uint8_t *dst, int width, int height, int radius)
+{
+    int x, y, kx, yOff, total, amount, amountInit;
+    amountInit = radius * 2 + 1;
+    for (y = 0; y < height; ++y)
+    {
+        total = 0;
+        yOff = y * width;
+        // Process entire window for first pixel
+        int size = MIN(radius + 1, width);
+        for ( kx = 0; kx < size; ++kx )
+            total += src[yOff + kx];
+        dst[yOff] = total / ( radius + 1 );
+        // Subsequent pixels just update window total
+        for ( x = 1; x < width; ++x )
+        {
+            amount = amountInit;
+            // Subtract pixel leaving window
+            if ( x - radius - 1 >= 0 )
+                total -= src[yOff + x - radius - 1];
+            else
+                amount -= radius - x;
+            // Add pixel entering window
+            if ( x + radius < width )
+                total += src[yOff + x + radius];
+            else
+                amount -= radius - width + x;
+            dst[yOff + x] = total / amount;
+        }
+    }
+}
+
+/** Blurs \param src vertically. \See funtion blur. */
+void blurVertical( uint8_t *src, uint8_t *dst, int width, int height, int radius)
+{
+    int x, y, ky, total, amount, amountInit;
+    amountInit = radius * 2 + 1;
+    for (x = 0; x < width; ++x)
+    {
+        total = 0;
+        int size = MIN(radius + 1, height);
+        for ( ky = 0; ky < size; ++ky )
+            total += src[x + ky * width];
+        dst[x] = total / ( radius + 1 );
+        for ( y = 1; y < height; ++y )
+        {
+            amount = amountInit;
+            if ( y - radius - 1 >= 0 )
+                total -= src[( y - radius - 1 ) * width + x];
+            else
+                amount -= radius - y;
+            if ( y + radius < height )
+                total += src[( y + radius ) * width + x];
+            else
+                amount -= radius - height + y;
+            dst[y * width + x] = total / amount;
+        }
+    }
+}
+
+/**
+ * Blurs the \param map using a simple "average" blur.
+ * \param map Will be blured; 1bpp
+ * \param width x dimension of channel stored in \param map
+ * \param height y dimension of channel stored in \param map
+ * \param radius blur radius
+ * \param passes blur passes
+ */
+void blur( uint8_t *map, int width, int height, int radius, int passes )
+{
+    uint8_t *src = mlt_pool_alloc( width * height );
+    uint8_t *tmp = mlt_pool_alloc( width * height );
+
+    int i;
+    for ( i = 0; i < passes; ++i )
+    {
+        memcpy( src, map, width * height );
+        blurHorizontal( src, tmp, width, height, radius );
+        blurVertical( tmp, map, width, height, radius );
+    }
+
+    mlt_pool_release(src);
+    mlt_pool_release(tmp);
+}
+
 /**
  * Determines which points are located in the polygon and sets their value in \param map to \param value
  * \param vertices points defining the polygon
@@ -135,14 +229,16 @@ int json2BCurves( cJSON *array, BPointF **points )
  * \param map array of integers of the dimension width * height.
  *            The map entries belonging to the points in the polygon will be set to \param set * 255 the others to !set * 255.
  */
-void fillMap( PointF *vertices, int count, int width, int height, uint8_t set, uint8_t *map )
+void fillMap( PointF *vertices, int count, int width, int height, int invert, uint8_t *map )
 {
-    int nodes, nodeX[1024], pixelY, i, j, offset;
+    int nodes, nodeX[1024], pixelY, i, j, value;
+
+    value = !invert * 255;
+    memset( map, invert * 255, width * height );
 
     // Loop through the rows of the image
     for ( pixelY = 0; pixelY < height; pixelY++ )
     {
-        offset = width * pixelY;
         /*
          * Build a list of nodes.
          * nodes are located at the borders of the polygon
@@ -155,15 +251,8 @@ void fillMap( PointF *vertices, int count, int width, int height, uint8_t set, u
 
         qsort( nodeX, nodes, sizeof( int ), ncompare );
 
-        if ( nodes && nodeX[0] > 0 )
-            for ( j = 0; j < nodeX[0] - 1; j++ )
-                map[offset + j] = !set * 255;
-        else if ( !nodes )
-            for ( j = 0; j < width; j++ )
-                map[offset + j] = !set * 255;
-
         // Set map values for points between the node pairs to 1
-        for ( i = 0; i < nodes - 1; i++ )
+        for ( i = 0; i < nodes; i += 2 )
         {
             if ( nodeX[i] >= width )
                 break;
@@ -172,18 +261,9 @@ void fillMap( PointF *vertices, int count, int width, int height, uint8_t set, u
             {
                 nodeX[i] = MAX( 0, nodeX[i] );
                 nodeX[i+1] = MIN( nodeX[i+1], width );
-                if ( i % 2 )
-                    for ( j = nodeX[i]; j < nodeX[i+1]; j++ )
-                        map[offset + j] = !set * 255;
-                else
-                    for ( j = nodeX[i]; j <= nodeX[i+1]; j++ )
-                        map[offset + j] = set * 255;
+                memset( map + width * pixelY + nodeX[i], value, nodeX[i+1] - nodeX[i] );
             }
         }
-
-        if ( nodes && nodeX[nodes-1] < width )
-            for ( j = nodeX[nodes-1] + 1; j < width; j++ )
-                map[offset + j] = !set * 255;
     }
 }
 
@@ -192,50 +272,47 @@ void fillMap( PointF *vertices, int count, int width, int height, uint8_t set, u
  */
 void deCasteljau( BPointF *p1, BPointF *p2, BPointF *mid )
 {
-    struct PointF ab, bc, cd, abbc, bccd, final;
+    struct PointF ab, bc, cd;
 
     lerpHalf( &(p1->p), &(p1->h2), &ab );
     lerpHalf( &(p1->h2), &(p2->h1), &bc );
     lerpHalf( &(p2->h1), &(p2->p), &cd );
-    lerpHalf( &ab, &bc, &abbc );
-    lerpHalf( &bc, &cd, &bccd );
-    lerpHalf( &abbc, &bccd, &final );
+    lerpHalf( &ab, &bc, &(mid->h1) ); // mid->h1 = abbc
+    lerpHalf( &bc, &cd, &(mid->h2) ); // mid->h2 = bccd
+    lerpHalf( &(mid->h1), &(mid->h2), &(mid->p) );
 
     p1->h2 = ab;
     p2->h1 = cd;
-    mid->h1 = abbc;
-    mid->p = final;
-    mid->h2 = bccd;
 }
 
 /**
  * Calculates points for the cubic Bézier curve defined by \param p1 and \param p2.
- * Points are calculated until the squared distanced between neighbour points is smaller than \param errorSquared.
+ * Points are calculated until the squared distanced between neighbour points is smaller than 2.
  * \param points Pointer to list of points. Will be allocted and filled with calculated points.
  * \param count Number of calculated points in \param points
  * \param size Allocated size of \param points (in elements not in bytes)
  */
-void curvePoints( BPointF p1, BPointF p2, PointF **points, int *count, int *size, const double *errorSquared )
+void curvePoints( BPointF p1, BPointF p2, PointF **points, int *count, int *size )
 {
     double errorSqr = SQR( p1.p.x - p2.p.x ) + SQR( p1.p.y - p2.p.y );
 
     if ( *size + 1 >= *count )
     {
-        *size += (int)sqrt( errorSqr / *errorSquared );
+        *size += (int)sqrt( errorSqr / 2 );
         *points = mlt_pool_realloc( *points, *size * sizeof ( struct PointF ) );
     }
     
     (*points)[(*count)++] = p1.p;
 
-    if ( errorSqr <= *errorSquared )
+    if ( errorSqr <= 2 )
         return;
 
     BPointF mid;
     deCasteljau( &p1, &p2, &mid );
 
-    curvePoints( p1, mid, points, count, size, errorSquared );
+    curvePoints( p1, mid, points, count, size );
 
-    curvePoints( mid, p2, points, count, size, errorSquared );
+    curvePoints( mid, p2, points, count, size );
 
     (*points)[*(count)++] = p2.p;
 }
@@ -246,9 +323,12 @@ static int filter_get_image( mlt_frame this, uint8_t **image, mlt_image_format *
 {
     mlt_properties properties = MLT_FRAME_PROPERTIES( this );
 
+    int mode = mlt_properties_get_int( properties, "mode" );
+
     // Get the image
-    *format = mlt_image_rgb24a;
-    int error = mlt_frame_get_image( this, image, format, width, height, 1 );
+    if ( mode == MODE_RGB )
+        *format = mlt_image_rgb24;
+    int error = mlt_frame_get_image( this, image, format, width, height, writable );
 
     // Only process if we have no error and a valid colour space
     if ( !error )
@@ -270,82 +350,158 @@ static int filter_get_image( mlt_frame this, uint8_t **image, mlt_image_format *
             bpoints[i].h2.y *= *height;
         }
 
-        double errorSqr = (double)SQR( mlt_properties_get_int( properties, "precision" ) );
         count = 0;
         size = 1;
         points = mlt_pool_alloc( size * sizeof( struct PointF ) );
         for ( i = 0; i < bcount; i++ )
         {
             j = (i + 1) % bcount;
-            curvePoints( bpoints[i], bpoints[j], &points, &count, &size, &errorSqr );
+            curvePoints( bpoints[i], bpoints[j], &points, &count, &size );
         }
 
         if ( count )
         {
             uint8_t *map = mlt_pool_alloc( *width * *height );
-            uint8_t setPoint = !mlt_properties_get_int( properties, "invert" );
-            fillMap( points, count, *width, *height, setPoint, map );
+            int invert = mlt_properties_get_int( properties, "invert" );
+            fillMap( points, count, *width, *height, invert, map );
+
+            int feather = mlt_properties_get_int( properties, "feather" );
+            if ( feather )
+                blur( map, *width, *height, feather, mlt_properties_get_int( properties, "feather_passes" ) );
+
+            double bpp = 4;
+            if ( mode != MODE_ALPHA )
+            {
+                if ( *format == mlt_image_rgb24 )
+                    bpp = 3;
+                else if ( *format == mlt_image_yuv422 )
+                    bpp = 2;
+                else if ( *format == mlt_image_yuv420p )
+                    bpp = 3 / 2.;
+            }
+
+            i = 0;
+            length = *width * *height;
 
             uint8_t *p = *image;
-            uint8_t *q = *image + *width * *height * 4;
+            uint8_t *q = *image + (int)( length * bpp );
+
+            uint8_t *alpha;
 
-            switch ( mlt_properties_get_int( properties, "mode" ) )
+            switch ( mode )
             {
             case MODE_RGB:
+                // *format == mlt_image_rgb24
                 while ( p != q )
                 {
-                    if ( !map[(p - *image) / 4] )
+                    if ( !map[i++] )
                         p[0] = p[1] = p[2] = 0;
-                    p += 4;
+                    p += 3;
+                }
+                break;
+            case MODE_LUMA:
+                switch ( *format )
+                {
+                    case mlt_image_rgb24:
+                    case mlt_image_rgb24a:
+                    case mlt_image_opengl:
+                        while ( p != q )
+                        {
+                            p[0] = p[1] = p[2] = map[i++];
+                            p += (int)bpp;
+                        }
+                        break;
+                    case mlt_image_yuv422:
+                        while ( p != q )
+                        {
+                            p[0] = map[i++];
+                            p[1] = 128;
+                            p += 2;
+                        }
+                        break;
+                    case mlt_image_yuv420p:
+                        memcpy( p, map, length );
+                        memset( p + length, 128, length / 2 );
+                        break;
+                    default:
+                        break;
                 }
                 break;
             case MODE_ALPHA:
-                switch ( mlt_properties_get_int( properties, "alpha_operation" ) )
+                switch ( *format )
                 {
-                case ALPHA_CLEAR:
-                    while ( p != q )
-                    {
-                        p[3] = map[(p - *image) / 4];
-                        p += 4;
-                    }
-                    break;
-                case ALPHA_MAX:
-                    while ( p != q )
-                    {
-                        p[3] = MAX( map[(p - *image) / 4], p[3] );
-                        p += 4;
-                    }
-                    break;
-                case ALPHA_MIN:
-                    while ( p != q )
-                    {
-                        p[3] = MIN( map[(p - *image) / 4], p[3] );
-                        p += 4;
-                    }
-                    break;
-                case ALPHA_ADD:
-                    while ( p != q )
+                case mlt_image_rgb24a:
+                case mlt_image_opengl:
+                    switch ( mlt_properties_get_int( properties, "alpha_operation" ) )
                     {
-                        p[3] = MIN( p[3] + map[(p - *image) / 4], 255 );
-                        p += 4;
+                    case ALPHA_CLEAR:
+                        while ( p != q )
+                        {
+                            p[3] = map[i++];
+                            p += 4;
+                        }
+                        break;
+                    case ALPHA_MAX:
+                        while ( p != q )
+                        {
+                            p[3] = MAX( p[3], map[i] );
+                            p += 4;
+                            i++;
+                        }
+                        break;
+                    case ALPHA_MIN:
+                        while ( p != q )
+                        {
+                            p[3] = MIN( p[3], map[i] );
+                            p += 4;
+                            i++;
+                        }
+                        break;
+                    case ALPHA_ADD:
+                        while ( p != q )
+                        {
+                            p[3] = MIN( p[3] + map[i], 255 );
+                            p += 4;
+                            i++;
+                        }
+                        break;
+                    case ALPHA_SUB:
+                        while ( p != q )
+                        {
+                            p[3] = MAX( p[3] - map[i], 0 );
+                            p += 4;
+                            i++;
+                        }
+                        break;
                     }
                     break;
-                case ALPHA_SUB:
-                    while ( p != q )
+                default:
+                    alpha = mlt_frame_get_alpha_mask( this );
+                    switch ( mlt_properties_get_int( properties, "alpha_operation" ) )
                     {
-                        p[3] = MAX( p[3] - map[(p - *image) / 4], 0 );
-                        p += 4;
+                    case ALPHA_CLEAR:
+                        memcpy( alpha, map, length );
+                        break;
+                    case ALPHA_MAX:
+                        for ( ; i < length; i++, alpha++ )
+                            *alpha = MAX( map[i], *alpha );
+                        break;
+                    case ALPHA_MIN:
+                        for ( ; i < length; i++, alpha++ )
+                            *alpha = MIN( map[i], *alpha );
+                        break;
+                    case ALPHA_ADD:
+                        for ( ; i < length; i++, alpha++ )
+                            *alpha = MIN( *alpha + map[i], 255 );
+                        break;
+                    case ALPHA_SUB:
+                        for ( ; i < length; i++, alpha++ )
+                            *alpha = MAX( *alpha - map[i], 0 );
+                        break;
                     }
                     break;
                 }
                 break;
-            case MODE_MATTE:
-                while ( p != q )
-                {
-                    p[0] = p[1] = p[2] = map[(p - *image) / 4];
-                    p += 4;
-                }
-                break;
             }
 
             mlt_pool_release( map );
@@ -363,10 +519,18 @@ static mlt_frame filter_process( mlt_filter this, mlt_frame frame )
 {
     mlt_properties properties = MLT_FILTER_PROPERTIES( this );
     mlt_properties frameProperties = MLT_FRAME_PROPERTIES( frame );
-    char *spline = mlt_properties_get( properties, "spline" );
+    int splineIsDirty = mlt_properties_get_int( properties, "_spline_is_dirty" );
     char *modeStr = mlt_properties_get( properties, "mode" );
+    cJSON *root = mlt_properties_get_data( properties, "_spline_parsed", NULL );
 
-    cJSON *root = cJSON_Parse( spline );
+    if ( splineIsDirty || root == NULL )
+    {
+        // we need to (re-)parse
+        char *spline = mlt_properties_get( properties, "spline" );
+        root = cJSON_Parse( spline );
+        mlt_properties_set_data( properties, "_spline_parsed", root, 0, (mlt_destructor)cJSON_Delete, NULL );
+        mlt_properties_set_int( properties, "_spline_is_dirty", 0 );
+    }
 
     if ( root == NULL )
         return frame;
@@ -377,11 +541,11 @@ static mlt_frame filter_process( mlt_filter this, mlt_frame frame )
     if ( root->type == cJSON_Array )
     {
         /*
-         * constant (over time)
+         * constant
          */
         count = json2BCurves( root, &points );
     }
-    else
+    else if ( root->type == cJSON_Object )
     {
         /*
          * keyframes
@@ -391,45 +555,23 @@ static mlt_frame filter_process( mlt_filter this, mlt_frame frame )
         time = mlt_frame_get_position( frame );
 
         cJSON *keyframe = root->child;
-        cJSON *keyframeOld = NULL;
+        cJSON *keyframeOld = keyframe;
+
+        if ( !keyframe )
+            return frame;
+
         while ( atoi( keyframe->string ) < time && keyframe->next )
         {
             keyframeOld = keyframe;
             keyframe = keyframe->next;
         }
 
-        if ( keyframeOld == NULL ) {
-            // parameter has only 1 keyframe or we are before the 1. keyframe
-            keyframeOld = keyframe;
-        }
-
-        if ( !keyframe )
-        {
-            if ( keyframeOld )
-            {
-                // parameter malformed
-                keyframe = keyframeOld;
-            }
-            else if ( root->child )
-            {
-                // parameter malformed
-                keyframe = root->child;
-                keyframeOld = keyframe;
-            }
-            else
-            {
-                // json object has no children
-                cJSON_Delete( root );
-                return frame;
-            }
-        }
-
-        pos2 = atoi( keyframe->string );
         pos1 = atoi( keyframeOld->string );
+        pos2 = atoi( keyframe->string );
 
         if ( pos1 >= pos2 || time >= pos2 )
         {
-            // keyframes in wrong order or after last keyframe
+            // keyframes in wrong order or before first / after last keyframe
             count = json2BCurves( keyframe, &points );
         }
         else
@@ -439,47 +581,36 @@ static mlt_frame filter_process( mlt_filter this, mlt_frame frame )
              */
 
             BPointF *p1, *p2;
-            int c1, c2;
-            c1 = json2BCurves( keyframeOld, &p1 );
-            c2 = json2BCurves( keyframe, &p2 );
+            int c1 = json2BCurves( keyframeOld, &p1 );
+            int c2 = json2BCurves( keyframe, &p2 );
+
+            // range 0-1
+            double position = ( time - pos1 ) / (double)( pos2 - pos1 + 1 );
 
-            if ( c1 > c2 )
+            count = MIN( c1, c2 );  // additional points are ignored
+            points = mlt_pool_alloc( count * sizeof( BPointF ) );
+            for ( i = 0; i < count; i++ )
             {
-                // number of points decreasing from p1 to p2; we can't handle this yet
-                count = c2;
-                points = mlt_pool_alloc( count * sizeof( BPointF ) );
-                memcpy( points, p2, count * sizeof( BPointF ) );
-                mlt_pool_release( p1 );
-                mlt_pool_release( p2 );
+                lerp( &(p1[i].h1), &(p2[i].h1), &(points[i].h1), position );
+                lerp( &(p1[i].p), &(p2[i].p), &(points[i].p), position );
+                lerp( &(p1[i].h2), &(p2[i].h2), &(points[i].h2), position );
             }
-            else
-            {
-                // range 0-1
-                double position = ( time - pos1 ) / (double)( pos2 - pos1 + 1 );
-
-                count = c1;  // additional points in p2 are ignored
-                points = mlt_pool_alloc( count * sizeof( BPointF ) );
-                for ( i = 0; i < count; i++ )
-                {
-                    lerp( &(p1[i].h1), &(p2[i].h1), &(points[i].h1), position );
-                    lerp( &(p1[i].p), &(p2[i].p), &(points[i].p), position );
-                    lerp( &(p1[i].h2), &(p2[i].h2), &(points[i].h2), position );
-                }
 
-                mlt_pool_release( p1 );
-                mlt_pool_release( p2 );
-            }
+            mlt_pool_release( p1 );
+            mlt_pool_release( p2 );
         }
     }
-    cJSON_Delete( root );
-
-    int length = count * sizeof( BPointF );
+    else
+    {
+        return frame;
+    }
 
-    mlt_properties_set_data( frameProperties, "points", points, length, (mlt_destructor)mlt_pool_release, NULL );
+    mlt_properties_set_data( frameProperties, "points", points, count * sizeof( BPointF ), (mlt_destructor)mlt_pool_release, NULL );
     mlt_properties_set_int( frameProperties, "mode", stringValue( modeStr, MODESTR, 3 ) );
     mlt_properties_set_int( frameProperties, "alpha_operation", stringValue( mlt_properties_get( properties, "alpha_operation" ), ALPHAOPERATIONSTR, 5 ) );
     mlt_properties_set_int( frameProperties, "invert", mlt_properties_get_int( properties, "invert" ) );
-    mlt_properties_set_int( frameProperties, "precision", mlt_properties_get_int( properties, "precision" ) );
+    mlt_properties_set_int( frameProperties, "feather", mlt_properties_get_int( properties, "feather" ) );
+    mlt_properties_set_int( frameProperties, "feather_passes", mlt_properties_get_int( properties, "feather_passes" ) );
     mlt_frame_push_get_image( frame, filter_get_image );
 
     return frame;
@@ -493,12 +624,16 @@ mlt_filter filter_rotoscoping_init( mlt_profile profile, mlt_service_type type,
         if ( this != NULL )
         {
                 this->process = filter_process;
-                mlt_properties_set( MLT_FILTER_PROPERTIES( this ), "mode", "rgb" );
-                mlt_properties_set( MLT_FILTER_PROPERTIES( this ), "alpha_operation", "clear" );
-                mlt_properties_set_int( MLT_FILTER_PROPERTIES( this ), "invert", 0 );
-                mlt_properties_set_int( MLT_FILTER_PROPERTIES( this ), "precision", 1 );
+                mlt_properties properties = MLT_FILTER_PROPERTIES( this );
+                mlt_properties_set( properties, "mode", "alpha" );
+                mlt_properties_set( properties, "alpha_operation", "clear" );
+                mlt_properties_set_int( properties, "invert", 0 );
+                mlt_properties_set_int( properties, "feather", 0 );
+                mlt_properties_set_int( properties, "feather_passes", 1 );
                 if ( arg != NULL )
-                    mlt_properties_set( MLT_FILTER_PROPERTIES( this ), "spline", arg );
+                    mlt_properties_set( properties, "spline", arg );
+
+                mlt_events_listen( properties, this, "property-changed", (mlt_listener)rotoPropertyChanged );
         }
         return this;
 }