Skip to main content

wolfram_library_link/
image.rs

1use std::{ffi::c_void, marker::PhantomData, os::raw::c_int};
2
3use static_assertions::assert_type_eq_all;
4
5use crate::{
6    rtl,
7    sys::{self, mbool, mint, MImage_CS_Type::*, MImage_Data_Type::*},
8};
9
10/// Native Wolfram [`Image`][ref/Image]<sub>WL</sub> or
11/// [`Image3D`][ref/Image3D]<sub>WL</sub>.
12///
13// TODO: This represents a 2-dimensional image.
14///
15/// Use [`UninitImage::new_2d()`] to construct a new 2-dimensional image.
16///
17/// [ref/Image]: https://reference.wolfram.com/language/ref/Image.html
18/// [ref/Image3D]: https://reference.wolfram.com/language/ref/Image3D.html
19// TODO: Provide better Debug formatting for this type.
20#[derive(Debug)]
21#[derive(ref_cast::RefCast)]
22#[repr(transparent)]
23pub struct Image<T = ()>(sys::MImage, PhantomData<T>);
24
25/// Represents an allocated [`Image`] whose image data has not yet been initialized.
26pub struct UninitImage<T: ImageData>(sys::MImage, PhantomData<T>);
27
28/// Type of data stored in an [`Image`].
29#[repr(i32)]
30#[allow(missing_docs)]
31pub enum ImageType {
32    Bit = MImage_Type_Bit,
33    Bit8 = MImage_Type_Bit8,
34    Bit16 = MImage_Type_Bit16,
35    Real32 = MImage_Type_Real32,
36    Real64 = MImage_Type_Real,
37}
38
39/// Color space used by an [`Image`].
40#[repr(i32)]
41#[allow(missing_docs)]
42pub enum ColorSpace {
43    Automatic = MImage_CS_Automatic,
44    CMYK = MImage_CS_CMYK,
45    Gray = MImage_CS_Gray,
46    HSB = MImage_CS_HSB,
47    LAB = MImage_CS_LAB,
48    LCH = MImage_CS_LCH,
49    LUV = MImage_CS_LUV,
50    RGB = MImage_CS_RGB,
51    XYZ = MImage_CS_XYZ,
52}
53
54/// Position of a pixel position in an [`Image`].
55#[derive(Copy, Clone)]
56pub enum Pixel {
57    /// Index in a 2-dimensional image.
58    ///
59    /// Fields are `[row, column]`.
60    D2([usize; 2]),
61    /// Index in a 3-dimensional image.
62    ///
63    /// Fields are `[slice, row, column]`.
64    D3([usize; 3]),
65}
66
67impl Pixel {
68    /// Construct a pixel position from a slice of indices.
69    ///
70    /// # Panics
71    ///
72    /// `pos` must contain exactly 2 or 3 elements, respectively indicating a 2-dimensional
73    /// or 3-dimensional position. This function will panic if another length is found.
74    pub fn from_slice(pos: &[usize]) -> Self {
75        match *pos {
76            [row, column] => Pixel::D2([row, column]),
77            [slice, row, column] => Pixel::D3([slice, row, column]),
78            _ => panic!(
79                "Pixel::from_slice: index should have 2 or 3 elements; got {} elements",
80                pos.len()
81            ),
82        }
83    }
84
85    fn as_slice(&self) -> &[usize] {
86        match self {
87            Pixel::D2(array) => array,
88            Pixel::D3(array) => array,
89        }
90    }
91}
92
93//======================================
94// Traits
95//======================================
96
97#[allow(missing_docs)]
98type Getter<T> = unsafe extern "C" fn(sys::MImage, *mut mint, mint, *mut T) -> c_int;
99type Setter<T> = unsafe extern "C" fn(sys::MImage, *mut mint, mint, T) -> c_int;
100
101/// Trait implemented for types that can *logically* be stored in an [`Image`].
102///
103/// The `STORAGE` associated type represents the data that is *physically* stored in the
104/// [`Image`] buffer.
105///
106/// The following logical types can be used in an image:
107///
108/// * [`bool`]
109/// * [`u8`], [`u16`]
110/// * [`f32`], [`f64`]
111///
112/// # Safety
113///
114/// This trait is already implemented for all types that can legally be stored in an
115/// [`Image`]. Implementing this trait for other types may lead to undefined behavior.
116pub unsafe trait ImageData: Copy + Default {
117    /// The type of the data that is *physically* stored in the [`Image`] buffer.
118    ///
119    /// In practice, this type is equal to `Self` for every logicaly type except `bool`,
120    /// which represents a bitmapped image that logically stores a single bit of boolean
121    /// data, but physically allocate one byte for each pixel/channel.
122    type STORAGE: Copy;
123
124    /// The [`ImageType`] variant represented by `Self`.
125    const TYPE: ImageType;
126
127    #[allow(missing_docs)]
128    fn getter() -> Getter<Self>;
129
130    #[allow(missing_docs)]
131    fn setter() -> Setter<Self>;
132
133    // TODO: This has the same restrictions as NumericArray::as_slice_mut(), based on
134    //       the share_count().
135    // fn set(image: &mut Image, pos: &[usize], value: Self);
136}
137
138//--------------------------------------
139// ImageData Impls
140//--------------------------------------
141
142assert_type_eq_all!(i8, sys::raw_t_bit);
143assert_type_eq_all!(u8, sys::raw_t_ubit8);
144assert_type_eq_all!(u16, sys::raw_t_ubit16);
145assert_type_eq_all!(f32, sys::raw_t_real32);
146assert_type_eq_all!(f64, sys::raw_t_real64);
147
148unsafe impl ImageData for bool {
149    type STORAGE = i8; // sys::raw_t_bit
150    const TYPE: ImageType = ImageType::Bit;
151
152    fn getter() -> Getter<Self> {
153        extern "C" fn bool_getter(
154            image: sys::MImage,
155            pos: *mut mint,
156            channel: mint,
157            value: *mut bool,
158        ) -> c_int {
159            let mut storage: <bool as ImageData>::STORAGE = 0;
160
161            let err_code =
162                unsafe { rtl::MImage_getBit(image, pos, channel, &mut storage) };
163
164            if err_code == 0 {
165                // FIXME: Is this meant to be non-negative vs negative, or zero vs non-zero?
166                //        This currently assumes zero vs non-zero.
167                let boole: bool = storage != 0;
168                unsafe { *value = boole };
169            }
170
171            err_code
172        }
173
174        bool_getter
175    }
176
177    fn setter() -> Setter<Self> {
178        extern "C" fn bool_setter(
179            image: sys::MImage,
180            pos: *mut mint,
181            channel: mint,
182            value: bool,
183        ) -> c_int {
184            unsafe { rtl::MImage_setBit(image, pos, channel, i8::from(value)) }
185        }
186
187        bool_setter
188    }
189}
190
191unsafe impl ImageData for u8 {
192    type STORAGE = Self; // sys::raw_t_ubit8
193    const TYPE: ImageType = ImageType::Bit8;
194
195    fn getter() -> Getter<Self> {
196        *rtl::MImage_getByte
197    }
198
199    fn setter() -> Setter<Self> {
200        *rtl::MImage_setByte
201    }
202}
203
204unsafe impl ImageData for u16 {
205    type STORAGE = Self; // sys::raw_t_ubit16
206    const TYPE: ImageType = ImageType::Bit16;
207
208    fn getter() -> Getter<Self> {
209        *rtl::MImage_getBit16
210    }
211
212    fn setter() -> Setter<Self> {
213        *rtl::MImage_setBit16
214    }
215}
216
217unsafe impl ImageData for f32 {
218    type STORAGE = Self; // sys::raw_t_real32
219    const TYPE: ImageType = ImageType::Real32;
220
221    fn getter() -> Getter<Self> {
222        *rtl::MImage_getReal32
223    }
224
225    fn setter() -> Setter<Self> {
226        *rtl::MImage_setReal32
227    }
228}
229
230unsafe impl ImageData for f64 {
231    type STORAGE = Self; // sys::raw_t_real64
232    const TYPE: ImageType = ImageType::Real64;
233
234    fn getter() -> Getter<Self> {
235        *rtl::MImage_getReal
236    }
237
238    fn setter() -> Setter<Self> {
239        *rtl::MImage_setReal
240    }
241}
242
243//======================================
244// Impls
245//======================================
246
247impl<T: ImageData> Image<T> {
248    /// Access the data in this [`Image`] as a flat buffer.
249    ///
250    /// The returned slice will have a length equal to
251    /// [`flattened_length()`][Image::flattened_length].
252    pub fn as_slice(&self) -> &[T::STORAGE] {
253        let raw: *mut c_void = unsafe { self.raw_data() };
254        let len: usize = self.flattened_length();
255
256        // Safety: The documentation for `MImage_getRawData` states that the number of
257        //         elements is equal to the value obtained by `MImage_getFlattenedLength`.
258        unsafe { std::slice::from_raw_parts(raw as *mut T::STORAGE, len) }
259    }
260
261    /// Get the value of the specified pixel and channel.
262    ///
263    /// # Example
264    ///
265    /// Get the value of the second channel of the top-left pixel in an image.
266    ///
267    /// ```no_run
268    /// # use wolfram_library_link::{Image, Pixel};
269    /// # let image: Image<u8> = todo!();
270    /// // let image: Image<u8> = ...
271    ///
272    /// let value: u8 = image.get(Pixel::D2([0, 0]), 2).unwrap();
273    /// ```
274    ///
275    /// In an [`RGB`][ColorSpace::RGB] image, this is the value of the green channel for
276    /// this pixel.
277    ///
278    /// In an [`HSB`][ColorSpace::HSB] image, this is the value of the saturation for this
279    /// pixel.
280    pub fn get(&self, pixel: Pixel, channel: usize) -> Option<T> {
281        let pixel_pos: &[usize] = pixel.as_slice();
282
283        // This is necessary for the `unsafe` call to be valid, otherwise the raw pixel
284        // getter function may read an uninitialized value if this is a 3D image but we
285        // only provided a 2D index.
286        assert_eq!(pixel_pos.len(), self.rank());
287
288        let getter: unsafe extern "C" fn(_, _, _, _) -> c_int = T::getter();
289
290        let mut value: T = T::default();
291
292        let err_code: c_int = unsafe {
293            getter(
294                self.as_raw(),
295                pixel_pos.as_ptr() as *mut mint,
296                channel as mint,
297                &mut value,
298            )
299        };
300
301        if err_code != 0 {
302            // TODO: Return the error code?
303            return None;
304        }
305
306        Some(value)
307    }
308}
309
310impl<T> Image<T> {
311    //
312    // Properties
313    //
314
315    /// The number of elements in the underlying flat data buffer.
316    ///
317    // TODO: This is the product of ...? See ref/ page.
318    ///
319    /// *LibraryLink C API Documentation:* [`MImage_getFlattenedLength`](https://reference.wolfram.com/language/LibraryLink/ref/callback/MImage_getFlattenedLength.html)
320    pub fn flattened_length(&self) -> usize {
321        let len: sys::mint = unsafe { rtl::MImage_getFlattenedLength(self.as_raw()) };
322
323        usize::try_from(len).expect("Image flattened length overflows usize")
324    }
325
326    /// *LibraryLink C API Documentation:* [`MImage_getChannels`](https://reference.wolfram.com/language/LibraryLink/ref/callback/MImage_getChannels.html)
327    pub fn channels(&self) -> usize {
328        let channels: sys::mint = unsafe { rtl::MImage_getChannels(self.as_raw()) };
329
330        usize::try_from(channels).expect("Image channels count overflows usize")
331    }
332
333    /// *LibraryLink C API Documentation:* [`MImage_getRank`](https://reference.wolfram.com/language/LibraryLink/ref/callback/MImage_getRank.html)
334    pub fn rank(&self) -> usize {
335        let rank: sys::mint = unsafe { rtl::MImage_getRank(self.as_raw()) };
336
337        usize::try_from(rank).expect("Image rank overflows usize")
338    }
339
340    /// *LibraryLink C API Documentation:* [`MImage_getRowCount`](https://reference.wolfram.com/language/LibraryLink/ref/callback/MImage_getRowCount.html)
341    pub fn row_count(&self) -> usize {
342        let count: sys::mint = unsafe { rtl::MImage_getRowCount(self.as_raw()) };
343
344        usize::try_from(count).expect("Image row count overflows usize")
345    }
346
347    /// *LibraryLink C API Documentation:* [`MImage_getColumnCount`](https://reference.wolfram.com/language/LibraryLink/ref/callback/MImage_getColumnCount.html)
348    pub fn column_count(&self) -> usize {
349        let count: sys::mint = unsafe { rtl::MImage_getColumnCount(self.as_raw()) };
350
351        usize::try_from(count).expect("Image column count overflows usize")
352    }
353
354    /// *LibraryLink C API Documentation:* [`MImage_getSliceCount`](https://reference.wolfram.com/language/LibraryLink/ref/callback/MImage_getSliceCount.html)
355    pub fn slice_count(&self) -> usize {
356        let count: sys::mint = unsafe { rtl::MImage_getSliceCount(self.as_raw()) };
357
358        usize::try_from(count).expect("Image slice count overflows usize")
359    }
360
361    /// Get the color space of this image.
362    ///
363    /// *LibraryLink C API Documentation:* [`MImage_getColorSpace`](https://reference.wolfram.com/language/LibraryLink/ref/callback/MImage_getColorSpace.html)
364    pub fn color_space(&self) -> ColorSpace {
365        ColorSpace::try_from(self.color_space_raw())
366            .expect("Image color space is not a known ColorSpace variant")
367    }
368
369    /// *LibraryLink C API Documentation:* [`MImage_getColorSpace`](https://reference.wolfram.com/language/LibraryLink/ref/callback/MImage_getColorSpace.html)
370    pub fn color_space_raw(&self) -> sys::colorspace_t {
371        unsafe { rtl::MImage_getColorSpace(self.as_raw()) }
372    }
373
374    /// Get the data type of this image.
375    pub fn data_type(&self) -> ImageType {
376        ImageType::try_from(self.data_type_raw())
377            .expect("Image data type is not a known ImageType variant")
378    }
379
380    /// *LibraryLink C API Documentation:* [`MImage_getDataType`](https://reference.wolfram.com/language/LibraryLink/ref/callback/MImage_getDataType.html)
381    pub fn data_type_raw(&self) -> sys::imagedata_t {
382        unsafe { rtl::MImage_getDataType(self.as_raw()) }
383    }
384
385    /// *LibraryLink C API Documentation:* [`MImage_alphaChannelQ`](https://reference.wolfram.com/language/LibraryLink/ref/callback/MImage_alphaChannelQ.html)
386    pub fn has_alpha_channel(&self) -> bool {
387        let boole: mbool = unsafe { rtl::MImage_alphaChannelQ(self.as_raw()) };
388
389        crate::bool_from_mbool(boole)
390    }
391
392    /// *LibraryLink C API Documentation:* [`MImage_interleavedQ`](https://reference.wolfram.com/language/LibraryLink/ref/callback/MImage_interleavedQ.html)
393    pub fn is_interleaved(&self) -> bool {
394        let boole: mbool = unsafe { rtl::MImage_interleavedQ(self.as_raw()) };
395
396        crate::bool_from_mbool(boole)
397    }
398
399    /// Returns the share count of this `Image`.
400    ///
401    /// If this `Image` is not shared, the share count is 0.
402    ///
403    /// If this `Image` was passed into the current library "by reference" due to
404    /// use of the `Automatic` or `"Constant"` memory management strategy, that reference
405    /// is not reflected in the `share_count()`.
406    ///
407    /// *LibraryLink C API Documentation:* [`MImage_shareCount`](https://reference.wolfram.com/language/LibraryLink/ref/callback/MImage_shareCount.html)
408    pub fn share_count(&self) -> usize {
409        let count: sys::mint = unsafe { rtl::MImage_shareCount(self.as_raw()) };
410
411        usize::try_from(count).expect("Image share count mint overflows usize")
412    }
413
414    //
415    // Raw Image's
416    //
417
418    /// Construct an `Image` from a raw [`MImage`][sys::MImage].
419    pub unsafe fn from_raw(raw: sys::MImage) -> Image<T> {
420        Image(raw, PhantomData)
421    }
422
423    /// Extract the raw [`MImage`][sys::MImage] instance from this `Image`.
424    pub unsafe fn into_raw(self) -> sys::MImage {
425        let raw = self.as_raw();
426
427        // Don't run Drop on `self`; ownership of this value is being given to the
428        // caller.
429        std::mem::forget(self);
430
431        raw
432    }
433
434    #[allow(missing_docs)]
435    #[inline]
436    pub unsafe fn as_raw(&self) -> sys::MImage {
437        let Image(raw, PhantomData) = *self;
438
439        raw
440    }
441
442    /// *LibraryLink C API Documentation:* [`MImage_getRawData`](https://reference.wolfram.com/language/LibraryLink/ref/callback/MImage_getRawData.html)
443    pub unsafe fn raw_data(&self) -> *mut c_void {
444        rtl::MImage_getRawData(self.as_raw())
445    }
446}
447
448impl<T: ImageData> UninitImage<T> {
449    /// Construct a new uninitialized `Image` with the specified properties.
450    ///
451    /// # Panics
452    ///
453    /// This function will panic if [`UninitImage::try_new_2d()`] returns an error.
454    pub fn new_2d(
455        width: usize,
456        height: usize,
457        channels: usize,
458        space: ColorSpace,
459        interleaving: bool,
460    ) -> UninitImage<T> {
461        UninitImage::try_new_2d(width, height, channels, space, interleaving)
462            .expect("UninitImage::new_2d: failed to create image")
463    }
464
465    /// Construct a new uninitialized 2D image.
466    // TODO: Use a better error type than i64.
467    pub fn try_new_2d(
468        width: usize,
469        height: usize,
470        channels: usize,
471        space: ColorSpace,
472        interleaving: bool,
473    ) -> Result<UninitImage<T>, i64> {
474        let width = mint::try_from(width).expect("image width overflows `mint`");
475        let height = mint::try_from(height).expect("image height overflows `mint`");
476        let channels =
477            mint::try_from(channels).expect("image channels count overflows `mint`");
478
479        let mut new_raw: sys::MImage = std::ptr::null_mut();
480
481        let err_code: c_int = unsafe {
482            rtl::MImage_new2D(
483                width,
484                height,
485                channels,
486                T::TYPE.as_raw(),
487                space.as_raw(),
488                mbool::from(interleaving),
489                &mut new_raw,
490            )
491        };
492
493        if err_code != 0 || new_raw.is_null() {
494            return Err(i64::from(err_code));
495        }
496
497        Ok(UninitImage(new_raw, PhantomData))
498    }
499
500    /// Efficiently set every pixel value in this image to zero.
501    ///
502    /// This fully initializes this image, albeit to a black image.
503    pub fn zero(&mut self) {
504        let UninitImage(raw, PhantomData) = *self;
505
506        let data_ptr: *mut c_void = unsafe { rtl::MImage_getRawData(raw) };
507        let data_ptr = data_ptr as *mut T::STORAGE;
508        let len: mint = unsafe { rtl::MImage_getFlattenedLength(raw) };
509        let len =
510            usize::try_from(len).expect("UninitImage flattened length overflows usize");
511
512        unsafe { std::ptr::write_bytes(data_ptr, 0, len) }
513    }
514
515    /// Set the value of the specified pixel and channel.
516    ///
517    /// # Panics
518    ///
519    /// This function will panic if the underlying call to [`ImageData::setter()`] fails.
520    /// This can happen if the specified `pixel` or `channel` does not exist.
521    pub fn set(&mut self, pixel: Pixel, channel: usize, value: T) {
522        let pixel_pos: &[usize] = pixel.as_slice();
523
524        let rank = unsafe { rtl::MImage_getRank(self.0) };
525
526        // Assert that we have two indices if this is a 2D image, and three indices if
527        // this is a 3D image.
528        assert_eq!(pixel_pos.len(), rank as usize);
529
530        let setter: unsafe extern "C" fn(_, _, _, T) -> c_int = T::setter();
531
532        let err_code: c_int = unsafe {
533            setter(
534                self.0,
535                pixel_pos.as_ptr() as *mut mint,
536                channel as mint,
537                value,
538            )
539        };
540
541        if err_code != 0 {
542            // TODO: Return the error code?
543            panic!("Image pixel set() failed with error code {}", err_code);
544        }
545    }
546
547    /// Assume that the data in this image has been initialized.
548    ///
549    /// Use [`UninitImage::zero()`] to quickly ensure that every pixel value has been
550    /// initialized.
551    ///
552    /// Use [`UninitImage::set()`] to set individual pixel/channel values.
553    ///
554    /// # Safety
555    ///
556    /// This function must only be called once all elements of this image have been
557    /// initialized. It is undefined behavior to construct an [`Image`] without first
558    /// initializing the data array. In practice, uninitialized values will be essentially
559    /// random, causing the resulting image to appear different each time it is created.
560    pub unsafe fn assume_init(self) -> Image<T> {
561        let UninitImage(raw, PhantomData) = self;
562
563        // Don't run Drop on `self`; ownership of this value is being given to the caller.
564        std::mem::forget(self);
565
566        Image::from_raw(raw)
567    }
568}
569
570impl ImageType {
571    #[allow(missing_docs)]
572    pub fn as_raw(self) -> sys::imagedata_t {
573        self as i32
574    }
575
576    /// Get the string name of this type, suitable for use in
577    /// [`Image`][ref/Image]<code>[<i>data</i>, &quot;<i>type</i>&quot;]</code>.
578    ///
579    /// [ref/Image]: https://reference.wolfram.com/language/ref/Image.html
580    pub fn name(&self) -> &'static str {
581        match self {
582            ImageType::Bit => "Bit",
583            // TODO: Is "Bit8" supported by LibraryLink? The C enum name uses Bit8, but
584            //       the ref/Image docs and the LibraryLink User Guide both say "Byte" is
585            //       the WL name for this type.
586            //
587            //       There is a similar inconsistence with Real vs Real64: the User Guide
588            //       lists "Real32" and "Real", but ref/Image uses "Real32" and "Real64".
589            ImageType::Bit8 => "Byte",
590            ImageType::Bit16 => "Bit16",
591            ImageType::Real32 => "Real32",
592            ImageType::Real64 => "Real64",
593        }
594    }
595}
596
597impl ColorSpace {
598    #[allow(missing_docs)]
599    pub fn as_raw(self) -> sys::colorspace_t {
600        self as i32
601    }
602}
603
604//======================================
605// Trait Impls
606//======================================
607
608impl TryFrom<sys::imagedata_t> for ImageType {
609    type Error = ();
610
611    fn try_from(value: sys::colorspace_t) -> Result<Self, Self::Error> {
612        #[allow(non_upper_case_globals)]
613        let ok = match value {
614            MImage_Type_Bit => ImageType::Bit,
615            MImage_Type_Bit8 => ImageType::Bit8,
616            MImage_Type_Bit16 => ImageType::Bit16,
617            MImage_Type_Real32 => ImageType::Real32,
618            MImage_Type_Real => ImageType::Real64,
619            _ => return Err(()),
620        };
621
622        Ok(ok)
623    }
624}
625
626impl TryFrom<sys::colorspace_t> for ColorSpace {
627    type Error = ();
628
629    fn try_from(value: sys::colorspace_t) -> Result<Self, Self::Error> {
630        use ColorSpace::*;
631
632        #[allow(non_upper_case_globals)]
633        let ok = match value {
634            MImage_CS_Automatic => Automatic,
635            MImage_CS_CMYK => CMYK,
636            MImage_CS_Gray => Gray,
637            MImage_CS_HSB => HSB,
638            MImage_CS_LAB => LAB,
639            MImage_CS_LCH => LCH,
640            MImage_CS_LUV => LUV,
641            MImage_CS_RGB => RGB,
642            MImage_CS_XYZ => XYZ,
643            _ => return Err(()),
644        };
645
646        Ok(ok)
647    }
648}