├── .clippy.toml ├── .gitignore ├── .gitmodules ├── .project ├── .rustfmt.toml ├── Cargo.toml ├── README.md ├── build_plugin.sh ├── examples └── basic │ ├── Cargo.toml │ ├── src │ ├── basic.rs │ ├── lib.rs │ └── tests.rs │ ├── testbed.ntp │ └── testbed2.ntp ├── ofx-sys ├── .project ├── Cargo.toml ├── build │ ├── macro_constants.h │ ├── main.rs │ └── wrapper.h └── src │ └── lib.rs ├── ofx ├── .project ├── Cargo.toml └── src │ ├── action.rs │ ├── enums.rs │ ├── handle.rs │ ├── image.rs │ ├── lib.rs │ ├── plugin.rs │ ├── property.rs │ ├── registry.rs │ ├── result.rs │ ├── suites.rs │ ├── types.rs │ └── util.rs ├── ofx_log4rs.yaml ├── test.py └── test_in_natron.sh /.clippy.toml: -------------------------------------------------------------------------------- 1 | cyclomatic-complexity-threshold=40 2 | too-many-arguments-threshold=10 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | *~ 3 | Cargo.lock 4 | *.ofx 5 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "native/openfx"] 2 | path = ofx-sys/native/openfx 3 | url = https://github.com/NatronGitHub/openfx 4 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | ofx 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | hard_tabs=true 2 | reorder_imports=true 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | 3 | members = [ 4 | "ofx-sys", 5 | "ofx", 6 | "examples/basic", 7 | ] -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## OpenFX bindings 2 | 3 | Replaces the client Support C++ layer, using the raw ofx* ABI via bindgen only. 4 | 5 | 01/2019 status: Not ready for production use but basic functionality implemented, available via `crates.io`. 6 | 7 | ### Design goals 8 | 9 | - data and type safe 10 | - ergonomic API for Image Effect plugin writers 11 | - one dll/so crate can contain multiple plugins 12 | - each plugin in its own Rust module 13 | - centralised plugin registry per crate 14 | 15 | ### Cargo.toml 16 | 17 | ``` 18 | [dependencies] 19 | ofx = "0.1" 20 | ``` 21 | 22 | ### Example code 23 | 24 | 25 | The example at https://github.com/itadinanta/ofx-rs/examples/basic is an almost line-by-line translation of the `basic.cpp` (https://github.com/NatronGitHub/openfx/tree/4fc7b53bc9ad86bb323e971d553b8482916a62d9/Examples/Basic) example in OpenFX. 26 | 27 | Tested in Linux only using (Natron)[https://natron.fr/] as the host application. See example in`test_in_natron.sh`. Requires configuration of Natron OFX plugin paths. 28 | 29 | ### Example plugin skeleton 30 | 31 | `lib.rs` 32 | 33 | ```rust 34 | extern crate ofx; 35 | 36 | mod simple_plugin; 37 | 38 | use ofx::*; 39 | 40 | register_modules!(simple_plugin); 41 | ``` 42 | 43 | `simple_plugin.rs` 44 | 45 | ```rust 46 | use ofx::*; 47 | 48 | // plugin declaration 49 | plugin_module!( 50 | "net.itadinanta.ofx-rs.simple_plugin_1", 51 | ApiVersion(1), 52 | PluginVersion(1, 0), 53 | SimplePlugin::new 54 | ); 55 | 56 | // custom plugin data goes here 57 | struct SimplePlugin {} 58 | 59 | impl SimplePlugin { 60 | pub fn new() -> SimplePlugin { 61 | SimplePlugin {} 62 | } 63 | } 64 | 65 | impl Execute for SimplePlugin { 66 | // plugin logic goes here 67 | } 68 | 69 | 70 | ``` -------------------------------------------------------------------------------- /build_plugin.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | cargo build && cp target/debug/libsimple_plugin.so examples/simple/resources/simple_plugin.ofx.bundle/Contents/Linux-x86-64/simple_plugin.ofx 3 | -------------------------------------------------------------------------------- /examples/basic/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ofx_rs_basic" 3 | version = "0.1.1" 4 | authors = ["Nicola Orru "] 5 | keywords = ["ofx", "openfx", "effects", "natron", "nuke"] 6 | description = "Bindings for OpenFX 1.4" 7 | repository = "https://github.com/itadinanta/ofx-rs" 8 | license = "Apache-2.0" 9 | publish = false # insurance against accidents 10 | 11 | [lib] 12 | name="ofx_rs_basic" 13 | crate-type = ["cdylib"] 14 | path="src/lib.rs" 15 | 16 | [dependencies] 17 | libc = "0.2" 18 | log="0.4" 19 | log4rs = "0.8" 20 | ofx = { path = "../../ofx" } 21 | -------------------------------------------------------------------------------- /examples/basic/src/basic.rs: -------------------------------------------------------------------------------- 1 | use ofx::*; 2 | use std::sync::{Arc, Mutex}; 3 | 4 | plugin_module!( 5 | "net.itadinanta.ofx-rs.basic", 6 | ApiVersion(1), 7 | PluginVersion(1, 0), 8 | SimplePlugin::new 9 | ); 10 | 11 | #[derive(Default)] 12 | struct SimplePlugin { 13 | host_supports_multiple_clip_depths: Bool, 14 | } 15 | 16 | impl SimplePlugin { 17 | pub fn new() -> SimplePlugin { 18 | SimplePlugin::default() 19 | } 20 | } 21 | #[allow(unused)] 22 | struct MyInstanceData { 23 | is_general_effect: bool, 24 | 25 | source_clip: ClipInstance, 26 | mask_clip: Option, 27 | output_clip: ClipInstance, 28 | 29 | scale_param: ParamHandle, 30 | 31 | per_component_scale_param: ParamHandle, 32 | 33 | scale_r_param: ParamHandle, 34 | scale_g_param: ParamHandle, 35 | scale_b_param: ParamHandle, 36 | scale_a_param: ParamHandle, 37 | } 38 | 39 | struct TileProcessor<'a, T, M> 40 | where 41 | T: PixelFormat, 42 | M: PixelFormatAlpha, 43 | { 44 | instance: ImageEffectHandle, 45 | scale: RGBAColourD, 46 | src: ImageDescriptor<'a, T>, 47 | dst: ImageTileMut<'a, T>, 48 | mask: Option>, 49 | render_window: RectI, 50 | } 51 | 52 | // Members of the TileProcessor are either: 53 | // - shared + read only 54 | // - shareable handles and memory blocks from OFX (for which we can spray and pray) 55 | // - owned by the tileprocessor 56 | // so we can assume it can be processed across multiple threads even if rustc says no 57 | // 58 | unsafe impl<'a, T, M> Send for TileProcessor<'a, T, M> 59 | where 60 | T: PixelFormat + ScaleMix, 61 | M: PixelFormatAlpha, 62 | { 63 | } 64 | 65 | impl<'a, T, M> TileProcessor<'a, T, M> 66 | where 67 | T: PixelFormat + ScaleMix, 68 | M: PixelFormatAlpha, 69 | { 70 | fn new( 71 | instance: ImageEffectHandle, 72 | r_scale: Double, 73 | g_scale: Double, 74 | b_scale: Double, 75 | a_scale: Double, 76 | src: ImageDescriptor<'a, T>, 77 | dst: ImageTileMut<'a, T>, 78 | mask: Option>, 79 | render_window: RectI, 80 | ) -> Self { 81 | let scale = RGBAColourD { 82 | r: r_scale, 83 | g: g_scale, 84 | b: b_scale, 85 | a: a_scale, 86 | }; 87 | TileProcessor { 88 | instance, 89 | scale, 90 | src, 91 | dst, 92 | mask, 93 | render_window, 94 | } 95 | } 96 | } 97 | 98 | struct TileDispatch<'a, T, M> 99 | where 100 | T: PixelFormat + ScaleMix, 101 | M: PixelFormatAlpha, 102 | { 103 | tiles: Arc>>>, 104 | } 105 | 106 | impl<'a, T, M> TileDispatch<'a, T, M> 107 | where 108 | T: PixelFormat + ScaleMix, 109 | M: PixelFormatAlpha, 110 | { 111 | fn new(tiles: Vec>) -> Self { 112 | TileDispatch { 113 | tiles: Arc::new(Mutex::new(tiles)), 114 | } 115 | } 116 | 117 | fn pop(&mut self) -> Option> { 118 | if let Ok(ref mut tiles) = self.tiles.lock() { 119 | tiles.pop() 120 | } else { 121 | None 122 | } 123 | } 124 | } 125 | 126 | impl<'a, T, M> Runnable for TileDispatch<'a, T, M> 127 | where 128 | T: PixelFormat + ScaleMix, 129 | M: PixelFormatAlpha, 130 | { 131 | fn run(&mut self, _thread_index: UnsignedInt, _thread_max: UnsignedInt) { 132 | while let Some(mut tile) = self.pop() { 133 | if tile.do_processing().is_err() { 134 | break; 135 | }; 136 | } 137 | } 138 | } 139 | 140 | trait ProcessRGBA<'a, T, M> { 141 | fn do_processing(&'a mut self) -> Result<()>; 142 | } 143 | 144 | impl<'a, T, M> ProcessRGBA<'a, T, M> for TileProcessor<'a, T, M> 145 | where 146 | T: PixelFormat + ScaleMix, 147 | M: PixelFormatAlpha, 148 | { 149 | fn do_processing(&'a mut self) -> Result<()> { 150 | let scale = self.scale; 151 | let proc_window = self.render_window; 152 | for y in self.dst.y1.max(proc_window.y1)..self.dst.y2.min(proc_window.y2) { 153 | let dst_row = self.dst.row_range(proc_window.x1, proc_window.x2, y); 154 | let src_row = self.src.row_range(proc_window.x1, proc_window.x2, y); 155 | 156 | if self.instance.abort()? { 157 | break; 158 | } 159 | 160 | let src_mask = self 161 | .mask 162 | .as_ref() 163 | .map(|mask| mask.row_range(proc_window.x1, proc_window.x2, y)); 164 | 165 | match src_mask { 166 | None => { 167 | for (dst, src) in dst_row.iter_mut().zip(src_row.iter()) { 168 | *dst = src.scaled(&scale); 169 | } 170 | } 171 | Some(src_mask) => { 172 | for ((dst, src), mask) in dst_row.iter_mut().zip(src_row.iter()).zip(src_mask) { 173 | let mask0 = mask.to_f32(); 174 | *dst = src.mix(&src.scaled(&scale), mask0); 175 | } 176 | } 177 | } 178 | } 179 | 180 | Ok(()) 181 | } 182 | } 183 | 184 | const PARAM_MAIN_NAME: &str = "Main"; 185 | const PARAM_SCALE_NAME: &str = "scale"; 186 | const PARAM_SCALE_R_NAME: &str = "scaleR"; 187 | const PARAM_SCALE_G_NAME: &str = "scaleG"; 188 | const PARAM_SCALE_B_NAME: &str = "scaleB"; 189 | const PARAM_SCALE_A_NAME: &str = "scaleA"; 190 | const PARAM_SCALE_COMPONENTS_NAME: &str = "scaleComponents"; 191 | const PARAM_COMPONENT_SCALES_NAME: &str = "componentScales"; 192 | 193 | impl Execute for SimplePlugin { 194 | #[allow(clippy::float_cmp)] 195 | fn execute(&mut self, plugin_context: &PluginContext, action: &mut Action) -> Result { 196 | use Action::*; 197 | match *action { 198 | Render(ref mut effect, ref in_args) => { 199 | let time = in_args.get_time()?; 200 | // TODO: what happens if render_window < full size? 201 | let render_window = in_args.get_render_window()?; 202 | let instance_data: &mut MyInstanceData = effect.get_instance_data()?; 203 | 204 | let source_image = instance_data.source_clip.get_image(time)?; 205 | let output_image = instance_data.output_clip.get_image_mut(time)?; 206 | let mask_image = match instance_data.mask_clip { 207 | None => None, 208 | Some(ref mask_clip) => { 209 | if instance_data.is_general_effect && mask_clip.get_connected()? { 210 | Some(mask_clip.get_image(time)?) 211 | } else { 212 | None 213 | } 214 | } 215 | }; 216 | 217 | let (sv, sr, sg, sb, sa) = instance_data.get_scale_components(time)?; 218 | let (r_scale, g_scale, b_scale, a_scale) = (sv * sr, sv * sg, sv * sb, sv * sa); 219 | let mut output_image = output_image.borrow_mut(); 220 | let num_threads = plugin_context.num_threads()?; 221 | let num_tiles = num_threads as usize; 222 | macro_rules! tiles { 223 | ($rgba_format:ty, $mask_format:ty) => {{ 224 | output_image 225 | .get_tiles_mut::<$rgba_format>(num_tiles)? 226 | .into_iter() 227 | .map(|tile| { 228 | let src = source_image.get_descriptor::<$rgba_format>().unwrap(); 229 | let mask = mask_image 230 | .as_ref() 231 | .and_then(|mask| mask.get_descriptor::<$mask_format>().ok()); 232 | TileProcessor::new( 233 | effect.clone(), 234 | r_scale, 235 | g_scale, 236 | b_scale, 237 | a_scale, 238 | src, 239 | tile, 240 | mask, 241 | render_window, 242 | ) 243 | }) 244 | }}; 245 | } 246 | 247 | macro_rules! process_tiles { 248 | ($rgba_format:ty, $mask_format:ty) => {{ 249 | let mut queue = 250 | TileDispatch::new(tiles!($rgba_format, $mask_format).collect()); 251 | plugin_context.run_in_threads(num_threads, &mut queue)?; 252 | }}; 253 | } 254 | match ( 255 | output_image.get_pixel_depth()?, 256 | output_image.get_components()?, 257 | ) { 258 | (BitDepth::Float, ImageComponent::RGBA) => process_tiles!(RGBAColourF, f32), 259 | (BitDepth::Byte, ImageComponent::RGBA) => process_tiles!(RGBAColourB, u8), 260 | (BitDepth::Short, ImageComponent::RGBA) => process_tiles!(RGBAColourS, u16), 261 | (BitDepth::Float, ImageComponent::Alpha) => process_tiles!(f32, f32), 262 | (BitDepth::Byte, ImageComponent::Alpha) => process_tiles!(u8, u8), 263 | (BitDepth::Short, ImageComponent::Alpha) => process_tiles!(u16, u16), 264 | (_, _) => return FAILED, 265 | } 266 | 267 | if effect.abort()? { 268 | FAILED 269 | } else { 270 | OK 271 | } 272 | } 273 | 274 | IsIdentity(ref mut effect, ref in_args, ref mut out_args) => { 275 | let time = in_args.get_time()?; 276 | let _render_window = in_args.get_render_window()?; 277 | let instance_data: &MyInstanceData = effect.get_instance_data()?; 278 | 279 | let (scale_value, sr, sg, sb, sa) = instance_data.get_scale_components(time)?; 280 | 281 | if scale_value == 1. && sr == 1. && sg == 1. && sb == 1. && sa == 1. { 282 | out_args.set_name(&image_effect_simple_source_clip_name())?; 283 | OK 284 | } else { 285 | REPLY_DEFAULT 286 | } 287 | } 288 | 289 | InstanceChanged(ref mut effect, ref in_args) => { 290 | if in_args.get_change_reason()? == Change::UserEdited { 291 | let obj_changed = in_args.get_name()?; 292 | let expected = match in_args.get_type()? { 293 | Type::Clip => Some(image_effect_simple_source_clip_name()), 294 | Type::Parameter => Some(PARAM_SCALE_COMPONENTS_NAME.to_owned()), 295 | _ => None, 296 | }; 297 | 298 | if expected == Some(obj_changed) { 299 | Self::set_per_component_scale_enabledness(effect)?; 300 | OK 301 | } else { 302 | REPLY_DEFAULT 303 | } 304 | } else { 305 | REPLY_DEFAULT 306 | } 307 | } 308 | 309 | GetRegionOfDefinition(ref mut effect, ref in_args, ref mut out_args) => { 310 | let time = in_args.get_time()?; 311 | let rod = effect 312 | .get_instance_data::()? 313 | .source_clip 314 | .get_region_of_definition(time)?; 315 | out_args.set_effect_region_of_definition(rod)?; 316 | 317 | OK 318 | } 319 | 320 | GetRegionsOfInterest(ref mut effect, ref in_args, ref mut out_args) => { 321 | let roi = in_args.get_region_of_interest()?; 322 | 323 | out_args.set_raw(image_clip_prop_roi!(clip_source!()), &roi)?; 324 | 325 | if effect 326 | .get_instance_data::()? 327 | .is_general_effect 328 | && effect.get_clip(clip_mask!())?.get_connected()? 329 | { 330 | out_args.set_raw(image_clip_prop_roi!(clip_mask!()), &roi)?; 331 | } 332 | 333 | OK 334 | } 335 | 336 | GetTimeDomain(ref mut effect, ref mut out_args) => { 337 | let my_data: &MyInstanceData = effect.get_instance_data()?; 338 | out_args.set_frame_range(my_data.source_clip.get_frame_range()?)?; 339 | 340 | OK 341 | } 342 | 343 | GetClipPreferences(ref mut effect, ref mut out_args) => { 344 | let my_data: &MyInstanceData = effect.get_instance_data()?; 345 | let bit_depth = my_data.source_clip.get_pixel_depth()?; 346 | let image_component = my_data.source_clip.get_components()?; 347 | let output_component = match image_component { 348 | ImageComponent::RGBA | ImageComponent::RGB => ImageComponent::RGBA, 349 | _ => ImageComponent::Alpha, 350 | }; 351 | out_args.set_raw( 352 | image_clip_prop_components!(clip_output!()), 353 | output_component.to_bytes(), 354 | )?; 355 | 356 | if self.host_supports_multiple_clip_depths { 357 | out_args 358 | .set_raw(image_clip_prop_depth!(clip_output!()), bit_depth.to_bytes())?; 359 | } 360 | 361 | if my_data.is_general_effect { 362 | let is_mask_connected = my_data 363 | .mask_clip 364 | .as_ref() 365 | .and_then(|mask| mask.get_connected().ok()) 366 | .unwrap_or_default(); 367 | 368 | if is_mask_connected { 369 | out_args.set_raw( 370 | image_clip_prop_components!(clip_mask!()), 371 | ImageComponent::Alpha.to_bytes(), 372 | )?; 373 | if self.host_supports_multiple_clip_depths { 374 | out_args.set_raw( 375 | image_clip_prop_depth!(clip_mask!()), 376 | bit_depth.to_bytes(), 377 | )?; 378 | } 379 | } 380 | } 381 | 382 | OK 383 | } 384 | 385 | CreateInstance(ref mut effect) => { 386 | let mut effect_props: EffectInstance = effect.properties()?; 387 | let mut param_set = effect.parameter_set()?; 388 | 389 | let is_general_effect = effect_props.get_context()?.is_general(); 390 | let per_component_scale_param = param_set.parameter(PARAM_SCALE_COMPONENTS_NAME)?; 391 | 392 | let source_clip = effect.get_simple_input_clip()?; 393 | let output_clip = effect.get_output_clip()?; 394 | let mask_clip = if is_general_effect { 395 | Some(effect.get_clip(clip_mask!())?) 396 | } else { 397 | None 398 | }; 399 | 400 | let scale_param = param_set.parameter(PARAM_SCALE_NAME)?; 401 | let scale_r_param = param_set.parameter(PARAM_SCALE_R_NAME)?; 402 | let scale_g_param = param_set.parameter(PARAM_SCALE_G_NAME)?; 403 | let scale_b_param = param_set.parameter(PARAM_SCALE_B_NAME)?; 404 | let scale_a_param = param_set.parameter(PARAM_SCALE_A_NAME)?; 405 | 406 | effect.set_instance_data(MyInstanceData { 407 | is_general_effect, 408 | source_clip, 409 | mask_clip, 410 | output_clip, 411 | per_component_scale_param, 412 | scale_param, 413 | scale_r_param, 414 | scale_g_param, 415 | scale_b_param, 416 | scale_a_param, 417 | })?; 418 | 419 | Self::set_per_component_scale_enabledness(effect)?; 420 | 421 | OK 422 | } 423 | 424 | DestroyInstance(ref mut _effect) => OK, 425 | 426 | DescribeInContext(ref mut effect, ref in_args) => { 427 | let mut output_clip = effect.new_output_clip()?; 428 | output_clip 429 | .set_supported_components(&[ImageComponent::RGBA, ImageComponent::Alpha])?; 430 | 431 | let mut input_clip = effect.new_simple_input_clip()?; 432 | input_clip 433 | .set_supported_components(&[ImageComponent::RGBA, ImageComponent::Alpha])?; 434 | 435 | if in_args.get_context()?.is_general() { 436 | let mut mask = effect.new_clip(clip_mask!())?; 437 | mask.set_supported_components(&[ImageComponent::Alpha])?; 438 | mask.set_optional(true)?; 439 | } 440 | 441 | fn define_scale_param( 442 | param_set: &mut ParamSetHandle, 443 | name: &str, 444 | label: &'static str, 445 | script_name: &'static str, 446 | hint: &'static str, 447 | parent: Option<&'static str>, 448 | ) -> Result<()> { 449 | let mut param_props = param_set.param_define_double(name)?; 450 | 451 | param_props.set_double_type(ParamDoubleType::Scale)?; 452 | param_props.set_label(label)?; 453 | param_props.set_default(1.0)?; 454 | param_props.set_display_min(1.0)?; 455 | param_props.set_display_min(1.0)?; 456 | param_props.set_display_max(100.0)?; 457 | param_props.set_hint(hint)?; 458 | param_props.set_script_name(script_name)?; 459 | 460 | if let Some(parent) = parent { 461 | param_props.set_parent(parent)?; 462 | } 463 | 464 | Ok(()) 465 | } 466 | 467 | let mut param_set = effect.parameter_set()?; 468 | define_scale_param( 469 | &mut param_set, 470 | PARAM_SCALE_NAME, 471 | "scale", 472 | PARAM_SCALE_NAME, 473 | "Scales all component in the image", 474 | None, 475 | )?; 476 | 477 | let mut param_props = 478 | param_set.param_define_boolean(PARAM_SCALE_COMPONENTS_NAME)?; 479 | param_props.set_default(false)?; 480 | param_props.set_hint("Enables scale on individual components")?; 481 | param_props.set_script_name(PARAM_SCALE_COMPONENTS_NAME)?; 482 | param_props.set_label("Scale Individual Components")?; 483 | 484 | let mut param_props = param_set.param_define_group(PARAM_COMPONENT_SCALES_NAME)?; 485 | param_props.set_hint("Scales on the individual component")?; 486 | param_props.set_label("Components")?; 487 | 488 | define_scale_param( 489 | &mut param_set, 490 | PARAM_SCALE_R_NAME, 491 | "red", 492 | PARAM_SCALE_R_NAME, 493 | "Scales the red component of the image", 494 | Some(PARAM_COMPONENT_SCALES_NAME), 495 | )?; 496 | define_scale_param( 497 | &mut param_set, 498 | PARAM_SCALE_G_NAME, 499 | "green", 500 | PARAM_SCALE_G_NAME, 501 | "Scales the green component of the image", 502 | Some(PARAM_COMPONENT_SCALES_NAME), 503 | )?; 504 | define_scale_param( 505 | &mut param_set, 506 | PARAM_SCALE_B_NAME, 507 | "blue", 508 | PARAM_SCALE_B_NAME, 509 | "Scales the blue component of the image", 510 | Some(PARAM_COMPONENT_SCALES_NAME), 511 | )?; 512 | define_scale_param( 513 | &mut param_set, 514 | PARAM_SCALE_A_NAME, 515 | "alpha", 516 | PARAM_SCALE_A_NAME, 517 | "Scales the alpha component of the image", 518 | Some(PARAM_COMPONENT_SCALES_NAME), 519 | )?; 520 | 521 | param_set 522 | .param_define_page(PARAM_MAIN_NAME)? 523 | .set_children(&[ 524 | PARAM_SCALE_NAME, 525 | PARAM_SCALE_COMPONENTS_NAME, 526 | PARAM_SCALE_R_NAME, 527 | PARAM_SCALE_G_NAME, 528 | PARAM_SCALE_B_NAME, 529 | PARAM_SCALE_A_NAME, 530 | ])?; 531 | 532 | OK 533 | } 534 | 535 | Describe(ref mut effect) => { 536 | self.host_supports_multiple_clip_depths = plugin_context 537 | .get_host() 538 | .get_supports_multiple_clip_depths()?; 539 | 540 | let mut effect_properties: EffectDescriptor = effect.properties()?; 541 | effect_properties.set_grouping("Ofx-rs")?; 542 | 543 | effect_properties.set_label("Ofx-rs basic")?; 544 | effect_properties.set_short_label("Ofx-rs basic")?; 545 | effect_properties.set_long_label("Ofx-rs basic examples")?; 546 | 547 | effect_properties.set_supported_pixel_depths(&[ 548 | BitDepth::Byte, 549 | BitDepth::Short, 550 | BitDepth::Float, 551 | ])?; 552 | effect_properties.set_supported_contexts(&[ 553 | ImageEffectContext::Filter, 554 | ImageEffectContext::General, 555 | ])?; 556 | 557 | OK 558 | } 559 | 560 | _ => REPLY_DEFAULT, 561 | } 562 | } 563 | } 564 | 565 | impl SimplePlugin { 566 | fn set_per_component_scale_enabledness(effect: &mut ImageEffectHandle) -> Result<()> { 567 | let instance_data: &mut MyInstanceData = effect.get_instance_data()?; 568 | let input_clip = effect.get_simple_input_clip()?; 569 | let is_input_rgb = input_clip.get_connected()? && input_clip.get_components()?.is_rgb(); 570 | instance_data 571 | .per_component_scale_param 572 | .set_enabled(is_input_rgb)?; 573 | let per_component_scale = 574 | is_input_rgb && instance_data.per_component_scale_param.get_value()?; 575 | for scale_param in &mut [ 576 | &mut instance_data.scale_r_param, 577 | &mut instance_data.scale_g_param, 578 | &mut instance_data.scale_b_param, 579 | &mut instance_data.scale_a_param, 580 | ] { 581 | scale_param.set_enabled(per_component_scale)?; 582 | instance_data 583 | .scale_param 584 | .set_enabled(!per_component_scale)? 585 | } 586 | 587 | Ok(()) 588 | } 589 | } 590 | 591 | impl MyInstanceData { 592 | fn get_scale_components(&self, time: Time) -> Result<(f64, f64, f64, f64, f64)> { 593 | let scale_value = self.scale_param.get_value_at_time(time)?; 594 | let per_component_scale = self.per_component_scale_param.get_value_at_time(time)?; 595 | if per_component_scale && self.source_clip.get_components()?.is_rgb() { 596 | Ok(( 597 | scale_value, 598 | self.scale_r_param.get_value_at_time(time)?, 599 | self.scale_g_param.get_value_at_time(time)?, 600 | self.scale_b_param.get_value_at_time(time)?, 601 | self.scale_a_param.get_value_at_time(time)?, 602 | )) 603 | } else { 604 | Ok((scale_value, 1., 1., 1., 1.)) 605 | } 606 | } 607 | } 608 | -------------------------------------------------------------------------------- /examples/basic/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate log; 2 | extern crate log4rs; 3 | extern crate ofx; 4 | 5 | mod basic; 6 | mod tests; 7 | 8 | use ofx::*; 9 | 10 | register_modules!(basic); 11 | -------------------------------------------------------------------------------- /examples/basic/src/tests.rs: -------------------------------------------------------------------------------- 1 | #[test] 2 | fn enumerate_plugins() { 3 | let descriptions = super::show_plugins(); 4 | assert!(descriptions.len() == 1); 5 | println!("{}", descriptions[0]); 6 | assert!(descriptions[0] == "module:ofx_rs_basic::basic id:\"net.itadinanta.ofx-rs.basic\" index:0"); 7 | } 8 | -------------------------------------------------------------------------------- /examples/basic/testbed.ntp: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 0 5 | 6 | 2 7 | 3 8 | 14 9 | tags/2.3.14 10 | 5bbf5ed14d010eac3d7e4d414d4deceed404d18d 11 | Linux 12 | 64 13 | 14 | 4 15 | 16 | Viewer1 17 | Viewer1 18 | fr.inria.built-in.Viewer 19 | 20 | 0 21 | 1 22 | 0 23 | 0 24 | 25 | 1 26 | 0 27 | 28 | 1 29 | Ofxrs_simple_plugin_sample1 30 | 31 | 32 | 0 33 | 34 | 0 35 | 0 36 | 37 | 0 38 | 0 39 | 0 40 | 41 | 0 42 | 2 43 | 44 | Viewer1 45 | 46 | 47 | Ofxrs_simple_plugin_sample1 48 | Ofxrs_simple_plugin_sample1 49 | net.itadinanta.ofx-rs.simple_plugin_1 50 | 51 | 0 52 | 1 53 | 0 54 | 8 55 | 56 | scale 57 | Double 58 | 1 59 | 0 60 | 0 61 | 62 | 1 63 | 0 64 | 2.85000000000000000e+01 65 | 1.00000000000000000e+00 66 | 0 67 | 68 | 0 69 | 70 | 0 71 | 72 | 73 | scaleComponents 74 | Bool 75 | 1 76 | 0 77 | 0 78 | 79 | 0 80 | 0 81 | 0 82 | 0 83 | 0 84 | 85 | 0 86 | 87 | 0 88 | 89 | 90 | scaleR 91 | Double 92 | 1 93 | 0 94 | 0 95 | 96 | 0 97 | 0 98 | 1.00000000000000000e+00 99 | 1.00000000000000000e+00 100 | 0 101 | 102 | 0 103 | 104 | 0 105 | 106 | 107 | scaleG 108 | Double 109 | 1 110 | 0 111 | 0 112 | 113 | 0 114 | 0 115 | 1.00000000000000000e+00 116 | 1.00000000000000000e+00 117 | 0 118 | 119 | 0 120 | 121 | 0 122 | 123 | 124 | scaleB 125 | Double 126 | 1 127 | 0 128 | 0 129 | 130 | 0 131 | 0 132 | 1.00000000000000000e+00 133 | 1.00000000000000000e+00 134 | 0 135 | 136 | 0 137 | 138 | 0 139 | 140 | 141 | scaleA 142 | Double 143 | 1 144 | 0 145 | 0 146 | 147 | 0 148 | 0 149 | 1.00000000000000000e+00 150 | 1.00000000000000000e+00 151 | 0 152 | 153 | 0 154 | 155 | 0 156 | 157 | 158 | userTextArea 159 | String 160 | 1 161 | 0 162 | 0 163 | 164 | 1 165 | 0 166 | <font size="6" color="#000000" face="Droid Sans"></font> 167 | 168 | 0 169 | 170 | 0 171 | 172 | 173 | 0 174 | 0 175 | 176 | 0 177 | 178 | 179 | Source_channels 180 | Choice 181 | 1 182 | 0 183 | 0 184 | 185 | 1 186 | 0 187 | 1 188 | 1 189 | 0 190 | 191 | 0 192 | 193 | uk.co.thefoundry.OfxImagePlaneColour 194 | 0 195 | 196 | 197 | 1 198 | 0 199 | 200 | Source 201 | CheckerBoard1 202 | 203 | 204 | 56 205 | 206 | 0 207 | 0 208 | 209 | 0 210 | 3 211 | Main 212 | Node 213 | Info 214 | 0 215 | 216 | 0 217 | 2 218 | 219 | Ofxrs_simple_plugin_sample1 220 | 221 | 222 | CheckerBoard1 223 | CheckerBoard1 224 | net.sf.openfx.CheckerBoardPlugin 225 | 226 | 0 227 | 1 228 | 0 229 | 6 230 | 231 | reformat 232 | Bool 233 | 1 234 | 1 235 | 0 236 | 237 | 0 238 | 0 239 | 0 240 | 0 241 | 0 242 | 243 | 0 244 | 245 | 0 246 | 247 | 248 | NatronParamFormatChoice 249 | Choice 250 | 1 251 | 1 252 | 0 253 | 254 | 0 255 | 0 256 | 0 257 | 6 258 | 0 259 | 260 | 0 261 | 262 | PC_Video 263 | 0 264 | 265 | 266 | bottomLeft 267 | Double 268 | 2 269 | 1 270 | 0 271 | 272 | 0 273 | 0 274 | 0.00000000000000000e+00 275 | 0.00000000000000000e+00 276 | 0 277 | 278 | 0 279 | 280 | 281 | 0 282 | 0 283 | 0.00000000000000000e+00 284 | 0.00000000000000000e+00 285 | 0 286 | 287 | 0 288 | 289 | 0 290 | 291 | 292 | size 293 | Double 294 | 2 295 | 1 296 | 0 297 | 298 | 0 299 | 0 300 | 1.92000000000000000e+03 301 | 1.00000000000000000e+00 302 | 0 303 | 304 | 0 305 | 306 | 307 | 0 308 | 0 309 | 1.08000000000000000e+03 310 | 1.00000000000000000e+00 311 | 0 312 | 313 | 0 314 | 315 | 0 316 | 317 | 318 | interactive 319 | Bool 320 | 1 321 | 1 322 | 0 323 | 324 | 0 325 | 0 326 | 0 327 | 0 328 | 0 329 | 330 | 0 331 | 332 | 0 333 | 334 | 335 | userTextArea 336 | String 337 | 1 338 | 0 339 | 0 340 | 341 | 1 342 | 0 343 | <font size="6" color="#000000" face="Droid Sans"></font> 344 | 345 | 0 346 | 347 | 0 348 | 349 | 350 | 0 351 | 0 352 | 353 | 0 354 | 355 | 356 | 0 357 | 0 358 | 359 | 0 360 | 361 | 0 362 | 0 363 | 364 | 0 365 | 3 366 | Controls 367 | Node 368 | Info 369 | 0 370 | 371 | 0 372 | 2 373 | 374 | CheckerBoard1 375 | 376 | 377 | Write1 378 | Write1 379 | fr.inria.built-in.Write 380 | 381 | 0 382 | 1 383 | 0 384 | 10 385 | 386 | encodingPluginID 387 | String 388 | 1 389 | 1 390 | 0 391 | 392 | 1 393 | 0 394 | fr.inria.openfx.WritePNG 395 | 396 | 0 397 | 398 | 0 399 | 400 | 401 | 0 402 | 0 403 | 404 | 0 405 | 406 | 407 | userTextArea 408 | String 409 | 1 410 | 0 411 | 0 412 | 413 | 1 414 | 0 415 | <font size="6" color="#000000" face="Droid Sans"><Natron>(test.png)</Natron></font> 416 | 417 | 0 418 | 419 | 0 420 | 421 | 422 | 0 423 | 0 424 | 425 | 0 426 | 427 | 428 | filename 429 | OutputFile 430 | 1 431 | 0 432 | 0 433 | 434 | 1 435 | 0 436 | /home/nico/Pictures/test.png 437 | 438 | 0 439 | 440 | 0 441 | 442 | 0 443 | 444 | 445 | NatronParamFormatChoice 446 | Choice 447 | 1 448 | 1 449 | 0 450 | 451 | 0 452 | 0 453 | 6 454 | 6 455 | 0 456 | 457 | 0 458 | 459 | HD 460 | 0 461 | 462 | 463 | ocioInputSpace 464 | String 465 | 1 466 | 1 467 | 0 468 | 469 | 0 470 | 0 471 | scene_linear 472 | scene_linear 473 | 0 474 | 475 | 0 476 | 477 | 478 | 0 479 | 0 480 | 481 | 0 482 | 483 | 484 | ocioOutputSpace 485 | String 486 | 1 487 | 1 488 | 0 489 | 490 | 0 491 | 0 492 | sRGB 493 | sRGB 494 | 0 495 | 496 | 0 497 | 498 | 499 | 0 500 | 0 501 | 502 | 0 503 | 504 | 505 | frameRange 506 | Choice 507 | 1 508 | 0 509 | 0 510 | 511 | 1 512 | 0 513 | 2 514 | 1 515 | 0 516 | 517 | 0 518 | 519 | manual 520 | 0 521 | 522 | 523 | firstFrame 524 | Int 525 | 1 526 | 0 527 | 0 528 | 529 | 1 530 | 0 531 | 1 532 | 0 533 | 0 534 | 535 | 0 536 | 537 | 0 538 | 539 | 540 | lastFrame 541 | Int 542 | 1 543 | 0 544 | 0 545 | 546 | 1 547 | 0 548 | 1 549 | 0 550 | 0 551 | 552 | 0 553 | 554 | 0 555 | 556 | 557 | ParamExistingInstance 558 | Bool 559 | 1 560 | 1 561 | 0 562 | 563 | 0 564 | 0 565 | 1 566 | 0 567 | 0 568 | 569 | 0 570 | 571 | 0 572 | 573 | 574 | 1 575 | 0 576 | 577 | Source 578 | Ofxrs_simple_plugin_sample1 579 | 580 | 581 | 4 582 | 583 | 0 584 | 0 585 | 586 | 0 587 | 3 588 | Controls 589 | Node 590 | Info 591 | 0 592 | 593 | 0 594 | 2 595 | 596 | Write1 597 | 598 | 599 | 2 600 | 601 | projectPaths 602 | Path 603 | 1 604 | 0 605 | 0 606 | 607 | 1 608 | 0 609 | <Name>OCIO</Name><Value>/opt/natron-dist/Natron-2.3.14-Linux-x86_64bit/Resources/OpenColorIO-Configs/blender</Value><Name>Project</Name><Value>/home/nico/Projects/itadinanta/ofx-rs/examples/simple</Value> 610 | 611 | 0 612 | 613 | 0 614 | 615 | 0 616 | 617 | 618 | lastSaveDate 619 | String 620 | 1 621 | 0 622 | 0 623 | 624 | 0 625 | 0 626 | Thu Dec 20 23:57:34 2018 627 | 628 | 0 629 | 630 | 0 631 | 632 | 633 | 0 634 | 0 635 | 636 | 0 637 | 638 | 639 | 0 640 | 3 641 | 642 | 1 643 | 1544916065852 644 | 645 | 646 | 647 | 4 648 | 6 649 | 650 | Viewer1 651 | 1.43375000000000000e+03 652 | 6.02500000000000000e+02 653 | 0 654 | 6.999923587e-01 655 | 6.999923587e-01 656 | 6.999923587e-01 657 | 0 658 | 1.04000000000000000e+02 659 | 3.10000000000000000e+01 660 | 0 661 | 0 662 | 663 | 664 | Ofxrs_simple_plugin_sample1 665 | 1.95875000000000000e+03 666 | 2.27000000000000000e+02 667 | 0 668 | 6.999923587e-01 669 | 6.999923587e-01 670 | 6.999923587e-01 671 | 0 672 | 8.00000000000000000e+01 673 | 5.50000000000000000e+01 674 | 0 675 | 0 676 | 677 | 678 | CheckerBoard1 679 | 1.94675000000000000e+03 680 | 5.77500000000000000e+01 681 | 0 682 | 3.000076413e-01 683 | 5.000076294e-01 684 | 2.000000030e-01 685 | 0 686 | 1.04000000000000000e+02 687 | 3.20000000000000000e+01 688 | 0 689 | 0 690 | 691 | 692 | Write1 693 | 1.94675000000000000e+03 694 | 3.84000000000000000e+02 695 | 0 696 | 7.499961853e-01 697 | 7.499961853e-01 698 | 0.000000000e+00 699 | 0 700 | 1.04000000000000000e+02 701 | 5.50000000000000000e+01 702 | 0 703 | 0 704 | 705 | 706 | 707 | 1 708 | 709 | 0 710 | 711 | 1348 726 712 | 1 713 | 1 714 | 715 | 989 988 716 | 2 717 | 0 718 | 719 | 720 | 1 721 | 0 722 | Viewer1 723 | 724 | 0 725 | pane1 726 | 1 727 | 728 | 0 729 | 730 | 731 | 4 732 | 0 733 | nodeGraph 734 | curveEditor 735 | dopeSheetEditor 736 | progress 737 | 738 | 0 739 | pane3 740 | 0 741 | 742 | 743 | 0 744 | 745 | 746 | 1 747 | 0 748 | properties 749 | 750 | 0 751 | pane2 752 | 0 753 | 754 | 755 | 1 756 | 313 757 | 33 758 | 2110 759 | 2008 760 | 761 | 762 | 763 | 1 764 | 0 765 | 766 | Viewer1 767 | 768 | -6.02417433414043217e+02 769 | -2.16000000000000014e+01 770 | 7.35398860398860488e-01 771 | 0 772 | 773 | 4.80000000000000000e+02 774 | 2.70000000000000000e+02 775 | 1.44000000000000000e+03 776 | 8.10000000000000000e+02 777 | 778 | 1 779 | 0 780 | 1.00000000000000000e+00 781 | 1.00000000000000000e+00 782 | sRGB 783 | Color.RGBA 784 | Color.A 785 | RGB 786 | 0 787 | 0 788 | 0 789 | 0 790 | 1 791 | 250 792 | 1 793 | 1 794 | 1 795 | 1 796 | 1 797 | 1 798 | 0 799 | 0 800 | 0 801 | 2.40000000000000000e+01 802 | 1 803 | 0 804 | 0 805 | 0 806 | 807 | 808 | 809 | 810 | 0 811 | 0 812 | 813 | 814 | 4 815 | 0 816 | Ofxrs_simple_plugin_sample1 817 | Write1 818 | CheckerBoard1 819 | Natron_Project_Settings_Panel 820 | 821 | 822 | 0 823 | 824 | 825 | 826 | -------------------------------------------------------------------------------- /examples/basic/testbed2.ntp: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 0 5 | 6 | 2 7 | 3 8 | 14 9 | tags/2.3.14 10 | 5bbf5ed14d010eac3d7e4d414d4deceed404d18d 11 | Linux 12 | 64 13 | 14 | 4 15 | 16 | Viewer1 17 | Viewer1 18 | fr.inria.built-in.Viewer 19 | 20 | 0 21 | 1 22 | 0 23 | 0 24 | 25 | 1 26 | 0 27 | 28 | 1 29 | Ofxrs_basic1 30 | 31 | 32 | 0 33 | 34 | 0 35 | 0 36 | 37 | 0 38 | 0 39 | 0 40 | 41 | 0 42 | 2 43 | 44 | Viewer1 45 | 46 | 47 | CheckerBoard1 48 | CheckerBoard1 49 | net.sf.openfx.CheckerBoardPlugin 50 | 51 | 0 52 | 1 53 | 0 54 | 7 55 | 56 | reformat 57 | Bool 58 | 1 59 | 1 60 | 0 61 | 62 | 0 63 | 0 64 | 0 65 | 0 66 | 0 67 | 68 | 0 69 | 70 | 0 71 | 72 | 73 | NatronParamFormatChoice 74 | Choice 75 | 1 76 | 1 77 | 0 78 | 79 | 0 80 | 0 81 | 0 82 | 6 83 | 0 84 | 85 | 0 86 | 87 | PC_Video 88 | 0 89 | 90 | 91 | bottomLeft 92 | Double 93 | 2 94 | 1 95 | 0 96 | 97 | 0 98 | 0 99 | 0.00000000000000000e+00 100 | 0.00000000000000000e+00 101 | 0 102 | 103 | 0 104 | 105 | 106 | 0 107 | 0 108 | 0.00000000000000000e+00 109 | 0.00000000000000000e+00 110 | 0 111 | 112 | 0 113 | 114 | 0 115 | 116 | 117 | size 118 | Double 119 | 2 120 | 1 121 | 0 122 | 123 | 0 124 | 0 125 | 1.92000000000000000e+03 126 | 1.00000000000000000e+00 127 | 0 128 | 129 | 0 130 | 131 | 132 | 0 133 | 0 134 | 1.08000000000000000e+03 135 | 1.00000000000000000e+00 136 | 0 137 | 138 | 0 139 | 140 | 0 141 | 142 | 143 | interactive 144 | Bool 145 | 1 146 | 1 147 | 0 148 | 149 | 0 150 | 0 151 | 0 152 | 0 153 | 0 154 | 155 | 0 156 | 157 | 0 158 | 159 | 160 | boxSize 161 | Double 162 | 2 163 | 0 164 | 0 165 | 166 | 1 167 | 0 168 | 6.55000000000000000e+01 169 | 6.40000000000000000e+01 170 | 0 171 | 172 | 0 173 | 174 | 175 | 1 176 | 0 177 | 6.55000000000000000e+01 178 | 6.40000000000000000e+01 179 | 0 180 | 181 | 0 182 | 183 | 0 184 | 185 | 186 | userTextArea 187 | String 188 | 1 189 | 0 190 | 0 191 | 192 | 1 193 | 0 194 | <font size="6" color="#000000" face="Droid Sans"></font> 195 | 196 | 0 197 | 198 | 0 199 | 200 | 201 | 0 202 | 0 203 | 204 | 0 205 | 206 | 207 | 0 208 | 0 209 | 210 | 582 211 | 212 | 0 213 | 0 214 | 215 | 0 216 | 3 217 | Controls 218 | Node 219 | Info 220 | 0 221 | 222 | 0 223 | 2 224 | 225 | CheckerBoard1 226 | 227 | 228 | Radial1 229 | Radial1 230 | net.sf.openfx.Radial 231 | 232 | 0 233 | 2 234 | 1 235 | 3 236 | 237 | NatronParamFormatChoice 238 | Choice 239 | 1 240 | 1 241 | 0 242 | 243 | 0 244 | 0 245 | 0 246 | 6 247 | 0 248 | 249 | 0 250 | 251 | PC_Video 252 | 0 253 | 254 | 255 | softness 256 | Double 257 | 1 258 | 0 259 | 0 260 | 261 | 1 262 | 0 263 | 2.99999999999999989e-01 264 | 1.00000000000000000e+00 265 | 0 266 | 267 | 0 268 | 269 | 0 270 | 271 | 272 | userTextArea 273 | String 274 | 1 275 | 0 276 | 0 277 | 278 | 1 279 | 0 280 | <font size="6" color="#000000" face="Droid Sans"></font> 281 | 282 | 0 283 | 284 | 0 285 | 286 | 287 | 0 288 | 0 289 | 290 | 0 291 | 292 | 293 | 0 294 | 0 295 | 296 | 197 297 | 298 | 0 299 | 0 300 | 301 | 0 302 | 3 303 | Controls 304 | Node 305 | Info 306 | 0 307 | 308 | 0 309 | 2 310 | 311 | Radial1 312 | 313 | 314 | Ofxrs_basic1 315 | Ofxrs_basic1 316 | net.itadinanta.ofx-rs.basic 317 | 318 | 0 319 | 1 320 | 0 321 | 9 322 | 323 | scale 324 | Double 325 | 1 326 | 0 327 | 0 328 | 329 | 1 330 | 0 331 | 3.60000000000000009e+00 332 | 1.00000000000000000e+00 333 | 0 334 | 335 | 0 336 | 337 | 0 338 | 339 | 340 | scaleR 341 | Double 342 | 1 343 | 0 344 | 0 345 | 346 | 0 347 | 0 348 | 1.00000000000000000e+00 349 | 1.00000000000000000e+00 350 | 0 351 | 352 | 0 353 | 354 | 0 355 | 356 | 357 | scaleG 358 | Double 359 | 1 360 | 0 361 | 0 362 | 363 | 0 364 | 0 365 | 1.00000000000000000e+00 366 | 1.00000000000000000e+00 367 | 0 368 | 369 | 0 370 | 371 | 0 372 | 373 | 374 | scaleB 375 | Double 376 | 1 377 | 0 378 | 0 379 | 380 | 0 381 | 0 382 | 1.00000000000000000e+00 383 | 1.00000000000000000e+00 384 | 0 385 | 386 | 0 387 | 388 | 0 389 | 390 | 391 | scaleA 392 | Double 393 | 1 394 | 0 395 | 0 396 | 397 | 0 398 | 0 399 | 1.00000000000000000e+00 400 | 1.00000000000000000e+00 401 | 0 402 | 403 | 0 404 | 405 | 0 406 | 407 | 408 | userTextArea 409 | String 410 | 1 411 | 0 412 | 0 413 | 414 | 1 415 | 0 416 | <font size="6" color="#000000" face="Droid Sans"></font> 417 | 418 | 0 419 | 420 | 0 421 | 422 | 423 | 0 424 | 0 425 | 426 | 0 427 | 428 | 429 | Source_channels 430 | Choice 431 | 1 432 | 0 433 | 0 434 | 435 | 1 436 | 0 437 | 1 438 | 1 439 | 0 440 | 441 | 0 442 | 443 | uk.co.thefoundry.OfxImagePlaneColour 444 | 0 445 | 446 | 447 | enableInput_Mask 448 | Bool 449 | 1 450 | 0 451 | 0 452 | 453 | 1 454 | 0 455 | 1 456 | 0 457 | 0 458 | 459 | 0 460 | 461 | 0 462 | 463 | 464 | inputChannel_Mask 465 | Choice 466 | 1 467 | 0 468 | 0 469 | 470 | 1 471 | 0 472 | 1 473 | 4 474 | 0 475 | 476 | 0 477 | 478 | uk.co.thefoundry.OfxImagePlaneColour.R 479 | 0 480 | 481 | 482 | 2 483 | 0 484 | 485 | Mask 486 | Radial1 487 | 488 | 489 | Source 490 | CheckerBoard1 491 | 492 | 493 | 128 494 | 495 | 0 496 | 0 497 | 498 | 0 499 | 3 500 | Main 501 | Node 502 | Info 503 | 0 504 | 505 | 0 506 | 2 507 | 508 | Ofxrs_basic1 509 | 510 | 511 | 2 512 | 513 | projectPaths 514 | Path 515 | 1 516 | 0 517 | 0 518 | 519 | 1 520 | 0 521 | <Name>OCIO</Name><Value>/opt/natron-dist/Natron-2.3.14-Linux-x86_64bit/Resources/OpenColorIO-Configs/blender</Value><Name>Project</Name><Value>/home/nico/Projects/itadinanta/ofx-rs/examples/basic</Value> 522 | 523 | 0 524 | 525 | 0 526 | 527 | 0 528 | 529 | 530 | lastSaveDate 531 | String 532 | 1 533 | 0 534 | 0 535 | 536 | 0 537 | 0 538 | Sun Feb 3 19:53:41 2019 539 | 540 | 0 541 | 542 | 0 543 | 544 | 545 | 0 546 | 0 547 | 548 | 0 549 | 550 | 551 | 0 552 | 3 553 | 554 | 1 555 | 1546647743811 556 | 557 | 558 | 559 | 4 560 | 6 561 | 562 | Viewer1 563 | 4.56750000000000000e+02 564 | 5.17500000000000000e+02 565 | 0 566 | 6.999923587e-01 567 | 6.999923587e-01 568 | 6.999923587e-01 569 | 0 570 | 1.04000000000000000e+02 571 | 3.10000000000000000e+01 572 | 0 573 | 0 574 | 575 | 576 | CheckerBoard1 577 | 7.22500000000000000e+02 578 | 1.37750000000000000e+02 579 | 0 580 | 3.000076413e-01 581 | 5.000076294e-01 582 | 2.000000030e-01 583 | 0 584 | 1.04000000000000000e+02 585 | 3.20000000000000000e+01 586 | 0 587 | 0 588 | 589 | 590 | Radial1 591 | 4.21000000000000000e+02 592 | 2.09375000000000000e+02 593 | 0 594 | 3.000076413e-01 595 | 5.000076294e-01 596 | 2.000000030e-01 597 | 0 598 | 1.04000000000000000e+02 599 | 3.20000000000000000e+01 600 | 0 601 | 0 602 | 603 | 604 | Ofxrs_basic1 605 | 6.56000000000000000e+02 606 | 3.58000000000000000e+02 607 | 0 608 | 6.999923587e-01 609 | 6.999923587e-01 610 | 6.999923587e-01 611 | 0 612 | 8.00000000000000000e+01 613 | 3.20000000000000000e+01 614 | 0 615 | 0 616 | 617 | 618 | 619 | 1 620 | 621 | 0 622 | 623 | 1355 729 624 | 1 625 | 1 626 | 627 | 989 988 628 | 2 629 | 0 630 | 631 | 632 | 1 633 | 0 634 | Viewer1 635 | 636 | 0 637 | pane1 638 | 1 639 | 640 | 0 641 | 642 | 643 | 3 644 | 0 645 | nodeGraph 646 | curveEditor 647 | dopeSheetEditor 648 | 649 | 0 650 | pane3 651 | 0 652 | 653 | 654 | 0 655 | 656 | 657 | 1 658 | 0 659 | properties 660 | 661 | 0 662 | pane2 663 | 0 664 | 665 | 666 | 1 667 | 1476 668 | 71 669 | 2120 670 | 2008 671 | 672 | 673 | 674 | 1 675 | 0 676 | 677 | Viewer1 678 | 679 | 5.79391355260436285e+01 680 | -2.06197931775521965e+01 681 | 6.75080128205127972e-01 682 | 0 683 | 684 | 4.80000000000000000e+02 685 | 2.70000000000000000e+02 686 | 1.44000000000000000e+03 687 | 8.10000000000000000e+02 688 | 689 | 1 690 | 0 691 | 1.00000000000000000e+00 692 | 1.00000000000000000e+00 693 | sRGB 694 | Color.RGBA 695 | Color.A 696 | RGB 697 | 0 698 | 0 699 | 1 700 | 0 701 | 1 702 | 250 703 | 1 704 | 1 705 | 1 706 | 1 707 | 1 708 | 1 709 | 0 710 | 0 711 | 0 712 | 2.40000000000000000e+01 713 | 1 714 | 0 715 | 0 716 | 0 717 | 718 | 719 | 720 | 721 | 0 722 | 0 723 | 724 | 725 | 4 726 | 0 727 | CheckerBoard1 728 | Ofxrs_basic1 729 | Radial1 730 | Natron_Project_Settings_Panel 731 | 732 | 733 | 0 734 | 735 | 736 | 737 | -------------------------------------------------------------------------------- /ofx-sys/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | ofx-sys 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /ofx-sys/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ofx_sys" 3 | version = "0.2.0" 4 | authors = ["Nicola Orru "] 5 | keywords = ["ofx", "openfx", "effects", "natron", "nuke"] 6 | #edition = "2018" 7 | description = "Bindings for OpenFX 1.4" 8 | repository = "https://github.com/itadinanta/ofx-rs" 9 | license = "Apache-2.0" 10 | # publish = false # insurance against accidents 11 | 12 | build = "build/main.rs" 13 | 14 | include = [ 15 | "Cargo.toml", 16 | "src/**", 17 | "build/**", 18 | "native/openfx/include/**" 19 | ] 20 | 21 | [lib] 22 | name="ofx_sys" 23 | crate-type = ["lib"] 24 | path="src/lib.rs" 25 | 26 | [dependencies] 27 | libc = "0.2" 28 | 29 | [build-dependencies] 30 | bindgen = "0.43" 31 | 32 | [dev-dependencies] 33 | cgmath = "0.16" 34 | -------------------------------------------------------------------------------- /ofx-sys/build/macro_constants.h: -------------------------------------------------------------------------------- 1 | enum eOfxStatus { 2 | Unused = -1, 3 | OK = kOfxStatOK, 4 | ReplyDefault = kOfxStatReplyDefault, 5 | Failed = kOfxStatFailed, 6 | ErrFatal = kOfxStatErrFatal, 7 | ErrBadHandle = kOfxStatErrBadHandle, 8 | ErrBadIndex = kOfxStatErrBadIndex, 9 | ErrValue = kOfxStatErrValue, 10 | ErrUnknown = kOfxStatErrUnknown, 11 | ErrMemory = kOfxStatErrMemory, 12 | ErrUnsupported = kOfxStatErrUnsupported, 13 | ErrMissingHostFeature = kOfxStatErrMissingHostFeature, 14 | }; 15 | 16 | #define kOfxImageEffectOpenGLRenderSuite "OfxImageEffectOpenGLRenderSuite" 17 | -------------------------------------------------------------------------------- /ofx-sys/build/main.rs: -------------------------------------------------------------------------------- 1 | extern crate bindgen; 2 | 3 | use std::env; 4 | use std::path::PathBuf; 5 | 6 | fn main() { 7 | // Tell cargo to tell rustc to link a library. 8 | // println!("cargo:rustc-link-lib=openfx"); 9 | let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); 10 | //println!("Bindings generated at {:?}", bindings_path); 11 | let bindings = bindgen::Builder::default() 12 | .clang_arg("-I./native/openfx/include") 13 | .clang_arg("-I./../../native/openfx/include") 14 | .rust_target(bindgen::RustTarget::Nightly) 15 | .rustfmt_bindings(true) 16 | .header("build/wrapper.h") 17 | .generate() 18 | .expect("Unable to generate bindings"); 19 | 20 | // Write the bindings to the $OUT_DIR/bindings.rs file. 21 | bindings 22 | .write_to_file(out_path.join("bindings.rs")) 23 | .expect("Couldn't write bindings!"); 24 | } 25 | -------------------------------------------------------------------------------- /ofx-sys/build/wrapper.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef __cplusplus 3 | #include 4 | #endif 5 | 6 | #include "ofxCore.h" 7 | #include "ofxImageEffect.h" 8 | #include "ofxOld.h" 9 | #include "ofxMemory.h" 10 | #include "ofxPixels.h" 11 | #include "ofxParam.h" 12 | #include "ofxMessage.h" 13 | #include "ofxProperty.h" 14 | #include "ofxDialog.h" 15 | #include "ofxInteract.h" 16 | #include "ofxProgress.h" 17 | #include "ofxKeySyms.h" 18 | #include "ofxMultiThread.h" 19 | #include "ofxNatron.h" 20 | #include "ofxOpenGLRender.h" 21 | #include "ofxParametricParam.h" 22 | #include "ofxSonyVegas.h" 23 | #include "ofxTimeLine.h" 24 | 25 | #include "macro_constants.h" 26 | -------------------------------------------------------------------------------- /ofx-sys/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_upper_case_globals)] 2 | #![allow(non_camel_case_types)] 3 | #![allow(non_snake_case)] 4 | #![allow(clippy::const_static_lifetime)] 5 | #![allow(clippy::unreadable_literal)] 6 | 7 | include!(concat!(env!("OUT_DIR"), "/bindings.rs")); 8 | -------------------------------------------------------------------------------- /ofx/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | ofx 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /ofx/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ofx" 3 | version = "0.3.0" 4 | authors = ["Nicola Orru "] 5 | keywords = ["ofx", "openfx", "effects", "natron", "nuke"] 6 | #edition = "2018" 7 | description = "Bindings for OpenFX 1.4" 8 | repository = "https://github.com/itadinanta/ofx-rs" 9 | license = "Apache-2.0" 10 | # publish = false # insurance against accidents 11 | 12 | [lib] 13 | name="ofx" 14 | crate-type = ["lib"] 15 | path="src/lib.rs" 16 | 17 | [dependencies] 18 | libc = "0.2" 19 | log="0.4" 20 | log4rs = "0.8" 21 | ofx_sys = "0.2" 22 | phf = "0.7" 23 | 24 | -------------------------------------------------------------------------------- /ofx/src/action.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::ffi::{CStr, CString}; 3 | use std::fmt; 4 | use std::fmt::Display; 5 | use std::marker::PhantomData; 6 | 7 | use enums::*; 8 | use handle::*; 9 | use ofx_sys::*; 10 | use plugin::PluginContext; 11 | use result::*; 12 | use types::*; 13 | 14 | #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] 15 | pub enum GlobalAction { 16 | Load, 17 | Describe, 18 | Unload, 19 | PurgeCaches, 20 | SyncPrivateData, 21 | CreateInstance, 22 | DestroyInstance, 23 | BeginInstanceChanged, 24 | InstanceChanged, 25 | EndInstanceChanged, 26 | BeginInstanceEdit, 27 | InstanceEdit, 28 | EndInstanceEdit, 29 | // DescribeInteract, 30 | // CreateInstanceInteract, 31 | // DestroyInstanceInteract, 32 | Dialog, 33 | } 34 | 35 | #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] 36 | pub enum ImageEffectAction { 37 | GetRegionOfDefinition, 38 | GetRegionsOfInterest, 39 | GetTimeDomain, 40 | GetFramesNeeded, 41 | GetClipPreferences, 42 | IsIdentity, 43 | Render, 44 | BeginSequenceRender, 45 | EndSequenceRender, 46 | DescribeInContext, 47 | GetInverseDistortion, 48 | InvokeHelp, 49 | InvokeAbout, 50 | VegasKeyframeUplift, 51 | } 52 | 53 | #[derive(Debug)] 54 | pub enum Action { 55 | Load, 56 | Unload, 57 | Describe(ImageEffectHandle), 58 | DescribeInContext(ImageEffectHandle, DescribeInContextInArgs), 59 | 60 | CreateInstance(ImageEffectHandle), 61 | DestroyInstance(ImageEffectHandle), 62 | 63 | GetRegionOfDefinition( 64 | ImageEffectHandle, 65 | GetRegionOfDefinitionInArgs, 66 | GetRegionOfDefinitionOutArgs, 67 | ), 68 | GetRegionsOfInterest( 69 | ImageEffectHandle, 70 | GetRegionsOfInterestInArgs, 71 | GetRegionsOfInterestOutArgs, 72 | ), 73 | 74 | BeginInstanceChanged(ImageEffectHandle, BeginInstanceChangedInArgs), 75 | InstanceChanged(ImageEffectHandle, InstanceChangedInArgs), 76 | EndInstanceChanged(ImageEffectHandle, EndInstanceChangedInArgs), 77 | 78 | SyncPrivateData(ImageEffectHandle), 79 | 80 | PurgeCaches(ImageEffectHandle), 81 | 82 | BeginSequenceRender(ImageEffectHandle, BeginSequenceRenderInArgs), 83 | Render(ImageEffectHandle, RenderInArgs), 84 | EndSequenceRender(ImageEffectHandle, EndSequenceRenderInArgs), 85 | 86 | GetClipPreferences(ImageEffectHandle, GetClipPreferencesOutArgs), 87 | GetTimeDomain(ImageEffectHandle, GetTimeDomainOutArgs), 88 | IsIdentity(ImageEffectHandle, IsIdentityInArgs, IsIdentityOutArgs), 89 | 90 | GenericGlobal(GlobalAction, GenericPluginHandle), 91 | GenericImageEffect(ImageEffectAction, ImageEffectHandle), 92 | } 93 | 94 | pub trait Execute { 95 | fn execute(&mut self, context: &PluginContext, action: &mut Action) -> Result { 96 | Ok(eOfxStatus_OK) 97 | } 98 | } 99 | 100 | pub trait Filter { 101 | fn before_execute(&mut self, action: &Action) -> Result; 102 | fn after_execute( 103 | &mut self, 104 | context: &PluginContext, 105 | action: &mut Action, 106 | status: Result, 107 | ) -> Result; 108 | } 109 | 110 | pub trait MapAction { 111 | fn map_action( 112 | &self, 113 | action: CharPtr, 114 | handle: VoidPtr, 115 | in_args: OfxPropertySetHandle, 116 | out_args: OfxPropertySetHandle, 117 | ) -> Result; 118 | } 119 | -------------------------------------------------------------------------------- /ofx/src/enums.rs: -------------------------------------------------------------------------------- 1 | use ofx_sys::*; 2 | use std::ffi::CStr; 3 | 4 | pub trait IdentifiedEnum: Sized { 5 | fn to_bytes(&self) -> &'static [u8]; 6 | fn from_bytes(ofx_name: &[u8]) -> Option; 7 | fn from_cstring(ofx_value: &CStr) -> Option { 8 | Self::from_bytes(ofx_value.to_bytes_with_nul()) 9 | } 10 | fn as_ptr(&self) -> *const u8 { 11 | self.to_bytes().as_ptr() 12 | } 13 | } 14 | 15 | // TODO allow mixing 16 | macro_rules! identified_enum { 17 | ($visibility:vis enum $name:ident { 18 | $($key:ident => $value:ident), 19 | * 20 | }) => 21 | { 22 | #[derive(Copy, Clone, Debug)] 23 | $visibility enum $name { 24 | $($key), 25 | * 26 | } 27 | 28 | impl IdentifiedEnum for $name { 29 | fn to_bytes(&self) -> &'static [u8] { 30 | match *self { 31 | $($name::$key => $value), 32 | * 33 | } 34 | } 35 | 36 | fn from_bytes(ofx_name: &[u8]) -> Option { 37 | $(if ofx_name == $value { Some($name::$key) } else) 38 | * 39 | { None } 40 | } 41 | } 42 | }; 43 | ($visibility:vis enum $name:ident { 44 | $($key:ident), 45 | * 46 | }) => { 47 | #[derive(Copy, Clone, Debug, PartialEq)] 48 | $visibility enum $name { 49 | $($key), 50 | * 51 | } 52 | 53 | impl IdentifiedEnum for $name { 54 | fn to_bytes(&self) -> &'static [u8] { 55 | match *self { 56 | $($name::$key => concat_idents!(kOfx, $name, $key)), 57 | * 58 | } 59 | } 60 | 61 | fn from_bytes(ofx_name: &[u8]) -> Option { 62 | // TODO: use PHF or some sort of hashing 63 | $(if ofx_name == &concat_idents!(kOfx, $name, $key)[..] { Some($name::$key) } else) 64 | * 65 | { None } 66 | } 67 | } 68 | } 69 | } 70 | 71 | identified_enum! { 72 | pub enum Type { 73 | ImageEffectHost, 74 | ImageEffect, 75 | ImageEffectInstance, 76 | Parameter, 77 | ParameterInstance, 78 | Clip, 79 | Image 80 | } 81 | } 82 | 83 | identified_enum! { 84 | pub enum ImageEffectContext { 85 | Filter, 86 | General 87 | } 88 | } 89 | 90 | impl ImageEffectContext { 91 | pub fn is_general(self) -> bool { 92 | self == ImageEffectContext::General 93 | } 94 | } 95 | 96 | identified_enum! { 97 | pub enum BitDepth { 98 | Byte, 99 | Short, 100 | Float 101 | } 102 | } 103 | 104 | impl BitDepth { 105 | pub fn bits(self) -> usize { 106 | match self { 107 | BitDepth::Byte => 8, 108 | BitDepth::Short => 16, 109 | BitDepth::Float => 32, 110 | } 111 | } 112 | } 113 | 114 | identified_enum! { 115 | pub enum ImageComponent { 116 | RGBA, 117 | RGB, 118 | Alpha 119 | } 120 | } 121 | 122 | impl ImageComponent { 123 | pub fn is_alpha(self) -> bool { 124 | self == ImageComponent::Alpha 125 | } 126 | 127 | pub fn is_rgb(self) -> bool { 128 | !self.is_alpha() 129 | } 130 | } 131 | 132 | identified_enum! { 133 | pub enum ParamType { 134 | Integer, 135 | Double, 136 | Boolean, 137 | Choice, 138 | RGBA, 139 | RGB, 140 | Integer2D, 141 | Double2D, 142 | Double3D, 143 | Integer3D, 144 | String, 145 | Custom, 146 | Group, 147 | Page, 148 | PushButton 149 | } 150 | } 151 | 152 | identified_enum! { 153 | pub enum ParamDoubleType { 154 | Plain, 155 | Angle, 156 | Scale, 157 | Time, 158 | AbsoluteTime, 159 | X, 160 | XAbsolute, 161 | Y, 162 | YAbsolute, 163 | XY, 164 | XYAbsolute 165 | } 166 | } 167 | 168 | identified_enum! { 169 | pub enum ImageField { 170 | None, 171 | Both, 172 | Lower, 173 | Upper 174 | } 175 | } 176 | 177 | identified_enum! { 178 | pub enum ImageFieldOrder { 179 | None => kOfxImageFieldNone, 180 | Lower => kOfxImageFieldLower, 181 | Upper => kOfxImageFieldUpper 182 | } 183 | } 184 | 185 | identified_enum! { 186 | pub enum ImageFieldExtraction { 187 | Both => kOfxImageFieldBoth, 188 | Single => kOfxImageFieldSingle, 189 | Doubled => kOfxImageFieldDoubled 190 | } 191 | } 192 | 193 | identified_enum! { 194 | pub enum Image { 195 | Opaque, 196 | PreMultiplied, 197 | UnPreMultiplied 198 | } 199 | } 200 | 201 | identified_enum! { 202 | pub enum ParamString { 203 | IsSingleLine, 204 | IsMultiLine, 205 | IsFilePath, 206 | IsDirectoryPath, 207 | IsLabel, 208 | IsRichTextFormat 209 | } 210 | } 211 | 212 | identified_enum! { 213 | pub enum HostNativeOrigin { 214 | BottomLeft, 215 | TopLeft, 216 | Center 217 | } 218 | } 219 | 220 | identified_enum! { 221 | pub enum ImageEffectRender { 222 | Unsafe, 223 | InstanceSafe, 224 | FullySafe 225 | } 226 | } 227 | 228 | identified_enum! { 229 | pub enum Change { 230 | UserEdited, 231 | PluginEdited, 232 | Time 233 | } 234 | } 235 | 236 | identified_enum! { 237 | pub enum ParamInvalidate { 238 | All, 239 | ValueChangeToEnd 240 | } 241 | } 242 | 243 | mod tests { 244 | use super::*; 245 | #[test] 246 | fn auto_enum_names() { 247 | assert!(ImageEffectContext::Filter.to_bytes() == kOfxImageEffectContextFilter); 248 | assert!(ImageEffectContext::General.to_bytes() == kOfxImageEffectContextGeneral); 249 | } 250 | 251 | #[test] 252 | fn from_enum_names() { 253 | assert!( 254 | ImageEffectContext::from_bytes(kOfxImageEffectContextFilter) 255 | == Some(ImageEffectContext::Filter) 256 | ); 257 | assert!( 258 | ImageEffectContext::from_bytes(kOfxImageEffectContextGeneral) 259 | == Some(ImageEffectContext::General) 260 | ); 261 | assert!( 262 | ImageEffectContext::from_bytes(b"OfxImageEffectContextGeneral\0") 263 | == Some(ImageEffectContext::General) 264 | ); 265 | let str_value = 266 | unsafe { CStr::from_bytes_with_nul_unchecked(b"OfxImageEffectContextGeneral\0") }; 267 | assert!(ImageEffectContext::from_cstring(&str_value) == Some(ImageEffectContext::General)); 268 | } 269 | 270 | } 271 | -------------------------------------------------------------------------------- /ofx/src/handle.rs: -------------------------------------------------------------------------------- 1 | use enums::*; 2 | use image::*; 3 | use ofx_sys::*; 4 | use property::*; 5 | use result::*; 6 | use std::borrow::Borrow; 7 | use std::cell::RefCell; 8 | use std::ffi::{CStr, CString}; 9 | use std::fmt; 10 | use std::marker::PhantomData; 11 | use std::rc::Rc; 12 | use types::*; 13 | 14 | #[derive(Debug, Clone)] 15 | pub struct PropertySetHandle { 16 | inner: OfxPropertySetHandle, 17 | property: Rc, 18 | } 19 | 20 | impl PropertySetHandle { 21 | pub(crate) fn new(inner: OfxPropertySetHandle, property: Rc) -> Self { 22 | PropertySetHandle { inner, property } 23 | } 24 | 25 | pub(crate) fn empty() -> Self { 26 | panic!("Do not use, only for type validation testing"); 27 | PropertySetHandle { 28 | inner: std::ptr::null::() as *mut _, 29 | property: unsafe { Rc::new(*std::ptr::null()) }, 30 | } 31 | } 32 | } 33 | 34 | #[derive(Clone)] 35 | pub struct GenericPluginHandle { 36 | inner: VoidPtr, 37 | property: &'static OfxPropertySuiteV1, 38 | } 39 | 40 | #[derive(Clone)] 41 | pub struct ImageEffectHost { 42 | inner: OfxPropertySetHandle, 43 | property: Rc, 44 | } 45 | 46 | impl ImageEffectHost { 47 | pub fn new(host: OfxPropertySetHandle, property: Rc) -> Self { 48 | ImageEffectHost { 49 | inner: host, 50 | property, 51 | } 52 | } 53 | } 54 | 55 | #[derive(Clone)] 56 | pub struct ImageEffectHandle { 57 | inner: OfxImageEffectHandle, 58 | property: Rc, 59 | image_effect: Rc, 60 | parameter: Rc, 61 | } 62 | 63 | #[derive(Clone)] 64 | pub struct ClipInstance { 65 | inner: OfxImageClipHandle, 66 | inner_properties: OfxPropertySetHandle, 67 | property: Rc, 68 | image_effect: Rc, 69 | } 70 | 71 | #[derive(Clone)] 72 | pub struct Image { 73 | inner: OfxPropertySetHandle, 74 | property: Rc, 75 | image_effect: Rc, 76 | } 77 | 78 | pub trait ParamHandleValue: Default + Clone {} 79 | impl ParamHandleValue for Int {} 80 | impl ParamHandleValue for Bool {} 81 | impl ParamHandleValue for Double {} 82 | 83 | pub trait ParamHandleValueDefault: ParamHandleValue + Default {} 84 | impl ParamHandleValueDefault for Int {} 85 | impl ParamHandleValueDefault for Double {} 86 | 87 | #[derive(Clone)] 88 | pub struct ParamHandle 89 | where 90 | T: ParamHandleValue, 91 | { 92 | inner: OfxParamHandle, 93 | inner_properties: OfxPropertySetHandle, 94 | property: Rc, 95 | parameter: Rc, 96 | _type: PhantomData, 97 | } 98 | 99 | #[derive(Clone)] 100 | pub struct ParamSetHandle { 101 | inner: OfxParamSetHandle, 102 | property: Rc, 103 | parameter: Rc, 104 | } 105 | 106 | // TODO: custom_derive? 107 | macro_rules! trivial_debug { 108 | ($($struct:ty),*) => { 109 | $(impl fmt::Debug for $struct { 110 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 111 | write!(f, "{} {{...}}", stringify!($struct)) 112 | } 113 | }) 114 | * 115 | } 116 | } 117 | 118 | trivial_debug!( 119 | ClipInstance, 120 | ImageEffectHandle, 121 | GenericPluginHandle, 122 | ImageEffectHost 123 | ); 124 | 125 | impl ImageEffectHandle { 126 | pub fn new( 127 | inner: OfxImageEffectHandle, 128 | property: Rc, 129 | image_effect: Rc, 130 | parameter: Rc, 131 | ) -> Self { 132 | ImageEffectHandle { 133 | inner, 134 | property, 135 | image_effect, 136 | parameter, 137 | } 138 | } 139 | } 140 | 141 | impl ParamHandle 142 | where 143 | T: ParamHandleValue + Default, 144 | { 145 | pub fn new( 146 | inner: OfxParamHandle, 147 | inner_properties: OfxPropertySetHandle, 148 | property: Rc, 149 | parameter: Rc, 150 | ) -> Self { 151 | ParamHandle { 152 | inner, 153 | inner_properties, 154 | property, 155 | parameter, 156 | _type: PhantomData, 157 | } 158 | } 159 | } 160 | 161 | impl ParamHandle 162 | where 163 | T: ParamHandleValueDefault, 164 | { 165 | pub fn get_value(&self) -> Result { 166 | let mut value: T = T::default(); 167 | suite_fn!(paramGetValue in self.parameter; self.inner, &mut value as *mut T)?; 168 | Ok(value) 169 | } 170 | 171 | pub fn get_value_at_time(&self, time: Time) -> Result { 172 | let mut value: T = T::default(); 173 | suite_fn!(paramGetValueAtTime in self.parameter; self.inner, time, &mut value as *mut T)?; 174 | Ok(value) 175 | } 176 | } 177 | 178 | impl ParamHandle { 179 | pub fn get_value(&self) -> Result { 180 | let mut value: Int = 0; 181 | suite_fn!(paramGetValue in self.parameter; self.inner, &mut value as *mut Int)?; 182 | Ok(value != 0) 183 | } 184 | 185 | pub fn get_value_at_time(&self, time: Time) -> Result { 186 | let mut value: Int = 0; 187 | suite_fn!(paramGetValueAtTime in self.parameter; self.inner, time, &mut value as *mut Int)?; 188 | Ok(value != 0) 189 | } 190 | } 191 | 192 | impl ClipInstance { 193 | pub fn new( 194 | inner: OfxImageClipHandle, 195 | inner_properties: OfxPropertySetHandle, 196 | property: Rc, 197 | image_effect: Rc, 198 | ) -> Self { 199 | ClipInstance { 200 | inner, 201 | inner_properties, 202 | property, 203 | image_effect, 204 | } 205 | } 206 | 207 | pub fn get_region_of_definition(&self, time: Time) -> Result { 208 | let mut value = RectD { 209 | x1: 0.0, 210 | y1: 0.0, 211 | x2: 0.0, 212 | y2: 0.0, 213 | }; 214 | suite_fn!(clipGetRegionOfDefinition in self.image_effect; self.inner, time, &mut value as *mut RectD)?; 215 | Ok(value) 216 | } 217 | 218 | pub fn get_image_mut(&mut self, time: Time) -> Result>> { 219 | self.get_image_rect_mut(time, None) 220 | } 221 | 222 | pub fn get_image(&self, time: Time) -> Result> { 223 | self.get_image_rect(time, None) 224 | } 225 | 226 | pub fn get_image_rect(&self, time: Time, region: Option) -> Result> { 227 | let mut image: OfxPropertySetHandle = std::ptr::null_mut(); 228 | let region_ptr = region 229 | .as_ref() 230 | .map(|m| m as *const RectD) 231 | .unwrap_or(std::ptr::null()); 232 | suite_fn!(clipGetImage in self.image_effect; self.inner, time, region_ptr, &mut image as *mut OfxPropertySetHandle)?; 233 | Ok(Rc::new(Image::new( 234 | image, 235 | self.property.clone(), 236 | self.image_effect.clone(), 237 | ))) 238 | } 239 | 240 | pub fn get_image_rect_mut( 241 | &mut self, 242 | time: Time, 243 | region: Option, 244 | ) -> Result>> { 245 | let mut image: OfxPropertySetHandle = std::ptr::null_mut(); 246 | let region_ptr = region 247 | .as_ref() 248 | .map(|m| m as *const RectD) 249 | .unwrap_or(std::ptr::null()); 250 | suite_fn!(clipGetImage in self.image_effect; self.inner, time, region_ptr, &mut image as *mut OfxPropertySetHandle)?; 251 | Ok(Rc::new(RefCell::new(Image::new( 252 | image, 253 | self.property.clone(), 254 | self.image_effect.clone(), 255 | )))) 256 | } 257 | } 258 | 259 | impl Drop for Image { 260 | fn drop(&mut self) { 261 | self.drop_image() 262 | .expect("Unable to drop image handle. This is likely a bug"); 263 | } 264 | } 265 | 266 | impl Image { 267 | pub fn new( 268 | inner: OfxPropertySetHandle, 269 | property: Rc, 270 | image_effect: Rc, 271 | ) -> Self { 272 | Image { 273 | inner, 274 | property, 275 | image_effect, 276 | } 277 | } 278 | 279 | pub fn get_descriptor(&self) -> Result> 280 | where 281 | T: PixelFormat, 282 | { 283 | let bounds = self.get_bounds()?; 284 | let row_bytes = self.get_row_bytes()?; 285 | let ptr = self.get_data()?; 286 | 287 | Ok(ImageDescriptor::new(bounds, row_bytes, ptr)) 288 | } 289 | 290 | fn get_descriptor_mut(&mut self) -> Result> 291 | where 292 | T: PixelFormat, 293 | { 294 | let bounds = self.get_bounds()?; 295 | let row_bytes = self.get_row_bytes()?; 296 | let mut ptr = self.get_data()?; 297 | 298 | Ok(ImageDescriptorMut::new(bounds, row_bytes, ptr)) 299 | } 300 | 301 | pub fn get_tiles_mut(&mut self, count: usize) -> Result>> 302 | where 303 | T: PixelFormat, 304 | { 305 | let bounds = self.get_bounds()?; 306 | let row_bytes = self.get_row_bytes()?; 307 | let mut ptr = self.get_data()?; 308 | 309 | Ok(ImageDescriptorMut::new(bounds, row_bytes, ptr).into_tiles(count)) 310 | } 311 | 312 | fn drop_image(&mut self) -> Result<()> { 313 | debug!("Releasing data for ImageHandle {:?}", self.inner); 314 | suite_fn!(clipReleaseImage in self.image_effect; self.inner) 315 | } 316 | } 317 | 318 | impl HasProperties for ClipInstance { 319 | fn properties(&self) -> Result { 320 | Ok(ClipDescriptor::new( 321 | self.inner_properties, 322 | self.property.clone(), 323 | )) 324 | } 325 | } 326 | 327 | trait IsPropertiesNewType { 328 | fn wrap(inner: PropertySetHandle) -> Self; 329 | } 330 | 331 | pub trait PropertiesNewTypeConstructor { 332 | fn build(host: OfxPropertySetHandle, property: Rc) -> Self; 333 | } 334 | 335 | #[inline] 336 | pub fn build_typed(host: OfxPropertySetHandle, property: Rc) -> T 337 | where 338 | T: PropertiesNewTypeConstructor, 339 | { 340 | T::build(host, property) 341 | } 342 | 343 | macro_rules! properties_newtype { 344 | ($name:ident) => { 345 | #[derive(Clone)] 346 | pub struct $name(PropertySetHandle); 347 | 348 | impl IsPropertiesNewType for $name { 349 | fn wrap(inner: PropertySetHandle) -> Self { 350 | $name(inner) 351 | } 352 | } 353 | 354 | impl PropertiesNewTypeConstructor for $name { 355 | fn build(host: OfxPropertySetHandle, property: Rc) -> Self { 356 | $name::new(host, property) 357 | } 358 | } 359 | 360 | impl $name { 361 | pub fn new(host: OfxPropertySetHandle, property: Rc) -> Self { 362 | $name(PropertySetHandle::new(host, property)) 363 | } 364 | } 365 | 366 | impl<'a> AsProperties for $name { 367 | fn handle(&self) -> OfxPropertySetHandle { 368 | self.0.inner 369 | } 370 | fn suite(&self) -> *const OfxPropertySuiteV1 { 371 | self.0.property.borrow() as *const _ 372 | } 373 | } 374 | 375 | trivial_debug!($name); 376 | }; 377 | } 378 | 379 | properties_newtype!(HostProperties); 380 | properties_newtype!(EffectDescriptor); 381 | properties_newtype!(EffectInstance); 382 | properties_newtype!(ClipDescriptor); 383 | 384 | properties_newtype!(DescribeInContextInArgs); 385 | 386 | properties_newtype!(GetRegionOfDefinitionInArgs); 387 | properties_newtype!(GetRegionOfDefinitionOutArgs); 388 | 389 | properties_newtype!(GetRegionsOfInterestInArgs); 390 | properties_newtype!(GetRegionsOfInterestOutArgs); 391 | 392 | properties_newtype!(GetClipPreferencesOutArgs); 393 | 394 | properties_newtype!(IsIdentityInArgs); 395 | properties_newtype!(IsIdentityOutArgs); 396 | 397 | properties_newtype!(BeginInstanceChangedInArgs); 398 | 399 | properties_newtype!(InstanceChangedInArgs); 400 | properties_newtype!(InstanceChangedOutArgs); 401 | 402 | properties_newtype!(EndInstanceChangedInArgs); 403 | properties_newtype!(EndInstanceChangedOutArgs); 404 | 405 | properties_newtype!(GetTimeDomainOutArgs); 406 | 407 | properties_newtype!(BeginSequenceRenderInArgs); 408 | properties_newtype!(RenderInArgs); 409 | properties_newtype!(EndSequenceRenderInArgs); 410 | 411 | properties_newtype!(ParamDouble); 412 | properties_newtype!(ParamInt); 413 | properties_newtype!(ParamBoolean); 414 | properties_newtype!(ParamPage); 415 | properties_newtype!(ParamGroup); 416 | 417 | properties_newtype!(ParameterSet); 418 | 419 | impl DescribeInContextInArgs {} 420 | 421 | impl HasProperties for ImageEffectHandle { 422 | fn properties(&self) -> Result { 423 | let property_set_handle = { 424 | let mut property_set_handle = std::ptr::null_mut(); 425 | 426 | suite_fn!(getPropertySet in self.image_effect; self.inner, &mut property_set_handle as *mut _)?; 427 | 428 | property_set_handle 429 | }; 430 | Ok(EffectInstance(PropertySetHandle::new( 431 | property_set_handle, 432 | self.property.clone(), 433 | ))) 434 | } 435 | } 436 | 437 | impl HasProperties for ImageEffectHandle { 438 | fn properties(&self) -> Result { 439 | let property_set_handle = { 440 | let mut property_set_handle = std::ptr::null_mut(); 441 | 442 | suite_fn!(getPropertySet in self.image_effect; self.inner, &mut property_set_handle as *mut _)?; 443 | 444 | property_set_handle 445 | }; 446 | Ok(EffectDescriptor(PropertySetHandle::new( 447 | property_set_handle, 448 | self.property.clone(), 449 | ))) 450 | } 451 | } 452 | 453 | 454 | impl ImageEffectHandle { 455 | fn clip_define(&self, clip_name: &[u8]) -> Result { 456 | let property_set_handle = { 457 | let mut property_set_handle = std::ptr::null_mut(); 458 | suite_fn!(clipDefine in self.image_effect; 459 | self.inner, clip_name.as_ptr() as *const i8, &mut property_set_handle as *mut _)?; 460 | property_set_handle 461 | }; 462 | Ok(ClipDescriptor(PropertySetHandle::new( 463 | property_set_handle, 464 | self.property.clone(), 465 | ))) 466 | } 467 | 468 | fn clip_get_handle(&self, clip_name: &[u8]) -> Result { 469 | let (clip_handle, clip_properties) = { 470 | let mut clip_handle = std::ptr::null_mut(); 471 | let mut clip_properties = std::ptr::null_mut(); 472 | suite_fn!(clipGetHandle in self.image_effect; 473 | self.inner, clip_name.as_ptr() as *const i8, &mut clip_handle as *mut _, &mut clip_properties as *mut _)?; 474 | (clip_handle, clip_properties) 475 | }; 476 | Ok(ClipInstance::new( 477 | clip_handle, 478 | clip_properties, 479 | self.property.clone(), 480 | self.image_effect.clone(), 481 | )) 482 | } 483 | 484 | pub fn abort(&self) -> Result { 485 | Ok(suite_call!(abort in self.image_effect; self.inner) != 0) 486 | } 487 | 488 | pub fn parameter_set(&self) -> Result { 489 | let parameters_set_handle = { 490 | let mut parameters_set_handle = std::ptr::null_mut(); 491 | suite_fn!(getParamSet in self.image_effect; self.inner, &mut parameters_set_handle as *mut _)?; 492 | parameters_set_handle 493 | }; 494 | Ok(ParamSetHandle::new( 495 | parameters_set_handle, 496 | self.parameter.clone(), 497 | self.property.clone(), 498 | )) 499 | } 500 | 501 | pub fn get_output_clip(&self) -> Result { 502 | self.clip_get_handle(ofx_sys::kOfxImageEffectOutputClipName) 503 | } 504 | 505 | pub fn get_simple_input_clip(&self) -> Result { 506 | self.clip_get_handle(ofx_sys::kOfxImageEffectSimpleSourceClipName) 507 | } 508 | 509 | pub fn get_clip(&self, name: &str) -> Result { 510 | let str_buf = CString::new(name)?.into_bytes_with_nul(); 511 | self.clip_get_handle(&str_buf) 512 | } 513 | 514 | pub fn new_output_clip(&self) -> Result { 515 | self.clip_define(ofx_sys::kOfxImageEffectOutputClipName) 516 | } 517 | 518 | pub fn new_simple_input_clip(&self) -> Result { 519 | self.clip_define(ofx_sys::kOfxImageEffectSimpleSourceClipName) 520 | } 521 | 522 | pub fn new_clip(&self, name: &str) -> Result { 523 | let str_buf = CString::new(name)?.into_bytes_with_nul(); 524 | self.clip_define(&str_buf) 525 | } 526 | 527 | unsafe fn get_pointer(&self) -> Result<*mut [u8]> { 528 | Err(Error::Unimplemented) 529 | } 530 | 531 | pub fn set_instance_data(&mut self, data: T) -> Result<()> 532 | where 533 | T: Sized, 534 | { 535 | let mut effect_props: EffectInstance = self.properties()?; 536 | let data_box = Box::new(data); 537 | let data_ptr = Box::into_raw(data_box); 538 | let status = suite_fn!(propSetPointer in self.property; 539 | effect_props.0.inner, kOfxPropInstanceData.as_ptr() as *const i8, 0, data_ptr as *mut _); 540 | if status.is_err() { 541 | unsafe { 542 | Box::from_raw(data_ptr); 543 | } 544 | } 545 | status 546 | } 547 | 548 | fn get_instance_data_ptr(&self) -> Result { 549 | let mut effect_props: EffectInstance = self.properties()?; 550 | let mut data_ptr = std::ptr::null_mut(); 551 | to_result! { suite_call!(propGetPointer in self.property; 552 | effect_props.0.inner, kOfxPropInstanceData.as_ptr() as *const i8, 0, &mut data_ptr) 553 | => data_ptr } 554 | } 555 | 556 | // TODO: this is not safe enough 557 | pub fn get_instance_data(&self) -> Result<&mut T> 558 | where 559 | T: Sized, 560 | { 561 | unsafe { 562 | let mut ptr = self.get_instance_data_ptr()?; 563 | let mut reference = ptr as *mut T; 564 | Ok(&mut *reference) 565 | } 566 | } 567 | 568 | pub fn drop_instance_data(&mut self) -> Result<()> { 569 | unsafe { 570 | let mut ptr = self.get_instance_data_ptr()?; 571 | if !ptr.is_null() { 572 | Box::from_raw(ptr); 573 | } 574 | } 575 | Ok(()) 576 | } 577 | } 578 | 579 | impl ParamSetHandle { 580 | pub fn new( 581 | inner: OfxParamSetHandle, 582 | parameter: Rc, 583 | property: Rc, 584 | ) -> Self { 585 | ParamSetHandle { 586 | inner, 587 | parameter, 588 | property, 589 | } 590 | } 591 | 592 | fn param_define(&mut self, param_type: ParamType, name: &str) -> Result 593 | where 594 | T: IsPropertiesNewType, 595 | { 596 | let name_buf = CString::new(name)?.into_bytes_with_nul(); 597 | let property_set_handle = { 598 | let mut property_set_handle = std::ptr::null_mut(); 599 | suite_fn!(paramDefine in self.parameter; 600 | self.inner, param_type.as_ptr() as *const _, name_buf.as_ptr() as *const _, &mut property_set_handle as *mut _)?; 601 | 602 | property_set_handle 603 | }; 604 | Ok(T::wrap(PropertySetHandle::new( 605 | property_set_handle, 606 | self.property.clone(), 607 | ))) 608 | } 609 | 610 | pub fn parameter(&self, name: &str) -> Result> 611 | where 612 | T: ParamHandleValue, 613 | { 614 | let name_buf = CString::new(name)?.into_bytes_with_nul(); 615 | let (param_handle, param_properties) = { 616 | let mut param_handle = std::ptr::null_mut(); 617 | let mut param_properties = std::ptr::null_mut(); 618 | suite_fn!(paramGetHandle in self.parameter; 619 | self.inner, name_buf.as_ptr() as *const _, &mut param_handle as *mut _, &mut param_properties as *mut _)?; 620 | (param_handle, param_properties) 621 | }; 622 | Ok(ParamHandle::new( 623 | param_handle, 624 | param_properties, 625 | self.property.clone(), 626 | self.parameter.clone(), 627 | )) 628 | } 629 | 630 | pub fn param_define_double(&mut self, name: &str) -> Result { 631 | self.param_define(ParamType::Double, name) 632 | } 633 | 634 | pub fn param_define_int(&mut self, name: &str) -> Result { 635 | self.param_define(ParamType::Integer, name) 636 | } 637 | 638 | pub fn param_define_boolean(&mut self, name: &str) -> Result { 639 | self.param_define(ParamType::Boolean, name) 640 | } 641 | 642 | pub fn param_define_group(&mut self, name: &str) -> Result { 643 | self.param_define(ParamType::Group, name) 644 | } 645 | 646 | pub fn param_define_page(&mut self, name: &str) -> Result { 647 | self.param_define(ParamType::Page, name) 648 | } 649 | } 650 | 651 | impl AsProperties for ImageEffectHost { 652 | fn handle(&self) -> OfxPropertySetHandle { 653 | self.inner 654 | } 655 | fn suite(&self) -> *const OfxPropertySuiteV1 { 656 | self.property.borrow() as *const _ 657 | } 658 | } 659 | 660 | impl AsProperties for ClipInstance { 661 | fn handle(&self) -> OfxPropertySetHandle { 662 | self.inner_properties 663 | } 664 | fn suite(&self) -> *const OfxPropertySuiteV1 { 665 | self.property.borrow() as *const _ 666 | } 667 | } 668 | 669 | impl AsProperties for Image { 670 | fn handle(&self) -> OfxPropertySetHandle { 671 | self.inner 672 | } 673 | fn suite(&self) -> *const OfxPropertySuiteV1 { 674 | self.property.borrow() as *const _ 675 | } 676 | } 677 | 678 | impl AsProperties for ParamHandle 679 | where 680 | T: ParamHandleValue, 681 | { 682 | fn handle(&self) -> OfxPropertySetHandle { 683 | self.inner_properties 684 | } 685 | fn suite(&self) -> *const OfxPropertySuiteV1 { 686 | self.property.borrow() as *const _ 687 | } 688 | } 689 | 690 | mod tests { 691 | use super::*; 692 | use property; 693 | use property::*; 694 | 695 | // do not run, just compile! 696 | fn prop_host() { 697 | let mut handle = EffectInstance(PropertySetHandle::empty()); 698 | 699 | handle.get::(); 700 | handle.get::(); 701 | } 702 | } 703 | -------------------------------------------------------------------------------- /ofx/src/image.rs: -------------------------------------------------------------------------------- 1 | use enums::{BitDepth, ImageComponent}; 2 | use std::marker::PhantomData; 3 | use types::*; 4 | 5 | pub trait ChannelFormat { 6 | fn range_max() -> f32; 7 | fn from_f32(src: f32) -> Self; 8 | fn to_f32(&self) -> f32; 9 | } 10 | 11 | impl ChannelFormat for f32 { 12 | #[inline] 13 | fn range_max() -> f32 { 14 | 1.0 15 | } 16 | #[inline] 17 | fn from_f32(src: f32) -> Self { 18 | src 19 | } 20 | #[inline] 21 | fn to_f32(&self) -> f32 { 22 | *self 23 | } 24 | } 25 | 26 | impl ChannelFormat for u16 { 27 | #[inline] 28 | fn range_max() -> f32 { 29 | f32::from(std::u16::MAX) 30 | } 31 | fn from_f32(src: f32) -> Self { 32 | let clamp = Self::range_max(); 33 | clamp.min(src * clamp) as Self 34 | } 35 | #[inline] 36 | fn to_f32(&self) -> f32 { 37 | f32::from(*self) / Self::range_max() 38 | } 39 | } 40 | 41 | impl ChannelFormat for u8 { 42 | #[inline] 43 | fn range_max() -> f32 { 44 | f32::from(std::u8::MAX) 45 | } 46 | #[inline] 47 | fn from_f32(src: f32) -> Self { 48 | let clamp = Self::range_max(); 49 | clamp.min(src * clamp) as Self 50 | } 51 | #[inline] 52 | fn to_f32(&self) -> f32 { 53 | f32::from(*self) / Self::range_max() 54 | } 55 | } 56 | 57 | pub trait PixelFormatRGB: PixelFormat { 58 | fn r(&self) -> &Self::ChannelValue; 59 | fn r_mut(&mut self) -> &mut Self::ChannelValue; 60 | fn g(&self) -> &Self::ChannelValue; 61 | fn g_mut(&mut self) -> &mut Self::ChannelValue; 62 | fn b(&self) -> &Self::ChannelValue; 63 | fn b_mut(&mut self) -> &mut Self::ChannelValue; 64 | } 65 | 66 | pub trait PixelFormatRGBA: PixelFormatRGB { 67 | fn new( 68 | r: Self::ChannelValue, 69 | g: Self::ChannelValue, 70 | b: Self::ChannelValue, 71 | a: Self::ChannelValue, 72 | ) -> Self; 73 | fn a(&self) -> &Self::ChannelValue; 74 | fn a_mut(&mut self) -> &mut Self::ChannelValue; 75 | } 76 | pub trait PixelFormatAlpha: PixelFormat + ChannelFormat {} 77 | 78 | pub trait ScaleMix { 79 | fn scaled(&self, scale: &RGBAColourD) -> Self; 80 | fn mix(&self, wet: &Self, mix: f32) -> Self; 81 | } 82 | 83 | impl ScaleMix for T 84 | where 85 | T: PixelFormatRGBA, 86 | { 87 | fn scaled(&self, scale: &RGBAColourD) -> Self { 88 | T::new( 89 | T::ChannelValue::from_f32(self.r().to_f32() * scale.r as f32), 90 | T::ChannelValue::from_f32(self.g().to_f32() * scale.g as f32), 91 | T::ChannelValue::from_f32(self.b().to_f32() * scale.b as f32), 92 | T::ChannelValue::from_f32(self.a().to_f32() * scale.a as f32), 93 | ) 94 | } 95 | 96 | fn mix(&self, wet: &Self, mix: f32) -> Self { 97 | if mix <= 0.0 { 98 | *self 99 | } else if mix >= 1.0 { 100 | *wet 101 | } else { 102 | let a0 = mix; 103 | let a1 = 1.0 - a0; 104 | T::new( 105 | T::ChannelValue::from_f32(wet.r().to_f32() * a0 + self.r().to_f32() * a1), 106 | T::ChannelValue::from_f32(wet.g().to_f32() * a0 + self.g().to_f32() * a1), 107 | T::ChannelValue::from_f32(wet.b().to_f32() * a0 + self.b().to_f32() * a1), 108 | T::ChannelValue::from_f32(wet.a().to_f32() * a0 + self.a().to_f32() * a1), 109 | ) 110 | } 111 | } 112 | } 113 | 114 | impl ScaleMix for f32 { 115 | fn scaled(&self, scale: &RGBAColourD) -> Self { 116 | *self * scale.a as f32 117 | } 118 | 119 | fn mix(&self, wet: &Self, mix: f32) -> Self { 120 | if mix <= 0.0 { 121 | *self 122 | } else if mix >= 1.0 { 123 | *wet 124 | } else { 125 | let a0 = mix; 126 | let a1 = 1.0 - a0; 127 | wet.to_f32() * a0 + self.to_f32() * a1 128 | } 129 | } 130 | } 131 | 132 | impl ScaleMix for u8 { 133 | fn scaled(&self, scale: &RGBAColourD) -> Self { 134 | u8::from_f32(self.to_f32() * scale.a as f32) 135 | } 136 | 137 | fn mix(&self, wet: &Self, mix: f32) -> Self { 138 | if mix <= 0.0 { 139 | *self 140 | } else if mix >= 1.0 { 141 | *wet 142 | } else { 143 | let a0 = mix; 144 | let a1 = 1.0 - a0; 145 | u8::from_f32(wet.to_f32() * a0 + self.to_f32() * a1) 146 | } 147 | } 148 | } 149 | 150 | impl ScaleMix for u16 { 151 | fn scaled(&self, scale: &RGBAColourD) -> Self { 152 | u16::from_f32(self.to_f32() * scale.a as f32) 153 | } 154 | 155 | fn mix(&self, wet: &Self, mix: f32) -> Self { 156 | if mix <= 0.0 { 157 | *self 158 | } else if mix >= 1.0 { 159 | *wet 160 | } else { 161 | let a0 = mix; 162 | let a1 = 1.0 - a0; 163 | u16::from_f32(wet.to_f32() * a0 + self.to_f32() * a1) 164 | } 165 | } 166 | } 167 | 168 | pub trait PixelFormat: Sized + Copy + Clone { 169 | type ChannelValue: ChannelFormat; 170 | 171 | #[inline] 172 | fn num_components() -> usize { 173 | std::mem::size_of::() / std::mem::size_of::() 174 | } 175 | 176 | #[inline] 177 | fn components() -> ImageComponent { 178 | match Self::num_components() { 179 | 1 => ImageComponent::Alpha, 180 | 3 => ImageComponent::RGB, 181 | 4 => ImageComponent::RGBA, 182 | _ => ImageComponent::RGBA, //? 183 | } 184 | } 185 | 186 | fn channel(&self, i: usize) -> &Self::ChannelValue; 187 | fn channel_mut(&mut self, i: usize) -> &mut Self::ChannelValue; 188 | 189 | #[inline] 190 | fn num_bits_depth() -> usize { 191 | 8 * std::mem::size_of::() 192 | } 193 | 194 | #[inline] 195 | fn bit_depth() -> BitDepth { 196 | match Self::num_bits_depth() { 197 | 8 => BitDepth::Byte, 198 | 16 => BitDepth::Short, 199 | 32 => BitDepth::Float, 200 | _ => BitDepth::Float, 201 | } 202 | } 203 | } 204 | 205 | macro_rules! pixel_format_rgba { 206 | ($rgba:ty, $channel_value:ty) => { 207 | impl PixelFormat for $rgba { 208 | type ChannelValue = $channel_value; 209 | fn channel(&self, i: usize) -> &Self::ChannelValue { 210 | match i { 211 | 0 => &self.r, 212 | 1 => &self.g, 213 | 2 => &self.b, 214 | 3 => &self.a, 215 | _ => panic!("Index out of range"), 216 | } 217 | } 218 | fn channel_mut(&mut self, i: usize) -> &mut Self::ChannelValue { 219 | match i { 220 | 0 => &mut self.r, 221 | 1 => &mut self.g, 222 | 2 => &mut self.b, 223 | 3 => &mut self.a, 224 | _ => panic!("Index out of range"), 225 | } 226 | } 227 | } 228 | 229 | impl PixelFormatRGB for $rgba { 230 | #[inline] 231 | fn r(&self) -> &Self::ChannelValue { 232 | &self.r 233 | } 234 | #[inline] 235 | fn r_mut(&mut self) -> &mut Self::ChannelValue { 236 | &mut self.r 237 | } 238 | #[inline] 239 | fn g(&self) -> &Self::ChannelValue { 240 | &self.g 241 | } 242 | #[inline] 243 | fn g_mut(&mut self) -> &mut Self::ChannelValue { 244 | &mut self.g 245 | } 246 | #[inline] 247 | fn b(&self) -> &Self::ChannelValue { 248 | &self.b 249 | } 250 | #[inline] 251 | fn b_mut(&mut self) -> &mut Self::ChannelValue { 252 | &mut self.b 253 | } 254 | } 255 | 256 | impl PixelFormatRGBA for $rgba { 257 | fn new( 258 | r: Self::ChannelValue, 259 | g: Self::ChannelValue, 260 | b: Self::ChannelValue, 261 | a: Self::ChannelValue, 262 | ) -> Self { 263 | let mut instance: $rgba = unsafe { std::mem::uninitialized() }; 264 | instance.r = r; 265 | instance.g = g; 266 | instance.b = b; 267 | instance.a = a; 268 | instance 269 | } 270 | 271 | #[inline] 272 | fn a(&self) -> &Self::ChannelValue { 273 | &self.a 274 | } 275 | #[inline] 276 | fn a_mut(&mut self) -> &mut Self::ChannelValue { 277 | &mut self.a 278 | } 279 | } 280 | }; 281 | } 282 | 283 | macro_rules! pixel_format_yuva { 284 | ($yuva:ty, $channel_value:ty) => { 285 | impl PixelFormat for $yuva { 286 | type ChannelValue = $channel_value; 287 | fn channel(&self, i: usize) -> &Self::ChannelValue { 288 | match i { 289 | 0 => &self.y, 290 | 1 => &self.u, 291 | 2 => &self.v, 292 | 3 => &self.a, 293 | _ => panic!("Index out of range"), 294 | } 295 | } 296 | fn channel_mut(&mut self, i: usize) -> &mut Self::ChannelValue { 297 | match i { 298 | 0 => &mut self.y, 299 | 1 => &mut self.u, 300 | 2 => &mut self.v, 301 | 3 => &mut self.a, 302 | _ => panic!("Index out of range"), 303 | } 304 | } 305 | } 306 | }; 307 | } 308 | 309 | macro_rules! pixel_format_rgb { 310 | ($rgb:ty, $channel_value:ty) => { 311 | impl PixelFormat for $rgb { 312 | type ChannelValue = $channel_value; 313 | fn channel(&self, i: usize) -> &Self::ChannelValue { 314 | match i { 315 | 0 => &self.r, 316 | 1 => &self.g, 317 | 2 => &self.b, 318 | _ => panic!("Index out of range"), 319 | } 320 | } 321 | fn channel_mut(&mut self, i: usize) -> &mut Self::ChannelValue { 322 | match i { 323 | 0 => &mut self.r, 324 | 1 => &mut self.g, 325 | 2 => &mut self.b, 326 | _ => panic!("Index out of range"), 327 | } 328 | } 329 | } 330 | 331 | impl PixelFormatRGB for $rgb { 332 | #[inline] 333 | fn r(&self) -> &Self::ChannelValue { 334 | &self.r 335 | } 336 | #[inline] 337 | fn r_mut(&mut self) -> &mut Self::ChannelValue { 338 | &mut self.r 339 | } 340 | #[inline] 341 | fn g(&self) -> &Self::ChannelValue { 342 | &self.g 343 | } 344 | #[inline] 345 | fn g_mut(&mut self) -> &mut Self::ChannelValue { 346 | &mut self.g 347 | } 348 | #[inline] 349 | fn b(&self) -> &Self::ChannelValue { 350 | &self.b 351 | } 352 | #[inline] 353 | fn b_mut(&mut self) -> &mut Self::ChannelValue { 354 | &mut self.b 355 | } 356 | } 357 | }; 358 | } 359 | 360 | macro_rules! pixel_format_alpha { 361 | ($channel_value:ty) => { 362 | impl PixelFormat for $channel_value { 363 | type ChannelValue = $channel_value; 364 | fn channel(&self, i: usize) -> &Self::ChannelValue { 365 | self 366 | } 367 | fn channel_mut(&mut self, i: usize) -> &mut Self::ChannelValue { 368 | self 369 | } 370 | } 371 | 372 | impl PixelFormatAlpha for $channel_value {} 373 | }; 374 | } 375 | 376 | pixel_format_rgba!(RGBAColourB, u8); 377 | pixel_format_rgba!(RGBAColourS, u16); 378 | pixel_format_rgba!(RGBAColourF, f32); 379 | pixel_format_rgb!(RGBColourB, u8); 380 | pixel_format_rgb!(RGBColourS, u16); 381 | pixel_format_rgb!(RGBColourF, f32); 382 | pixel_format_alpha!(u8); 383 | pixel_format_alpha!(u16); 384 | pixel_format_alpha!(f32); 385 | pixel_format_yuva!(YUVAColourB, u8); 386 | pixel_format_yuva!(YUVAColourS, u16); 387 | pixel_format_yuva!(YUVAColourF, f32); 388 | 389 | pub struct ImageBuffer<'a, T> 390 | where 391 | T: PixelFormat, 392 | { 393 | bounds: RectI, 394 | row_bytes: isize, 395 | t_size: usize, 396 | data: &'a mut u8, 397 | pixel_type: PhantomData, 398 | } 399 | 400 | impl<'a, T> Clone for ImageBuffer<'a, T> 401 | where 402 | T: PixelFormat, 403 | { 404 | fn clone(&self) -> Self { 405 | // TODO: possibly unsound 406 | let data = self.data as *const u8; 407 | ImageBuffer { 408 | bounds: self.bounds, 409 | row_bytes: self.row_bytes, 410 | t_size: self.t_size, 411 | data: unsafe { &mut *(data as *mut u8) }, 412 | pixel_type: PhantomData, 413 | } 414 | } 415 | } 416 | 417 | impl<'a, T> ImageBuffer<'a, T> 418 | where 419 | T: PixelFormat, 420 | { 421 | fn new(bounds: RectI, row_bytes: Int, data: *mut u8) -> Self { 422 | let t_size = std::mem::size_of::(); 423 | ImageBuffer { 424 | bounds, 425 | row_bytes: row_bytes as isize, 426 | t_size, 427 | data: unsafe { &mut *data }, 428 | pixel_type: PhantomData, 429 | } 430 | } 431 | 432 | #[inline] 433 | fn byte_offset(&self, x: Int, y: Int) -> isize { 434 | (y - self.bounds.y1) as isize * self.row_bytes + (x - self.bounds.x1) as isize 435 | } 436 | 437 | fn bounds(&self) -> RectI { 438 | self.bounds 439 | } 440 | 441 | fn bytes(&self) -> usize { 442 | self.row_bytes.abs() as usize * (self.bounds.y2 - self.bounds.y1) as usize 443 | } 444 | 445 | fn dimensions(&self) -> (u32, u32) { 446 | ( 447 | (self.bounds.x2 - self.bounds.x1) as u32, 448 | (self.bounds.y2 - self.bounds.y1) as u32, 449 | ) 450 | } 451 | 452 | fn pixel_bytes(&self) -> usize { 453 | self.t_size 454 | } 455 | 456 | fn ptr(&self, offset: isize) -> *const u8 { 457 | unsafe { 458 | let ptr: *const u8 = &*self.data; 459 | ptr.offset(offset) 460 | } 461 | } 462 | 463 | fn ptr_mut(&mut self, offset: isize) -> *mut u8 { 464 | unsafe { 465 | let ptr: *mut u8 = &mut *self.data; 466 | ptr.offset(offset) 467 | } 468 | } 469 | 470 | fn make_slice(&self, x: Int, y: Int, length: usize) -> &[T] { 471 | let start = self.byte_offset(x, y); 472 | unsafe { std::slice::from_raw_parts(self.ptr(start) as *const T, length) } 473 | } 474 | 475 | fn make_slice_mut(&mut self, x: Int, y: Int, length: usize) -> &mut [T] { 476 | let start = self.byte_offset(x, y); 477 | unsafe { std::slice::from_raw_parts_mut(self.ptr_mut(start) as *mut T, length) } 478 | } 479 | 480 | fn row(&self, y: Int) -> &[T] { 481 | self.make_slice( 482 | self.bounds.x1, 483 | y, 484 | (self.bounds.x2 - self.bounds.x1) as usize, 485 | ) 486 | } 487 | 488 | fn row_mut(&mut self, y: Int) -> &mut [T] { 489 | let x1 = self.bounds.x1; 490 | let x2 = self.bounds.x2; 491 | self.make_slice_mut(x1, y, (x2 - x1) as usize) 492 | } 493 | 494 | fn chunk(&mut self, y1: Int, y2: Int) -> Self { 495 | let y1 = y1.max(self.bounds.y1); 496 | let y2 = y2.min(self.bounds.y2); 497 | let bounds = RectI { 498 | x1: self.bounds.x1, 499 | y1, 500 | x2: self.bounds.x2, 501 | y2, 502 | }; 503 | debug!("Chunk {}-{}", y1, y2); 504 | // TODO: potentially unsound 505 | let offset = self.byte_offset(self.bounds.x1, y1); 506 | let ptr_mut = self.ptr_mut(offset); 507 | let data = unsafe { &mut *ptr_mut }; 508 | ImageBuffer { 509 | bounds, 510 | row_bytes: self.row_bytes, 511 | t_size: self.t_size, 512 | data, 513 | pixel_type: PhantomData, 514 | } 515 | } 516 | 517 | pub fn chunks_mut(mut self, chunk_size: usize) -> impl Iterator> { 518 | let rows = (self.bounds.y2 - self.bounds.y1) as usize; 519 | let n_chunks = (rows + chunk_size - 1) / chunk_size; 520 | let rows_per_chunk = chunk_size; 521 | let y1 = self.bounds.y1; 522 | (0..n_chunks).map(move |chunk| { 523 | self.chunk( 524 | y1 + (chunk * rows_per_chunk) as Int, 525 | y1 + ((chunk + 1) * rows_per_chunk) as Int, 526 | ) 527 | }) 528 | } 529 | } 530 | 531 | pub struct Row<'a, T> 532 | where 533 | T: PixelFormat, 534 | { 535 | y: Int, 536 | data: ImageBuffer<'a, T>, 537 | } 538 | 539 | pub struct RowWalk<'a, T> 540 | where 541 | T: PixelFormat, 542 | { 543 | data: ImageBuffer<'a, T>, 544 | y: Int, 545 | last_y: Int, 546 | } 547 | 548 | impl<'a, T> RowWalk<'a, T> where T: PixelFormat {} 549 | 550 | impl<'a, T> Iterator for RowWalk<'a, T> 551 | where 552 | T: PixelFormat, 553 | { 554 | type Item = Row<'a, T>; 555 | 556 | fn next(&mut self) -> Option> { 557 | if self.y < self.last_y { 558 | let row = Row { 559 | data: self.data.clone(), 560 | y: self.y, 561 | }; 562 | self.y += 1; 563 | Some(row) 564 | } else { 565 | None 566 | } 567 | } 568 | } 569 | 570 | #[derive(Clone)] 571 | pub struct ImageDescriptor<'a, T> 572 | where 573 | T: PixelFormat, 574 | { 575 | data: ImageBuffer<'a, T>, 576 | } 577 | 578 | pub struct ImageDescriptorMut<'a, T> 579 | where 580 | T: PixelFormat, 581 | { 582 | data: ImageBuffer<'a, T>, 583 | } 584 | 585 | pub struct ImageTileMut<'a, T> 586 | where 587 | T: PixelFormat, 588 | { 589 | pub y1: Int, 590 | pub y2: Int, 591 | data: ImageBuffer<'a, T>, 592 | } 593 | 594 | impl<'a, T> ImageDescriptor<'a, T> 595 | where 596 | T: PixelFormat, 597 | { 598 | pub fn new(bounds: RectI, row_bytes: Int, ptr: VoidPtrMut) -> Self { 599 | ImageDescriptor { 600 | data: ImageBuffer::new(bounds, row_bytes, ptr as *mut u8), 601 | } 602 | } 603 | 604 | pub fn row(&self, y: Int) -> &[T] { 605 | self.data.row(y) 606 | } 607 | 608 | pub fn row_range(&self, x1: Int, x2: Int, y: Int) -> &[T] { 609 | let slice = self.row(y); 610 | &slice[(x1 - self.data.bounds.x1) as usize..(x2 - self.data.bounds.x1) as usize] 611 | } 612 | } 613 | 614 | impl<'a, T> ImageDescriptorMut<'a, T> 615 | where 616 | T: PixelFormat, 617 | { 618 | pub fn new(bounds: RectI, row_bytes: Int, ptr: VoidPtrMut) -> Self { 619 | ImageDescriptorMut { 620 | data: ImageBuffer::new(bounds, row_bytes, ptr as *mut u8), 621 | } 622 | } 623 | 624 | pub fn row(&mut self, y: Int) -> &mut [T] { 625 | self.data.row_mut(y) 626 | } 627 | 628 | pub fn row_range(&mut self, x1: Int, x2: Int, y: Int) -> &mut [T] { 629 | let x0 = self.data.bounds.x1; 630 | let slice = self.row(y); 631 | let x1 = (x1 - x0) as usize; 632 | let x2 = (x2 - x0) as usize; 633 | &mut slice[x1..x2] 634 | } 635 | 636 | pub fn into_tiles(self, n_chunks: usize) -> Vec> { 637 | let (width, height) = self.data.dimensions(); 638 | let rows_per_chunk = height as usize / n_chunks; 639 | self.data 640 | .chunks_mut(rows_per_chunk as usize) 641 | .enumerate() 642 | .map(|(chunk_index, chunk)| { 643 | let y1 = (chunk_index * rows_per_chunk) as Int; 644 | let y2 = (height as Int).min(y1 + rows_per_chunk as Int); 645 | ImageTileMut::new(y1, y2, chunk) 646 | }) 647 | .collect() 648 | } 649 | } 650 | 651 | impl<'a, T> ImageTileMut<'a, T> 652 | where 653 | T: PixelFormat, 654 | { 655 | pub fn new(y1: Int, y2: Int, data: ImageBuffer<'a, T>) -> Self { 656 | ImageTileMut { y1, y2, data } 657 | } 658 | 659 | pub fn row(&mut self, y: Int) -> &mut [T] { 660 | self.data.row_mut(y) 661 | } 662 | 663 | pub fn row_range(&mut self, x1: Int, x2: Int, y: Int) -> &mut [T] { 664 | let x0 = self.data.bounds.x1; 665 | let slice = self.row(y); 666 | let x1 = (x1 - x0) as usize; 667 | let x2 = (x2 - x0) as usize; 668 | &mut slice[x1..x2] 669 | } 670 | } 671 | -------------------------------------------------------------------------------- /ofx/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused)] 2 | #![feature(concat_idents)] 3 | #![feature(specialization)] 4 | 5 | extern crate ofx_sys; 6 | #[macro_use] 7 | extern crate log; 8 | extern crate log4rs; 9 | 10 | use std::collections::HashMap; 11 | use std::ffi::{CStr, CString}; 12 | use std::fmt; 13 | use std::fmt::Display; 14 | use std::marker::PhantomData; 15 | 16 | #[macro_use] 17 | mod result; 18 | 19 | #[macro_use] 20 | mod suites; 21 | #[macro_use] 22 | mod util; 23 | mod action; 24 | mod enums; 25 | mod handle; 26 | mod plugin; 27 | mod property; 28 | mod types; 29 | #[macro_use] 30 | mod registry; 31 | mod image; 32 | pub use action::*; 33 | pub use enums::*; 34 | pub use enums::Type as Type; 35 | pub use handle::*; 36 | pub use plugin::*; 37 | pub use property::*; 38 | pub use result::*; 39 | pub use types::*; 40 | pub use util::*; 41 | pub use image::*; 42 | use registry::*; 43 | 44 | pub use ofx_sys::{OfxHost, OfxPlugin, OfxPropertySetHandle}; 45 | pub use registry::{ 46 | get_registry, init_registry, main_entry_for_plugin, set_host_for_plugin, Registry, 47 | }; 48 | 49 | #[macro_export] 50 | macro_rules! register_modules { 51 | ( $ ($module:ident), *) => { 52 | fn register_plugins(registry: &mut ofx::Registry) { 53 | $(register_plugin!(registry, $module); 54 | )* 55 | } 56 | 57 | build_plugin_registry!(register_plugins); 58 | }; 59 | } 60 | 61 | -------------------------------------------------------------------------------- /ofx/src/plugin.rs: -------------------------------------------------------------------------------- 1 | #![feature(plugin)] 2 | 3 | use action::*; 4 | use enums::*; 5 | use handle::*; 6 | use ofx_sys::*; 7 | use property::*; 8 | use result::*; 9 | use std::collections::HashMap; 10 | use std::ffi::{CStr, CString}; 11 | use std::fmt; 12 | use std::fmt::Display; 13 | use std::rc::Rc; 14 | use suites::*; 15 | use types::*; 16 | 17 | pub struct ApiVersion(pub Int); 18 | pub struct PluginVersion(pub UnsignedInt, pub UnsignedInt); 19 | 20 | #[derive(Debug)] 21 | struct EnumIndex 22 | where 23 | T: std::cmp::Eq + std::hash::Hash + Clone, 24 | { 25 | map: HashMap, 26 | inverse_map: HashMap, 27 | } 28 | 29 | impl EnumIndex 30 | where 31 | T: std::cmp::Eq + std::hash::Hash + Clone, 32 | { 33 | pub fn new() -> EnumIndex { 34 | EnumIndex { 35 | map: HashMap::new(), 36 | inverse_map: HashMap::new(), 37 | } 38 | } 39 | 40 | pub fn insert(&mut self, key_bytes: &[u8], value: T) { 41 | if let Ok(cstr) = CStr::from_bytes_with_nul(key_bytes) { 42 | if let Ok(key) = cstr.to_str() { 43 | self.map.insert(key.to_owned(), value.clone()); 44 | self.inverse_map.insert(value, key.to_owned()); 45 | } 46 | } else { 47 | error!("Was unable to add {:?} key, this is a bug", key_bytes) 48 | } 49 | } 50 | 51 | pub fn find(&self, c_key: &[u8]) -> Option { 52 | let cstr = CString::new(c_key).ok()?; 53 | let key = cstr.into_string().ok()?; 54 | self.map.get(&key).cloned() 55 | } 56 | } 57 | 58 | #[derive(Debug)] 59 | pub enum RawMessage { 60 | SetHost { 61 | host: OfxHost, 62 | }, 63 | MainEntry { 64 | action: CharPtr, 65 | handle: VoidPtr, 66 | in_args: OfxPropertySetHandle, 67 | out_args: OfxPropertySetHandle, 68 | }, 69 | } 70 | 71 | pub trait Dispatch { 72 | fn dispatch(&mut self, message: RawMessage) -> Result { 73 | OK 74 | } 75 | } 76 | 77 | pub trait Plugin: Dispatch + MapAction + Execute { 78 | fn suites(&self) -> &Suites; 79 | } 80 | 81 | pub struct PluginDescriptor { 82 | plugin_id: CString, 83 | module_name: String, 84 | plugin_index: usize, 85 | host: Option, 86 | suites: Option, 87 | cached_handle: Option, 88 | instance: Box, 89 | global_action_index: EnumIndex, 90 | image_effect_action_index: EnumIndex, 91 | ofx_plugin: OfxPlugin, // need an owned copy for the lifetime of the plugin 92 | } 93 | 94 | pub struct PluginContext { 95 | host: ImageEffectHost, 96 | suites: Suites, 97 | } 98 | 99 | pub trait Runnable: Sized + Send + Sync { 100 | fn run(&mut self, thread_index: UnsignedInt, thread_max: UnsignedInt); 101 | unsafe extern "C" fn run_myself( 102 | thread_index: UnsignedInt, 103 | thread_max: UnsignedInt, 104 | me: VoidPtrMut, 105 | ) { 106 | (*(me as *mut Self)).run(thread_index, thread_max) 107 | } 108 | } 109 | 110 | impl PluginContext { 111 | pub fn get_host(&self) -> ImageEffectHost { 112 | self.host.clone() 113 | } 114 | 115 | pub fn num_threads(&self) -> Result { 116 | let mut c_num_threads: UnsignedInt = 0; 117 | to_result! { suite_call!(multiThreadNumCPUs in self.suites.multi_thread; &mut c_num_threads as *mut UnsignedInt) 118 | => c_num_threads} 119 | } 120 | 121 | fn run_in_threads_internal( 122 | &self, 123 | function: ThreadFunction, 124 | n_threads: UnsignedInt, 125 | custom_arg: VoidPtrMut, 126 | ) -> Result<()> { 127 | suite_fn!(multiThread in self.suites.multi_thread; function, n_threads, custom_arg) 128 | } 129 | 130 | pub fn run_in_threads(&self, n_threads: UnsignedInt, runnable: &mut R) -> Result<()> 131 | where 132 | R: Runnable, 133 | { 134 | self.run_in_threads_internal( 135 | Some(R::run_myself), 136 | n_threads, 137 | (runnable as *mut R) as VoidPtrMut, 138 | )?; 139 | Ok(()) 140 | } 141 | } 142 | 143 | impl Display for PluginDescriptor { 144 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 145 | write!( 146 | f, 147 | "module:{} id:{:?} index:{}", 148 | self.module_name, self.plugin_id, self.plugin_index 149 | ) 150 | } 151 | } 152 | 153 | impl MapAction for PluginDescriptor { 154 | fn map_action( 155 | &self, 156 | action: CharPtr, 157 | handle: VoidPtr, 158 | in_args: OfxPropertySetHandle, 159 | out_args: OfxPropertySetHandle, 160 | ) -> Result { 161 | macro_rules! map_args { 162 | ($action:ident()) => { 163 | Ok(Action::$action(self.new_image_effect_raw(handle)?)) 164 | }; 165 | ($action:ident(in_args)) => { 166 | Ok(Action::$action( 167 | self.new_image_effect_raw(handle)?, 168 | self.typed_properties(build_typed::, in_args)?, 169 | )) 170 | }; 171 | ($action:ident(out_args)) => { 172 | Ok(Action::$action( 173 | self.new_image_effect_raw(handle)?, 174 | self.typed_properties( 175 | build_typed::, 176 | out_args, 177 | )?, 178 | )) 179 | }; 180 | ($action:ident(in_args, out_args)) => { 181 | Ok(Action::$action( 182 | self.new_image_effect_raw(handle)?, 183 | self.typed_properties(build_typed::, in_args)?, 184 | self.typed_properties( 185 | build_typed::, 186 | out_args, 187 | )?, 188 | )) 189 | }; 190 | }; 191 | let name = unsafe { CStr::from_ptr(action) }.to_bytes(); 192 | if let Some(action) = self.image_effect_action_index.find(name) { 193 | use ImageEffectAction::*; 194 | match action { 195 | DescribeInContext => map_args! { DescribeInContext(in_args) }, 196 | GetRegionOfDefinition => map_args! { GetRegionOfDefinition(in_args, out_args) }, 197 | GetRegionsOfInterest => map_args! { GetRegionsOfInterest(in_args, out_args) }, 198 | IsIdentity => map_args! { IsIdentity(in_args, out_args) }, 199 | GetClipPreferences => map_args! { GetClipPreferences(out_args) }, 200 | GetTimeDomain => map_args!(GetTimeDomain(out_args)), 201 | BeginSequenceRender => map_args! { BeginSequenceRender(in_args) }, 202 | Render => map_args! { Render(in_args) }, 203 | EndSequenceRender => map_args!(EndSequenceRender(in_args)), 204 | _ => Err(Error::InvalidAction), 205 | } 206 | } else if let Some(action) = self.global_action_index.find(name) { 207 | use GlobalAction::*; 208 | match action { 209 | Load => Ok(Action::Load), // handled by the library 210 | Unload => Ok(Action::Unload), // handled by the library 211 | Describe => map_args!(Describe()), 212 | SyncPrivateData => map_args! { SyncPrivateData() }, 213 | PurgeCaches => map_args! { PurgeCaches() }, 214 | CreateInstance => map_args!(CreateInstance()), 215 | BeginInstanceChanged => map_args!(BeginInstanceChanged(in_args)), 216 | InstanceChanged => map_args!(InstanceChanged(in_args)), 217 | EndInstanceChanged => map_args!(EndInstanceChanged(in_args)), 218 | DestroyInstance => map_args!(DestroyInstance()), // handled by the library 219 | _ => Err(Error::InvalidAction), 220 | } 221 | } else { 222 | warn!("map_action: No action matching {:?}", unsafe { 223 | CStr::from_ptr(action) 224 | }); 225 | Err(Error::InvalidAction) 226 | } 227 | } 228 | } 229 | 230 | impl Filter for PluginDescriptor { 231 | fn before_execute(&mut self, action: &Action) -> Result { 232 | match action { 233 | Action::Load => self.load(), 234 | Action::Unload => self.unload(), 235 | Action::Describe(ref handle) => self.describe(handle.clone()), 236 | _ => OK, 237 | }?; 238 | 239 | OK 240 | } 241 | 242 | fn after_execute( 243 | &mut self, 244 | context: &PluginContext, 245 | action: &mut Action, 246 | _status: Result, 247 | ) -> Result { 248 | match action { 249 | Action::DestroyInstance(ref mut effect) => effect.drop_instance_data(), 250 | _ => Ok(()), 251 | }?; 252 | 253 | OK 254 | } 255 | } 256 | 257 | impl Dispatch for PluginDescriptor { 258 | fn dispatch(&mut self, message: RawMessage) -> Result { 259 | match message { 260 | RawMessage::SetHost { host } => { 261 | self.host = Some(host); 262 | OK 263 | } 264 | RawMessage::MainEntry { 265 | action, 266 | handle, 267 | in_args, 268 | out_args, 269 | } => { 270 | let mut mapped_action = self.map_action(action, handle, in_args, out_args)?; 271 | 272 | debug!("Mapped action found: {:?}", mapped_action); 273 | self.before_execute(&mapped_action)?; 274 | 275 | if let (Some(host), Some(suites)) = (self.host, self.suites.clone()) { 276 | let plugin_context = PluginContext { 277 | host: ImageEffectHost::new(host.host, suites.property()), 278 | suites, 279 | }; 280 | let status = self.execute(&plugin_context, &mut mapped_action); 281 | self.after_execute(&plugin_context, &mut mapped_action, status)?; 282 | status 283 | } else { 284 | OK 285 | } 286 | } 287 | } 288 | } 289 | } 290 | 291 | impl Execute for PluginDescriptor { 292 | fn execute(&mut self, context: &PluginContext, action: &mut Action) -> Result { 293 | let result = self.instance.execute(context, action); 294 | debug!( 295 | "Executed {:?} of {} -> {:?}", 296 | action, self.module_name, result 297 | ); 298 | result 299 | } 300 | } 301 | 302 | impl PluginDescriptor { 303 | pub(crate) fn new( 304 | plugin_index: usize, 305 | module_name: &'static str, 306 | name: &'static str, 307 | api_version: ApiVersion, 308 | plugin_version: PluginVersion, 309 | instance: Box, 310 | set_host: SetHost, 311 | main_entry: MainEntry, 312 | ) -> PluginDescriptor { 313 | let plugin_id = CString::new(name).unwrap(); 314 | 315 | let ofx_plugin = OfxPlugin { 316 | pluginApi: static_str!(kOfxImageEffectPluginApi), 317 | apiVersion: api_version.0, 318 | pluginVersionMajor: plugin_version.0, 319 | pluginVersionMinor: plugin_version.1, 320 | pluginIdentifier: plugin_id.as_ptr(), 321 | setHost: Some(set_host), 322 | mainEntry: Some(main_entry), 323 | }; 324 | 325 | let mut global_action_index = EnumIndex::new(); 326 | let mut image_effect_action_index = EnumIndex::new(); 327 | use ofx_sys::*; 328 | macro_rules! global_add { 329 | ($id:ident) => { 330 | debug!( 331 | "kOfxAction{} GlobalAction::{}", 332 | stringify!($id), 333 | stringify!($id) 334 | ); 335 | global_action_index.insert(concat_idents!(kOfxAction, $id), GlobalAction::$id) 336 | }; 337 | } 338 | macro_rules! image_effect_add { 339 | ($id:ident) => { 340 | debug!( 341 | "kOfxImageEffectAction{} ImageEffectAction::{}", 342 | stringify!($id), 343 | stringify!($id) 344 | ); 345 | image_effect_action_index.insert( 346 | concat_idents!(kOfxImageEffectAction, $id), 347 | ImageEffectAction::$id, 348 | ) 349 | }; 350 | } 351 | 352 | global_add!(Load); 353 | global_add!(Describe); 354 | global_add!(Unload); 355 | global_add!(PurgeCaches); 356 | global_add!(SyncPrivateData); 357 | global_add!(CreateInstance); 358 | global_add!(DestroyInstance); 359 | global_add!(BeginInstanceChanged); 360 | global_add!(InstanceChanged); 361 | global_add!(EndInstanceChanged); 362 | global_add!(BeginInstanceEdit); 363 | global_add!(EndInstanceEdit); 364 | global_add!(Dialog); 365 | 366 | image_effect_add!(GetRegionOfDefinition); 367 | image_effect_add!(GetRegionsOfInterest); 368 | image_effect_add!(GetTimeDomain); 369 | image_effect_add!(GetFramesNeeded); 370 | image_effect_add!(GetClipPreferences); 371 | image_effect_add!(IsIdentity); 372 | image_effect_add!(BeginSequenceRender); 373 | image_effect_add!(Render); 374 | image_effect_add!(EndSequenceRender); 375 | image_effect_add!(DescribeInContext); 376 | image_effect_add!(GetInverseDistortion); 377 | image_effect_add!(InvokeHelp); 378 | image_effect_add!(InvokeAbout); 379 | image_effect_add!(VegasKeyframeUplift); 380 | 381 | PluginDescriptor { 382 | plugin_index, 383 | module_name: module_name.to_owned(), 384 | plugin_id, 385 | instance, 386 | host: None, 387 | suites: None, 388 | cached_handle: None, 389 | global_action_index, 390 | image_effect_action_index, 391 | ofx_plugin, 392 | } 393 | } 394 | 395 | fn suites(&self) -> Result<&Suites> { 396 | self.suites.as_ref().ok_or(Error::SuiteNotInitialized) 397 | } 398 | 399 | fn new_image_effect_raw(&self, ptr: VoidPtr) -> Result { 400 | self.new_image_effect(unsafe { ptr as OfxImageEffectHandle }) 401 | } 402 | 403 | fn new_image_effect(&self, handle: OfxImageEffectHandle) -> Result { 404 | let suites = self.suites()?; 405 | let property_suite = suites.property(); 406 | let image_effect_suite = suites.image_effect(); 407 | let parameter_suite = suites.parameter(); 408 | Ok(ImageEffectHandle::new( 409 | handle, 410 | property_suite, 411 | image_effect_suite, 412 | parameter_suite, 413 | )) 414 | } 415 | 416 | fn typed_properties(&self, constructor: F, handle: OfxPropertySetHandle) -> Result 417 | where 418 | F: Fn(OfxPropertySetHandle, Rc) -> T, 419 | { 420 | let property_suite = self.suites()?.property(); 421 | Ok(constructor(handle, property_suite)) 422 | } 423 | 424 | fn load(&mut self) -> Result { 425 | let host = self.host.ok_or(Error::HostNotReady)?; 426 | let fetch_suite = host.fetchSuite.ok_or(Error::HostNotReady)?; 427 | 428 | const V1: Int = 1; 429 | const V2: Int = 2; 430 | 431 | debug!("Fetching suites"); 432 | macro_rules! fetch_suite { 433 | ($suite_name:ident, $suite_version:ident) => { 434 | unsafe { 435 | let suiteptr = fetch_suite( 436 | host.host as OfxPropertySetHandle, 437 | CStr::from_bytes_with_nul_unchecked(concat_idents!( 438 | kOfx, 439 | $suite_name, 440 | Suite 441 | )) 442 | .as_ptr(), 443 | $suite_version, 444 | ); 445 | if suiteptr.is_null() { 446 | error!("Failed to load {}", stringify!($suite_name)); 447 | None 448 | } else { 449 | debug!( 450 | "Found suite '{}' at {:?}", 451 | stringify!($suite_name), 452 | suiteptr 453 | ); 454 | unsafe { 455 | Some(*unsafe { 456 | suiteptr 457 | as *const concat_idents!( 458 | Ofx, 459 | $suite_name, 460 | Suite, 461 | $suite_version 462 | ) 463 | }) 464 | } 465 | } 466 | } 467 | }; 468 | }; 469 | 470 | self.suites = Some(Suites::new( 471 | fetch_suite!(ImageEffect, V1).ok_or(Error::InvalidSuite)?, 472 | fetch_suite!(Property, V1).ok_or(Error::InvalidSuite)?, 473 | fetch_suite!(Parameter, V1).ok_or(Error::InvalidSuite)?, 474 | fetch_suite!(Memory, V1).ok_or(Error::InvalidSuite)?, 475 | fetch_suite!(MultiThread, V1).ok_or(Error::InvalidSuite)?, 476 | fetch_suite!(Message, V1).ok_or(Error::InvalidSuite)?, 477 | fetch_suite!(Message, V2), 478 | fetch_suite!(Progress, V1).ok_or(Error::InvalidSuite)?, 479 | fetch_suite!(Progress, V2), 480 | fetch_suite!(TimeLine, V1).ok_or(Error::InvalidSuite)?, 481 | fetch_suite!(ParametricParameter, V1), 482 | fetch_suite!(ImageEffectOpenGLRender, V1), 483 | )); 484 | info!("Loaded plugin"); 485 | OK 486 | } 487 | 488 | fn unload(&mut self) -> Result { 489 | OK 490 | } 491 | 492 | fn cache_handle(&mut self, handle: ImageEffectHandle) { 493 | self.cached_handle = Some(handle); 494 | } 495 | 496 | fn describe(&mut self, handle: ImageEffectHandle) -> Result { 497 | info!("Caching plugin instance handle {:?}", handle); 498 | self.cache_handle(handle); 499 | OK 500 | } 501 | 502 | pub fn ofx_plugin(&self) -> &OfxPlugin { 503 | &self.ofx_plugin 504 | } 505 | } 506 | 507 | impl Plugin for PluginDescriptor { 508 | fn suites(&self) -> &Suites { 509 | &self.suites.as_ref().unwrap() 510 | } 511 | } 512 | -------------------------------------------------------------------------------- /ofx/src/property.rs: -------------------------------------------------------------------------------- 1 | #![feature(concat_idents)] 2 | 3 | use enums::{ 4 | BitDepth, Change, HostNativeOrigin, IdentifiedEnum, ImageComponent, ImageEffectContext, 5 | ImageEffectRender, ImageField, ImageFieldExtraction, ImageFieldOrder, ParamDoubleType, 6 | Type as EType, 7 | }; 8 | use handle::Image; 9 | use handle::*; 10 | use ofx_sys::*; 11 | use result; 12 | use result::*; 13 | use std::ffi::{CStr, CString}; 14 | use std::fmt::Debug; 15 | use std::marker::PhantomData; 16 | use types::*; 17 | 18 | macro_rules! raw_getter_impl { 19 | (|$readable:ident, $c_name:ident, $index:ident| -> $value_type: ty $stmt:block) => { 20 | /// Adds the capability to set arbitrary properties to $value_type 21 | impl RawGetter for $value_type 22 | where 23 | R: Readable + AsProperties, 24 | { 25 | fn get_at($readable: &R, $c_name: CharPtr, $index: usize) -> Result { 26 | let value = { $stmt }; 27 | debug!( 28 | "{:?}.{:?}[{}] -> {:?}", 29 | $readable.handle(), 30 | unsafe { CStr::from_ptr($c_name) }, 31 | $index, 32 | value 33 | ); 34 | value 35 | } 36 | } 37 | }; 38 | } 39 | 40 | macro_rules! raw_setter_impl { 41 | (| $writable:ident, $c_name:ident, $index:ident, $value:ident : &$value_type:ty| $stmt:block) => { 42 | impl RawSetter for $value_type 43 | where 44 | W: Writable + AsProperties, 45 | { 46 | fn set_at($writable: &mut W, $c_name: CharPtr, $index: usize, $value: &Self) -> Result<()> 47 | $stmt 48 | } 49 | }; 50 | } 51 | 52 | macro_rules! trace_setter { 53 | ($writable: expr, $c_name:expr, $index: expr, str $value:expr) => { 54 | debug!( 55 | "{:?}.{:?}[{}] <- &{:?}", 56 | $writable, 57 | unsafe { CStr::from_ptr($c_name) }, 58 | $index, 59 | unsafe { CStr::from_bytes_with_nul_unchecked($value) } 60 | ) 61 | }; 62 | 63 | ($writable: expr, $c_name:expr, $index: expr, $value:expr) => { 64 | debug!( 65 | "{:?}.{:?}[{}] <- {:?}", 66 | $writable, 67 | unsafe { CStr::from_ptr($c_name) }, 68 | $index, 69 | $value 70 | ) 71 | }; 72 | } 73 | 74 | macro_rules! property_assign_name { 75 | ($ofx_name:ident as $name:ident : (&$value_type:ty) -> $return_type:ty) => { 76 | pub struct $name; 77 | impl Get for $name { 78 | type ReturnType = $return_type; 79 | } 80 | impl Set for $name { 81 | type ValueType = $value_type; 82 | } 83 | impl Named for $name { 84 | fn name() -> &'static [u8] { 85 | $ofx_name 86 | } 87 | } 88 | }; 89 | 90 | ($ofx_name:ident as $name:ident : $return_type:ty) => { 91 | property_assign_name!($ofx_name as $name: (&$return_type) -> $return_type); 92 | }; 93 | 94 | ($ofx_name:ident as $name:ident : () -> $return_type:ty) => { 95 | pub struct $name; 96 | impl Get for $name { 97 | type ReturnType = $return_type; 98 | } 99 | impl Named for $name { 100 | fn name() -> &'static [u8] { 101 | $ofx_name 102 | } 103 | } 104 | }; 105 | } 106 | 107 | macro_rules! property_define_setter_trait { 108 | ($function_name: ident, &$property_name:path) => { 109 | fn $function_name(&mut self, value: &<$property_name as Set>::ValueType) -> Result<()>{ 110 | self.set::<$property_name>(value) 111 | } 112 | }; 113 | 114 | ($function_name: ident, $property_name:path) => { 115 | property_define_setter_trait!($function_name, $property_name, <$property_name as Set>::ValueType); 116 | }; 117 | 118 | ($function_name: ident, $property_name:path, &[enum $enum_value_type:ty]) => { 119 | fn $function_name(&mut self, values: &[$enum_value_type]) -> Result<()> { 120 | for (index, value) in values.iter().enumerate() { 121 | self.set_at::<$property_name>(index, value.to_bytes())?; 122 | } 123 | Ok(()) 124 | } 125 | }; 126 | 127 | ($function_name: ident, $property_name:path, &seq [$value_type:ty]) => { 128 | fn $function_name(&mut self, values: &[$value_type]) -> Result<()> { 129 | for (index, value) in values.iter().enumerate() { 130 | self.set_at::<$property_name>(index, value)?; 131 | } 132 | Ok(()) 133 | } 134 | }; 135 | 136 | ($function_name: ident, $property_name:path, enum $enum_value_type:ty) => { 137 | fn $function_name(&mut self, value: $enum_value_type) -> Result<()> { 138 | self.set::<$property_name>(value.to_bytes()) 139 | } 140 | }; 141 | 142 | ($function_name: ident, $property_name:path, &$value_type:ty) => { 143 | fn $function_name<'a>(&mut self, value: &'a $value_type) -> Result<()>{ 144 | self.set::<$property_name>(value) 145 | } 146 | }; 147 | 148 | ($function_name: ident, $property_name:path, $value_type:ty) => { 149 | fn $function_name(&mut self, value: $value_type) -> Result<()>{ 150 | self.set::<$property_name>(&value) 151 | } 152 | }; 153 | 154 | ($function_name: ident, $property_name:path, into &$value_type:ty) => { 155 | fn $function_name<'a, S>(&mut self, value: &'a S) -> Result<()> where S: Into<&'a $value_type> { 156 | self.set::<$property_name>(value.into()) 157 | } 158 | }; 159 | 160 | ($trait_name:ident => $($tail:tt)*) => { 161 | pub trait $trait_name: Writable { 162 | property_define_setter_trait!($($tail)*); 163 | } 164 | }; 165 | } 166 | 167 | macro_rules! property_define_getter_trait { 168 | ($function_name: ident, $property_name:path, enum $enum_value_type:ident) => { 169 | fn $function_name(&self) -> Result<$enum_value_type> { 170 | let str_value = self.get::<$property_name>()?; 171 | $enum_value_type::from_cstring(&str_value).ok_or(Error::EnumNotFound) 172 | } 173 | }; 174 | 175 | ($function_name: ident, $property_name:path) => { 176 | fn $function_name(&self) -> Result<<$property_name as Get>::ReturnType> { 177 | self.get::<$property_name>() 178 | } 179 | }; 180 | 181 | ($trait_name:ident => $($tail:tt)*) => { 182 | pub trait $trait_name: Readable { 183 | property_define_getter_trait!($($tail)*); 184 | } 185 | }; 186 | } 187 | 188 | macro_rules! property { 189 | ($name:ident, $get_name:ident, $set_name:ident => $($tail:tt)*) => { 190 | #[allow(non_snake_case)] 191 | pub mod $name { 192 | use super::*; 193 | $($tail)* 194 | } 195 | pub use self::$name::CanGet as $get_name; 196 | pub use self::$name::CanSet as $set_name; 197 | }; 198 | 199 | ($name:ident, $get_name:ident => $($tail:tt)*) => { 200 | #[allow(non_snake_case)] 201 | pub mod $name { 202 | use super::*; 203 | $($tail)* 204 | } 205 | pub use self::$name::CanGet as $get_name; 206 | }; 207 | 208 | ($prop_name:ident as $name:ident { 209 | $get_name:ident () -> $get_type:ty; 210 | $set_name:ident (&$set_type:ty); 211 | }) => { 212 | property! { $name, $get_name, $set_name => 213 | property_assign_name!($prop_name as Property: (&$set_type) -> $get_type); 214 | property_define_getter_trait!(CanGet => $get_name, Property); 215 | property_define_setter_trait!(CanSet => $set_name, &Property); 216 | } 217 | }; 218 | 219 | ($prop_name:ident as $name:ident { 220 | $get_name:ident () -> $get_type:ty; 221 | $set_name:ident ($set_type:ty); 222 | }) => { 223 | property! { $name, $get_name, $set_name => 224 | property_assign_name!($prop_name as Property: (&$set_type) -> $get_type); 225 | property_define_getter_trait!(CanGet => $get_name, Property); 226 | property_define_setter_trait!(CanSet => $set_name, Property); 227 | } 228 | }; 229 | 230 | ($prop_name:ident as $name:ident { 231 | $get_name:ident () -> $get_type:ty; 232 | $set_name:ident (&$set_type:ty as $($tail:tt)*); 233 | }) => { 234 | property! { $name, $get_name, $set_name => 235 | property_assign_name!($prop_name as Property: (&$set_type) -> $get_type); 236 | property_define_getter_trait!(CanGet => $get_name, Property); 237 | property_define_setter_trait!(CanSet => $set_name, Property, $($tail)*); 238 | } 239 | }; 240 | 241 | ($prop_name:ident as $name:ident { 242 | $get_name:ident () -> $get_type:ty as enum $enum_get:ident; 243 | $set_name:ident (&$set_type:ty as enum $enum_set:ty); 244 | }) => { 245 | property! { $name, $get_name, $set_name => 246 | property_assign_name!($prop_name as Property: (&$set_type) -> $get_type); 247 | property_define_getter_trait!(CanGet => $get_name, Property, enum $enum_get); 248 | property_define_setter_trait!(CanSet => $set_name, Property, enum $enum_set); 249 | } 250 | }; 251 | 252 | ($prop_name:ident as $name:ident { 253 | $get_name:ident () -> $get_type:ty as enum $enum_get:ident; 254 | }) => { 255 | property! { $name, $get_name => 256 | property_assign_name!($prop_name as Property: () -> $get_type); 257 | property_define_getter_trait!(CanGet => $get_name, Property, enum $enum_get); 258 | } 259 | }; 260 | 261 | ($prop_name:ident as $name:ident { 262 | $get_name:ident () -> $get_type:ty; 263 | }) => { 264 | property! { $name, $get_name => 265 | property_assign_name!($prop_name as Property: () -> $get_type); 266 | property_define_getter_trait!(CanGet => $get_name, Property); 267 | } 268 | }; 269 | 270 | ($prop_name:ident as $name:ident { 271 | $get_name:ident () -> $get_type:ty as $($tail:tt)* 272 | }) => { 273 | property! { $name, $get_name => 274 | property_assign_name!($prop_name as Property: () -> $get_type); 275 | property_define_getter_trait!(CanGet => $get_name, Property, $($tail)*); 276 | } 277 | }; 278 | } 279 | 280 | macro_rules! property_group { 281 | (@impl $trait:ident => ) => {}; 282 | 283 | (@impl $trait:ident => $property:ident read+write, $($tail:tt)*) => { 284 | impl $property::CanGet for T where T: $trait {} 285 | impl $property::CanSet for T where T: $trait {} 286 | property_group!(@impl $trait => $($tail)*); 287 | }; 288 | 289 | (@impl $trait:ty => $property:ident write, $($tail:tt)*) => { 290 | impl $property::CanSet for T where T: $trait {} 291 | property_group!(@impl $trait => $($tail)*); 292 | }; 293 | 294 | (@impl $trait:ident => $property:ident read, $($tail:tt)*) => { 295 | impl $property::CanGet for T where T: $trait {} 296 | property_group!(@impl $trait => $($tail)*); 297 | }; 298 | 299 | ($trait:ident { $head1:tt $head2:tt, $($tail:tt)* }) => { 300 | pub trait $trait: AsProperties + Clone {} 301 | property_group!(@impl $trait => $head1 $head2, $($tail)*); 302 | }; 303 | } 304 | 305 | macro_rules! object_properties { 306 | (@tail $trait:ty => ) => {}; 307 | 308 | (@tail $trait:ty => $property:ident read+write, $($tail:tt)*) => { 309 | impl $property::CanGet for $trait {} 310 | impl $property::CanSet for $trait {} 311 | object_properties!(@tail $trait => $($tail)*); 312 | }; 313 | 314 | (@tail $trait:ty => $property:ident write, $($tail:tt)*) => { 315 | impl $property::CanSet for $trait {} 316 | object_properties!(@tail $trait => $($tail)*); 317 | }; 318 | 319 | (@tail $trait:ty => $property:ident read, $($tail:tt)*) => { 320 | impl $property::CanGet for $trait {} 321 | object_properties!(@tail $trait => $($tail)*); 322 | }; 323 | 324 | (@tail $trait:ty => $capability:ident inherit, $($tail:tt)*) => { 325 | impl $capability for $trait {} 326 | object_properties!(@tail $trait => $($tail)*); 327 | }; 328 | 329 | ($trait:ty { $($tail:tt)* }) => { 330 | object_properties!(@tail $trait => $($tail)*); 331 | }; 332 | } 333 | 334 | pub trait AsProperties { 335 | fn handle(&self) -> OfxPropertySetHandle; 336 | fn suite(&self) -> *const OfxPropertySuiteV1; 337 | } 338 | 339 | pub trait HasProperties 340 | where 341 | T: AsProperties + Sized + Clone, 342 | { 343 | fn properties(&self) -> Result; 344 | } 345 | 346 | pub trait Readable: AsProperties + Sized + Clone { 347 | fn get

