#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 */
struct PointF h2;
} BPointF;
-enum MODES { MODE_RGB, MODE_ALPHA, MODE_MASK };
-const char *MODESTR[3] = { "rgb", "alpha", "mask" };
+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" };
/** Returns the index of \param string in \param stringList.
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;
/** 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. */
{
int count = cJSON_GetArraySize( array );
cJSON *child = array->child;
- *points = malloc( count * sizeof( BPointF ) );
+ *points = mlt_pool_alloc( count * sizeof( BPointF ) );
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 = realloc( *points, i * sizeof( BPointF ) );
+ *points = mlt_pool_realloc( *points, i * sizeof( BPointF ) );
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
* \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
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;
{
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;
}
}
*/
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 );
- *points = realloc( *points, *size * sizeof ( struct PointF ) );
+ *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;
}
{
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 )
bpoints[i].h2.y *= *height;
}
- double errorSqr = (double)SQR( mlt_properties_get_int( properties, "precision" ) );
count = 0;
size = 1;
- points = malloc( size * sizeof( struct PointF ) );
+ 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 = calloc( *width * *height, sizeof( uint8_t ) );
- uint8_t setPoint = !mlt_properties_get_int( properties, "invert" );
- fillMap( points, count, *width, *height, setPoint, map );
+ uint8_t *map = mlt_pool_alloc( *width * *height );
+ 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 );
- switch ( mlt_properties_get_int( properties, "mode" ) )
+ uint8_t *alpha;
+
+ 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_ALPHA:
- while ( p != q )
+ case MODE_LUMA:
+ switch ( *format )
{
- p[3] = map[(p - *image) / 4];
- p += 4;
+ 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_MASK:
- while ( p != q )
+ case MODE_ALPHA:
+ switch ( *format )
{
- p[0] = p[1] = p[2] = map[(p - *image) / 4];
- p += 4;
+ case mlt_image_rgb24a:
+ case mlt_image_opengl:
+ switch ( mlt_properties_get_int( properties, "alpha_operation" ) )
+ {
+ 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;
+ default:
+ alpha = mlt_frame_get_alpha_mask( this );
+ switch ( mlt_properties_get_int( properties, "alpha_operation" ) )
+ {
+ 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;
}
- free( map );
+ mlt_pool_release( map );
}
- free( points );
+ mlt_pool_release( points );
}
return error;
{
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;
if ( root->type == cJSON_Array )
{
/*
- * constant (over time)
+ * constant
*/
count = json2BCurves( root, &points );
}
- else
+ else if ( root->type == cJSON_Object )
{
/*
* keyframes
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
*/
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 = malloc( count * sizeof( BPointF ) );
- memcpy( points, p2, count * sizeof( BPointF ) );
- free( p1 );
- free( 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 = malloc( 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 );
- }
-
- free( p1 );
- free( 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, free, 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;
if ( this != NULL )
{
this->process = filter_process;
- mlt_properties_set( MLT_FILTER_PROPERTIES( this ), "mode", "rgb" );
- 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;
}