Coverage Report

Created: 2026-01-19 07:25

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/image/src/io/format.rs
Line
Count
Source
1
use std::ffi::OsStr;
2
use std::path::Path;
3
4
use 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]
11
pub 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
63
impl 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
0
    pub fn from_extension<S>(ext: S) -> Option<Self>
76
0
    where
77
0
        S: AsRef<OsStr>,
78
    {
79
        // thin wrapper function to strip generics
80
0
        fn inner(ext: &OsStr) -> Option<ImageFormat> {
81
0
            let ext = ext.to_str()?.to_ascii_lowercase();
82
            // NOTE: when updating this, also update extensions_str()
83
0
            Some(match ext.as_str() {
84
0
                "avif" => ImageFormat::Avif,
85
0
                "jpg" | "jpeg" | "jfif" => ImageFormat::Jpeg,
86
0
                "png" | "apng" => ImageFormat::Png,
87
0
                "gif" => ImageFormat::Gif,
88
0
                "webp" => ImageFormat::WebP,
89
0
                "tif" | "tiff" => ImageFormat::Tiff,
90
0
                "tga" => ImageFormat::Tga,
91
0
                "dds" => ImageFormat::Dds,
92
0
                "bmp" => ImageFormat::Bmp,
93
0
                "ico" => ImageFormat::Ico,
94
0
                "hdr" => ImageFormat::Hdr,
95
0
                "exr" => ImageFormat::OpenExr,
96
0
                "pbm" | "pam" | "ppm" | "pgm" | "pnm" => ImageFormat::Pnm,
97
0
                "ff" => ImageFormat::Farbfeld,
98
0
                "qoi" => ImageFormat::Qoi,
99
0
                _ => return None,
100
            })
101
0
        }
102
103
0
        inner(ext.as_ref())
104
0
    }
Unexecuted instantiation: <image::io::format::ImageFormat>::from_extension::<&std::ffi::os_str::OsStr>
Unexecuted instantiation: <image::io::format::ImageFormat>::from_extension::<&std::ffi::os_str::OsString>
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
0
    pub fn from_path<P>(path: P) -> ImageResult<Self>
120
0
    where
121
0
        P: AsRef<Path>,
122
    {
123
        // thin wrapper function to strip generics
124
0
        fn inner(path: &Path) -> ImageResult<ImageFormat> {
125
0
            let exact_ext = path.extension();
126
0
            exact_ext
127
0
                .and_then(ImageFormat::from_extension)
128
0
                .ok_or_else(|| {
129
0
                    let format_hint = match exact_ext {
130
0
                        None => ImageFormatHint::Unknown,
131
0
                        Some(os) => ImageFormatHint::PathExtension(os.into()),
132
                    };
133
0
                    ImageError::Unsupported(format_hint.into())
134
0
                })
135
0
        }
136
137
0
        inner(path.as_ref())
138
0
    }
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
0
    pub fn from_mime_type<M>(mime_type: M) -> Option<Self>
151
0
    where
152
0
        M: AsRef<str>,
153
    {
154
0
        match mime_type.as_ref() {
155
0
            "image/avif" => Some(ImageFormat::Avif),
156
0
            "image/jpeg" => Some(ImageFormat::Jpeg),
157
0
            "image/png" => Some(ImageFormat::Png),
158
0
            "image/gif" => Some(ImageFormat::Gif),
159
0
            "image/webp" => Some(ImageFormat::WebP),
160
0
            "image/tiff" => Some(ImageFormat::Tiff),
161
0
            "image/x-targa" | "image/x-tga" => Some(ImageFormat::Tga),
162
0
            "image/vnd-ms.dds" => Some(ImageFormat::Dds),
163
0
            "image/bmp" => Some(ImageFormat::Bmp),
164
0
            "image/x-icon" | "image/vnd.microsoft.icon" => Some(ImageFormat::Ico),
165
0
            "image/vnd.radiance" => Some(ImageFormat::Hdr),
166
0
            "image/x-exr" => Some(ImageFormat::OpenExr),
167
0
            "image/x-portable-bitmap"
168
0
            | "image/x-portable-graymap"
169
0
            | "image/x-portable-pixmap"
170
0
            | "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
0
            "image/x-qoi" => Some(ImageFormat::Qoi),
174
0
            _ => None,
175
        }
176
0
    }
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
0
    pub fn to_mime_type(&self) -> &'static str {
