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>, "<i>type</i>"]</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}