fltk/
image.rs

1use crate::enums::ColorDepth;
2use crate::prelude::*;
3use crate::utils::FlString;
4use fltk_sys::image::*;
5use std::{
6    ffi::CString,
7    mem,
8    sync::atomic::{AtomicBool, Ordering},
9};
10
11/// Basically a check for image support
12#[allow(dead_code)]
13static IMAGES_REGISTERED: AtomicBool = AtomicBool::new(false);
14
15/// Check if fltk-rs was initialized
16#[allow(dead_code)]
17fn images_registered() -> bool {
18    IMAGES_REGISTERED.load(Ordering::Relaxed)
19}
20
21/// Registers all images supported by `SharedImage`
22#[allow(dead_code)]
23fn register_images() {
24    #[cfg(feature = "use-images")]
25    unsafe {
26        fltk_sys::image::Fl_register_images();
27        fltk_sys::fl::Fl_load_system_icons();
28    }
29}
30
31type ImageRC<T> = std::rc::Rc<T>;
32
33/// Wrapper around `Fl_Image`, used to wrap other image types
34#[derive(Debug)]
35pub struct Image {
36    inner: ImageRC<*mut Fl_Image>,
37}
38
39/// The scaling algorithm to use for raster images
40#[repr(i32)]
41#[derive(Debug, Copy, Clone, PartialEq, Eq)]
42pub enum RgbScaling {
43    /// Default RGB image scaling algorithm
44    Nearest = 0,
45    /// More accurate, but slower RGB image scaling algorithm
46    Bilinear,
47}
48
49crate::macros::image::impl_image_ext!(Image, Fl_Image);
50
51impl Image {
52    /// Sets the scaling algorithm
53    pub fn set_scaling_algorithm(algorithm: RgbScaling) {
54        unsafe { Fl_Image_set_scaling_algorithm(algorithm as i32) }
55    }
56
57    /// Gets the scaling algorithm
58    pub fn scaling_algorithm() -> RgbScaling {
59        unsafe { mem::transmute(Fl_Image_scaling_algorithm()) }
60    }
61}
62
63#[cfg(feature = "use-images")]
64/// Creates a struct holding a shared image
65#[derive(Debug)]
66pub struct SharedImage {
67    inner: ImageRC<*mut Fl_Shared_Image>,
68}
69
70#[cfg(feature = "use-images")]
71crate::macros::image::impl_image_ext!(SharedImage, Fl_Shared_Image);
72
73#[cfg(feature = "use-images")]
74impl SharedImage {
75    /// Loads a `SharedImage` from a path
76    /// # Errors
77    /// Errors on non-existent path or invalid format, also errors if `app::App` was not initialized
78    pub fn load<P: AsRef<std::path::Path>>(path: P) -> Result<SharedImage, FltkError> {
79        Self::load_(path.as_ref())
80    }
81
82    fn load_(path: &std::path::Path) -> Result<SharedImage, FltkError> {
83        if !path.exists() {
84            return Err(FltkError::Internal(FltkErrorKind::ResourceNotFound));
85        }
86        unsafe {
87            let temp = path.to_str().ok_or_else(|| {
88                FltkError::Unknown(String::from("Failed to convert path to string"))
89            })?;
90            if !images_registered() {
91                register_images();
92            }
93            let temp = CString::safe_new(temp);
94            let x = Fl_Shared_Image_get(temp.as_ptr(), 0, 0);
95            if x.is_null() {
96                Err(FltkError::Internal(FltkErrorKind::ResourceNotFound))
97            } else {
98                if Fl_Shared_Image_fail(x) < 0 {
99                    return Err(FltkError::Internal(FltkErrorKind::ImageFormatError));
100                }
101                Ok(SharedImage {
102                    inner: ImageRC::from(x),
103                })
104            }
105        }
106    }
107
108    /// Loads a `SharedImage` from an image
109    /// # Errors
110    /// Errors on unsupported `SharedImage` types
111    pub fn from_image<I: ImageExt>(image: &I) -> Result<SharedImage, FltkError> {
112        unsafe {
113            assert!(!image.was_deleted());
114            let x = Fl_Shared_Image_from_rgb(image.as_image_ptr() as *mut Fl_RGB_Image, 1);
115            if x.is_null() {
116                Err(FltkError::Internal(FltkErrorKind::ResourceNotFound))
117            } else {
118                if Fl_Shared_Image_fail(x) < 0 {
119                    return Err(FltkError::Internal(FltkErrorKind::ImageFormatError));
120                }
121                Ok(SharedImage {
122                    inner: ImageRC::from(x),
123                })
124            }
125        }
126    }
127}
128
129#[cfg(feature = "use-images")]
130/// Creates a struct holding a Jpeg image
131#[derive(Debug)]
132pub struct JpegImage {
133    inner: ImageRC<*mut Fl_JPEG_Image>,
134}
135
136#[cfg(feature = "use-images")]
137crate::macros::image::impl_image_ext!(JpegImage, Fl_JPEG_Image);
138
139#[cfg(feature = "use-images")]
140impl JpegImage {
141    /// Loads the image from a filesystem path, doesn't check for the validity of the data
142    /// # Errors
143    /// Errors on non-existent path or invalid format
144    pub fn load<P: AsRef<std::path::Path>>(path: P) -> Result<JpegImage, FltkError> {
145        Self::load_(path.as_ref())
146    }
147
148    fn load_(path: &std::path::Path) -> Result<JpegImage, FltkError> {
149        if !path.exists() {
150            return Err(FltkError::Internal(FltkErrorKind::ResourceNotFound));
151        }
152        unsafe {
153            let temp = path.to_str().ok_or_else(|| {
154                FltkError::Unknown(String::from("Failed to convert path to string"))
155            })?;
156            if !images_registered() {
157                register_images();
158            }
159            let temp = CString::safe_new(temp);
160            let image_ptr = Fl_JPEG_Image_new(temp.as_ptr());
161            if image_ptr.is_null() {
162                Err(FltkError::Internal(FltkErrorKind::ResourceNotFound))
163            } else {
164                if Fl_JPEG_Image_fail(image_ptr) < 0 {
165                    return Err(FltkError::Internal(FltkErrorKind::ImageFormatError));
166                }
167                Ok(JpegImage {
168                    inner: ImageRC::from(image_ptr),
169                })
170            }
171        }
172    }
173
174    /// Loads the image from data/memory
175    /// # Errors
176    /// Errors on invalid format
177    pub fn from_data(data: &[u8]) -> Result<JpegImage, FltkError> {
178        unsafe {
179            if data.is_empty() {
180                Err(FltkError::Internal(FltkErrorKind::ResourceNotFound))
181            } else {
182                if !images_registered() {
183                    register_images();
184                }
185                let x = Fl_JPEG_Image_from(data.as_ptr());
186                if x.is_null() {
187                    Err(FltkError::Internal(FltkErrorKind::ResourceNotFound))
188                } else {
189                    if Fl_JPEG_Image_fail(x) < 0 {
190                        return Err(FltkError::Internal(FltkErrorKind::ImageFormatError));
191                    }
192                    Ok(JpegImage {
193                        inner: ImageRC::from(x),
194                    })
195                }
196            }
197        }
198    }
199}
200
201#[cfg(feature = "use-images")]
202/// Creates a struct holding a PNG image
203#[derive(Debug)]
204pub struct PngImage {
205    inner: ImageRC<*mut Fl_PNG_Image>,
206}
207
208#[cfg(feature = "use-images")]
209crate::macros::image::impl_image_ext!(PngImage, Fl_PNG_Image);
210
211#[cfg(feature = "use-images")]
212impl PngImage {
213    /// Loads the image from a filesystem path, doesn't check for the validity of the data
214    /// # Errors
215    /// Errors on non-existent path or invalid format
216    pub fn load<P: AsRef<std::path::Path>>(path: P) -> Result<PngImage, FltkError> {
217        Self::load_(path.as_ref())
218    }
219
220    fn load_(path: &std::path::Path) -> Result<PngImage, FltkError> {
221        if !path.exists() {
222            return Err(FltkError::Internal(FltkErrorKind::ResourceNotFound));
223        }
224        unsafe {
225            let temp = path.to_str().ok_or_else(|| {
226                FltkError::Unknown(String::from("Failed to convert path to string"))
227            })?;
228            if !images_registered() {
229                register_images();
230            }
231            let temp = CString::safe_new(temp);
232            let image_ptr = Fl_PNG_Image_new(temp.as_ptr());
233            if image_ptr.is_null() {
234                Err(FltkError::Internal(FltkErrorKind::ResourceNotFound))
235            } else {
236                if Fl_PNG_Image_fail(image_ptr) < 0 {
237                    return Err(FltkError::Internal(FltkErrorKind::ImageFormatError));
238                }
239                Ok(PngImage {
240                    inner: ImageRC::from(image_ptr),
241                })
242            }
243        }
244    }
245
246    /// Loads the image from data/memory
247    /// # Errors
248    /// Errors on invalid format
249    pub fn from_data(data: &[u8]) -> Result<PngImage, FltkError> {
250        unsafe {
251            if data.is_empty() {
252                Err(FltkError::Internal(FltkErrorKind::ResourceNotFound))
253            } else {
254                if !images_registered() {
255                    register_images();
256                }
257                let x = Fl_PNG_Image_from(data.as_ptr(), data.len() as i32);
258                if x.is_null() {
259                    Err(FltkError::Internal(FltkErrorKind::ResourceNotFound))
260                } else {
261                    if Fl_PNG_Image_fail(x) < 0 {
262                        return Err(FltkError::Internal(FltkErrorKind::ImageFormatError));
263                    }
264                    Ok(PngImage {
265                        inner: ImageRC::from(x),
266                    })
267                }
268            }
269        }
270    }
271}
272
273#[cfg(feature = "use-images")]
274/// Creates a struct holding an SVG image
275#[derive(Debug)]
276pub struct SvgImage {
277    inner: ImageRC<*mut Fl_SVG_Image>,
278}
279
280#[cfg(feature = "use-images")]
281crate::macros::image::impl_image_ext!(SvgImage, Fl_SVG_Image);
282
283#[cfg(feature = "use-images")]
284impl SvgImage {
285    /// Loads the image from a filesystem path, doesn't check for the validity of the data
286    /// # Errors
287    /// Errors on non-existent path or invalid format
288    pub fn load<P: AsRef<std::path::Path>>(path: P) -> Result<SvgImage, FltkError> {
289        Self::load_(path.as_ref())
290    }
291
292    fn load_(path: &std::path::Path) -> Result<SvgImage, FltkError> {
293        if !path.exists() {
294            return Err(FltkError::Internal(FltkErrorKind::ResourceNotFound));
295        }
296        unsafe {
297            let temp = path.to_str().ok_or_else(|| {
298                FltkError::Unknown(String::from("Failed to convert path to string"))
299            })?;
300            if !images_registered() {
301                register_images();
302            }
303            let temp = CString::safe_new(temp);
304            let image_ptr = Fl_SVG_Image_new(temp.as_ptr());
305            if image_ptr.is_null() {
306                Err(FltkError::Internal(FltkErrorKind::ResourceNotFound))
307            } else {
308                if Fl_SVG_Image_fail(image_ptr) < 0 {
309                    return Err(FltkError::Internal(FltkErrorKind::ImageFormatError));
310                }
311                Ok(SvgImage {
312                    inner: ImageRC::from(image_ptr),
313                })
314            }
315        }
316    }
317
318    /// Loads the image from data/memory
319    /// # Errors
320    /// Errors on invalid format
321    pub fn from_data(data: &str) -> Result<SvgImage, FltkError> {
322        if data.is_empty() {
323            Err(FltkError::Internal(FltkErrorKind::ResourceNotFound))
324        } else {
325            let data = CString::safe_new(data);
326            if !images_registered() {
327                register_images();
328            }
329            unsafe {
330                let x = Fl_SVG_Image_from(data.as_ptr());
331                if x.is_null() {
332                    Err(FltkError::Internal(FltkErrorKind::ResourceNotFound))
333                } else {
334                    if Fl_SVG_Image_fail(x) < 0 {
335                        return Err(FltkError::Internal(FltkErrorKind::ImageFormatError));
336                    }
337                    Ok(SvgImage {
338                        inner: ImageRC::from(x),
339                    })
340                }
341            }
342        }
343    }
344
345    /// Rasterize an `SvgImage`
346    pub fn normalize(&mut self) {
347        assert!(!self.was_deleted());
348        unsafe { Fl_SVG_Image_normalize(*self.inner) }
349    }
350}
351
352#[cfg(feature = "use-images")]
353/// Creates a struct holding a BMP image
354#[derive(Debug)]
355pub struct BmpImage {
356    inner: ImageRC<*mut Fl_BMP_Image>,
357}
358
359#[cfg(feature = "use-images")]
360crate::macros::image::impl_image_ext!(BmpImage, Fl_BMP_Image);
361
362#[cfg(feature = "use-images")]
363impl BmpImage {
364    /// Loads the image from a filesystem path, doesn't check for the validity of the data
365    /// # Errors
366    /// Errors on non-existent path or invalid format
367    pub fn load<P: AsRef<std::path::Path>>(path: P) -> Result<BmpImage, FltkError> {
368        Self::load_(path.as_ref())
369    }
370
371    fn load_(path: &std::path::Path) -> Result<BmpImage, FltkError> {
372        if !path.exists() {
373            return Err(FltkError::Internal(FltkErrorKind::ResourceNotFound));
374        }
375        unsafe {
376            let temp = path.to_str().ok_or_else(|| {
377                FltkError::Unknown(String::from("Failed to convert path to string"))
378            })?;
379            if !images_registered() {
380                register_images();
381            }
382            let temp = CString::safe_new(temp);
383            let image_ptr = Fl_BMP_Image_new(temp.as_ptr());
384            if image_ptr.is_null() {
385                Err(FltkError::Internal(FltkErrorKind::ResourceNotFound))
386            } else {
387                if Fl_BMP_Image_fail(image_ptr) < 0 {
388                    return Err(FltkError::Internal(FltkErrorKind::ImageFormatError));
389                }
390                Ok(BmpImage {
391                    inner: ImageRC::from(image_ptr),
392                })
393            }
394        }
395    }
396
397    /// Loads the image from data/memory
398    /// # Errors
399    /// Errors on invalid format
400    pub fn from_data(data: &[u8]) -> Result<BmpImage, FltkError> {
401        unsafe {
402            if data.is_empty() {
403                Err(FltkError::Internal(FltkErrorKind::ResourceNotFound))
404            } else {
405                if !images_registered() {
406                    register_images();
407                }
408                let x = Fl_BMP_Image_from(data.as_ptr(), data.len() as _);
409                if x.is_null() {
410                    Err(FltkError::Internal(FltkErrorKind::ResourceNotFound))
411                } else {
412                    if Fl_BMP_Image_fail(x) < 0 {
413                        return Err(FltkError::Internal(FltkErrorKind::ImageFormatError));
414                    }
415                    Ok(BmpImage {
416                        inner: ImageRC::from(x),
417                    })
418                }
419            }
420        }
421    }
422}
423
424#[cfg(feature = "use-images")]
425/// Creates a struct holding a GIF image
426#[derive(Debug)]
427pub struct GifImage {
428    inner: ImageRC<*mut Fl_GIF_Image>,
429}
430
431#[cfg(feature = "use-images")]
432crate::macros::image::impl_image_ext!(GifImage, Fl_GIF_Image);
433
434#[cfg(feature = "use-images")]
435impl GifImage {
436    /// Loads the image from a filesystem path, doesn't check for the validity of the data
437    /// # Errors
438    /// Errors on non-existent path or invalid format
439    pub fn load<P: AsRef<std::path::Path>>(path: P) -> Result<GifImage, FltkError> {
440        Self::load_(path.as_ref())
441    }
442
443    fn load_(path: &std::path::Path) -> Result<GifImage, FltkError> {
444        if !path.exists() {
445            return Err(FltkError::Internal(FltkErrorKind::ResourceNotFound));
446        }
447        unsafe {
448            let temp = path.to_str().ok_or_else(|| {
449                FltkError::Unknown(String::from("Failed to convert path to string"))
450            })?;
451            if !images_registered() {
452                register_images();
453            }
454            let temp = CString::safe_new(temp);
455            let image_ptr = Fl_GIF_Image_new(temp.as_ptr());
456            if image_ptr.is_null() {
457                Err(FltkError::Internal(FltkErrorKind::ResourceNotFound))
458            } else {
459                if Fl_GIF_Image_fail(image_ptr) < 0 {
460                    return Err(FltkError::Internal(FltkErrorKind::ImageFormatError));
461                }
462                Ok(GifImage {
463                    inner: ImageRC::from(image_ptr),
464                })
465            }
466        }
467    }
468
469    /// Loads the image from data/memory
470    /// # Errors
471    /// Errors on invalid format
472    pub fn from_data(data: &[u8]) -> Result<GifImage, FltkError> {
473        unsafe {
474            if data.is_empty() {
475                Err(FltkError::Internal(FltkErrorKind::ResourceNotFound))
476            } else {
477                if !images_registered() {
478                    register_images();
479                }
480                let x = Fl_GIF_Image_from(data.as_ptr(), data.len() as _);
481                if x.is_null() {
482                    Err(FltkError::Internal(FltkErrorKind::ResourceNotFound))
483                } else {
484                    if Fl_GIF_Image_fail(x) < 0 {
485                        return Err(FltkError::Internal(FltkErrorKind::ImageFormatError));
486                    }
487                    Ok(GifImage {
488                        inner: ImageRC::from(x),
489                    })
490                }
491            }
492        }
493    }
494}
495
496#[cfg(feature = "use-images")]
497bitflags::bitflags! {
498    /// Defines AnimGifImage flags
499    #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
500    pub struct AnimGifImageFlags: u16 {
501        /// No Event
502        const None = 0;
503        /// This flag indicates to the loader that it should not start
504        /// the animation immediately after successful load, which is
505        /// the default.
506        /// It can be started later using the \ref start() method.
507        const DONT_START = 1;
508        /// This flag indicates to the loader that it should not
509        /// resize the canvas widget of the animation to the dimensions
510        /// of the animation, which is the default.
511        /// Needed for special use cases.
512        const DONT_RESIZE_CANVAS = 2;
513        /// This flag indicates to the loader that it should not
514        /// set the animation as \ref image() member of the canvas widget,
515        /// which is the default.
516        /// Needed for special use cases.
517        const DONT_SET_AS_IMAGE = 4;
518        /// Often frames change just a small area of the animation canvas.
519        /// This flag indicates to the loader to try using less memory
520        /// by storing frame data not as canvas-sized images but use the
521        /// sizes defined in the GIF file.
522        /// The drawbacks are higher cpu usage during playback and maybe
523        //// minor artefacts when resized.\
524        const OPTIMIZE_MEMORY = 8;
525        /// This flag can be used to print informations about the decoding process to the console.
526        const LOG_FLAG = 64;
527        /// This flag can be used to print even more informations about the decoding process to the console.
528        const DEBUG_FLAG = 128;
529    }
530}
531
532#[cfg(feature = "use-images")]
533/// Creates a struct holding an animated GIF image
534#[derive(Debug)]
535pub struct AnimGifImage {
536    inner: ImageRC<*mut Fl_Anim_GIF_Image>,
537}
538
539#[cfg(feature = "use-images")]
540crate::macros::image::impl_image_ext!(AnimGifImage, Fl_Anim_GIF_Image);
541
542#[cfg(feature = "use-images")]
543impl AnimGifImage {
544    /// Loads the image from a filesystem path, doesn't check for the validity of the data
545    /// # Errors
546    /// Errors on non-existent path or invalid format
547    pub fn load<P: AsRef<std::path::Path>, W: WidgetExt>(
548        path: P,
549        w: &mut W,
550        flags: AnimGifImageFlags,
551    ) -> Result<AnimGifImage, FltkError> {
552        Self::load_(path.as_ref(), w, flags)
553    }
554
555    fn load_<W: WidgetExt>(
556        path: &std::path::Path,
557        w: &mut W,
558        flags: AnimGifImageFlags,
559    ) -> Result<AnimGifImage, FltkError> {
560        if !path.exists() {
561            return Err(FltkError::Internal(FltkErrorKind::ResourceNotFound));
562        }
563        unsafe {
564            let temp = path.to_str().ok_or_else(|| {
565                FltkError::Unknown(String::from("Failed to convert path to string"))
566            })?;
567            if !images_registered() {
568                register_images();
569            }
570            let temp = CString::safe_new(temp);
571            let image_ptr =
572                Fl_Anim_GIF_Image_new(temp.as_ptr(), w.as_widget_ptr() as _, flags.bits());
573            if image_ptr.is_null() {
574                Err(FltkError::Internal(FltkErrorKind::ResourceNotFound))
575            } else {
576                if Fl_Anim_GIF_Image_fail(image_ptr) < 0 {
577                    return Err(FltkError::Internal(FltkErrorKind::ImageFormatError));
578                }
579                Ok(AnimGifImage {
580                    inner: ImageRC::from(image_ptr),
581                })
582            }
583        }
584    }
585
586    /// Loads the image from data/memory
587    /// # Errors
588    /// Errors on invalid format
589    pub fn from_data<W: WidgetExt>(
590        data: &[u8],
591        w: &mut W,
592        flags: AnimGifImageFlags,
593    ) -> Result<AnimGifImage, FltkError> {
594        unsafe {
595            if data.is_empty() {
596                Err(FltkError::Internal(FltkErrorKind::ResourceNotFound))
597            } else {
598                if !images_registered() {
599                    register_images();
600                }
601                let x = Fl_Anim_GIF_Image_from(
602                    std::ptr::null() as _,
603                    data.as_ptr(),
604                    data.len() as _,
605                    w.as_widget_ptr() as _,
606                    flags.bits(),
607                );
608                if x.is_null() {
609                    Err(FltkError::Internal(FltkErrorKind::ResourceNotFound))
610                } else {
611                    if Fl_Anim_GIF_Image_fail(x) < 0 {
612                        return Err(FltkError::Internal(FltkErrorKind::ImageFormatError));
613                    }
614                    Ok(AnimGifImage {
615                        inner: ImageRC::from(x),
616                    })
617                }
618            }
619        }
620    }
621
622    /// Returns the delay of the frame
623    pub fn delay(&self, frame: i32) -> f64 {
624        unsafe { Fl_Anim_GIF_Image_delay(*self.inner, frame) }
625    }
626
627    /// Sets the delay of the frame
628    pub fn set_delay(&mut self, frame: i32, delay: f64) {
629        unsafe { Fl_Anim_GIF_Image_set_delay(*self.inner, frame, delay) }
630    }
631
632    /// Returns whether the Gif is animated
633    pub fn is_animated(&self) -> bool {
634        unsafe { Fl_Anim_GIF_Image_is_animated(*self.inner) != 0 }
635    }
636
637    /// Sets the animation speed
638    pub fn set_speed(&mut self, speed: f64) {
639        unsafe { Fl_Anim_GIF_Image_set_speed(*self.inner, speed) }
640    }
641
642    /// Returns the animation speed
643    pub fn speed(&mut self) -> f64 {
644        unsafe { Fl_Anim_GIF_Image_speed(*self.inner) }
645    }
646
647    /// Starts the animation
648    pub fn start(&mut self) -> bool {
649        unsafe { Fl_Anim_GIF_Image_start(*self.inner) != 0 }
650    }
651
652    /// Stops the animation
653    pub fn stop(&mut self) -> bool {
654        unsafe { Fl_Anim_GIF_Image_stop(*self.inner) != 0 }
655    }
656
657    /// Show the next frame if the animation is stopped. Errors if the Gif has no more frames
658    pub fn next_frame(&mut self) -> Result<(), FltkError> {
659        unsafe {
660            if Fl_Anim_GIF_Image_next(*self.inner) != 0 {
661                Ok(())
662            } else {
663                Err(FltkError::Internal(FltkErrorKind::FailedOperation))
664            }
665        }
666    }
667
668    /// Returns whether the Gif is playing
669    pub fn playing(&self) -> bool {
670        unsafe { Fl_Anim_GIF_Image_playing(*self.inner) != 0 }
671    }
672}
673
674/// Creates a struct holding a XPM image
675#[derive(Debug)]
676pub struct XpmImage {
677    inner: ImageRC<*mut Fl_XPM_Image>,
678}
679
680crate::macros::image::impl_image_ext!(XpmImage, Fl_XPM_Image);
681
682impl XpmImage {
683    /// Loads the image from a filesystem path, doesn't check for the validity of the data
684    /// # Errors
685    /// Errors on non-existent path or invalid format
686    pub fn load<P: AsRef<std::path::Path>>(path: P) -> Result<XpmImage, FltkError> {
687        Self::load_(path.as_ref())
688    }
689
690    fn load_(path: &std::path::Path) -> Result<XpmImage, FltkError> {
691        if !path.exists() {
692            return Err(FltkError::Internal(FltkErrorKind::ResourceNotFound));
693        }
694        unsafe {
695            let temp = path.to_str().ok_or_else(|| {
696                FltkError::Unknown(String::from("Failed to convert path to string"))
697            })?;
698            let temp = CString::safe_new(temp);
699            let image_ptr = Fl_XPM_Image_new(temp.as_ptr());
700            if image_ptr.is_null() {
701                Err(FltkError::Internal(FltkErrorKind::ResourceNotFound))
702            } else {
703                if Fl_XPM_Image_fail(image_ptr) < 0 {
704                    return Err(FltkError::Internal(FltkErrorKind::ImageFormatError));
705                }
706                Ok(XpmImage {
707                    inner: ImageRC::from(image_ptr),
708                })
709            }
710        }
711    }
712}
713
714/// Creates a struct holding a XBM image
715#[derive(Debug)]
716pub struct XbmImage {
717    inner: ImageRC<*mut Fl_XBM_Image>,
718}
719
720crate::macros::image::impl_image_ext!(XbmImage, Fl_XBM_Image);
721
722impl XbmImage {
723    /// Loads the image from a filesystem path, doesn't check for the validity of the data
724    /// # Errors
725    /// Errors on non-existent path or invalid format
726    pub fn load<P: AsRef<std::path::Path>>(path: P) -> Result<XbmImage, FltkError> {
727        Self::load_(path.as_ref())
728    }
729
730    fn load_(path: &std::path::Path) -> Result<XbmImage, FltkError> {
731        if !path.exists() {
732            return Err(FltkError::Internal(FltkErrorKind::ResourceNotFound));
733        }
734        unsafe {
735            let temp = path.to_str().ok_or_else(|| {
736                FltkError::Unknown(String::from("Failed to convert path to string"))
737            })?;
738            let temp = CString::safe_new(temp);
739            let image_ptr = Fl_XBM_Image_new(temp.as_ptr());
740            if image_ptr.is_null() {
741                Err(FltkError::Internal(FltkErrorKind::ResourceNotFound))
742            } else {
743                if Fl_XBM_Image_fail(image_ptr) < 0 {
744                    return Err(FltkError::Internal(FltkErrorKind::ImageFormatError));
745                }
746                Ok(XbmImage {
747                    inner: ImageRC::from(image_ptr),
748                })
749            }
750        }
751    }
752}
753
754#[cfg(feature = "use-images")]
755/// Creates a struct holding a PNM image
756#[derive(Debug)]
757pub struct PnmImage {
758    inner: ImageRC<*mut Fl_PNM_Image>,
759}
760
761#[cfg(feature = "use-images")]
762crate::macros::image::impl_image_ext!(PnmImage, Fl_PNM_Image);
763
764#[cfg(feature = "use-images")]
765impl PnmImage {
766    /// Loads the image from a filesystem path, doesn't check for the validity of the data
767    /// # Errors
768    /// Errors on non-existent path or invalid format
769    pub fn load<P: AsRef<std::path::Path>>(path: P) -> Result<PnmImage, FltkError> {
770        Self::load_(path.as_ref())
771    }
772
773    fn load_(path: &std::path::Path) -> Result<PnmImage, FltkError> {
774        if !path.exists() {
775            return Err(FltkError::Internal(FltkErrorKind::ResourceNotFound));
776        }
777        unsafe {
778            let temp = path.to_str().ok_or_else(|| {
779                FltkError::Unknown(String::from("Failed to convert path to string"))
780            })?;
781            if !images_registered() {
782                register_images();
783            }
784            let temp = CString::safe_new(temp);
785            let image_ptr = Fl_PNM_Image_new(temp.as_ptr());
786            if image_ptr.is_null() {
787                Err(FltkError::Internal(FltkErrorKind::ResourceNotFound))
788            } else {
789                if Fl_PNM_Image_fail(image_ptr) < 0 {
790                    return Err(FltkError::Internal(FltkErrorKind::ImageFormatError));
791                }
792                Ok(PnmImage {
793                    inner: ImageRC::from(image_ptr),
794                })
795            }
796        }
797    }
798}
799
800/// Creates a struct holding a tiled image
801#[derive(Debug)]
802pub struct TiledImage {
803    inner: ImageRC<*mut Fl_Tiled_Image>,
804}
805
806crate::macros::image::impl_image_ext!(TiledImage, Fl_Tiled_Image);
807
808impl TiledImage {
809    /// Loads the image from a filesystem path, doesn't check for the validity of the data
810    pub fn new<Img: ImageExt>(img: &Img, w: i32, h: i32) -> TiledImage {
811        unsafe {
812            assert!(!img.was_deleted());
813            let ptr = Fl_Tiled_Image_new(img.as_image_ptr(), w, h);
814            assert!(!ptr.is_null());
815            TiledImage {
816                inner: ImageRC::from(ptr),
817            }
818        }
819    }
820}
821
822/// Creates a struct holding a pixmap image
823#[derive(Debug)]
824pub struct Pixmap {
825    inner: ImageRC<*mut Fl_Pixmap>,
826}
827
828crate::macros::image::impl_image_ext!(Pixmap, Fl_Pixmap);
829
830impl Pixmap {
831    /// Creates a new Pixmap image
832    /// # Errors
833    /// Errors on invalid or unsupported image format
834    pub fn new(data: &[&str]) -> Result<Pixmap, FltkError> {
835        if data.is_empty() {
836            Err(FltkError::Internal(FltkErrorKind::ResourceNotFound))
837        } else {
838            let data: Vec<*const std::ffi::c_char> = data
839                .iter()
840                .map(|x| CString::safe_new(x).into_raw() as *const std::ffi::c_char)
841                .collect();
842            unsafe {
843                let x = Fl_Pixmap_new(data.as_ptr() as _);
844                for x in &data {
845                    let _ = CString::from_raw(*x as _);
846                }
847                if x.is_null() {
848                    Err(FltkError::Internal(FltkErrorKind::ResourceNotFound))
849                } else {
850                    if Fl_Pixmap_fail(x) < 0 {
851                        return Err(FltkError::Internal(FltkErrorKind::ImageFormatError));
852                    }
853                    Ok(Pixmap {
854                        inner: ImageRC::from(x),
855                    })
856                }
857            }
858        }
859    }
860}
861
862/// Creates a struct holding a raw RGB image
863#[derive(Debug)]
864pub struct RgbImage {
865    pub(crate) inner: ImageRC<*mut Fl_RGB_Image>,
866}
867
868crate::macros::image::impl_image_ext!(RgbImage, Fl_RGB_Image);
869
870impl RgbImage {
871    /// Initializes a new raw `RgbImage`, copies the data and handles its lifetime.
872    /// If you need to work with RGB data,
873    /// # Errors
874    /// Errors on invalid or unsupported image format
875    pub fn new(data: &[u8], w: i32, h: i32, depth: ColorDepth) -> Result<RgbImage, FltkError> {
876        let sz = w * h * depth as i32;
877        if sz > data.len() as i32 {
878            return Err(FltkError::Internal(FltkErrorKind::ImageFormatError));
879        }
880        unsafe {
881            let img = Fl_RGB_Image_new(data.as_ptr(), w, h, depth as i32, 0);
882            if img.is_null() || Fl_RGB_Image_fail(img) < 0 {
883                Err(FltkError::Internal(FltkErrorKind::ImageFormatError))
884            } else {
885                Ok(RgbImage {
886                    inner: ImageRC::from(img),
887                })
888            }
889        }
890    }
891
892    /// Initializes a new raw `RgbImage` from shared data, doesn't handle the data's lifetime
893    /// # Safety
894    /// The data must be valid for the lifetime of the image
895    /// # Errors
896    /// Errors on invalid or unsupported image format
897    pub unsafe fn from_data(
898        data: &[u8],
899        w: i32,
900        h: i32,
901        depth: ColorDepth,
902    ) -> Result<RgbImage, FltkError> {
903        unsafe {
904            let sz = w * h * depth as i32;
905            if sz > data.len() as i32 {
906                return Err(FltkError::Internal(FltkErrorKind::ImageFormatError));
907            }
908            let img = Fl_RGB_Image_from_data(data.as_ptr(), w, h, depth as i32, 0);
909            if img.is_null() || Fl_RGB_Image_fail(img) < 0 {
910                Err(FltkError::Internal(FltkErrorKind::ImageFormatError))
911            } else {
912                Ok(RgbImage {
913                    inner: ImageRC::from(img),
914                })
915            }
916        }
917    }
918
919    /// Initializes a new raw `RgbImage`, copies the data and handles its lifetime.
920    /// If you need to work with RGB data,
921    /// # Errors
922    /// Errors on invalid or unsupported image format
923    /// # Safety
924    /// Passing wrong line data can read to over or underflow
925    pub unsafe fn new_ext(
926        data: &[u8],
927        w: i32,
928        h: i32,
929        depth: i32,
930        line_data: i32,
931    ) -> Result<RgbImage, FltkError> {
932        unsafe {
933            let img = Fl_RGB_Image_new(data.as_ptr(), w, h, depth, line_data);
934            if img.is_null() || Fl_RGB_Image_fail(img) < 0 {
935                Err(FltkError::Internal(FltkErrorKind::ImageFormatError))
936            } else {
937                Ok(RgbImage {
938                    inner: ImageRC::from(img),
939                })
940            }
941        }
942    }
943
944    /// Initializes a new raw `RgbImage` from shared data, doesn't handle the data's lifetime
945    /// # Safety
946    /// The data must be valid for the lifetime of the image
947    /// # Errors
948    /// Errors on invalid or unsupported image format
949    /// # Safety
950    /// Passing wrong line data can read to over or underflow
951    pub unsafe fn from_data_ext(
952        data: &[u8],
953        w: i32,
954        h: i32,
955        depth: i32,
956        line_data: i32,
957    ) -> Result<RgbImage, FltkError> {
958        unsafe {
959            let img = Fl_RGB_Image_from_data(data.as_ptr(), w, h, depth, line_data);
960            if img.is_null() || Fl_RGB_Image_fail(img) < 0 {
961                Err(FltkError::Internal(FltkErrorKind::ImageFormatError))
962            } else {
963                Ok(RgbImage {
964                    inner: ImageRC::from(img),
965                })
966            }
967        }
968    }
969
970    /// Creates an `RgbImage` from a pixmap
971    pub fn from_pixmap(image: &Pixmap) -> RgbImage {
972        unsafe { RgbImage::from_image_ptr(Fl_RGB_Image_from_pixmap(*image.inner as _) as _) }
973    }
974
975    /// Deconstructs a raw `RgbImage` into parts
976    /// # Safety
977    /// Destructures the image into its raw elements
978    pub unsafe fn into_parts(self) -> (Vec<u8>, i32, i32) {
979        let w = self.data_w();
980        let h = self.data_h();
981        (self.as_rgb_data(), w, h)
982    }
983
984    #[allow(clippy::too_many_lines)]
985    /// Convert from one `ColorDepth` to another `ColorDepth`
986    pub fn convert(&self, new_depth: ColorDepth) -> Result<RgbImage, FltkError> {
987        let depth = self.depth() as i32;
988        let new_depth = new_depth as i32;
989        if depth == new_depth {
990            Ok(self.copy())
991        } else {
992            let w = self.data_w();
993            let h = self.data_h();
994            let data = self.as_rgb_data();
995            let mut temp = Vec::new();
996            match depth {
997                1 => match new_depth {
998                    2 => {
999                        for i in data {
1000                            temp.push(i);
1001                            temp.push(255);
1002                        }
1003                        assert!(temp.len() as i32 == w * h * 2);
1004                        RgbImage::new(&temp, w, h, ColorDepth::La8)
1005                    }
1006                    3 => {
1007                        for i in data {
1008                            temp.push(i);
1009                            temp.push(i);
1010                            temp.push(i);
1011                        }
1012                        assert!(temp.len() as i32 == w * h * 3);
1013                        RgbImage::new(&temp, w, h, ColorDepth::Rgb8)
1014                    }
1015                    4 => {
1016                        for i in data {
1017                            temp.push(i);
1018                            temp.push(i);
1019                            temp.push(i);
1020                            temp.push(255);
1021                        }
1022                        assert!(temp.len() as i32 == w * h * 4);
1023                        RgbImage::new(&temp, w, h, ColorDepth::Rgba8)
1024                    }
1025                    _ => unreachable!(),
1026                },
1027                2 => match new_depth {
1028                    1 => {
1029                        for (i, item) in data.iter().enumerate() {
1030                            if i % 2 == 0 {
1031                                temp.push(*item);
1032                            } else {
1033                                // skip
1034                            }
1035                        }
1036                        assert!(temp.len() as i32 == w * h);
1037                        RgbImage::new(&temp, w, h, ColorDepth::L8)
1038                    }
1039                    3 => {
1040                        for (i, item) in data.iter().enumerate() {
1041                            if i % 2 == 0 {
1042                                temp.push(*item);
1043                                temp.push(*item);
1044                                temp.push(*item);
1045                            } else {
1046                                // skip
1047                            }
1048                        }
1049                        assert!(temp.len() as i32 == w * h * 3);
1050                        RgbImage::new(&temp, w, h, ColorDepth::Rgb8)
1051                    }
1052                    4 => {
1053                        for (i, item) in data.iter().enumerate() {
1054                            temp.push(*item);
1055                            if i % 2 == 0 {
1056                                temp.push(*item);
1057                                temp.push(*item);
1058                            }
1059                        }
1060                        assert!(temp.len() as i32 == w * h * 4);
1061                        RgbImage::new(&temp, w, h, ColorDepth::Rgba8)
1062                    }
1063                    _ => unreachable!(),
1064                },
1065                3 => match new_depth {
1066                    1 => {
1067                        for pixel in data.chunks_exact(3) {
1068                            temp.push(
1069                                (f32::from(pixel[0]) * 0.299
1070                                    + f32::from(pixel[1]) * 0.587
1071                                    + f32::from(pixel[2]) * 0.114)
1072                                    as u8,
1073                            );
1074                        }
1075                        assert!(temp.len() as i32 == w * h);
1076                        RgbImage::new(&temp, w, h, ColorDepth::L8)
1077                    }
1078                    2 => {
1079                        for pixel in data.chunks_exact(3) {
1080                            temp.push(
1081                                (f32::from(pixel[0]) * 0.299
1082                                    + f32::from(pixel[1]) * 0.587
1083                                    + f32::from(pixel[2]) * 0.114)
1084                                    as u8,
1085                            );
1086                            temp.push(255);
1087                        }
1088                        assert!(temp.len() as i32 == w * h * 2);
1089                        RgbImage::new(&temp, w, h, ColorDepth::La8)
1090                    }
1091                    4 => {
1092                        for pixel in data.chunks_exact(3) {
1093                            temp.push(pixel[0]);
1094                            temp.push(pixel[1]);
1095                            temp.push(pixel[2]);
1096                            temp.push(255);
1097                        }
1098                        assert!(temp.len() as i32 == w * h * 4);
1099                        RgbImage::new(&temp, w, h, ColorDepth::Rgba8)
1100                    }
1101                    _ => unreachable!(),
1102                },
1103                4 => match new_depth {
1104                    1 => {
1105                        for pixel in data.chunks_exact(4) {
1106                            temp.push(
1107                                (f32::from(pixel[0]) * 0.299
1108                                    + f32::from(pixel[1]) * 0.587
1109                                    + f32::from(pixel[2]) * 0.114)
1110                                    as u8,
1111                            );
1112                        }
1113                        assert!(temp.len() as i32 == w * h);
1114                        RgbImage::new(&temp, w, h, ColorDepth::L8)
1115                    }
1116                    2 => {
1117                        for pixel in data.chunks_exact(4) {
1118                            temp.push(
1119                                (f32::from(pixel[0]) * 0.299
1120                                    + f32::from(pixel[1]) * 0.587
1121                                    + f32::from(pixel[2]) * 0.114)
1122                                    as u8,
1123                            );
1124                            temp.push(pixel[3]);
1125                        }
1126                        assert!(temp.len() as i32 == w * h * 2);
1127                        RgbImage::new(&temp, w, h, ColorDepth::La8)
1128                    }
1129                    3 => {
1130                        for pixel in data.chunks_exact(4) {
1131                            temp.push(pixel[0]);
1132                            temp.push(pixel[1]);
1133                            temp.push(pixel[2]);
1134                        }
1135                        assert!(temp.len() as i32 == w * h * 3);
1136                        RgbImage::new(&temp, w, h, ColorDepth::Rgb8)
1137                    }
1138                    _ => unreachable!(),
1139                },
1140                _ => unreachable!(),
1141            }
1142        }
1143    }
1144
1145    /// Convert black pixels to transparent
1146    pub fn convert_transparent(&self) -> Result<RgbImage, FltkError> {
1147        let depth = self.depth() as i32;
1148        if depth == 2 || depth == 4 {
1149            Ok(self.copy())
1150        } else {
1151            let w = self.w();
1152            let h = self.h();
1153            let data = self.as_rgb_data();
1154            let mut temp = Vec::new();
1155            match depth {
1156                1 => {
1157                    for i in data {
1158                        temp.push(i);
1159                        if i == 0 {
1160                            temp.push(0);
1161                        } else {
1162                            temp.push(255);
1163                        }
1164                    }
1165                    assert!(temp.len() as i32 == w * h * 2);
1166                    RgbImage::new(&temp, w, h, ColorDepth::La8)
1167                }
1168                3 => {
1169                    for pixel in data.chunks_exact(3) {
1170                        let r = pixel[0];
1171                        let g = pixel[1];
1172                        let b = pixel[2];
1173                        temp.push(r);
1174                        temp.push(g);
1175                        temp.push(b);
1176                        if r == 0 && g == 0 && b == 0 {
1177                            temp.push(0);
1178                        } else {
1179                            temp.push(255);
1180                        }
1181                    }
1182                    assert!(temp.len() as i32 == w * h * 4);
1183                    RgbImage::new(&temp, w, h, ColorDepth::Rgba8)
1184                }
1185                _ => unreachable!(),
1186            }
1187        }
1188    }
1189
1190    /// Blur the image
1191    /// Adapted from <https://www.cairographics.org/cookbook/blur.c/>
1192    pub fn blur(&self, radius: u32) -> Result<RgbImage, FltkError> {
1193        assert!(self.depth() == ColorDepth::Rgba8);
1194        let radius = radius as i32;
1195        let mut src = self.as_rgb_data();
1196        let width = self.w();
1197        let height = self.h();
1198        let depth = self.depth();
1199        let mut dst = vec![0u8; (width * height * depth as i32) as usize];
1200        let mut kernel = [0u8; 17];
1201        let size = kernel.len() as i32;
1202        let half = size / 2;
1203        let src_stride = width * depth as i32;
1204        let dst_stride = src_stride;
1205
1206        let mut x: u32;
1207        let mut y: u32;
1208        let mut z: u32;
1209        let mut w: u32;
1210        let mut p: u32;
1211
1212        let mut a: u32 = 0;
1213        for i in 0..size {
1214            let f = i - half;
1215            let f = f as f32;
1216            kernel[i as usize] = ((-f * f / 30.0).exp() * 80.0) as u8;
1217            a += u32::from(kernel[i as usize]);
1218        }
1219
1220        // Horizontally blur from surface -> temp
1221        for i in 0..height {
1222            let s: &[u32] = unsafe { src[(i * src_stride) as usize..].align_to::<u32>().1 };
1223            let d: &mut [u32] = unsafe { dst[(i * dst_stride) as usize..].align_to_mut::<u32>().1 };
1224            for j in 0..width {
1225                if radius < j && j < width - radius {
1226                    let j = j as usize;
1227                    d[j] = s[j];
1228                    continue;
1229                }
1230
1231                x = 0;
1232                y = 0;
1233                z = 0;
1234                w = 0;
1235                for k in 0..size {
1236                    if j - half + k < 0 || j - half + k >= width {
1237                        continue;
1238                    }
1239
1240                    p = s[(j - half + k) as usize];
1241                    let k = k as usize;
1242
1243                    x += ((p >> 24) & 0xff) * u32::from(kernel[k]);
1244                    y += ((p >> 16) & 0xff) * u32::from(kernel[k]);
1245                    z += ((p >> 8) & 0xff) * u32::from(kernel[k]);
1246                    w += (p & 0xff) * u32::from(kernel[k]);
1247                }
1248                d[j as usize] = ((x / a) << 24) | ((y / a) << 16) | ((z / a) << 8) | (w / a);
1249            }
1250        }
1251
1252        // Then vertically blur from tmp -> surface
1253        for i in 0..height {
1254            let mut s: &mut [u32] =
1255                unsafe { dst[(i * dst_stride) as usize..].align_to_mut::<u32>().1 };
1256            let d: &mut [u32] = unsafe { src[(i * src_stride) as usize..].align_to_mut::<u32>().1 };
1257            for j in 0..width {
1258                if radius < i && i < height - radius {
1259                    let j = j as usize;
1260                    d[j] = s[j];
1261                    continue;
1262                }
1263
1264                x = 0;
1265                y = 0;
1266                z = 0;
1267                w = 0;
1268                for k in 0..size {
1269                    if i - half + k < 0 || i - half + k >= height {
1270                        continue;
1271                    }
1272
1273                    s = unsafe {
1274                        dst[((i - half + k) * dst_stride) as usize..]
1275                            .align_to_mut::<u32>()
1276                            .1
1277                    };
1278                    p = s[j as usize];
1279                    let k = k as usize;
1280                    x += ((p >> 24) & 0xff) * u32::from(kernel[k]);
1281                    y += ((p >> 16) & 0xff) * u32::from(kernel[k]);
1282                    z += ((p >> 8) & 0xff) * u32::from(kernel[k]);
1283                    w += (p & 0xff) * u32::from(kernel[k]);
1284                }
1285                d[j as usize] = ((x / a) << 24) | ((y / a) << 16) | ((z / a) << 8) | (w / a);
1286            }
1287        }
1288        RgbImage::new(&dst, width, height, depth)
1289    }
1290
1291    /// Returns an SRGB image
1292    pub fn to_srgb_image(&self) -> Result<RgbImage, FltkError> {
1293        assert!(self.depth() as i32 >= 3);
1294        let depth = self.depth() as i32;
1295        let w = self.w();
1296        let h = self.h();
1297        fn correct_gamma(v: f32) -> f32 {
1298            if v <= 0.003_130_8 {
1299                v * 12.92
1300            } else {
1301                1.055 * v.powf(1.0 / 2.4) - 0.055
1302            }
1303        }
1304        let mut temp = vec![];
1305        let data = self.as_rgb_data();
1306        match depth {
1307            3 => {
1308                for pixel in data.chunks_exact(3) {
1309                    let r = (correct_gamma(f32::from(pixel[0]) / 255.0) * 255.0) as u8;
1310                    let g = (correct_gamma(f32::from(pixel[1]) / 255.0) * 255.0) as u8;
1311                    let b = (correct_gamma(f32::from(pixel[2]) / 255.0) * 255.0) as u8;
1312                    temp.push(r);
1313                    temp.push(g);
1314                    temp.push(b);
1315                }
1316                assert!(temp.len() as i32 == w * h * 3);
1317                RgbImage::new(&temp, w, h, ColorDepth::Rgb8)
1318            }
1319            4 => {
1320                for pixel in data.chunks_exact(4) {
1321                    let r = (correct_gamma(f32::from(pixel[0]) / 255.0) * 255.0) as u8;
1322                    let g = (correct_gamma(f32::from(pixel[1]) / 255.0) * 255.0) as u8;
1323                    let b = (correct_gamma(f32::from(pixel[2]) / 255.0) * 255.0) as u8;
1324                    temp.push(r);
1325                    temp.push(g);
1326                    temp.push(b);
1327                    temp.push(pixel[3]);
1328                }
1329                assert!(temp.len() as i32 == w * h * 4);
1330                RgbImage::new(&temp, w, h, ColorDepth::Rgba8)
1331            }
1332            _ => unreachable!(),
1333        }
1334    }
1335
1336    /// Sets the scaling algorithm
1337    pub fn set_scaling_algorithm(algorithm: RgbScaling) {
1338        unsafe { Fl_RGB_Image_set_scaling_algorithm(algorithm as i32) }
1339    }
1340
1341    /// Gets the scaling algorithm
1342    pub fn scaling_algorithm() -> RgbScaling {
1343        unsafe { mem::transmute(Fl_RGB_Image_scaling_algorithm()) }
1344    }
1345}
1346
1347#[cfg(feature = "use-images")]
1348/// Creates a struct holding a Windows icon (.ico) image
1349#[derive(Debug)]
1350pub struct IcoImage {
1351    inner: ImageRC<*mut Fl_ICO_Image>,
1352}
1353
1354#[cfg(feature = "use-images")]
1355crate::macros::image::impl_image_ext!(IcoImage, Fl_ICO_Image);
1356
1357#[cfg(feature = "use-images")]
1358impl IcoImage {
1359    /// Loads the image from a filesystem path, doesn't check for the validity of the data
1360    /// # Errors
1361    /// Errors on non-existent path or invalid format
1362    pub fn load<P: AsRef<std::path::Path>>(path: P) -> Result<IcoImage, FltkError> {
1363        Self::load_(path.as_ref())
1364    }
1365
1366    fn load_(path: &std::path::Path) -> Result<IcoImage, FltkError> {
1367        if !path.exists() {
1368            return Err(FltkError::Internal(FltkErrorKind::ResourceNotFound));
1369        }
1370        unsafe {
1371            let temp = path.to_str().ok_or_else(|| {
1372                FltkError::Unknown(String::from("Failed to convert path to string"))
1373            })?;
1374            if !images_registered() {
1375                register_images();
1376            }
1377            let temp = CString::safe_new(temp);
1378            let image_ptr = Fl_ICO_Image_new(temp.as_ptr(), -1);
1379            if image_ptr.is_null() {
1380                Err(FltkError::Internal(FltkErrorKind::ResourceNotFound))
1381            } else {
1382                if Fl_ICO_Image_fail(image_ptr) < 0 {
1383                    return Err(FltkError::Internal(FltkErrorKind::ImageFormatError));
1384                }
1385                Ok(IcoImage {
1386                    inner: ImageRC::from(image_ptr),
1387                })
1388            }
1389        }
1390    }
1391
1392    /// Loads the image from data/memory
1393    /// # Errors
1394    /// Errors on invalid format
1395    pub fn from_data(data: &[u8]) -> Result<IcoImage, FltkError> {
1396        unsafe {
1397            if data.is_empty() {
1398                Err(FltkError::Internal(FltkErrorKind::ResourceNotFound))
1399            } else {
1400                if !images_registered() {
1401                    register_images();
1402                }
1403                let x = Fl_ICO_Image_from_data(data.as_ptr(), data.len() as _, -1);
1404                if x.is_null() {
1405                    Err(FltkError::Internal(FltkErrorKind::ResourceNotFound))
1406                } else {
1407                    if Fl_ICO_Image_fail(x) < 0 {
1408                        return Err(FltkError::Internal(FltkErrorKind::ImageFormatError));
1409                    }
1410                    Ok(IcoImage {
1411                        inner: ImageRC::from(x),
1412                    })
1413                }
1414            }
1415        }
1416    }
1417
1418    /// Get the icon directory entry
1419    pub fn icon_dir_entry(&self) -> Vec<IconDirEntry> {
1420        unsafe {
1421            let mut size = 0;
1422            let ret = Fl_ICO_Image_icondirentry(*self.inner, &mut size) as *mut IconDirEntry;
1423            std::slice::from_raw_parts(ret, size as _).to_owned()
1424        }
1425    }
1426}
1427
1428#[cfg(feature = "use-images")]
1429use std::os::raw::c_int;
1430
1431#[cfg(feature = "use-images")]
1432/// Icon directory entry
1433#[repr(C)]
1434#[derive(Debug, Copy, Clone, PartialEq, Eq)]
1435pub struct IconDirEntry {
1436    ///  Image width
1437    b_width: c_int,
1438    ///  Image height
1439    b_height: c_int,
1440    ///  Number of colors (0 if  ≥ 8bpp)
1441    b_color_count: c_int,
1442    ///  Reserved
1443    b_reserve: c_int,
1444    ///  Color Planes
1445    w_planes: c_int,
1446    ///  Bits per pixel
1447    w_bit_count: c_int,
1448    ///  Resource size in bytes
1449    dw_bytes_in_res: c_int,
1450    ///  Offset to the image
1451    dw_image_offset: c_int,
1452}