1919
2020#include "mlt_openfx.h"
2121
22+ #include <ctype.h>
2223#include <framework/mlt.h>
2324#include <ofxImageEffect.h>
2425#include <string.h>
@@ -28,6 +29,154 @@ extern mlt_properties mltofx_context;
2829extern mlt_properties mltofx_dl ;
2930
3031static const char OFX_IMAGE_EFFECT [] = "_ofx_image_effect" ;
32+ static const char NATRON_FORMAT_CHOICE_PARAM [] = "NatronParamFormatChoice" ;
33+ static const char NATRON_FORMAT_SIZE_PARAM [] = "NatronParamFormatSize" ;
34+
35+ typedef struct
36+ {
37+ int saw_choice ;
38+ int width ;
39+ int height ;
40+ } natron_format_compat_state ;
41+
42+ static int parse_choice_dimensions (const char * choice , int * out_w , int * out_h )
43+ {
44+ if (!choice || !out_w || !out_h )
45+ return 0 ;
46+
47+ for (const char * p = choice ; * p ; ++ p ) {
48+ if (!isdigit ((unsigned char ) * p ))
49+ continue ;
50+
51+ // Only start at the beginning of a numeric token, not the middle of one.
52+ if (p > choice && isdigit ((unsigned char ) p [-1 ]))
53+ continue ;
54+
55+ const char * q = p ;
56+ int w = 0 ;
57+ while (isdigit ((unsigned char ) * q )) {
58+ w = w * 10 + (* q - '0' );
59+ ++ q ;
60+ }
61+
62+ if (* q != 'x' && * q != 'X' )
63+ continue ;
64+ ++ q ;
65+ if (!isdigit ((unsigned char ) * q ))
66+ continue ;
67+
68+ int h = 0 ;
69+ while (isdigit ((unsigned char ) * q )) {
70+ h = h * 10 + (* q - '0' );
71+ ++ q ;
72+ }
73+
74+ // Height token must also end cleanly (space/end/punctuation), not in the middle of digits.
75+ if (isdigit ((unsigned char ) * q ))
76+ continue ;
77+
78+ if (w > 0 && h > 0 ) {
79+ * out_w = w ;
80+ * out_h = h ;
81+ return 1 ;
82+ }
83+ }
84+
85+ return 0 ;
86+ }
87+
88+ static void natron_compat_track_choice (natron_format_compat_state * state ,
89+ const char * param_name ,
90+ const char * value )
91+ {
92+ if (!state || !param_name || !value )
93+ return ;
94+ if (strcmp (param_name , NATRON_FORMAT_CHOICE_PARAM ) != 0 )
95+ return ;
96+
97+ state -> saw_choice = 1 ;
98+ parse_choice_dimensions (value , & state -> width , & state -> height );
99+ }
100+
101+ static void natron_compat_apply_size (mlt_properties image_effect_params ,
102+ const natron_format_compat_state * state )
103+ {
104+ if (!image_effect_params || !state )
105+ return ;
106+ if (!state -> saw_choice || state -> width <= 0 || state -> height <= 0 )
107+ return ;
108+
109+ mltofx_param_set_value (image_effect_params ,
110+ (char * ) NATRON_FORMAT_SIZE_PARAM ,
111+ mltofx_prop_int2d ,
112+ state -> width ,
113+ state -> height );
114+ }
115+
116+ static void reset_image_effect_action_props (mlt_properties image_effect , const char * name )
117+ {
118+ mlt_properties old_props = mlt_properties_get_properties (image_effect , name );
119+ mlt_properties_set_properties (image_effect , name , mlt_properties_new ());
120+ if (old_props )
121+ mlt_properties_close (old_props );
122+ }
123+
124+ static void init_image_effect_action_props (mlt_properties image_effect )
125+ {
126+ reset_image_effect_action_props (image_effect , "begin_sequence_props" );
127+ reset_image_effect_action_props (image_effect , "end_sequence_props" );
128+ reset_image_effect_action_props (image_effect , "get_rod_in_args" );
129+ reset_image_effect_action_props (image_effect , "get_rod_out_args" );
130+ reset_image_effect_action_props (image_effect , "get_roi_in_args" );
131+ reset_image_effect_action_props (image_effect , "get_roi_out_args" );
132+ reset_image_effect_action_props (image_effect , "get_clippref_args" );
133+ reset_image_effect_action_props (image_effect , "render_in_args" );
134+ }
135+
136+ static mlt_properties create_image_effect_instance (OfxPlugin * plugin ,
137+ mlt_properties filter_properties ,
138+ int sync_param_backlink )
139+ {
140+ mlt_properties params = mlt_properties_new ();
141+ if (!params )
142+ return NULL ;
143+
144+ mlt_properties image_effect = mltofx_fetch_params (plugin , params , NULL );
145+ if (!image_effect ) {
146+ mlt_properties_close (params );
147+ return NULL ;
148+ }
149+
150+ if (sync_param_backlink ) {
151+ mlt_properties image_effect_params = mlt_properties_get_properties (image_effect , "params" );
152+ int param_count = mlt_properties_count (image_effect_params );
153+ for (int i = 0 ; i < param_count ; ++ i ) {
154+ char * param_name = mlt_properties_get_name (image_effect_params , i );
155+ mlt_properties param = mlt_properties_get_properties (image_effect_params , param_name );
156+ if (!param )
157+ continue ;
158+ mlt_properties param_props = mlt_properties_get_properties (param , "p" );
159+ if (!param_props )
160+ continue ;
161+ mlt_properties_set_data (param_props ,
162+ "_filter_properties" ,
163+ filter_properties ,
164+ 0 ,
165+ NULL ,
166+ NULL );
167+ }
168+ }
169+
170+ mltofx_create_instance (plugin , image_effect );
171+ init_image_effect_action_props (image_effect );
172+ mlt_properties_set_data (image_effect ,
173+ "_runtime_params" ,
174+ params ,
175+ 0 ,
176+ (mlt_destructor ) mlt_properties_close ,
177+ NULL );
178+ return image_effect ;
179+ }
31180
32181static mlt_image_format select_image_format (mlt_image_format incoming ,
33182 mltofx_depths_mask plugin_support_depths ,
@@ -60,6 +209,8 @@ static void update_plugin_params(mlt_properties properties,
60209 mlt_position position ,
61210 mlt_position length )
62211{
212+ natron_format_compat_state natron_compat = {0 };
213+
63214 int params_count = mlt_properties_count (params );
64215 for (int i = 0 ; i < params_count ; ++ i ) {
65216 char * param_key = mlt_properties_get_name (params , i );
@@ -69,6 +220,8 @@ static void update_plugin_params(mlt_properties properties,
69220 char * param_name = mlt_properties_get (param , "identifier" );
70221 if (!param_name )
71222 continue ;
223+ if (!mlt_properties_exists (properties , param_name ))
224+ continue ;
72225 char * type = mlt_properties_get (param , "type" );
73226 char * widget = mlt_properties_get (param , "widget" );
74227 if (!type )
@@ -77,6 +230,12 @@ static void update_plugin_params(mlt_properties properties,
77230 && strcmp (type , "float" ) == 0 ) {
78231 mlt_rect value = mlt_properties_anim_get_rect (properties , param_name , position , length );
79232 mltofx_param_set_value (image_effect_params , param_name , mltofx_prop_double2d , value );
233+ } else if (widget && (strcmp (widget , "point" ) == 0 || strcmp (widget , "size" ) == 0 )
234+ && strcmp (type , "integer" ) == 0 ) {
235+ mlt_rect value = mlt_properties_anim_get_rect (properties , param_name , position , length );
236+ int x = (int ) value .x ;
237+ int y = (int ) value .y ;
238+ mltofx_param_set_value (image_effect_params , param_name , mltofx_prop_int2d , x , y );
80239 } else if (strcmp (type , "float" ) == 0 ) {
81240 double value = mlt_properties_anim_get_double (properties , param_name , position , length );
82241 mltofx_param_set_value (image_effect_params , param_name , mltofx_prop_double , value );
@@ -85,14 +244,18 @@ static void update_plugin_params(mlt_properties properties,
85244 mltofx_param_set_value (image_effect_params , param_name , mltofx_prop_int , value );
86245 } else if (strcmp (type , "string" ) == 0 ) {
87246 char * value = mlt_properties_anim_get (properties , param_name , position , length );
88- if (value )
247+ if (value ) {
248+ natron_compat_track_choice (& natron_compat , param_name , value );
89249 mltofx_param_set_value (image_effect_params , param_name , mltofx_prop_string , value );
250+ }
90251 } else if (strcmp (type , "color" ) == 0 ) {
91252 mlt_color value
92253 = mlt_properties_anim_get_color (properties , param_name , position , length );
93254 mltofx_param_set_value (image_effect_params , param_name , mltofx_prop_color , value );
94255 }
95256 }
257+
258+ natron_compat_apply_size (image_effect_params , & natron_compat );
96259}
97260
98261static int filter_get_image (mlt_frame frame ,
@@ -105,9 +268,18 @@ static int filter_get_image(mlt_frame frame,
105268 mlt_filter filter = (mlt_filter ) mlt_frame_pop_service (frame );
106269 mlt_properties properties = MLT_FILTER_PROPERTIES (filter );
107270 OfxPlugin * plugin = mlt_properties_get_data (properties , "ofx_plugin" , NULL );
108- mlt_properties image_effect = mlt_properties_get_properties (properties , OFX_IMAGE_EFFECT );
271+ mlt_properties base_image_effect = mlt_properties_get_properties (properties , OFX_IMAGE_EFFECT );
272+ int allow_frame_threading = mlt_properties_get_int (properties , "_allow_frame_threading" );
273+ mlt_properties image_effect = base_image_effect ;
274+ if (allow_frame_threading ) {
275+ image_effect = create_image_effect_instance (plugin , properties , 0 );
276+ if (!image_effect )
277+ image_effect = base_image_effect ;
278+ }
279+
109280 mlt_properties params = mlt_properties_get_properties (image_effect , "mltofx_params" );
110281 mlt_properties image_effect_params = mlt_properties_get_properties (image_effect , "params" );
282+ int lock_service = (image_effect == base_image_effect );
111283
112284 mlt_image_format requested_format = * format ;
113285 mltofx_depths_mask plugin_support_depths = mltofx_plugin_supported_depths (image_effect );
@@ -133,6 +305,10 @@ static int filter_get_image(mlt_frame frame,
133305 if (pixel_aspect_ratio <= 0.0 )
134306 pixel_aspect_ratio = 1.0 ;
135307
308+ mlt_profile profile = mlt_service_profile (MLT_FILTER_SERVICE (filter ));
309+ double render_scale_x = mlt_profile_scale_width (profile , * width );
310+ double render_scale_y = mlt_profile_scale_height (profile , * height );
311+
136312 mlt_position position = mlt_filter_get_position (filter , frame );
137313 mlt_position length = mlt_filter_get_length2 (filter , frame );
138314 double ofx_time = (double ) position ;
@@ -195,7 +371,11 @@ static int filter_get_image(mlt_frame frame,
195371 : use_float ? (uint8_t * ) float_out
196372 : * image ;
197373
198- mlt_service_lock (MLT_FILTER_SERVICE (filter ));
374+ if (lock_service )
375+ mlt_service_lock (MLT_FILTER_SERVICE (filter ));
376+
377+ // In serialized mode, image_effect is shared across frames; update scale under lock.
378+ mltofx_set_render_scale (image_effect , render_scale_x , render_scale_y );
199379
200380 update_plugin_params (properties , image_effect_params , params , position , length );
201381
@@ -219,8 +399,10 @@ static int filter_get_image(mlt_frame frame,
219399 pixel_aspect_ratio ,
220400 ofx_depth );
221401
222- // OFX pre-render action order: GetClipPreferences → GetRegionsOfInterest → BeginSequenceRender
402+ // OFX pre-render action order:
403+ // GetClipPreferences -> GetRegionOfDefinition -> GetRegionsOfInterest -> BeginSequenceRender
223404 mltofx_get_clip_preferences (plugin , image_effect );
405+ mltofx_get_region_of_definition (plugin , image_effect , ofx_time );
224406 mltofx_get_regions_of_interest (plugin , image_effect , ofx_time , (double ) * width , (double ) * height );
225407 mltofx_begin_sequence_render (plugin , image_effect , ofx_time );
226408
@@ -245,7 +427,13 @@ static int filter_get_image(mlt_frame frame,
245427 mltofx_action_render (plugin , image_effect , ofx_time , * width , * height );
246428 mltofx_end_sequence_render (plugin , image_effect , ofx_time );
247429
248- mlt_service_unlock (MLT_FILTER_SERVICE (filter ));
430+ if (lock_service )
431+ mlt_service_unlock (MLT_FILTER_SERVICE (filter ));
432+
433+ if (image_effect != base_image_effect ) {
434+ mltofx_destroy_instance (plugin , image_effect );
435+ mlt_properties_close (image_effect );
436+ }
249437
250438 // Convert output back to rgba64 in-place — outside the lock, per-frame only.
251439 if (use_half ) {
@@ -319,30 +507,24 @@ mlt_filter filter_openfx_init(mlt_profile profile, mlt_service_type type, const
319507 mlt_filter_close (filter );
320508 return NULL ;
321509 }
322- mlt_properties params = mlt_properties_new ();
323- mlt_properties image_effect = mltofx_fetch_params (pt , params , NULL );
324- mltofx_create_instance (pt , image_effect );
325-
326- mlt_properties_set_properties (image_effect , "begin_sequence_props" , mlt_properties_new ());
327- mlt_properties_close (mlt_properties_get_properties (image_effect , "get_rod_in_args" ));
328- mlt_properties_set_properties (image_effect , "end_sequence_props" , mlt_properties_new ());
329- mlt_properties_close (mlt_properties_get_properties (image_effect , "get_rod_out_args" ));
330- mlt_properties_set_properties (image_effect , "get_rod_in_args" , mlt_properties_new ());
331- mlt_properties_close (mlt_properties_get_properties (image_effect , "get_roi_in_args" ));
332- mlt_properties_set_properties (image_effect , "get_rod_out_args" , mlt_properties_new ());
333- mlt_properties_close (mlt_properties_get_properties (image_effect , "get_roi_out_args" ));
334- mlt_properties_set_properties (image_effect , "get_roi_in_args" , mlt_properties_new ());
335- mlt_properties_close (mlt_properties_get_properties (image_effect , "get_roi_in_args" ));
336- mlt_properties_set_properties (image_effect , "get_roi_out_args" , mlt_properties_new ());
337- mlt_properties_close (mlt_properties_get_properties (image_effect , "get_roi_out_args" ));
338- mlt_properties_set_properties (image_effect , "get_clippref_args" , mlt_properties_new ());
339- mlt_properties_close (mlt_properties_get_properties (image_effect , "get_clippref_args" ));
340- mlt_properties_set_properties (image_effect , "render_in_args" , mlt_properties_new ());
341- mlt_properties_close (mlt_properties_get_properties (image_effect , "render_in_args" ));
510+ mlt_properties image_effect = create_image_effect_instance (pt , properties , 1 );
511+ if (!image_effect ) {
512+ mlt_log_error (filter , "Failed to create OpenFX image effect instance: %s\n" , id );
513+ mlt_filter_close (filter );
514+ return NULL ;
515+ }
516+
517+ mlt_properties_set_int (properties ,
518+ "_allow_frame_threading" ,
519+ mltofx_allows_frame_threading (image_effect ));
520+ mlt_log_info (filter ,
521+ "OpenFX threading mode for %s: %s\n" ,
522+ id ,
523+ mlt_properties_get_int (properties , "_allow_frame_threading" ) ? "frame-threaded"
524+ : "serialized" );
342525 mlt_properties_set_data (properties , "ofx_plugin" , pt , 0 , NULL , NULL );
343526 mlt_properties_set_properties (properties , OFX_IMAGE_EFFECT , image_effect );
344527 mlt_properties_close (image_effect );
345- mlt_properties_close (params );
346528 filter -> process = filter_process ;
347529 filter -> close = filter_close ;
348530 return filter ;
0 commit comments