200
0
        match self {
201
0
            ImageFormat::Avif => "image/avif",
202
0
            ImageFormat::Jpeg => "image/jpeg",
203
0
            ImageFormat::Png => "image/png",
204
0
            ImageFormat::Gif => "image/gif",
205
0
            ImageFormat::WebP => "image/webp",
206
0
            ImageFormat::Tiff => "image/tiff",
207
            // the targa MIME type has two options, but this one seems to be used more
208
0
            ImageFormat::Tga => "image/x-targa",
209
0
            ImageFormat::Dds => "image/vnd-ms.dds",
210
0
            ImageFormat::Bmp => "image/bmp",
211
0
            ImageFormat::Ico => "image/x-icon",
212
0
            ImageFormat::Hdr => "image/vnd.radiance",
213
0
            ImageFormat::OpenExr => "image/x-exr",
214
            // return the most general MIME type
215
0
            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
0
            ImageFormat::Qoi => "image/x-qoi",
219
            // farbfeld's MIME type taken from https://www.wikidata.org/wiki/Q28206109
220
0
            ImageFormat::Farbfeld => "application/octet-stream",
221
            #[allow(deprecated)]
222
0
            ImageFormat::Pcx => "image/vnd.zbrush.pcx",
223
        }
224
0
    }
225
226
    /// Return if the `ImageFormat` can be decoded by the lib.
227
    #[inline]
228
    #[must_use]
229
0
    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
0
        match self {
232
0
            ImageFormat::Png => true,
233
0
            ImageFormat::Gif => true,
234
0
            ImageFormat::Jpeg => true,
235
0
            ImageFormat::WebP => true,
236
0
            ImageFormat::Tiff => true,
237
0
            ImageFormat::Tga => true,
238
0
            ImageFormat::Dds => false,
239
0
            ImageFormat::Bmp => true,
240
0
            ImageFormat::Ico => true,
241
0
            ImageFormat::Hdr => true,
242
0
            ImageFormat::OpenExr => true,
243
0
            ImageFormat::Pnm => true,
244
0
            ImageFormat::Farbfeld => true,
245
0
            ImageFormat::Avif => true,
246
0
            ImageFormat::Qoi => true,
247
            #[allow(deprecated)]
248
0
            ImageFormat::Pcx => false,
249
        }
250
0
    }
251
252
    /// Return if the `ImageFormat` can be encoded by the lib.
253
    #[inline]
254
    #[must_use]
255
0
    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
0
        match self {
258
0
            ImageFormat::Gif => true,
259
0
            ImageFormat::Ico => true,
260
0
            ImageFormat::Jpeg => true,
261
0
            ImageFormat::Png => true,
262
0
            ImageFormat::Bmp => true,
263
0
            ImageFormat::Tiff => true,
264
0
            ImageFormat::Tga => true,
265
0
            ImageFormat::Pnm => true,
266
0
            ImageFormat::Farbfeld => true,
267
0
            ImageFormat::Avif => true,
268
0
            ImageFormat::WebP => true,
269
0
            ImageFormat::Hdr => true,
270
0
            ImageFormat::OpenExr => true,
271
0
            ImageFormat::Dds => false,
272
0
            ImageFormat::Qoi => true,
273
            #[allow(deprecated)]
274
0
            ImageFormat::Pcx => false,
275
        }
276
0
    }
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
0
    pub fn extensions_str(self) -> &'static [&'static str] {
289
        // NOTE: when updating this, also update from_extension()
290
0
        match self {
291
0
            ImageFormat::Png => &["png"],
292
0
            ImageFormat::Jpeg => &["jpg", "jpeg"],
293
0
            ImageFormat::Gif => &["gif"],
294
0
            ImageFormat::WebP => &["webp"],
295
0
            ImageFormat::Pnm => &["pbm", "pam", "ppm", "pgm", "pnm"],
296
0
            ImageFormat::Tiff => &["tiff", "tif"],
297
0
            ImageFormat::Tga => &["tga"],
298
0
            ImageFormat::Dds => &["dds"],
299
0
            ImageFormat::Bmp => &["bmp"],
300
0
            ImageFormat::Ico => &["ico"],
301
0
            ImageFormat::Hdr => &["hdr"],
302
0
            ImageFormat::OpenExr => &["exr"],
303
0
            ImageFormat::Farbfeld => &["ff"],
304
            // According to: https://aomediacodec.github.io/av1-avif/#mime-registration
305
0
            ImageFormat::Avif => &["avif"],
306
0
            ImageFormat::Qoi => &["qoi"],
307
            #[allow(deprecated)]
308
0
            ImageFormat::Pcx => &["pcx"],
309
        }
310
0
    }
