Skip to main content

image_extras/
lib.rs

1//! This crate provides additional formats for the image crate.
2//!
3//! Call the [`register`] function at program startup:
4//!
5//!  ```rust,no_run
6//! image_extras::register();
7//!
8//! // Now you can use the image crate as normal
9//! let img = image::open("path/to/image.pcx").unwrap();
10//! ```
11//!
12//! By default, all supported formats are enabled. For finer control, enable
13//! individual formats via Cargo features.
14
15#![forbid(unsafe_code)]
16
17#[cfg(feature = "dds")]
18pub mod dds;
19
20#[cfg(feature = "icns")]
21pub mod icns;
22
23#[cfg(feature = "ora")]
24pub mod ora;
25
26#[cfg(feature = "otb")]
27pub mod otb;
28
29#[cfg(feature = "pcx")]
30pub mod pcx;
31
32#[cfg(feature = "sgi")]
33pub mod sgi;
34
35#[cfg(feature = "wbmp")]
36pub mod wbmp;
37
38#[cfg(feature = "xbm")]
39pub mod xbm;
40
41#[cfg(feature = "xpm")]
42pub mod xpm;
43
44#[allow(unused_imports)]
45use image::hooks::{register_decoding_hook, register_format_detection_hook};
46
47static REGISTER: std::sync::Once = std::sync::Once::new();
48
49/// Register all enabled extra formats with the image crate.
50///
51/// Calling this function more than once is allowed but has no additional
52/// effect.
53pub fn register() {
54    REGISTER.call_once(|| {
55        #[cfg(feature = "dds")]
56        if register_decoding_hook(
57            "dds".into(),
58            Box::new(|r| Ok(Box::new(dds::DdsDecoder::new(r)?))),
59        ) {
60            register_format_detection_hook("dds".into(), b"DDS ", None);
61        }
62
63        #[cfg(feature = "icns")]
64        if register_decoding_hook(
65            "icns".into(),
66            Box::new(|r| {
67                Ok(Box::new(icns::IcnsDecoder::new_with_decode_func(
68                    r,
69                    Box::new(icns::decode_jpeg2000_using_hook),
70                )?))
71            }),
72        ) {
73            register_format_detection_hook("icns".into(), b"icns", None);
74        }
75
76        // OpenRaster images are ZIP files and have no simple signature to distinguish them
77        // from ZIP files containing other content
78        #[cfg(feature = "ora")]
79        register_decoding_hook(
80            "ora".into(),
81            Box::new(|r| {
82                Ok(Box::new(ora::OpenRasterDecoder::with_limits(
83                    r,
84                    image::Limits::no_limits(),
85                )?))
86            }),
87        );
88
89        #[cfg(feature = "otb")]
90        image::hooks::register_decoding_hook(
91            "otb".into(),
92            Box::new(|r| Ok(Box::new(otb::OtbDecoder::new(r)?))),
93        );
94
95        #[cfg(feature = "pcx")]
96        if register_decoding_hook(
97            "pcx".into(),
98            Box::new(|r| Ok(Box::new(pcx::PCXDecoder::new(r)?))),
99        ) {
100            register_format_detection_hook("pcx".into(), &[0x0a, 0x0], Some(b"\xFF\xF8"));
101        }
102
103        // SGI RGB images generally show up with a .rgb ending (whether or not they
104        // have 3 channels), and sometimes .bw (when grayscale) and .rgba. The
105        // extensions .sgi and .iris, while unambiguous, do not seem to have been
106        // used much. The extension .rgb is also used for a variety of other files,
107        // including bare image data, so to be sure it would be best to check both
108        // extension and leading bytes
109        #[cfg(feature = "sgi")]
110        {
111            let hook: for<'a> fn(
112                image::hooks::GenericReader<'a>,
113            )
114                -> image::ImageResult<Box<dyn image::ImageDecoder + 'a>> =
115                |r| Ok(Box::new(sgi::SgiDecoder::new(r)?));
116            image::hooks::register_decoding_hook("bw".into(), Box::new(hook));
117            image::hooks::register_decoding_hook("rgb".into(), Box::new(hook));
118            image::hooks::register_decoding_hook("rgba".into(), Box::new(hook));
119            image::hooks::register_decoding_hook("iris".into(), Box::new(hook));
120            if image::hooks::register_decoding_hook("sgi".into(), Box::new(hook)) {
121                // The main signature bytes are technically just 01 da, but this is short
122                // and the following storage and bpc fields are constrained well enough to
123                // efficiently match them as well
124                image::hooks::register_format_detection_hook(
125                    "sgi".into(),
126                    b"\x01\xda\x00\x01",
127                    None,
128                );
129                image::hooks::register_format_detection_hook(
130                    "sgi".into(),
131                    b"\x01\xda\x01\x01",
132                    None,
133                );
134                image::hooks::register_format_detection_hook(
135                    "sgi".into(),
136                    b"\x01\xda\x00\x02",
137                    None,
138                );
139                image::hooks::register_format_detection_hook(
140                    "sgi".into(),
141                    b"\x01\xda\x01\x02",
142                    None,
143                );
144            }
145        }
146
147        #[cfg(feature = "wbmp")]
148        image::hooks::register_decoding_hook(
149            "wbmp".into(),
150            Box::new(|r| Ok(Box::new(wbmp::WbmpDecoder::new(r)?))),
151        );
152
153        #[cfg(feature = "xbm")]
154        {
155            register_decoding_hook(
156                "xbm".into(),
157                Box::new(|r| Ok(Box::new(xbm::XbmDecoder::new(r)?))),
158            );
159            register_decoding_hook(
160                "bm".into(),
161                Box::new(|r| Ok(Box::new(xbm::XbmDecoder::new(r)?))),
162            );
163        }
164
165        #[cfg(feature = "xpm")]
166        if register_decoding_hook(
167            "xpm".into(),
168            Box::new(|r| Ok(Box::new(xpm::XpmDecoder::new(r)?))),
169        ) {
170            register_format_detection_hook("xpm".into(), b"/* XPM */", None);
171        }
172    });
173}