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}