image/io/
format.rs

1use std::ffi::OsStr;
2use std::path::Path;
3
4use crate::error::{ImageError, ImageFormatHint, ImageResult};
5
6/// An enumeration of supported image formats.
7/// Not all formats support both encoding and decoding.
8#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
9#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
10#[non_exhaustive]
11pub enum ImageFormat {
12    /// An Image in PNG Format
13    Png,
14
15    /// An Image in JPEG Format
16    Jpeg,
17
18    /// An Image in GIF Format
19    Gif,
20
21    /// An Image in WEBP Format
22    WebP,
23
24    /// An Image in general PNM Format
25    Pnm,
26
27    /// An Image in TIFF Format
28    Tiff,
29
30    /// An Image in TGA Format
31    Tga,
32
33    /// An Image in DDS Format
34    Dds,
35
36    /// An Image in BMP Format
37    Bmp,
38
39    /// An Image in ICO Format
40    Ico,
41
42    /// An Image in Radiance HDR Format
43    Hdr,
44
45    /// An Image in OpenEXR Format
46    OpenExr,
47
48    /// An Image in farbfeld Format
49    Farbfeld,
50
51    /// An Image in AVIF Format
52    Avif,
53
54    /// An Image in QOI Format
55    Qoi,
56
57    /// An Image in PCX Format
58    #[cfg_attr(not(feature = "serde"), deprecated)]
59    #[doc(hidden)]
60    Pcx,
61}
62
63impl ImageFormat {
64    /// Return the image format specified by a path's file extension.
65    ///
66    /// # Example
67    ///
68    /// ```
69    /// use image::ImageFormat;
70    ///
71    /// let format = ImageFormat::from_extension("jpg");
72    /// assert_eq!(format, Some(ImageFormat::Jpeg));
73    /// ```
74    #[inline]
75    pub fn from_extension<S>(ext: S) -> Option<Self>
76    where
77        S: AsRef<OsStr>,
78    {
79        // thin wrapper function to strip generics
80        fn inner(ext: &OsStr) -> Option<ImageFormat> {
81            let ext = ext.to_str()?.to_ascii_lowercase();
82            // NOTE: when updating this, also update extensions_str()
83            Some(match ext.as_str() {
84                "avif" => ImageFormat::Avif,
85                "jpg" | "jpeg" | "jfif" => ImageFormat::Jpeg,
86                "png" | "apng" => ImageFormat::Png,
87                "gif" => ImageFormat::Gif,
88                "webp" => ImageFormat::WebP,
89                "tif" | "tiff" => ImageFormat::Tiff,
90                "tga" => ImageFormat::Tga,
91                "dds" => ImageFormat::Dds,
92                "bmp" => ImageFormat::Bmp,
93                "ico" => ImageFormat::Ico,
94                "hdr" => ImageFormat::Hdr,
95                "exr" => ImageFormat::OpenExr,
96                "pbm" | "pam" | "ppm" | "pgm" | "pnm" => ImageFormat::Pnm,
97                "ff" => ImageFormat::Farbfeld,
98                "qoi" => ImageFormat::Qoi,
99                _ => return None,
100            })
101        }
102
103        inner(ext.as_ref())
104    }
105
106    /// Return the image format specified by the path's file extension.
107    ///
108    /// # Example
109    ///
110    /// ```
111    /// use image::ImageFormat;
112    ///
113    /// let format = ImageFormat::from_path("images/ferris.png")?;
114    /// assert_eq!(format, ImageFormat::Png);
115    ///
116    /// # Ok::<(), image::error::ImageError>(())
117    /// ```
118    #[inline]
119    pub fn from_path<P>(path: P) -> ImageResult<Self>
120    where
121        P: AsRef<Path>,
122    {
123        // thin wrapper function to strip generics
124        fn inner(path: &Path) -> ImageResult<ImageFormat> {
125            let exact_ext = path.extension();
126            exact_ext
127                .and_then(ImageFormat::from_extension)
128                .ok_or_else(|| {
129                    let format_hint = match exact_ext {
130                        None => ImageFormatHint::Unknown,
131                        Some(os) => ImageFormatHint::PathExtension(os.into()),
132                    };
133                    ImageError::Unsupported(format_hint.into())
134                })
135        }
136
137        inner(path.as_ref())
138    }
139
140    /// Return the image format specified by a MIME type.
141    ///
142    /// # Example
143    ///
144    /// ```
145    /// use image::ImageFormat;
146    ///
147    /// let format = ImageFormat::from_mime_type("image/png").unwrap();
148    /// assert_eq!(format, ImageFormat::Png);
149    /// ```
150    pub fn from_mime_type<M>(mime_type: M) -> Option<Self>
151    where
152        M: AsRef<str>,
153    {
154        match mime_type.as_ref() {
155            "image/avif" => Some(ImageFormat::Avif),
156            "image/jpeg" => Some(ImageFormat::Jpeg),
157            "image/png" => Some(ImageFormat::Png),
158            "image/gif" => Some(ImageFormat::Gif),
159            "image/webp" => Some(ImageFormat::WebP),
160            "image/tiff" => Some(ImageFormat::Tiff),
161            "image/x-targa" | "image/x-tga" => Some(ImageFormat::Tga),
162            "image/vnd-ms.dds" => Some(ImageFormat::Dds),
163            "image/bmp" => Some(ImageFormat::Bmp),
164            "image/x-icon" | "image/vnd.microsoft.icon" => Some(ImageFormat::Ico),
165            "image/vnd.radiance" => Some(ImageFormat::Hdr),
166            "image/x-exr" => Some(ImageFormat::OpenExr),
167            "image/x-portable-bitmap"
168            | "image/x-portable-graymap"
169            | "image/x-portable-pixmap"
170            | "image/x-portable-anymap" => Some(ImageFormat::Pnm),
171            // Qoi's MIME type is being worked on.
172            // See: https://github.com/phoboslab/qoi/issues/167
173            "image/x-qoi" => Some(ImageFormat::Qoi),
174            _ => None,
175        }
176    }
177
178    /// Return the MIME type for this image format or "application/octet-stream" if no MIME type
179    /// exists for the format.
180    ///
181    /// Some notes on a few of the MIME types:
182    ///
183    /// - The portable anymap format has a separate MIME type for the pixmap, graymap and bitmap
184    ///   formats, but this method returns the general "image/x-portable-anymap" MIME type.
185    /// - The Targa format has two common MIME types, "image/x-targa"  and "image/x-tga"; this
186    ///   method returns "image/x-targa" for that format.
187    /// - The QOI MIME type is still a work in progress. This method returns "image/x-qoi" for
188    ///   that format.
189    ///
190    /// # Example
191    ///
192    /// ```
193    /// use image::ImageFormat;
194    ///
195    /// let mime_type = ImageFormat::Png.to_mime_type();
196    /// assert_eq!(mime_type, "image/png");
197    /// ```
198    #[must_use]
199    pub fn to_mime_type(&self) -> &'static str {
200        match self {
201            ImageFormat::Avif => "image/avif",
202            ImageFormat::Jpeg => "image/jpeg",
203            ImageFormat::Png => "image/png",
204            ImageFormat::Gif => "image/gif",
205            ImageFormat::WebP => "image/webp",
206            ImageFormat::Tiff => "image/tiff",
207            // the targa MIME type has two options, but this one seems to be used more
208            ImageFormat::Tga => "image/x-targa",
209            ImageFormat::Dds => "image/vnd-ms.dds",
210            ImageFormat::Bmp => "image/bmp",
211            ImageFormat::Ico => "image/x-icon",
212            ImageFormat::Hdr => "image/vnd.radiance",
213            ImageFormat::OpenExr => "image/x-exr",
214            // return the most general MIME type
215            ImageFormat::Pnm => "image/x-portable-anymap",
216            // Qoi's MIME type is being worked on.
217            // See: https://github.com/phoboslab/qoi/issues/167
218            ImageFormat::Qoi => "image/x-qoi",
219            // farbfeld's MIME type taken from https://www.wikidata.org/wiki/Q28206109
220            ImageFormat::Farbfeld => "application/octet-stream",
221            #[allow(deprecated)]
222            ImageFormat::Pcx => "image/vnd.zbrush.pcx",
223        }
224    }
225
226    /// Return if the `ImageFormat` can be decoded by the lib.
227    #[inline]
228    #[must_use]
229    pub fn can_read(&self) -> bool {
230        // Needs to be updated once a new variant's decoder is added to free_functions.rs::load
231        match self {
232            ImageFormat::Png => true,
233            ImageFormat::Gif => true,
234            ImageFormat::Jpeg => true,
235            ImageFormat::WebP => true,
236            ImageFormat::Tiff => true,
237            ImageFormat::Tga => true,
238            ImageFormat::Dds => false,
239            ImageFormat::Bmp => true,
240            ImageFormat::Ico => true,
241            ImageFormat::Hdr => true,
242            ImageFormat::OpenExr => true,
243            ImageFormat::Pnm => true,
244            ImageFormat::Farbfeld => true,
245            ImageFormat::Avif => true,
246            ImageFormat::Qoi => true,
247            #[allow(deprecated)]
248            ImageFormat::Pcx => false,
249        }
250    }
251
252    /// Return if the `ImageFormat` can be encoded by the lib.
253    #[inline]
254    #[must_use]
255    pub fn can_write(&self) -> bool {
256        // Needs to be updated once a new variant's encoder is added to free_functions.rs::save_buffer_with_format_impl
257        match self {
258            ImageFormat::Gif => true,
259            ImageFormat::Ico => true,
260            ImageFormat::Jpeg => true,
261            ImageFormat::Png => true,
262            ImageFormat::Bmp => true,
263            ImageFormat::Tiff => true,
264            ImageFormat::Tga => true,
265            ImageFormat::Pnm => true,
266            ImageFormat::Farbfeld => true,
267            ImageFormat::Avif => true,
268            ImageFormat::WebP => true,
269            ImageFormat::Hdr => true,
270            ImageFormat::OpenExr => true,
271            ImageFormat::Dds => false,
272            ImageFormat::Qoi => true,
273            #[allow(deprecated)]
274            ImageFormat::Pcx => false,
275        }
276    }
277
278    /// Return a list of applicable extensions for this format.
279    ///
280    /// All currently recognized image formats specify at least on extension but for future
281    /// compatibility you should not rely on this fact. The list may be empty if the format has no
282    /// recognized file representation, for example in case it is used as a purely transient memory
283    /// format.
284    ///
285    /// The method name `extensions` remains reserved for introducing another method in the future
286    /// that yields a slice of `OsStr` which is blocked by several features of const evaluation.
287    #[must_use]
288    pub fn extensions_str(self) -> &'static [&'static str] {
289        // NOTE: when updating this, also update from_extension()
290        match self {
291            ImageFormat::Png => &["png"],
292            ImageFormat::Jpeg => &["jpg", "jpeg"],
293            ImageFormat::Gif => &["gif"],
294            ImageFormat::WebP => &["webp"],
295            ImageFormat::Pnm => &["pbm", "pam", "ppm", "pgm", "pnm"],
296            ImageFormat::Tiff => &["tiff", "tif"],
297            ImageFormat::Tga => &["tga"],
298            ImageFormat::Dds => &["dds"],
299            ImageFormat::Bmp => &["bmp"],
300            ImageFormat::Ico => &["ico"],
301            ImageFormat::Hdr => &["hdr"],
302            ImageFormat::OpenExr => &["exr"],
303            ImageFormat::Farbfeld => &["ff"],
304            // According to: https://aomediacodec.github.io/av1-avif/#mime-registration
305            ImageFormat::Avif => &["avif"],
306            ImageFormat::Qoi => &["qoi"],
307            #[allow(deprecated)]
308            ImageFormat::Pcx => &["pcx"],
309        }
310    }
311
312    /// Return the `ImageFormat`s which are enabled for reading.
313    #[inline]
314    #[must_use]
315    pub fn reading_enabled(&self) -> bool {
316        match self {
317            ImageFormat::Png => cfg!(feature = "png"),
318            ImageFormat::Gif => cfg!(feature = "gif"),
319            ImageFormat::Jpeg => cfg!(feature = "jpeg"),
320            ImageFormat::WebP => cfg!(feature = "webp"),
321            ImageFormat::Tiff => cfg!(feature = "tiff"),
322            ImageFormat::Tga => cfg!(feature = "tga"),
323            ImageFormat::Bmp => cfg!(feature = "bmp"),
324            ImageFormat::Ico => cfg!(feature = "ico"),
325            ImageFormat::Hdr => cfg!(feature = "hdr"),
326            ImageFormat::OpenExr => cfg!(feature = "exr"),
327            ImageFormat::Pnm => cfg!(feature = "pnm"),
328            ImageFormat::Farbfeld => cfg!(feature = "ff"),
329            ImageFormat::Avif => cfg!(feature = "avif"),
330            ImageFormat::Qoi => cfg!(feature = "qoi"),
331            #[allow(deprecated)]
332            ImageFormat::Pcx => false,
333            ImageFormat::Dds => false,
334        }
335    }
336
337    /// Return the `ImageFormat`s which are enabled for writing.
338    #[inline]
339    #[must_use]
340    pub fn writing_enabled(&self) -> bool {
341        match self {
342            ImageFormat::Gif => cfg!(feature = "gif"),
343            ImageFormat::Ico => cfg!(feature = "ico"),
344            ImageFormat::Jpeg => cfg!(feature = "jpeg"),
345            ImageFormat::Png => cfg!(feature = "png"),
346            ImageFormat::Bmp => cfg!(feature = "bmp"),
347            ImageFormat::Tiff => cfg!(feature = "tiff"),
348            ImageFormat::Tga => cfg!(feature = "tga"),
349            ImageFormat::Pnm => cfg!(feature = "pnm"),
350            ImageFormat::Farbfeld => cfg!(feature = "ff"),
351            ImageFormat::Avif => cfg!(feature = "avif"),
352            ImageFormat::WebP => cfg!(feature = "webp"),
353            ImageFormat::OpenExr => cfg!(feature = "exr"),
354            ImageFormat::Qoi => cfg!(feature = "qoi"),
355            ImageFormat::Hdr => cfg!(feature = "hdr"),
356            #[allow(deprecated)]
357            ImageFormat::Pcx => false,
358            ImageFormat::Dds => false,
359        }
360    }
361
362    /// Return all `ImageFormat`s
363    pub fn all() -> impl Iterator<Item = ImageFormat> {
364        [
365            ImageFormat::Gif,
366            ImageFormat::Ico,
367            ImageFormat::Jpeg,
368            ImageFormat::Png,
369            ImageFormat::Bmp,
370            ImageFormat::Tiff,
371            ImageFormat::Tga,
372            ImageFormat::Pnm,
373            ImageFormat::Farbfeld,
374            ImageFormat::Avif,
375            ImageFormat::WebP,
376            ImageFormat::OpenExr,
377            ImageFormat::Qoi,
378            ImageFormat::Dds,
379            ImageFormat::Hdr,
380            #[allow(deprecated)]
381            ImageFormat::Pcx,
382        ]
383        .iter()
384        .copied()
385    }
386}
387
388#[cfg(test)]
389mod tests {
390    use std::collections::HashSet;
391    use std::path::Path;
392
393    use super::{ImageFormat, ImageResult};
394
395    #[test]
396    fn test_image_format_from_path() {
397        fn from_path(s: &str) -> ImageResult<ImageFormat> {
398            ImageFormat::from_path(Path::new(s))
399        }
400        assert_eq!(from_path("./a.jpg").unwrap(), ImageFormat::Jpeg);
401        assert_eq!(from_path("./a.jpeg").unwrap(), ImageFormat::Jpeg);
402        assert_eq!(from_path("./a.JPEG").unwrap(), ImageFormat::Jpeg);
403        assert_eq!(from_path("./a.pNg").unwrap(), ImageFormat::Png);
404        assert_eq!(from_path("./a.gif").unwrap(), ImageFormat::Gif);
405        assert_eq!(from_path("./a.webp").unwrap(), ImageFormat::WebP);
406        assert_eq!(from_path("./a.tiFF").unwrap(), ImageFormat::Tiff);
407        assert_eq!(from_path("./a.tif").unwrap(), ImageFormat::Tiff);
408        assert_eq!(from_path("./a.tga").unwrap(), ImageFormat::Tga);
409        assert_eq!(from_path("./a.dds").unwrap(), ImageFormat::Dds);
410        assert_eq!(from_path("./a.bmp").unwrap(), ImageFormat::Bmp);
411        assert_eq!(from_path("./a.Ico").unwrap(), ImageFormat::Ico);
412        assert_eq!(from_path("./a.hdr").unwrap(), ImageFormat::Hdr);
413        assert_eq!(from_path("./a.exr").unwrap(), ImageFormat::OpenExr);
414        assert_eq!(from_path("./a.pbm").unwrap(), ImageFormat::Pnm);
415        assert_eq!(from_path("./a.pAM").unwrap(), ImageFormat::Pnm);
416        assert_eq!(from_path("./a.Ppm").unwrap(), ImageFormat::Pnm);
417        assert_eq!(from_path("./a.pgm").unwrap(), ImageFormat::Pnm);
418        assert_eq!(from_path("./a.AViF").unwrap(), ImageFormat::Avif);
419        assert!(from_path("./a.txt").is_err());
420        assert!(from_path("./a").is_err());
421    }
422
423    #[test]
424    fn image_formats_are_recognized() {
425        use ImageFormat::*;
426        const ALL_FORMATS: &[ImageFormat] = &[
427            Avif, Png, Jpeg, Gif, WebP, Pnm, Tiff, Tga, Dds, Bmp, Ico, Hdr, Farbfeld, OpenExr,
428        ];
429        for &format in ALL_FORMATS {
430            let mut file = Path::new("file.nothing").to_owned();
431            for ext in format.extensions_str() {
432                assert!(file.set_extension(ext));
433                match ImageFormat::from_path(&file) {
434                    Err(_) => panic!("Path {} not recognized as {:?}", file.display(), format),
435                    Ok(result) => assert_eq!(format, result),
436                }
437            }
438        }
439    }
440
441    #[test]
442    fn all() {
443        let all_formats: HashSet<ImageFormat> = ImageFormat::all().collect();
444        assert!(all_formats.contains(&ImageFormat::Avif));
445        assert!(all_formats.contains(&ImageFormat::Gif));
446        assert!(all_formats.contains(&ImageFormat::Bmp));
447        assert!(all_formats.contains(&ImageFormat::Farbfeld));
448        assert!(all_formats.contains(&ImageFormat::Jpeg));
449    }
450
451    #[test]
452    fn reading_enabled() {
453        assert_eq!(cfg!(feature = "jpeg"), ImageFormat::Jpeg.reading_enabled());
454        assert_eq!(
455            cfg!(feature = "ff"),
456            ImageFormat::Farbfeld.reading_enabled()
457        );
458        assert!(!ImageFormat::Dds.reading_enabled());
459    }
460
461    #[test]
462    fn writing_enabled() {
463        assert_eq!(cfg!(feature = "jpeg"), ImageFormat::Jpeg.writing_enabled());
464        assert_eq!(
465            cfg!(feature = "ff"),
466            ImageFormat::Farbfeld.writing_enabled()
467        );
468        assert!(!ImageFormat::Dds.writing_enabled());
469    }
470}