(&self) -> Result 348 | where 349 | P: Named + Get, 350 | P::ReturnType: ValueType + Sized + Getter, 351 | { 352 | self.get_at::

(0) 353 | } 354 | 355 | fn get_at

(&self, index: usize) -> Result 356 | where 357 | P: Named + Get, 358 | P::ReturnType: ValueType + Sized + Getter, 359 | { 360 | >::get_at(self, index) 361 | } 362 | } 363 | 364 | pub trait RawReadable: AsProperties + Sized + Clone { 365 | #[inline] 366 | fn get_raw(&self, id: I) -> Result 367 | where 368 | I: StringId, 369 | R: ValueType + Sized + RawGetter, 370 | { 371 | self.get_raw_at(id, 0) 372 | } 373 | 374 | fn get_raw_at(&self, id: I, index: usize) -> Result 375 | where 376 | I: StringId, 377 | R: ValueType + Sized + RawGetter, 378 | { 379 | let c_buf = id.c_string()?; 380 | let c_name = c_buf.as_ptr(); 381 | >::get_at(&self, c_name, index) 382 | } 383 | } 384 | 385 | pub trait Writable: AsProperties + Sized + Clone { 386 | #[inline] 387 | fn set

(&mut self, new_value: &P::ValueType) -> Result<()> 388 | where 389 | P: Named + Set, 390 | P::ValueType: ValueType + Setter, 391 | { 392 | self.set_at::

(0, new_value) 393 | } 394 | 395 | fn set_at

(&mut self, index: usize, new_value: &P::ValueType) -> Result<()> 396 | where 397 | P: Named + Set, 398 | P::ValueType: ValueType + Setter, 399 | { 400 | >::set_at(self, index, new_value) 401 | } 402 | } 403 | 404 | pub trait RawWritable: AsProperties + Sized + Clone { 405 | #[inline] 406 | fn set_raw(&mut self, id: I, new_value: &V) -> Result<()> 407 | where 408 | I: StringId, 409 | V: ValueType + RawSetter + ?Sized + Debug, 410 | { 411 | self.set_raw_at(id, 0, new_value) 412 | } 413 | 414 | fn set_raw_at(&mut self, id: I, index: usize, new_value: &V) -> Result<()> 415 | where 416 | I: StringId, 417 | V: ValueType + RawSetter + ?Sized + Debug, 418 | { 419 | let buf = id.c_string()?; 420 | let c_name = buf.as_ptr(); 421 | >::set_at(self, c_name, index, new_value) 422 | } 423 | } 424 | 425 | impl Readable for R where R: AsProperties + Clone {} 426 | 427 | impl Writable for W where W: AsProperties + ?Sized + Clone {} 428 | 429 | pub trait StringId { 430 | fn c_string(self) -> Result; 431 | } 432 | 433 | impl StringId for &str { 434 | fn c_string(self) -> Result { 435 | Ok(CString::new(self)?) 436 | } 437 | } 438 | 439 | impl StringId for &[u8] { 440 | fn c_string(self) -> Result { 441 | Ok(CStr::from_bytes_with_nul(self) 442 | .map_err(|_| Error::InvalidNameEncoding)? 443 | .to_owned()) 444 | } 445 | } 446 | 447 | pub trait ValueType {} 448 | impl ValueType for Bool {} 449 | impl ValueType for Int {} 450 | impl ValueType for Double {} 451 | impl ValueType for PointI {} 452 | impl ValueType for PointD {} 453 | impl ValueType for RangeI {} 454 | impl ValueType for RangeD {} 455 | impl ValueType for RectI {} 456 | impl ValueType for RectD {} 457 | impl ValueType for String {} 458 | impl ValueType for str {} 459 | impl ValueType for [u8] {} 460 | impl ValueType for CharPtr {} 461 | impl ValueType for VoidPtr {} 462 | impl ValueType for VoidPtrMut {} 463 | impl ValueType for CString {} 464 | 465 | type StaticName = &'static [u8]; 466 | pub trait Named { 467 | fn name() -> StaticName; 468 | } 469 | 470 | pub trait Get: Named { 471 | type ReturnType: ValueType; 472 | } 473 | 474 | pub trait Set: Named { 475 | type ValueType: ValueType + ?Sized; 476 | } 477 | 478 | pub trait RawGetter 479 | where 480 | Self: ValueType + Sized, 481 | R: Readable + AsProperties, 482 | { 483 | fn get_at(readable: &R, name: CharPtr, index: usize) -> Result; 484 | } 485 | 486 | raw_getter_impl! { |readable, c_name, index| -> Bool { 487 | let mut c_int_out: Int = 0; 488 | to_result! { suite_call!(propGetInt in *readable.suite(); readable.handle(), c_name, index as Int, &mut c_int_out as *mut Int) 489 | => c_int_out != 0 } 490 | }} 491 | 492 | raw_getter_impl! { |readable, c_name, index| -> VoidPtr { 493 | let mut c_ptr_out: *mut std::ffi::c_void = std::ptr::null_mut(); 494 | to_result! { suite_call!(propGetPointer in *readable.suite(); readable.handle(), c_name, index as Int, &mut c_ptr_out as *mut VoidPtrMut) 495 | => c_ptr_out as VoidPtr } 496 | }} 497 | 498 | raw_getter_impl! { |readable, c_name, index| -> VoidPtrMut { 499 | let mut c_ptr_out: *mut std::ffi::c_void = std::ptr::null_mut(); 500 | to_result! { suite_call!(propGetPointer in *readable.suite(); readable.handle(), c_name, index as Int, &mut c_ptr_out as *mut VoidPtrMut) 501 | => c_ptr_out as VoidPtrMut } 502 | }} 503 | 504 | raw_getter_impl! { |readable, c_name, index| -> Int { 505 | let mut c_int_out: Int = 0; 506 | to_result! { suite_call!(propGetInt in *readable.suite(); readable.handle(), c_name, index as Int, &mut c_int_out as *mut Int) 507 | => c_int_out } 508 | }} 509 | 510 | raw_getter_impl! { |readable, c_name, index| -> PointI { 511 | let mut c_struct_out: PointI = unsafe { std::mem::zeroed() }; 512 | to_result! { suite_call!(propGetIntN in *readable.suite(); readable.handle(), c_name, POINT_ELEMENTS, &mut c_struct_out.x as *mut Int) 513 | => c_struct_out} 514 | }} 515 | 516 | raw_getter_impl! { |readable, c_name, index| -> RangeI { 517 | let mut c_struct_out: RangeI = unsafe { std::mem::zeroed() }; 518 | to_result! { suite_call!(propGetIntN in *readable.suite(); readable.handle(), c_name, RANGE_ELEMENTS, &mut c_struct_out.min as *mut Int) 519 | => c_struct_out} 520 | }} 521 | 522 | raw_getter_impl! { |readable, c_name, index| -> RectI { 523 | let mut c_struct_out: RectI = unsafe { std::mem::zeroed() }; 524 | to_result! { suite_call!(propGetIntN in *readable.suite(); readable.handle(), c_name, RECT_ELEMENTS, &mut c_struct_out.x1 as *mut Int) 525 | => c_struct_out} 526 | }} 527 | 528 | raw_getter_impl! { |readable, c_name, index| -> Double { 529 | let mut c_double_out: Double = 0.0; 530 | to_result! { suite_call!(propGetDouble in *readable.suite(); readable.handle(), c_name, index as Int, &mut c_double_out as *mut Double) 531 | => c_double_out} 532 | }} 533 | 534 | raw_getter_impl! { |readable, c_name, index| -> PointD { 535 | let mut c_struct_out: PointD = unsafe { std::mem::zeroed() }; 536 | to_result! { suite_call!(propGetDoubleN in *readable.suite(); readable.handle(), c_name, POINT_ELEMENTS, &mut c_struct_out.x as *mut Double) 537 | => c_struct_out} 538 | }} 539 | 540 | raw_getter_impl! { |readable, c_name, index| -> RangeD { 541 | let mut c_struct_out: RangeD = unsafe { std::mem::zeroed() }; 542 | to_result! { suite_call!(propGetDoubleN in *readable.suite(); readable.handle(), c_name, RANGE_ELEMENTS, &mut c_struct_out.min as *mut Double) 543 | => c_struct_out} 544 | }} 545 | 546 | raw_getter_impl! { |readable, c_name, index| -> RectD { 547 | let mut c_struct_out: RectD = unsafe { std::mem::zeroed() }; 548 | to_result! { suite_call!(propGetDoubleN in *readable.suite(); readable.handle(), c_name, RECT_ELEMENTS, &mut c_struct_out.x1 as *mut Double) 549 | => c_struct_out} 550 | }} 551 | 552 | raw_getter_impl! { |readable, c_name, index| -> CString { 553 | let mut c_ptr_out: CharPtr = std::ptr::null(); 554 | to_result! { suite_call!(propGetString in *readable.suite(); readable.handle(), c_name, index as Int, &mut c_ptr_out as *mut CharPtr) 555 | => unsafe { CStr::from_ptr(c_ptr_out).to_owned() }} 556 | }} 557 | 558 | raw_getter_impl! { |readable, c_name, index| -> String { 559 | let mut c_ptr_out: CharPtr = std::ptr::null(); 560 | to_result! { suite_call!(propGetString in *readable.suite(); readable.handle(), c_name, index as Int, &mut c_ptr_out as *mut CharPtr) 561 | => unsafe { CStr::from_ptr(c_ptr_out).to_str()?.to_owned() }} 562 | }} 563 | 564 | pub trait Getter: RawGetter 565 | where 566 | Self: ValueType + Sized, 567 | R: Readable + AsProperties, 568 | P: Named + Get, 569 | { 570 | fn get_at(readable: &R, index: usize) -> Result { 571 | let c_name = P::name().as_ptr(); 572 | RawGetter::get_at(readable, c_name as CharPtr, index) 573 | } 574 | } 575 | 576 | impl Getter for T 577 | where 578 | T: RawGetter, 579 | R: Readable + AsProperties, 580 | P: Named + Get, 581 | { 582 | } 583 | 584 | pub trait RawSetter 585 | where 586 | Self: ValueType, 587 | W: Writable + AsProperties, 588 | { 589 | fn set_at(writable: &mut W, name: CharPtr, index: usize, value: &Self) -> Result<()>; 590 | } 591 | 592 | raw_setter_impl! { |writable, c_name, index, value: &str| { 593 | let c_str_in = CString::new(value)?; 594 | let c_ptr_in = c_str_in.as_c_str().as_ptr(); 595 | trace_setter!(writable.handle(), c_name, index, value); 596 | suite_fn!(propSetString in *writable.suite(); writable.handle(), c_name, index as Int, c_ptr_in as CharPtrMut) 597 | }} 598 | 599 | raw_setter_impl! { |writable, c_name, index, value: &[u8]| { 600 | trace_setter!(writable.handle(), c_name, index, str value); 601 | suite_fn!(propSetString in *writable.suite(); writable.handle(), c_name, index as Int, value.as_ptr() as CharPtrMut) 602 | }} 603 | 604 | raw_setter_impl! { |writable, c_name, index, value: &VoidPtr| { 605 | trace_setter!(writable.handle(), c_name, index, value); 606 | suite_fn!(propSetPointer in *writable.suite(); writable.handle(), c_name, index as Int, *value as VoidPtrMut) 607 | }} 608 | 609 | raw_setter_impl! { |writable, c_name, index, value: &Int| { 610 | trace_setter!(writable.handle(), c_name, index, value); 611 | suite_fn!(propSetInt in *writable.suite(); writable.handle(), c_name, index as Int, *value) 612 | }} 613 | 614 | raw_setter_impl! { |writable, c_name, index, value: &PointI| { 615 | trace_setter!(writable.handle(), c_name, index, value); 616 | suite_fn!(propSetIntN in *writable.suite(); writable.handle(), c_name, POINT_ELEMENTS, &value.x as *const Int) 617 | }} 618 | 619 | raw_setter_impl! { |writable, c_name, index, value: &RangeI| { 620 | trace_setter!(writable.handle(), c_name, index, value); 621 | suite_fn!(propSetIntN in *writable.suite(); writable.handle(), c_name, RANGE_ELEMENTS, &value.min as *const Int) 622 | }} 623 | 624 | raw_setter_impl! { |writable, c_name, index, value: &RectI| { 625 | trace_setter!(writable.handle(), c_name, index, value); 626 | suite_fn!(propSetIntN in *writable.suite(); writable.handle(), c_name, RECT_ELEMENTS, &value.x1 as *const Int) 627 | }} 628 | 629 | raw_setter_impl! { |writable, c_name, index, value: &Bool| { 630 | trace_setter!(writable.handle(), c_name, index, value); 631 | suite_fn!(propSetInt in *writable.suite(); writable.handle(), c_name, index as Int, if *value { 1 } else { 0 }) 632 | }} 633 | 634 | raw_setter_impl! { |writable, c_name, index, value: &Double| { 635 | trace_setter!(writable.handle(), c_name, index, value); 636 | suite_fn!(propSetDouble in *writable.suite(); writable.handle(), c_name, index as Int, *value) 637 | }} 638 | 639 | raw_setter_impl! { |writable, c_name, index, value: &PointD| { 640 | trace_setter!(writable.handle(), c_name, index, value); 641 | suite_fn!(propSetDoubleN in *writable.suite(); writable.handle(), c_name, POINT_ELEMENTS, &value.x as *const Double) 642 | }} 643 | 644 | raw_setter_impl! { |writable, c_name, index, value: &RangeD| { 645 | trace_setter!(writable.handle(), c_name, index, value); 646 | suite_fn!(propSetDoubleN in *writable.suite(); writable.handle(), c_name, RANGE_ELEMENTS, &value.min as *const Double) 647 | }} 648 | 649 | raw_setter_impl! { |writable, c_name, index, value: &RectD| { 650 | trace_setter!(writable.handle(), c_name, index, value); 651 | suite_fn!(propSetDoubleN in *writable.suite(); writable.handle(), c_name, RECT_ELEMENTS, &value.x1 as *const Double) 652 | }} 653 | 654 | pub trait Setter: RawSetter 655 | where 656 | Self: ValueType + Debug, 657 | W: Writable + AsProperties, 658 | P: Named + Set, 659 | { 660 | fn set_at(writable: &mut W, index: usize, value: &Self) -> Result<()> { 661 | let property_name = P::name(); 662 | let c_name = property_name.as_ptr(); 663 | RawSetter::set_at(writable, c_name as CharPtr, index, value) 664 | } 665 | } 666 | 667 | impl Setter for T 668 | where 669 | T: RawSetter + ?Sized + Debug, 670 | W: Writable + AsProperties, 671 | P: Named + Set, 672 | { 673 | } 674 | 675 | mod tests { 676 | // just compiling 677 | use super::*; 678 | pub struct DummyProperty; 679 | impl Set for DummyProperty { 680 | type ValueType = [u8]; 681 | } 682 | impl Named for DummyProperty { 683 | fn name() -> &'static [u8] { 684 | b"kOfxDummyProperty\0" 685 | } 686 | } 687 | pub trait CanSetDummyProperty: Writable { 688 | fn set_dummy_property<'a>(&mut self, value: &'a [u8]) -> Result<()> { 689 | self.set::(value) 690 | } 691 | } 692 | } 693 | 694 | property! { kOfxPluginPropFilePath as FilePath { 695 | get_file_path() -> String; 696 | }} 697 | 698 | property! { kOfxPropType as Type { 699 | get_type() -> CString as enum EType; 700 | }} 701 | 702 | property! { kOfxPropName as Name { 703 | get_name() -> String; 704 | set_name(&str); 705 | }} 706 | 707 | property! { kOfxPropLabel as Label { 708 | get_label() -> String; 709 | set_label(&str); 710 | }} 711 | 712 | property! { kOfxPropShortLabel as ShortLabel { 713 | get_short_label() -> String; 714 | set_short_label(&str); 715 | }} 716 | 717 | property! { kOfxPropLongLabel as LongLabel { 718 | get_long_label() -> String; 719 | set_long_label(&str); 720 | }} 721 | 722 | property! { kOfxPropVersion as Version { 723 | get_version() -> String; 724 | }} 725 | 726 | property! { kOfxPropVersionLabel as VersionLabel { 727 | get_version_label() -> String; 728 | }} 729 | 730 | property! { kOfxPropAPIVersion as APIVersion { 731 | get_api_version() -> String; 732 | }} 733 | 734 | property! { kOfxPropTime as Time { 735 | get_time() -> Double; 736 | set_time(Double); 737 | }} 738 | 739 | property! { kOfxPropIsInteractive as IsInteractive { 740 | get_is_interactive() -> Bool; 741 | }} 742 | 743 | property! { kOfxPropPluginDescription as PluginDescription { 744 | get_plugin_description() -> String; 745 | set_plugin_description(&str); 746 | }} 747 | 748 | property! { kOfxPropChangeReason as ChangeReason { 749 | get_change_reason() -> CString as enum Change; 750 | }} 751 | 752 | property! { kOfxPropHostOSHandle as HostOSHandle { 753 | get_host_os_handle() -> VoidPtrMut; 754 | }} 755 | 756 | property! { kOfxImageEffectHostPropIsBackground as IsBackground { 757 | get_is_background() -> Bool; 758 | }} 759 | 760 | property! { kOfxImageEffectHostPropNativeOrigin as NativeOrigin { 761 | get_native_origin() -> CString as enum HostNativeOrigin; 762 | }} 763 | 764 | property! { kOfxParamHostPropSupportsCustomInteract as SupportsCustomInteract { 765 | get_supports_custom_interact() -> Bool; 766 | }} 767 | 768 | property! { kOfxParamHostPropSupportsCustomAnimation as SupportsCustomAnimation { 769 | get_supports_custom_animation() -> Bool; 770 | }} 771 | 772 | property! { kOfxParamHostPropSupportsStringAnimation as SupportsStringAnimation { 773 | get_supports_string_animation() -> Bool; 774 | }} 775 | 776 | property! { kOfxParamHostPropSupportsChoiceAnimation as SupportsChoiceAnimation { 777 | get_supports_choice_animation() -> Bool; 778 | }} 779 | 780 | property! { kOfxParamHostPropSupportsBooleanAnimation as SupportsBooleanAnimation { 781 | get_supports_boolean_animation() -> Bool; 782 | }} 783 | 784 | property! { kOfxParamHostPropSupportsParametricAnimation as SupportsParametricAnimation { 785 | get_supports_parametric_animation() -> Bool; 786 | }} 787 | 788 | property! { kOfxParamHostPropMaxParameters as MaxParameters { 789 | get_max_parameters() -> Int; 790 | }} 791 | 792 | property! { kOfxParamHostPropMaxPages as MaxPages { 793 | get_max_pages() -> Int; 794 | }} 795 | 796 | property! { kOfxParamHostPropPageRowColumnCount as PageRowColumnCount { 797 | get_page_row_column_count() -> RectI; 798 | }} 799 | 800 | property! { kOfxImageEffectPluginPropGrouping as Grouping { 801 | get_grouping() -> String; 802 | set_grouping(&str); 803 | }} 804 | 805 | property! { kOfxImageEffectPluginPropFieldRenderTwiceAlways as FieldRenderTwiceAlways { 806 | get_field_render_twice_always() -> Bool; 807 | set_field_render_twice_always(Bool); 808 | }} 809 | 810 | property! { kOfxImageEffectPluginPropSingleInstance as SingleInstance { 811 | get_single_instance() -> Bool; 812 | set_single_instance(Bool); 813 | }} 814 | 815 | property! { kOfxImageEffectPluginPropHostFrameThreading as HostFrameThreading { 816 | get_host_frame_threading() -> Bool; 817 | set_host_frame_threading(Bool); 818 | }} 819 | 820 | property! { kOfxImageEffectPluginRenderThreadSafety as RenderThreadSafety { 821 | get_render_thread_safety() -> CString as enum ImageEffectRender; 822 | set_render_thread_safety(&[u8] as enum ImageEffectRender); 823 | }} 824 | 825 | property! { kOfxImageEffectPropContext as Context { 826 | get_context() -> CString as enum ImageEffectContext; 827 | }} 828 | 829 | property! { kOfxImageEffectPropComponents as Components { 830 | get_components() -> CString as enum ImageComponent; 831 | }} 832 | 833 | property! { kOfxImageEffectPropPixelDepth as PixelDepth { 834 | get_pixel_depth() -> CString as enum BitDepth; 835 | }} 836 | 837 | property! { kOfxImageEffectPropProjectSize as ProjectSize { 838 | get_project_size() -> PointD; 839 | set_project_size(PointD); 840 | }} 841 | 842 | property! { kOfxImageEffectPropProjectOffset as ProjectOffset { 843 | get_project_offset() -> PointD; 844 | set_project_offset(PointD); 845 | }} 846 | 847 | property! { kOfxImageEffectPropProjectExtent as ProjectExtent { 848 | get_project_extent() -> PointD; 849 | set_project_extent(PointD); 850 | }} 851 | 852 | property! { kOfxImageEffectPropProjectPixelAspectRatio as ProjectPixelAspectRatio { 853 | get_project_pixel_aspect_ratio() -> Double; 854 | set_project_pixel_aspect_ratio(Double); 855 | }} 856 | 857 | property! { kOfxImageEffectPropFrameRate as FrameRate { 858 | get_frame_rate() -> Double; 859 | set_frame_rate(Double); 860 | }} 861 | 862 | property! { kOfxImageEffectPropUnmappedFrameRate as UnmappedFrameRate { 863 | get_unmapped_frame_rate() -> Double; 864 | set_unmapped_frame_rate(Double); 865 | }} 866 | 867 | property! { kOfxImageEffectPropSupportsOverlays as SupportsOverlays { 868 | get_supports_overlays() -> Bool; 869 | }} 870 | 871 | property! { kOfxImageEffectPropSupportsMultiResolution as SupportsMultiResolution { 872 | get_supports_multi_resolution() -> Bool; 873 | set_supports_multi_resolution(Bool); 874 | }} 875 | 876 | property! { kOfxImageEffectPropSupportsTiles as SupportsTiles { 877 | get_supports_tiles() -> Bool; 878 | set_supports_tiles(Bool); 879 | }} 880 | 881 | property! { kOfxImageEffectPropSupportsMultipleClipDepths as SupportsMultipleClipDepths { 882 | get_supports_multiple_clip_depths() -> Bool; 883 | set_supports_multiple_clip_depths(Bool); 884 | }} 885 | 886 | property! { kOfxImageEffectPropSupportsMultipleClipPARs as SupportsMultipleClipPARs { 887 | get_supports_multiple_clip_pars() -> Bool; 888 | set_supports_multiple_clip_pars(Bool); 889 | }} 890 | 891 | property! { kOfxImageEffectPropSetableFrameRate as SetableFrameRate { 892 | get_setable_frame_rate() -> Bool; 893 | }} 894 | 895 | property! { kOfxImageEffectPropSetableFielding as SetableFielding { 896 | get_setable_fielding() -> Bool; 897 | }} 898 | 899 | // TODO: allow multiple returns 900 | property! { kOfxImageEffectPropSupportedContexts as SupportedContexts { 901 | get_supported_contexts() -> CString; 902 | set_supported_contexts(&[u8] as &[enum ImageEffectContext]); 903 | }} 904 | 905 | property! { kOfxImageEffectPropSupportedPixelDepths as SupportedPixelDepths { 906 | get_supported_pixel_depths() -> CString; 907 | set_supported_pixel_depths(&[u8] as &[enum BitDepth]); 908 | }} 909 | 910 | property! { kOfxImageEffectPropSupportedComponents as SupportedComponents { 911 | get_supported_components() -> CString; 912 | set_supported_components(&[u8] as &[enum ImageComponent]); 913 | }} 914 | 915 | property! { kOfxImageEffectPropPreMultiplication as PreMultiplication { 916 | get_pre_multiplication() -> Bool; 917 | set_pre_multiplication(Bool); 918 | }} 919 | 920 | property! { kOfxImageEffectPropRenderWindow as RenderWindow { 921 | get_render_window() -> RectI; 922 | set_render_window(RectI); 923 | }} 924 | 925 | property! { kOfxImageEffectPropRenderScale as RenderScale { 926 | get_render_scale() -> PointD; 927 | set_render_scale(PointD); 928 | }} 929 | 930 | property! { kOfxImageEffectPropRegionOfInterest as RegionOfInterest { 931 | get_region_of_interest() -> RectD; 932 | set_region_of_interest(RectD); 933 | }} 934 | 935 | // there are two RegionOfDefinition, one for clips and one for images, 936 | property! { kOfxImageEffectPropRegionOfDefinition as EffectRegionOfDefinition{ 937 | get_effect_region_of_definition() -> RectD; 938 | set_effect_region_of_definition(RectD); 939 | }} 940 | 941 | property! { kOfxImageEffectPropFrameRange as FrameRange { 942 | get_frame_range() -> RangeD; 943 | set_frame_range(RangeD); 944 | }} 945 | 946 | property! { kOfxImageEffectPropUnmappedFrameRange as UnmappedFrameRange { 947 | get_unmapped_frame_range() -> RangeD; 948 | set_unmapped_frame_range(RangeD); 949 | }} 950 | 951 | property! { kOfxImageEffectPropFrameStep as FrameStep { 952 | get_frame_step() -> Double; 953 | }} 954 | 955 | property! { kOfxImageEffectPropFieldToRender as FieldToRender { 956 | get_field_to_render() -> CString as enum ImageField; 957 | }} 958 | 959 | property! { kOfxImageEffectPropTemporalClipAccess as TemporalClipAccess { 960 | get_temporal_clip_access() -> Bool; 961 | set_temporal_clip_access(Bool); 962 | }} 963 | 964 | // todo: return multiple strings 965 | property! { kOfxImageEffectPropClipPreferencesSlaveParam as ClipPreferencesSlaveParam { 966 | get_clip_preferences_slave_param() -> String; 967 | set_clip_preferences_slave_param(&str); 968 | }} 969 | 970 | property! { kOfxImageEffectPropSequentialRenderStatus as SequentialRenderStatus { 971 | get_sequential_render_status() -> Bool; 972 | }} 973 | 974 | property! { kOfxImageEffectPropInteractiveRenderStatus as InteractiveRenderStatus { 975 | get_interactive_render_status() -> Bool; 976 | }} 977 | 978 | property! { kOfxImageEffectPropOpenGLRenderSupported as OpenGLRenderSupported { 979 | get_opengl_render_supported() -> Bool; 980 | set_opengl_render_supported(Bool); 981 | }} 982 | 983 | property! { kOfxImageEffectPropRenderQualityDraft as RenderQualityDraft { 984 | get_render_quality_draft() -> Bool; 985 | }} 986 | 987 | property! { kOfxImageEffectInstancePropEffectDuration as EffectDuration { 988 | get_effect_duration() -> Double; 989 | set_effect_duration(Double); 990 | }} 991 | 992 | property! { kOfxImageEffectInstancePropSequentialRender as SequentialRender { 993 | get_sequential_render() -> Bool; 994 | set_sequential_render(Bool); 995 | }} 996 | 997 | property! { kOfxImageClipPropConnected as Connected { 998 | get_connected() -> Bool; 999 | }} 1000 | 1001 | property! { kOfxImageClipPropUnmappedComponents as UnmappedComponents { 1002 | get_unmapped_components() -> CString as enum ImageComponent; 1003 | }} 1004 | 1005 | property! { kOfxImageClipPropUnmappedPixelDepth as UnmappedPixelDepth { 1006 | get_unmapped_pixel_depth() -> CString as enum BitDepth; 1007 | }} 1008 | 1009 | property! { kOfxImageClipPropFieldExtraction as FieldExtraction { 1010 | get_field_extraction() -> CString as enum ImageFieldExtraction; 1011 | set_field_extraction(&[u8] as enum ImageFieldExtraction); 1012 | }} 1013 | 1014 | property! { kOfxImageClipPropFieldOrder as FieldOrder { 1015 | get_field_order() -> CString as enum ImageFieldOrder; 1016 | set_field_order(&[u8] as enum ImageFieldOrder); 1017 | }} 1018 | 1019 | property! { kOfxImageClipPropOptional as Optional { 1020 | get_optional() -> Bool; 1021 | set_optional(Bool); 1022 | }} 1023 | 1024 | property! { kOfxImageClipPropIsMask as IsMask { 1025 | get_is_mask() -> Bool; 1026 | set_is_mask(Bool); 1027 | }} 1028 | 1029 | property! { kOfxImageClipPropContinuousSamples as ContinuousSamples { 1030 | get_continuous_samples() -> Bool; 1031 | set_continuous_samples(Bool); 1032 | }} 1033 | 1034 | property! { kOfxImagePropRowBytes as RowBytes { 1035 | get_row_bytes() -> Int; 1036 | }} 1037 | 1038 | property! { kOfxImagePropBounds as Bounds { 1039 | get_bounds() -> RectI; 1040 | }} 1041 | 1042 | property! { kOfxImagePropData as Data { 1043 | get_data() -> VoidPtrMut; 1044 | }} 1045 | 1046 | property! { kOfxImagePropField as Field { 1047 | get_field() -> CString as enum ImageField; 1048 | }} 1049 | 1050 | property! { kOfxImagePropPixelAspectRatio as PixelAspectRatio { 1051 | get_pixel_aspect_ratio() -> Double; 1052 | }} 1053 | 1054 | // there are two RegionOfDefinition, one for clips and one for images, 1055 | property! { kOfxImagePropRegionOfDefinition as RegionOfDefinition { 1056 | get_region_of_definition() -> RectI; 1057 | }} 1058 | 1059 | property! { kOfxImagePropUniqueIdentifier as UniqueIdentifier { 1060 | get_unique_identifier() -> String; 1061 | }} 1062 | 1063 | property! { kOfxParamPropEnabled as Enabled { 1064 | get_enabled() -> Bool; 1065 | set_enabled(Bool); 1066 | }} 1067 | 1068 | property! { kOfxPropParamSetNeedsSyncing as NeedsSyncing { 1069 | get_needs_syncing() -> Bool; 1070 | }} 1071 | 1072 | property! { kOfxParamPropHint as Hint { 1073 | get_hint() -> String; 1074 | set_hint(&str); 1075 | }} 1076 | 1077 | property! { kOfxParamPropParent as Parent { 1078 | get_parent() -> String; 1079 | set_parent(&str); 1080 | }} 1081 | 1082 | property! { kOfxParamPropScriptName as ScriptName { 1083 | get_script_name() -> String; 1084 | set_script_name(&str); 1085 | }} 1086 | 1087 | property_group! { CommonParameters { 1088 | Type read, 1089 | Label read+write, 1090 | Hint read+write, 1091 | Parent read+write, 1092 | ScriptName read+write, 1093 | Enabled read+write, 1094 | }} 1095 | 1096 | pub mod double { 1097 | use super::*; 1098 | property_assign_name!(kOfxParamPropDoubleType as DoubleType: (&[u8]) -> CString); 1099 | property_assign_name!(kOfxParamPropDefault as Default: Double); 1100 | property_assign_name!(kOfxParamPropDisplayMax as DisplayMax: Double); 1101 | property_assign_name!(kOfxParamPropDisplayMin as DisplayMin: Double); 1102 | } 1103 | 1104 | pub mod boolean { 1105 | use super::*; 1106 | property_assign_name!(kOfxParamPropDefault as Default: Bool); 1107 | } 1108 | 1109 | pub mod page { 1110 | use super::*; 1111 | property_assign_name!(kOfxParamPropPageChild as Child: (&str) -> String); 1112 | } 1113 | 1114 | #[allow(non_snake_case)] 1115 | pub mod Children { 1116 | use super::*; 1117 | property_define_setter_trait!(CanSet => set_children, page::Child, &seq[&str]); 1118 | } 1119 | pub use Children::CanSet as CanSetChildren; 1120 | 1121 | #[allow(non_snake_case)] 1122 | pub mod Labels { 1123 | use super::*; 1124 | pub trait CanSet: Label::CanSet + ShortLabel::CanSet + LongLabel::CanSet { 1125 | fn set_labels(&mut self, label: &str, short: &str, long: &str) -> Result<()> { 1126 | self.set_label(label)?; 1127 | self.set_short_label(short)?; 1128 | self.set_long_label(long)?; 1129 | Ok(()) 1130 | } 1131 | } 1132 | } 1133 | 1134 | impl Labels::CanSet for T where T: Label::CanSet + ShortLabel::CanSet + LongLabel::CanSet {} 1135 | 1136 | #[allow(non_snake_case)] 1137 | pub mod NameRaw { 1138 | use super::*; 1139 | pub trait CanSet: Name::CanSet { 1140 | fn set_name_raw(&mut self, name_raw: &[u8]) -> Result<()> { 1141 | self.set_name(CStr::from_bytes_with_nul(name_raw)?.to_str()?) 1142 | } 1143 | } 1144 | } 1145 | pub use NameRaw::CanSet as CanSetNameRaw; 1146 | 1147 | #[allow(non_snake_case)] 1148 | pub mod DoubleParams { 1149 | use super::*; 1150 | pub trait CanSet: Writable { 1151 | property_define_setter_trait!(set_double_type, double::DoubleType, enum ParamDoubleType); 1152 | property_define_setter_trait!(set_default, double::Default); 1153 | property_define_setter_trait!(set_display_max, double::DisplayMax); 1154 | property_define_setter_trait!(set_display_min, double::DisplayMin); 1155 | } 1156 | } 1157 | 1158 | pub use DoubleParams::CanSet as CanSetDoubleParams; 1159 | 1160 | #[allow(non_snake_case)] 1161 | pub mod BooleanParams { 1162 | use super::*; 1163 | pub trait CanSet: Writable { 1164 | property_define_setter_trait!(set_default, boolean::Default); 1165 | } 1166 | } 1167 | 1168 | pub use BooleanParams::CanSet as CanSetBooleanParams; 1169 | 1170 | impl CommonParameters for ParamHandle where T: ParamHandleValue + Clone {} 1171 | 1172 | // https://openfx.readthedocs.io/en/doc/Reference/ofxPropertiesByObject.html#properties-on-an-effect-descriptor 1173 | object_properties! { ImageEffectHost { 1174 | Name read, 1175 | Label read, 1176 | Version read, 1177 | VersionLabel read, 1178 | IsBackground read, 1179 | SupportsOverlays read, 1180 | SupportsMultiResolution read, 1181 | SupportsTiles read, 1182 | TemporalClipAccess read, 1183 | SupportedComponents read, 1184 | SupportedContexts read, 1185 | SupportsMultipleClipDepths read, 1186 | SupportsMultipleClipPARs read, 1187 | SetableFrameRate read, 1188 | SetableFielding read, 1189 | SupportsCustomInteract read, 1190 | SupportsStringAnimation read, 1191 | SupportsChoiceAnimation read, 1192 | SupportsBooleanAnimation read, 1193 | SupportsCustomAnimation read, 1194 | MaxParameters read, 1195 | MaxPages read, 1196 | PageRowColumnCount read, 1197 | HostOSHandle read, 1198 | SupportsParametricAnimation read, 1199 | SequentialRender read, 1200 | OpenGLRenderSupported read, 1201 | RenderQualityDraft read, 1202 | NativeOrigin read, 1203 | }} 1204 | 1205 | // TODO: canset should be only exposed in the "Describe" action 1206 | object_properties! { EffectDescriptor { 1207 | Type read, 1208 | Label read+write, 1209 | ShortLabel read+write, 1210 | LongLabel read+write, 1211 | Version read, 1212 | VersionLabel read, 1213 | PluginDescription read+write, 1214 | SupportedContexts read+write, 1215 | Grouping read+write, 1216 | SingleInstance read+write, 1217 | RenderThreadSafety read+write, 1218 | HostFrameThreading read+write, 1219 | // TODO: missing yet 1220 | // OverlayInteractV1 read+write, 1221 | SupportsMultiResolution read+write, 1222 | SupportsTiles read+write, 1223 | TemporalClipAccess read+write, 1224 | SupportedPixelDepths read+write, 1225 | FieldRenderTwiceAlways read, 1226 | SupportsMultipleClipDepths read+write, 1227 | SupportsMultipleClipPARs read+write, 1228 | OpenGLRenderSupported read+write, 1229 | ClipPreferencesSlaveParam read+write, 1230 | FilePath read, 1231 | // convenience extras 1232 | Labels write, 1233 | }} 1234 | 1235 | // Image Effect Instance 1236 | object_properties! { EffectInstance { 1237 | Type read, 1238 | Context read, 1239 | Label read, 1240 | ProjectSize read, 1241 | ProjectOffset read, 1242 | ProjectExtent read, 1243 | ProjectPixelAspectRatio read, 1244 | EffectDuration read, 1245 | SequentialRender read+write, 1246 | SupportsTiles read+write, 1247 | SupportsMultiResolution read+write, 1248 | OpenGLRenderSupported read+write, 1249 | FrameRate read, 1250 | SupportedPixelDepths read+write, 1251 | IsInteractive read, 1252 | }} 1253 | 1254 | // Clip Descriptor 1255 | object_properties! { ClipDescriptor { 1256 | Type read, 1257 | Name read, 1258 | Label read+write, 1259 | ShortLabel read+write, 1260 | LongLabel read+write, 1261 | SupportedComponents read+write, 1262 | TemporalClipAccess read+write, 1263 | Optional read+write, 1264 | FieldExtraction read+write, 1265 | IsMask read+write, 1266 | SupportsTiles read+write, 1267 | }} 1268 | 1269 | // Clip Instance 1270 | object_properties! { ClipInstance { 1271 | Type read, 1272 | Name read, 1273 | Label read, 1274 | ShortLabel read, 1275 | LongLabel read, 1276 | SupportedComponents read, 1277 | TemporalClipAccess read, 1278 | Optional read, 1279 | FieldExtraction read, 1280 | IsMask read, 1281 | SupportsTiles read, 1282 | PixelDepth read, 1283 | Components read, 1284 | UnmappedPixelDepth read, 1285 | UnmappedComponents read, 1286 | PreMultiplication read, 1287 | PixelAspectRatio read, 1288 | FrameRate read, 1289 | FrameRange read, 1290 | FieldOrder read, 1291 | Connected read, 1292 | UnmappedFrameRange read, 1293 | UnmappedFrameRate read, 1294 | ContinuousSamples read, 1295 | }} 1296 | 1297 | object_properties! { Image { 1298 | Type read, 1299 | PixelDepth read, 1300 | Components read, 1301 | PreMultiplication read, 1302 | RenderScale read, 1303 | PixelAspectRatio read, 1304 | Data read, 1305 | Bounds read, 1306 | RegionOfDefinition read, 1307 | RowBytes read, 1308 | Field read, 1309 | UniqueIdentifier read, 1310 | }} 1311 | 1312 | object_properties! { ParamDouble { 1313 | CommonParameters inherit, 1314 | DoubleParams write, 1315 | }} 1316 | 1317 | object_properties! { ParamBoolean { 1318 | CommonParameters inherit, 1319 | BooleanParams write, 1320 | }} 1321 | 1322 | object_properties! { ParamPage { 1323 | CommonParameters inherit, 1324 | Children write, 1325 | }} 1326 | 1327 | object_properties! { ParamGroup { 1328 | CommonParameters inherit, 1329 | }} 1330 | 1331 | object_properties! { ParameterSet { 1332 | NeedsSyncing read, 1333 | }} 1334 | 1335 | object_properties! { DescribeInContextInArgs { 1336 | Context read, 1337 | }} 1338 | 1339 | object_properties! { IsIdentityInArgs { 1340 | Time read, 1341 | FieldToRender read, 1342 | RenderWindow read, 1343 | RenderScale read, 1344 | }} 1345 | 1346 | object_properties! { IsIdentityOutArgs { 1347 | Name write, 1348 | Time write, 1349 | }} 1350 | 1351 | object_properties! { GetRegionOfDefinitionInArgs { 1352 | Time read, 1353 | RegionOfDefinition read, 1354 | }} 1355 | 1356 | object_properties! { GetRegionOfDefinitionOutArgs { 1357 | EffectRegionOfDefinition write, 1358 | }} 1359 | 1360 | object_properties! { GetRegionsOfInterestInArgs { 1361 | RegionOfInterest read, 1362 | }} 1363 | 1364 | object_properties! { GetRegionsOfInterestOutArgs { 1365 | RawWritable inherit, 1366 | RegionOfInterest write, 1367 | }} 1368 | 1369 | object_properties! { GetClipPreferencesOutArgs { 1370 | RawWritable inherit, 1371 | }} 1372 | 1373 | object_properties! { InstanceChangedInArgs { 1374 | Type read, 1375 | Name read, 1376 | Time read, 1377 | ChangeReason read, 1378 | RenderScale read, 1379 | }} 1380 | 1381 | object_properties! { BeginInstanceChangedInArgs { 1382 | ChangeReason read, 1383 | }} 1384 | 1385 | object_properties! { EndInstanceChangedInArgs { 1386 | ChangeReason read, 1387 | }} 1388 | 1389 | object_properties! { RenderInArgs { 1390 | Time read, 1391 | FieldToRender read, 1392 | RenderWindow read, 1393 | RenderScale read, 1394 | SequentialRenderStatus read, 1395 | InteractiveRenderStatus read, 1396 | RenderQualityDraft read, 1397 | }} 1398 | 1399 | object_properties! { BeginSequenceRenderInArgs { 1400 | FrameRange read, 1401 | FrameStep read, 1402 | IsInteractive read, 1403 | RenderScale read, 1404 | SequentialRenderStatus read, 1405 | InteractiveRenderStatus read, 1406 | RenderQualityDraft read, 1407 | }} 1408 | 1409 | object_properties! { EndSequenceRenderInArgs { 1410 | FrameRange read, 1411 | FrameStep read, 1412 | IsInteractive read, 1413 | RenderScale read, 1414 | SequentialRenderStatus read, 1415 | InteractiveRenderStatus read, 1416 | RenderQualityDraft read, 1417 | }} 1418 | 1419 | object_properties! { GetTimeDomainOutArgs { 1420 | FrameRange write, 1421 | }} 1422 | -------------------------------------------------------------------------------- /ofx/src/registry.rs: -------------------------------------------------------------------------------- 1 | use action::*; 2 | use ofx_sys::*; 3 | use plugin::*; 4 | use result::*; 5 | use std::collections::HashMap; 6 | use types::*; 7 | 8 | #[derive(Default)] 9 | pub struct Registry { 10 | plugins: Vec, 11 | plugin_modules: HashMap, 12 | } 13 | 14 | impl Registry { 15 | pub fn new() -> Registry { 16 | Self::default() 17 | } 18 | 19 | pub fn add( 20 | &mut self, 21 | module_name: &'static str, 22 | name: &'static str, 23 | api_version: ApiVersion, 24 | plugin_version: PluginVersion, 25 | instance: Box, 26 | set_host: SetHost, 27 | main_entry: MainEntry, 28 | ) -> usize { 29 | let plugin_index = self.plugins.len(); 30 | 31 | self.plugin_modules 32 | .insert(module_name.to_owned(), plugin_index as usize); 33 | 34 | let plugin = PluginDescriptor::new( 35 | plugin_index, 36 | module_name, 37 | name, 38 | api_version, 39 | plugin_version, 40 | instance, 41 | set_host, 42 | main_entry, 43 | ); 44 | 45 | self.plugins.push(plugin); 46 | plugin_index 47 | } 48 | 49 | pub fn count(&self) -> Int { 50 | self.plugins.len() as Int 51 | } 52 | 53 | pub fn get_plugin_mut(&mut self, index: usize) -> &mut PluginDescriptor { 54 | &mut self.plugins[index as usize] 55 | } 56 | 57 | pub fn get_plugin(&self, index: usize) -> &PluginDescriptor { 58 | &self.plugins[index as usize] 59 | } 60 | 61 | pub fn ofx_plugin(&'static self, index: Int) -> &'static OfxPlugin { 62 | &self.plugins[index as usize].ofx_plugin() 63 | } 64 | 65 | pub fn dispatch(&mut self, plugin_module: &str, message: RawMessage) -> Result { 66 | info!("{}:{:?}", plugin_module, message); 67 | let found_plugin = self.plugin_modules.get(plugin_module).cloned(); 68 | if let Some(plugin_index) = found_plugin { 69 | let plugin = self.get_plugin_mut(plugin_index); 70 | plugin.dispatch(message) 71 | } else { 72 | Err(Error::PluginNotFound) 73 | } 74 | } 75 | } 76 | 77 | pub unsafe fn set_host_for_plugin(plugin_module: &str, host: *mut OfxHost) { 78 | unsafe { 79 | get_registry_mut() 80 | .dispatch(plugin_module, RawMessage::SetHost { host: *host }) 81 | .ok(); 82 | } 83 | } 84 | 85 | static mut _GLOBAL_REGISTRY: Option = None; 86 | 87 | pub fn main_entry_for_plugin( 88 | plugin_module: &str, 89 | action: CharPtr, 90 | handle: VoidPtr, 91 | in_args: OfxPropertySetHandle, 92 | out_args: OfxPropertySetHandle, 93 | ) -> Int { 94 | unsafe { 95 | get_registry_mut() 96 | .dispatch( 97 | plugin_module, 98 | RawMessage::MainEntry { 99 | action, 100 | handle, 101 | in_args, 102 | out_args, 103 | }, 104 | ) 105 | .ok() 106 | .unwrap_or(-1) 107 | } 108 | } 109 | 110 | pub fn init_registry(init_function: F) 111 | where 112 | F: Fn(&mut Registry), 113 | { 114 | unsafe { 115 | if _GLOBAL_REGISTRY.is_none() { 116 | use log4rs::append::console::*; 117 | use log4rs::config::*; 118 | 119 | if let Err(e) = log4rs::init_file("ofx_log4rs.yaml", Default::default()) { 120 | let config = Config::builder() 121 | .appender(Appender::builder().build( 122 | "stdout".to_string(), 123 | Box::new(ConsoleAppender::builder().build()), 124 | )) 125 | .logger(Logger::builder().build("ofx".to_string(), log::LevelFilter::Warn)) 126 | .build( 127 | Root::builder() 128 | .appender("stdout".to_string()) 129 | .build(log::LevelFilter::Error), 130 | ); 131 | log4rs::init_config(config.unwrap()).unwrap(); 132 | } 133 | let mut registry = Registry::new(); 134 | init_function(&mut registry); 135 | for plugin in ®istry.plugins { 136 | info!("Registered plugin {}", plugin); 137 | } 138 | _GLOBAL_REGISTRY = Some(registry); 139 | } 140 | } 141 | } 142 | 143 | fn get_registry_mut() -> &'static mut Registry { 144 | unsafe { _GLOBAL_REGISTRY.as_mut().unwrap() } 145 | } 146 | 147 | pub fn get_registry() -> &'static Registry { 148 | unsafe { _GLOBAL_REGISTRY.as_ref().unwrap() } 149 | } 150 | 151 | #[macro_export] 152 | macro_rules! plugin_module { 153 | ($name:expr, $api_version:expr, $plugin_version:expr, $factory:expr) => { 154 | pub fn name() -> &'static str { 155 | $name 156 | } 157 | 158 | pub fn module_name() -> &'static str { 159 | module_path!() //.split("::").last().as_ref().unwrap() 160 | } 161 | 162 | pub fn new_instance() -> Box { 163 | Box::new($factory()) 164 | } 165 | 166 | pub fn api_version() -> ApiVersion { 167 | $api_version 168 | } 169 | 170 | pub fn plugin_version() -> PluginVersion { 171 | $plugin_version 172 | } 173 | 174 | pub extern "C" fn set_host(host: *mut ofx::OfxHost) { 175 | unsafe { ofx::set_host_for_plugin(module_name(), host) } 176 | } 177 | 178 | pub extern "C" fn main_entry( 179 | action: ofx::CharPtr, 180 | handle: ofx::VoidPtr, 181 | in_args: ofx::OfxPropertySetHandle, 182 | out_args: ofx::OfxPropertySetHandle, 183 | ) -> super::Int { 184 | ofx::main_entry_for_plugin(module_name(), action, handle, in_args, out_args) 185 | } 186 | }; 187 | } 188 | 189 | #[macro_export] 190 | macro_rules! register_plugin { 191 | ($registry:ident, $module:ident) => { 192 | $registry.add( 193 | $module::module_name(), 194 | $module::name(), 195 | $module::api_version(), 196 | $module::plugin_version(), 197 | $module::new_instance(), 198 | $module::set_host, 199 | $module::main_entry, 200 | ); 201 | }; 202 | } 203 | 204 | #[macro_export] 205 | macro_rules! build_plugin_registry { 206 | ($init_callback:ident) => { 207 | fn init() { 208 | init_registry($init_callback); 209 | } 210 | 211 | #[no_mangle] 212 | pub extern "C" fn OfxGetNumberOfPlugins() -> Int { 213 | init(); 214 | get_registry().count() 215 | } 216 | 217 | #[no_mangle] 218 | pub extern "C" fn OfxGetPlugin(nth: Int) -> *const OfxPlugin { 219 | init(); 220 | get_registry().ofx_plugin(nth) as *const OfxPlugin 221 | } 222 | 223 | pub fn show_plugins() -> Vec { 224 | unsafe { 225 | let n = OfxGetNumberOfPlugins(); 226 | for i in 0..n { 227 | OfxGetPlugin(i); 228 | } 229 | (0..n) 230 | .map(|i| { 231 | let plugin = get_registry().get_plugin(i as usize); 232 | format!("{}", plugin) 233 | }) 234 | .collect() 235 | } 236 | } 237 | }; 238 | } 239 | -------------------------------------------------------------------------------- /ofx/src/result.rs: -------------------------------------------------------------------------------- 1 | use ofx_sys::*; 2 | use std::fmt; 3 | use std::fmt::Display; 4 | use types::*; 5 | 6 | pub use ofx_sys::eOfxStatus_ErrBadHandle; 7 | pub use ofx_sys::eOfxStatus_ErrBadIndex; 8 | pub use ofx_sys::eOfxStatus_ErrValue; 9 | pub use ofx_sys::eOfxStatus_ReplyDefault; 10 | pub use ofx_sys::eOfxStatus_OK; 11 | 12 | #[derive(Debug, Clone, Copy)] 13 | pub enum Error { 14 | PluginNotFound, 15 | InvalidAction, 16 | InvalidImageEffectAction, 17 | InvalidNameEncoding, 18 | InvalidResultEncoding, 19 | InvalidHandle, 20 | InvalidValue, 21 | InvalidSuite, 22 | InvalidIndex, 23 | PluginNotReady, 24 | PropertyIndexOutOfBounds, 25 | HostNotReady, 26 | EnumNotFound, 27 | SuiteNotInitialized, 28 | Unimplemented, 29 | UnknownError, 30 | } 31 | 32 | pub const OK: Result = Ok(eOfxStatus_OK); 33 | pub const REPLY_DEFAULT: Result = Ok(eOfxStatus_ReplyDefault); 34 | pub const FAILED: Result = Ok(eOfxStatus_Failed); 35 | pub const UNIMPLEMENTED: Result = Err(Error::Unimplemented); 36 | 37 | impl From for Error { 38 | fn from(status: OfxStatus) -> Error { 39 | match status { 40 | ofx_sys::eOfxStatus_ErrBadHandle => Error::InvalidHandle, 41 | ofx_sys::eOfxStatus_ErrBadIndex => Error::InvalidIndex, 42 | ofx_sys::eOfxStatus_ErrValue => Error::InvalidValue, 43 | _ => Error::UnknownError, 44 | } 45 | } 46 | } 47 | 48 | macro_rules! to_result { 49 | {$ofx_status:expr => $result:expr} => { 50 | match $ofx_status { 51 | ofx_sys::eOfxStatus_OK => Ok($result), 52 | other => Err(Error::from(other)), 53 | } 54 | }; 55 | ($ofx_status:expr) => { 56 | to_result!($ofx_status => ()) 57 | }; 58 | } 59 | 60 | impl From for Error { 61 | fn from(_src: std::ffi::NulError) -> Error { 62 | Error::InvalidNameEncoding 63 | } 64 | } 65 | 66 | impl From for Error { 67 | fn from(_src: std::ffi::IntoStringError) -> Error { 68 | Error::InvalidNameEncoding 69 | } 70 | } 71 | 72 | impl From for Error { 73 | fn from(_src: std::ffi::FromBytesWithNulError) -> Error { 74 | Error::InvalidResultEncoding 75 | } 76 | } 77 | 78 | impl From for Error { 79 | fn from(_src: std::str::Utf8Error) -> Error { 80 | Error::InvalidResultEncoding 81 | } 82 | } 83 | 84 | impl fmt::Display for Error { 85 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 86 | write!(f, "Openfx error") 87 | } 88 | } 89 | 90 | impl std::error::Error for Error {} 91 | 92 | pub type Result = std::result::Result; 93 | -------------------------------------------------------------------------------- /ofx/src/suites.rs: -------------------------------------------------------------------------------- 1 | use ofx_sys::*; 2 | use result::*; 3 | use std::borrow::Borrow; 4 | use std::rc::Rc; 5 | 6 | #[derive(Clone)] 7 | pub struct Suites { 8 | image_effect: Rc, 9 | property: Rc, 10 | parameter: Rc, 11 | memory: Rc, 12 | pub(crate) multi_thread: Rc, 13 | message: Rc, 14 | message_v2: Option>, 15 | progress: Rc, 16 | progress_v2: Option>, 17 | time_line: Rc, 18 | parametric_parameter: Option>, 19 | image_effect_opengl_render: Option>, 20 | } 21 | 22 | macro_rules! suite_call { 23 | ($function:ident in $suite:expr; $($arg:expr),*) => { 24 | unsafe { ($suite).$function.ok_or(Error::SuiteNotInitialized)?($($arg),*) } 25 | }; 26 | } 27 | 28 | macro_rules! suite_fn { 29 | ($($tail:tt)*) => { to_result!{suite_call!($($tail)*)} } 30 | } 31 | 32 | 33 | 34 | #[allow(clippy::too_many_arguments)] 35 | impl Suites { 36 | pub fn new( 37 | image_effect: OfxImageEffectSuiteV1, 38 | property: OfxPropertySuiteV1, 39 | parameter: OfxParameterSuiteV1, 40 | memory: OfxMemorySuiteV1, 41 | multi_thread: OfxMultiThreadSuiteV1, 42 | message: OfxMessageSuiteV1, 43 | message_v2: Option, 44 | progress: OfxProgressSuiteV1, 45 | progress_v2: Option, 46 | time_line: OfxTimeLineSuiteV1, 47 | parametric_parameter: Option, 48 | image_effect_opengl_render: Option, 49 | ) -> Self { 50 | Suites { 51 | image_effect: Rc::new(image_effect), 52 | property: Rc::new(property), 53 | parameter: Rc::new(parameter), 54 | memory: Rc::new(memory), 55 | multi_thread: Rc::new(multi_thread), 56 | message: Rc::new(message), 57 | message_v2: message_v2.map(Rc::new), 58 | progress: Rc::new(progress), 59 | progress_v2: progress_v2.map(Rc::new), 60 | time_line: Rc::new(time_line), 61 | parametric_parameter: parametric_parameter.map(Rc::new), 62 | image_effect_opengl_render: image_effect_opengl_render.map(Rc::new), 63 | } 64 | } 65 | 66 | pub fn image_effect(&self) -> Rc { 67 | self.image_effect.clone() 68 | } 69 | 70 | pub fn property(&self) -> Rc { 71 | self.property.clone() 72 | } 73 | 74 | pub fn parameter(&self) -> Rc { 75 | self.parameter.clone() 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /ofx/src/types.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused)] 2 | 3 | use ofx_sys::OfxStatus; 4 | use std::os::raw::*; 5 | 6 | pub type Int = c_int; 7 | pub type UnsignedInt = c_uint; 8 | pub type Double = c_double; 9 | pub type Float = c_float; 10 | pub type Bool = bool; 11 | pub type Char = c_char; 12 | pub type CharPtr = *const c_char; 13 | pub type CharPtrMut = *mut c_char; 14 | pub type CStr = *const c_char; 15 | pub type Void = c_void; 16 | pub type VoidPtr = *const c_void; 17 | pub type VoidPtrMut = *mut c_void; 18 | pub type Status = OfxStatus; 19 | pub type PointI = ofx_sys::OfxPointI; 20 | pub type PointD = ofx_sys::OfxPointD; 21 | pub const POINT_ELEMENTS: Int = 2; 22 | pub type RangeI = ofx_sys::OfxRangeI; 23 | pub type RangeD = ofx_sys::OfxRangeD; 24 | pub const RANGE_ELEMENTS: Int = 2; 25 | pub type RectI = ofx_sys::OfxRectI; 26 | pub type RectD = ofx_sys::OfxRectD; 27 | pub const RECT_ELEMENTS: Int = 4; 28 | pub type Time = ofx_sys::OfxTime; 29 | pub type ThreadFunction = ofx_sys::OfxThreadFunctionV1; 30 | pub type RGBAColourB = ofx_sys::OfxRGBAColourB; 31 | pub type RGBAColourS = ofx_sys::OfxRGBAColourS; 32 | pub type RGBAColourF = ofx_sys::OfxRGBAColourF; 33 | pub type RGBAColourD = ofx_sys::OfxRGBAColourD; 34 | pub type RGBColourB = ofx_sys::OfxRGBColourB; 35 | pub type RGBColourS = ofx_sys::OfxRGBColourS; 36 | pub type RGBColourF = ofx_sys::OfxRGBColourF; 37 | pub type RGBColourD = ofx_sys::OfxRGBColourD; 38 | pub type YUVAColourB = ofx_sys::OfxYUVAColourB; 39 | pub type YUVAColourS = ofx_sys::OfxYUVAColourS; 40 | pub type YUVAColourF = ofx_sys::OfxYUVAColourF; 41 | 42 | pub(crate) type SetHost = unsafe extern "C" fn(*mut ofx_sys::OfxHost); 43 | pub(crate) type MainEntry = unsafe extern "C" fn( 44 | *const i8, 45 | VoidPtr, 46 | *mut ofx_sys::OfxPropertySetStruct, 47 | *mut ofx_sys::OfxPropertySetStruct, 48 | ) -> Int; 49 | -------------------------------------------------------------------------------- /ofx/src/util.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use ofx_sys::*; 3 | use std::ffi::{CStr, CString}; 4 | 5 | pub fn static_bytes_to_string(src: &[u8]) -> String { 6 | let s = unsafe { CStr::from_bytes_with_nul_unchecked(src) }; 7 | String::from(s.to_string_lossy()) 8 | } 9 | 10 | pub fn image_effect_simple_source_clip_name() -> String { 11 | static_bytes_to_string(kOfxImageEffectSimpleSourceClipName) 12 | } 13 | 14 | #[macro_export] 15 | macro_rules! clip_mask { 16 | () => { 17 | "Mask" 18 | }; 19 | } 20 | 21 | #[macro_export] 22 | macro_rules! clip_source { 23 | () => { 24 | "Source" 25 | }; 26 | } 27 | 28 | #[macro_export] 29 | macro_rules! clip_output { 30 | () => { 31 | "Output" 32 | }; 33 | } 34 | 35 | #[macro_export] 36 | macro_rules! image_clip_prop_components { 37 | ($clip:expr) => { 38 | concat!("OfxImageClipPropComponents_", $clip) 39 | }; 40 | } 41 | 42 | #[macro_export] 43 | macro_rules! image_clip_prop_roi { 44 | ($clip:expr) => { 45 | concat!("OfxImageClipPropRoI_", $clip) 46 | }; 47 | } 48 | 49 | #[macro_export] 50 | macro_rules! image_clip_prop_depth { 51 | ($clip:expr) => { 52 | concat!("OfxImageClipPropDepth_", $clip) 53 | }; 54 | } 55 | 56 | #[macro_export] 57 | macro_rules! static_str { 58 | ($name:expr) => { unsafe { CStr::from_bytes_with_nul_unchecked($name).as_ptr() } } 59 | } 60 | -------------------------------------------------------------------------------- /ofx_log4rs.yaml: -------------------------------------------------------------------------------- 1 | refresh_rate: 30 seconds 2 | appenders: 3 | stdout: 4 | kind: console 5 | root: 6 | level: debug 7 | appenders: 8 | - stdout 9 | -------------------------------------------------------------------------------- /test.py: -------------------------------------------------------------------------------- 1 | print("Rendering test image...") 2 | write = app1.createWriter("target/filtered_test_####.png") 3 | 4 | source = app1.createNode("net.sf.openfx.CheckerBoardPlugin") 5 | mask = app1.createNode("net.sf.openfx.Radial") 6 | 7 | under_test = app1.createNode("net.itadinanta.ofx-rs.basic") 8 | under_test.connectInput(0, source) 9 | under_test.connectInput(1, mask) 10 | 11 | under_test.getParam("scaleComponents").setValue(True) 12 | under_test.getParam("scale").setValue(1.0) 13 | under_test.getParam("scaleR").setValue(1.5) 14 | 15 | write.connectInput(0, under_test) 16 | 17 | app1.render(write, 1, 1) 18 | 19 | quit() 20 | 21 | -------------------------------------------------------------------------------- /test_in_natron.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | mkdir -p examples/basic/resources/ofx_rs_basic.ofx.bundle/Contents/Linux-x86-64/ 3 | cargo build && cp target/debug/libofx_rs_basic.so examples/basic/resources/ofx_rs_basic.ofx.bundle/Contents/Linux-x86-64/ofx_rs_basic.ofx && Natron -t $PWD/test.py 4 | --------------------------------------------------------------------------------