nsi_core/output/mod.rs
1#![cfg_attr(feature = "nightly", doc(cfg(feature = "output")))]
2//! Output driver callbacks.
3//!
4//! This module declares several closure types. These can be passed via
5//! [`Callback`](crate::Callback)s to an
6//! [`OutputDriver`](crate::OUTPUT_DRIVER) node to stream
7//! pixels during and/or after a render, in-memory.
8//!
9//! There are three types of closure:
10//! * [`FnOpen`] is called once when the [`OutputDriver`](crate::OUTPUT_LAYER)
11//! is *opened* by the renderer.
12//!
13//! * [`FnWrite`] is called for each bucket of pixel data the renderer sends to
14//! the [`OutputDriver`](crate::OUTPUT_DRIVER).
15//!
16//! * [`FnFinish`] is called once when the
17//! [`OutputDriver`](crate::OUTPUT_DRIVER) is *closed* by the renderer.
18//!
19//! As a user you can choose how to use this API.
20//!
21//! * To get a single buffer of pixel data when rendering is finished it is
22//! enough to implement an [`FnFinish`] closure.
23//!
24//! * To get the pixel buffer updated while the renderer is working implement an
25//! [`FnWrite`] closure.
26//!
27//! The format of the [`Vec<f32>`] buffer is described by the [`PixelFormat`]
28//! parameter which is passed to both of these closures.
29//!
30//! ## Example
31//! ```
32//! # use nsi_core as nsi;
33//! # fn write_exr(_: &str, _: usize, _: usize, _: usize, _: &[f32]) {}
34//! # let ctx = nsi::Context::new(None).unwrap();
35//! // Setup a screen.
36//! ctx.create("screen", nsi::SCREEN, None);
37//! // We pretend we defined a camera node earlier.
38//! ctx.connect("screen", None, "camera", "screens", None);
39//! ctx.set_attribute(
40//! "screen",
41//! &[
42//! // The resolution becomes the width & height passed to
43//! // FnOpen, FnWrite and FnFinish type callbacks.
44//! nsi::integers!("resolution", &[1920, 1080]).array_len(2),
45//! ],
46//! );
47//!
48//! // Setup an RGBA output layer.
49//! ctx.create("beauty", nsi::OUTPUT_LAYER, None);
50//! ctx.set_attribute(
51//! "beauty",
52//! &[
53//! // The Ci variable comes from Open Shading Language.
54//! nsi::string!("variablename", "Ci"),
55//! // We want the data as raw, 32 bit float.
56//! nsi::string!("scalarformat", "float"),
57//! nsi::integer!("withalpha", 1),
58//! ],
59//! );
60//! ctx.connect("beauty", None, "screen", "outputlayers", None);
61//!
62//! // Setup an output driver.
63//! ctx.create("driver", nsi::OUTPUT_DRIVER, None);
64//! ctx.connect("driver", None, "beauty", "outputdrivers", None);
65//!
66//! // Our FnFinish callback. We will be called once.
67//! let finish = nsi::output::FinishCallback::new(
68//! |// Passed from the output driver node, below.
69//! image_filename: String,
70//! // Passed from the screen node, above.
71//! width: usize,
72//! // Passed from the screen node, above.
73//! height: usize,
74//! pixel_format: nsi::output::PixelFormat,
75//! pixel_data: Vec<f32>| {
76//! // Call some function to write our image to an OpenEXR file.
77//! write_exr(
78//! (String::from(image_filename) + ".exr").as_str(),
79//! width,
80//! height,
81//! pixel_format.len(),
82//! &pixel_data,
83//! );
84//! nsi::output::Error::None
85//! },
86//! );
87//!
88//! ctx.set_attribute(
89//! "driver",
90//! &[
91//! // Important: FERRIS is the built-in output driver
92//! // that understands the closure parameters.
93//! nsi::string!("drivername", nsi::output::FERRIS),
94//! // This will end up in the `name` parameter passed
95//! // to finish().
96//! nsi::string!("imagefilename", "render"),
97//! nsi::callback!("callback.finish", finish),
98//! ],
99//! );
100//!
101//! ctx.render_control(nsi::Action::Start, None);
102//!
103//! // The finish() closure will be called once, before the next call returns.
104//! ctx.render_control(nsi::Action::Wait, None);
105//! ```
106//!
107//! ## Color Profiles
108//!
109//! The pixel color data that the renderer generates is linear and
110//! scene-referred. I.e. relative to whatever units you used to describe
111//! illuminants in your scene.
112//!
113//! Using the
114//! [`"colorprofile"` attribute](https://nsi.readthedocs.io/en/latest/nodes.html?highlight=outputlayer#the-outputlayer-node)
115//! of an [`OutputLayer`](crate::OUTPUT_LAYER) you can ask the
116//! renderer to apply an [Open Color IO](https://opencolorio.org/) (OCIO)
117//! [profile/LUT](https://github.com/colour-science/OpenColorIO-Configs/tree/feature/aces-1.2-config/aces_1.2/luts)
118//! before quantizing (see below).
119//!
120//! Once OCIO has a [Rust wrapper](https://crates.io/crates/opencolorio) you can easily choose to
121//! do these color conversions yourself. In the meantime there is the
122//! [`colorspace`](https://crates.io/crates/colorspace) crate which has some useful profiles built
123//! in, e.g. [ACEScg](https://en.wikipedia.org/wiki/Academy_Color_Encoding_System#ACEScg).
124//!
125//! ```
126//! # use nsi_core as nsi;
127//! # let ctx = nsi::Context::new(None).unwrap();
128//! ctx.create("beauty", nsi::OUTPUT_LAYER, None);
129//! ctx.set_attribute(
130//! "beauty",
131//! &[
132//! // The Ci variable comes from Open Shading Language.
133//! nsi::string!("variablename", "Ci"),
134//! // We want the pixel data 'display-referred' in sRGB and quantized down to 0.0..255.0.
135//! nsi::string!("colorprofile", "/home/luts/linear_to_sRGB.spi1d"),
136//! nsi::string!("scalarformat", "uint8"),
137//! ],
138//! );
139//! ```
140//!
141//! ## Quantization
142//!
143//! Using the [`"scalarformat"`
144//! attribute](https://nsi.readthedocs.io/en/latest/nodes.html?highlight=outputlayer#the-outputlayer-node)
145//! of an [`OutputLayer`](crate::OUTPUT_LAYER) you can ask the
146//! renderer to quantize data down to a suitable range. For example, setting
147//! this to `"uint16"` will get you valid `u16` values from `0.0..65535.0`, but
148//! stored in the `f32`s of the `pixel_data` buffer. The value of `1.0` will map
149//! to `65535.0` and everything above will be clipped. You can convert
150//! such a value straight via `f32 as u16`.
151//!
152//! Unless you asked the renderer to also apply some color profile (see above)
153//! the data is linear. To look good on a screen it needs to be made
154//! display-referred.
155//!
156//! See the `output` example on how to do this with a simple, display-referred
157//! `sRGB` curve.
158use crate::argument::CallbackPtr;
159use std::{
160 ffi::{CStr, CString},
161 os::raw::{c_char, c_int, c_void},
162};
163
164pub mod pixel_format;
165pub use pixel_format::*;
166
167/// This is the name of the crate’s built-in output driver that understands the
168/// "closure.*" attributes.
169pub static FERRIS: &str = "ferris";
170
171/// An error type the callbacks return to communicate with the
172/// renderer.
173#[repr(u32)]
174#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, num_enum::IntoPrimitive)]
175pub enum Error {
176 /// Everything is dandy.
177 None = ndspy_sys::PtDspyError::None as _,
178 /// We ran out of memory.
179 NoMemory = ndspy_sys::PtDspyError::NoMemory as _,
180 /// We do no support this request.
181 Unsupported = ndspy_sys::PtDspyError::Unsupported as _,
182 BadParameters = ndspy_sys::PtDspyError::BadParams as _,
183 NoResource = ndspy_sys::PtDspyError::NoResource as _,
184 /// Something else went wrong.
185 Undefined = ndspy_sys::PtDspyError::Undefined as _,
186 /// Stop the render.
187 Stop = ndspy_sys::PtDspyError::Stop as _,
188}
189
190impl From<Error> for ndspy_sys::PtDspyError {
191 fn from(item: Error) -> ndspy_sys::PtDspyError {
192 match item {
193 Error::None => ndspy_sys::PtDspyError::None,
194 Error::NoMemory => ndspy_sys::PtDspyError::NoMemory,
195 Error::Unsupported => ndspy_sys::PtDspyError::Unsupported,
196 Error::BadParameters => ndspy_sys::PtDspyError::BadParams,
197 Error::NoResource => ndspy_sys::PtDspyError::NoResource,
198 Error::Undefined => ndspy_sys::PtDspyError::Undefined,
199 Error::Stop => ndspy_sys::PtDspyError::Stop,
200 }
201 }
202}
203
204/// A closure which is called once per
205/// [`OutputDriver`](crate::OUTPUT_DRIVER) instance.
206///
207/// It is passed to ɴsɪ via the `"callback.open"` attribute on that node.
208///
209/// The closure is called once, before the renderer starts sending pixels to the
210/// output driver.
211///
212/// # Arguments
213/// The `pixel_format` parameter is an array of strings that details the
214/// composition of the `f32` data that the renderer will send to the [`FnWrite`]
215/// and/or [`FnFinish`] closures.
216///
217/// # Example
218/// ```
219/// # #[cfg(feature = "output")]
220/// # {
221/// # use nsi_core as nsi;
222/// # use nsi::output::PixelFormat;
223/// # let ctx = nsi::Context::new(None).unwrap();
224/// # ctx.create("display_driver", nsi::OUTPUT_DRIVER, None);
225/// let open = nsi::output::OpenCallback::new(
226/// |name: &str,
227/// width: usize,
228/// height: usize,
229/// pixel_format: &nsi::output::PixelFormat| {
230/// println!(
231/// "Resolution: {}×{}\nPixel Format:\n{:?}",
232/// width, height, pixel_format
233/// );
234/// nsi::output::Error::None
235/// },
236/// );
237/// # }
238/// ```
239pub trait FnOpen<'a>: FnMut(
240 // Filename.
241 &str,
242 // Width.
243 usize,
244 // Height.
245 usize,
246 // Pixel format.
247 &PixelFormat,
248) -> Error
249+ 'a {}
250
251#[doc(hidden)]
252impl<'a, T: FnMut(&str, usize, usize, &PixelFormat) -> Error + 'a> FnOpen<'a>
253 for T
254{
255}
256
257// FIXME once trait aliases are in stable.
258/*
259trait FnOpen<'a> = FnMut(
260 // Filename.
261 &str,
262 // Width.
263 usize,
264 // Height.
265 usize,
266 // Pixel format.
267 &PixelFormat,
268 ) -> Error
269 + 'a
270*/
271
272/// A closure which is called for each bucket of pixels the
273/// [`OutputDriver`](crate::OUTPUT_DRIVER) instance sends
274/// during rendering.
275///
276/// It is passed to ɴsɪ via the `"callback.write"` attribute on that node.
277/// # Example
278/// ```
279/// # #[cfg(feature = "output")]
280/// # {
281/// # use nsi_core as nsi;
282/// # let ctx = nsi::Context::new(None).unwrap();
283/// # ctx.create("display_driver", nsi::OUTPUT_DRIVER, None);
284/// let write = nsi::output::WriteCallback::new(
285/// |name: &str,
286/// width: usize,
287/// height: usize,
288/// x_min: usize,
289/// x_max_plus_one: usize,
290/// y_min: usize,
291/// y_max_plus_one: usize,
292/// pixel_format: &nsi::output::PixelFormat,
293/// pixel_data: &[f32]| {
294/// /// Send our pixels to some texture for realtime display.
295/// nsi::output::Error::None
296/// },
297/// );
298///
299/// ctx.set_attribute(
300/// "oxidized_output_driver",
301/// &[
302/// nsi::string!("drivername", "ferris"),
303/// // While rendering, send all pixel buckets to the write closure.
304/// nsi::callback!("callback.write", write),
305/// ],
306/// );
307/// # }
308/// ```
309pub trait FnWrite<'a>: FnMut(
310 // Filename.
311 &str,
312 // Width.
313 usize,
314 // Height.
315 usize,
316 // x_min.
317 usize,
318 // x_max_plus_one
319 usize,
320 // y_min.
321 usize,
322 // y_max_plus_one,
323 usize,
324 // Pixel format.
325 &PixelFormat,
326 // Pixel data.
327 &[f32],
328 ) -> Error
329 + 'a {}
330
331#[doc(hidden)]
332impl<
333 'a,
334 T: FnMut(
335 &str,
336 usize,
337 usize,
338 usize,
339 usize,
340 usize,
341 usize,
342 &PixelFormat,
343 &[f32],
344 ) -> Error
345 + 'a,
346 > FnWrite<'a> for T
347{
348}
349
350// FIXME once trait aliases are in stable.
351/*
352pub trait FnWrite<'a> = FnMut(
353 // Filename.
354 &str,
355 // Width.
356 usize,
357 // Height.
358 usize,
359 // x_min.
360 usize,
361 // x_max_plus_one
362 usize,
363 // y_min.
364 usize,
365 // y_max_plus_one,
366 usize,
367 // Pixel format.
368 &PixelFormat,
369 // Pixel data.
370 &mut [f32],
371) -> Error
372+ 'a
373*/
374
375/// A closure which is called once per
376/// [`OutputDriver`](crate::OUTPUT_DRIVER) instance.
377///
378/// It is passed to ɴsɪ via the `"callback.finish"` attribute on that node.
379///
380/// The closure is called once, before after renderer has finished sending
381/// pixels to the output driver.
382/// # Example
383/// ```
384/// # #[cfg(feature = "output")]
385/// # {
386/// # use nsi_core as nsi;
387/// # let ctx = nsi::Context::new(None).unwrap();
388/// # ctx.create("display_driver", nsi::OUTPUT_DRIVER, None);
389/// let finish = nsi::output::FinishCallback::new(
390/// |name: String,
391/// width: usize,
392/// height: usize,
393/// pixel_format: nsi::output::PixelFormat,
394/// pixel_data: Vec<f32>| {
395/// println!(
396/// "The top, left pixel of the first channel in the {:?} layer has the value {}.",
397/// pixel_format[0].name(),
398/// pixel_data[0],
399/// );
400/// nsi::output::Error::None
401/// },
402/// );
403///
404/// ctx.set_attribute(
405/// "oxidized_output_driver",
406/// &[
407/// nsi::string!("drivername", "ferris"),
408/// // When done, send all pixels to the finish closure.
409/// nsi::callback!("callback.finish", finish),
410/// ],
411/// );
412/// # }
413/// ```
414pub trait FnFinish<'a>: FnMut(
415 // Filename.
416 String,
417 // Width.
418 usize,
419 // Height.
420 usize,
421 // Pixel format.
422 PixelFormat,
423 // Pixel data.
424 Vec<f32>,
425) -> Error
426+ 'a {}
427
428#[doc(hidden)]
429impl<
430 'a,
431 T: FnMut(String, usize, usize, PixelFormat, Vec<f32>) -> Error + 'a,
432 > FnFinish<'a> for T
433{
434}
435
436// FIXME once trait aliases are in stable.
437/*
438pub trait FnFinish<'a> = dyn FnMut(
439 // Filename.
440 &str,
441 // Width.
442 usize,
443 // Height.
444 usize,
445 // Pixel format.
446 PixelFormat,
447 // Pixel data.
448 Vec<f32>,
449 ) -> Error
450 + 'a;
451*/
452
453enum Query {}
454
455trait FnQuery<'a>: FnMut(Query) -> Error + 'a {}
456impl<'a, T: FnMut(Query) -> Error + 'a> FnQuery<'a> for T {}
457
458// FIXME once trait aliases are in stable.
459/*
460pub trait FnQuery<'a> = dyn FnMut(Query) -> Error + 'a;
461*/
462
463/// Wrapper to pass an [`FnOpen`] closure to an
464/// [`OutputDriver`](crate::OUTPUT_DRIVER) node.
465pub struct OpenCallback<'a>(Box<Box<Box<dyn FnOpen<'a>>>>);
466
467// Why do we need a triple Box here? Why does a Box<Box<T>> not suffice?
468// No idea and neither had anyone from the Rust community. But omitting a
469// single Box wrapper layer leads to an instant segfault.
470impl<'a> OpenCallback<'a> {
471 pub fn new<F>(fn_open: F) -> Self
472 where
473 F: FnOpen<'a>,
474 {
475 OpenCallback(Box::new(Box::new(Box::new(fn_open))))
476 }
477}
478
479impl CallbackPtr for OpenCallback<'_> {
480 #[doc(hidden)]
481 fn to_ptr(self) -> *const core::ffi::c_void {
482 Box::into_raw(self.0) as *const _ as _
483 }
484}
485/// Wrapper to pass an [`FnWrite`] closure to an
486/// [`OutputDriver`](crate::OUTPUT_DRIVER) node.
487pub struct WriteCallback<'a>(Box<Box<Box<dyn FnWrite<'a>>>>);
488
489impl<'a> WriteCallback<'a> {
490 pub fn new<F>(fn_write: F) -> Self
491 where
492 F: FnWrite<'a>,
493 {
494 WriteCallback(Box::new(Box::new(Box::new(fn_write))))
495 }
496}
497
498impl CallbackPtr for WriteCallback<'_> {
499 #[doc(hidden)]
500 fn to_ptr(self) -> *const core::ffi::c_void {
501 Box::into_raw(self.0) as *const _ as _
502 }
503}
504
505/// Wrapper to pass an [`FnFinish`] closure to an
506/// [`OutputDriver`](crate::OUTPUT_DRIVER) node.
507pub struct FinishCallback<'a>(Box<Box<Box<dyn FnFinish<'a>>>>);
508
509impl<'a> FinishCallback<'a> {
510 pub fn new<F>(fn_finish: F) -> Self
511 where
512 F: FnFinish<'a>,
513 {
514 FinishCallback(Box::new(Box::new(Box::new(fn_finish))))
515 }
516}
517
518impl CallbackPtr for FinishCallback<'_> {
519 #[doc(hidden)]
520 fn to_ptr(self) -> *const core::ffi::c_void {
521 Box::into_raw(self.0) as *const _ as _
522 }
523}
524
525struct DisplayData<'a> {
526 name: String,
527 width: usize,
528 height: usize,
529 pixel_format: PixelFormat,
530 pixel_data: Vec<f32>,
531 fn_write: Option<Box<Box<Box<dyn FnWrite<'a>>>>>,
532 fn_finish: Option<Box<Box<Box<dyn FnFinish<'a>>>>>,
533 // FIXME: unused atm.
534 fn_query: Option<Box<Box<Box<dyn FnQuery<'a>>>>>,
535}
536
537fn get_parameter_triple_box<T: ?Sized>(
538 name: &str,
539 type_: u8,
540 len: usize,
541 parameters: &mut [ndspy_sys::UserParameter],
542) -> Option<Box<Box<Box<T>>>> {
543 for p in parameters.iter() {
544 let p_name = unsafe { CStr::from_ptr(p.name) }.to_str().unwrap();
545
546 if name == p_name
547 && type_ == p.valueType as _
548 && len == p.valueCount as _
549 {
550 if !p.value.is_null() {
551 return Some(unsafe {
552 Box::from_raw(p.value as *mut Box<Box<T>>)
553 });
554 } else {
555 // Parameter exists but value is missing –
556 // exit quietly.
557 break;
558 }
559 }
560 }
561 None
562}
563
564// Trampoline function for the FnOpen callback.
565#[no_mangle]
566pub(crate) extern "C" fn image_open(
567 image_handle_ptr: *mut ndspy_sys::PtDspyImageHandle,
568 _driver_name: *const c_char,
569 output_filename: *const c_char,
570 width: c_int,
571 height: c_int,
572 parameters_count: c_int,
573 parameters: *const ndspy_sys::UserParameter,
574 format_count: c_int,
575 format: *mut ndspy_sys::PtDspyDevFormat,
576 flag_stuff: *mut ndspy_sys::PtFlagStuff,
577) -> ndspy_sys::PtDspyError {
578 // FIXME: check that driver_name is "ferris".
579 if (image_handle_ptr.is_null()) || (output_filename.is_null()) {
580 return Error::BadParameters.into();
581 }
582
583 let parameters = unsafe {
584 // We need to const->mut transmute() here because we need
585 // pointers to FnMut below.
586 std::slice::from_raw_parts_mut(
587 parameters as *mut ndspy_sys::UserParameter,
588 parameters_count as _,
589 )
590 };
591
592 let mut display_data = Box::new(DisplayData {
593 name: unsafe {
594 CString::from(CStr::from_ptr(output_filename))
595 .into_string()
596 .unwrap()
597 },
598 width: width as _,
599 height: height as _,
600 pixel_format: PixelFormat::default(),
601 pixel_data: vec![0.0f32; (width * height * format_count) as _],
602 fn_write: get_parameter_triple_box::<dyn FnWrite>(
603 "callback.write",
604 b'p',
605 1,
606 parameters,
607 ),
608 fn_finish: get_parameter_triple_box::<dyn FnFinish>(
609 "callback.finish",
610 b'p',
611 1,
612 parameters,
613 ),
614 fn_query: None, /* get_parameter_triple_box::<FnQuery>("callback.
615 * query", b'p', 1,
616 * parameters), */
617 });
618
619 let format =
620 unsafe { std::slice::from_raw_parts_mut(format, format_count as _) };
621
622 // We want f32/channel data.
623 format
624 .iter_mut()
625 .for_each(|format| format.type_ = ndspy_sys::PkDspyFloat32);
626
627 display_data.pixel_format = PixelFormat::new(format);
628
629 let error = if let Some(mut fn_open) = get_parameter_triple_box::<dyn FnOpen>(
630 "callback.open",
631 b'p',
632 1,
633 parameters,
634 ) {
635 let error = fn_open(
636 &display_data.name,
637 width as _,
638 height as _,
639 &display_data.pixel_format,
640 );
641 // wtf?
642 Box::leak(fn_open);
643
644 error
645 } else {
646 Error::None
647 };
648
649 unsafe {
650 *image_handle_ptr = Box::into_raw(display_data) as _;
651 // We want to be called for all buckets.
652 (*flag_stuff).flags = ndspy_sys::PkDspyFlagsWantsEmptyBuckets as _;
653 }
654
655 error.into()
656}
657
658// FIXME: this will be used for a FnProgress callback later.
659#[no_mangle]
660pub(crate) extern "C" fn image_query(
661 _image_handle_ptr: ndspy_sys::PtDspyImageHandle,
662 query_type: ndspy_sys::PtDspyQueryType,
663 data_len: c_int,
664 data: *mut c_void,
665) -> ndspy_sys::PtDspyError {
666 match query_type {
667 ndspy_sys::PtDspyQueryType::RenderProgress => {
668 if (data_len as usize)
669 < core::mem::size_of::<ndspy_sys::PtDspyRenderProgressFuncPtr>()
670 {
671 Error::BadParameters
672 } else {
673 *unsafe {
674 &mut std::mem::transmute::<
675 _,
676 ndspy_sys::PtDspyRenderProgressFuncPtr,
677 >(data)
678 } = Some(image_progress);
679 Error::None
680 }
681 }
682 _ => Error::Unsupported,
683 }
684 .into()
685}
686
687// Trampoline function for the FnWrite callback.
688#[no_mangle]
689pub(crate) extern "C" fn image_write(
690 image_handle_ptr: ndspy_sys::PtDspyImageHandle,
691 x_min: c_int,
692 x_max_plus_one: c_int,
693 y_min: c_int,
694 y_max_plus_one: c_int,
695 entry_size: c_int,
696 pixel_data: *const u8,
697) -> ndspy_sys::PtDspyError {
698 let display_data = unsafe { &mut *(image_handle_ptr as *mut DisplayData) };
699
700 // entry_size is pixel_length in u8s, we need pixel length in f32s.
701 debug_assert!(entry_size >> 2 == display_data.pixel_format.channels() as _);
702 let pixel_length = display_data.pixel_format.channels();
703
704 let pixel_data = unsafe {
705 std::slice::from_raw_parts(
706 pixel_data as *const f32,
707 pixel_length
708 * ((x_max_plus_one - x_min) * (y_max_plus_one - y_min))
709 as usize,
710 )
711 };
712
713 let bucket_width = pixel_length * (x_max_plus_one - x_min) as usize;
714
715 let mut source_index = 0;
716 for y in y_min as usize..y_max_plus_one as _ {
717 let dest_index =
718 (y * display_data.width + x_min as usize) * pixel_length;
719
720 // We memcpy() each scanline.
721 display_data.pixel_data[dest_index..dest_index + bucket_width]
722 .copy_from_slice(
723 &(pixel_data[source_index..source_index + bucket_width]),
724 );
725
726 source_index += bucket_width;
727 }
728
729 // Call the closure.
730 if let Some(ref mut fn_write) = display_data.fn_write {
731 fn_write(
732 &display_data.name,
733 display_data.width,
734 display_data.height,
735 x_min as _,
736 x_max_plus_one as _,
737 y_min as _,
738 y_max_plus_one as _,
739 &display_data.pixel_format,
740 display_data.pixel_data.as_mut_slice(),
741 )
742 } else {
743 Error::None
744 }
745 .into()
746}
747
748// Trampoline function for the FnFinish callback.
749#[no_mangle]
750pub(crate) extern "C" fn image_close(
751 image_handle_ptr: ndspy_sys::PtDspyImageHandle,
752) -> ndspy_sys::PtDspyError {
753 let mut display_data =
754 unsafe { Box::from_raw(image_handle_ptr as *mut DisplayData) };
755
756 let error = if let Some(ref mut fn_finish) = display_data.fn_finish {
757 fn_finish(
758 display_data.name,
759 display_data.width,
760 display_data.height,
761 display_data.pixel_format,
762 display_data.pixel_data,
763 )
764 } else {
765 Error::None
766 };
767
768 // FIXME: These boxes somehow get deallocated twice if we don't suppress
769 // this here. No f*cking idea why.
770 if let Some(fn_write) = display_data.fn_write {
771 Box::leak(fn_write);
772 }
773 if let Some(fn_query) = display_data.fn_query {
774 Box::leak(fn_query);
775 }
776 if let Some(fn_finish) = display_data.fn_finish {
777 Box::leak(fn_finish);
778 }
779
780 error.into()
781}
782
783#[no_mangle]
784extern "C" fn image_progress(
785 _image_handle_ptr: ndspy_sys::PtDspyImageHandle,
786 progress: f32,
787) -> ndspy_sys::PtDspyError {
788 println!("\rProgress: {}", progress * 100.0);
789 Error::None.into()
790}