311
312
    /// Return the `ImageFormat`s which are enabled for reading.
313
    #[inline]
314
    #[must_use]
315
0
    pub fn reading_enabled(&self) -> bool {
316
0
        match self {
317
0
            ImageFormat::Png => cfg!(feature = "png"),
318
0
            ImageFormat::Gif => cfg!(feature = "gif"),
319
0
            ImageFormat::Jpeg => cfg!(feature = "jpeg"),
320
0
            ImageFormat::WebP => cfg!(feature = "webp"),
321
0
            ImageFormat::Tiff => cfg!(feature = "tiff"),
322
0
            ImageFormat::Tga => cfg!(feature = "tga"),
323
0
            ImageFormat::Bmp => cfg!(feature = "bmp"),
324
0
            ImageFormat::Ico => cfg!(feature = "ico"),
325
0
            ImageFormat::Hdr => cfg!(feature = "hdr"),
326
0
            ImageFormat::OpenExr => cfg!(feature = "exr"),
327
0
            ImageFormat::Pnm => cfg!(feature = "pnm"),
328
0
            ImageFormat::Farbfeld => cfg!(feature = "ff"),
329
0
            ImageFormat::Avif => cfg!(feature = "avif"),
330
0
            ImageFormat::Qoi => cfg!(feature = "qoi"),
331
            #[allow(deprecated)]
332
0
            ImageFormat::Pcx => false,
333
0
            ImageFormat::Dds => false,
334
        }
335
0
    }
336
337
    /// Return the `ImageFormat`s which are enabled for writing.
338
    #[inline]
339
    #[must_use]
340
0
    pub fn writing_enabled(&self) -> bool {
341
0
        match self {
342
0
            ImageFormat::Gif => cfg!(feature = "gif"),
343
0
            ImageFormat::Ico => cfg!(feature = "ico"),
344
0
            ImageFormat::Jpeg => cfg!(feature = "jpeg"),
345
0
            ImageFormat::Png => cfg!(feature = "png"),
346
0
            ImageFormat::Bmp => cfg!(feature = "bmp"),
347
0
            ImageFormat::Tiff => cfg!(feature = "tiff"),
348
0
            ImageFormat::Tga => cfg!(feature = "tga"),
349
0
            ImageFormat::Pnm => cfg!(feature = "pnm"),
350
0
            ImageFormat::Farbfeld => cfg!(feature = "ff"),
351
0
            ImageFormat::Avif => cfg!(feature = "avif"),
352
0
            ImageFormat::WebP => cfg!(feature = "webp"),
353
0
            ImageFormat::OpenExr => cfg!(feature = "exr"),
354
0
            ImageFormat::Qoi => cfg!(feature = "qoi"),
355
0
            ImageFormat::Hdr => cfg!(feature = "hdr"),
356
            #[allow(deprecated)]
357
0
            ImageFormat::Pcx => false,
358
0
            ImageFormat::Dds => false,
359
        }
360
0
    }
361
362
    /// Return all `ImageFormat`s
363
0
    pub fn all() -> impl Iterator<Item = ImageFormat> {
364
0
        [
365
0
            ImageFormat::Gif,
366
0
            ImageFormat::Ico,
367
0
            ImageFormat::Jpeg,
368
0
            ImageFormat::Png,
369
0
            ImageFormat::Bmp,
370
0
            ImageFormat::Tiff,
371
0
            ImageFormat::Tga,
372
0
            ImageFormat::Pnm,
373
0
            ImageFormat::Farbfeld,
374
0
            ImageFormat::Avif,
375
0
            ImageFormat::WebP,
376
0
            ImageFormat::OpenExr,
377
0
            ImageFormat::Qoi,
378
0
            ImageFormat::Dds,
379
0
            ImageFormat::Hdr,
380
0
            #[allow(deprecated)]
381
0
            ImageFormat::Pcx,
382
0
        ]
383
0
        .iter()
384
0
        .copied()
385
0
    }
386
}
387
388
#[cfg(test)]
389
mod 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
}