1#[cfg(feature = "std")]
2use std::ffi::OsStr;
3#[cfg(feature = "std")]
4use std::path::Path;
5
6#[cfg(feature = "std")]
7use crate::error::{ImageError, ImageFormatHint, ImageResult};
8
9#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
12#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
13#[non_exhaustive]
14pub enum ImageFormat {
15 Png,
17
18 Jpeg,
20
21 Gif,
23
24 WebP,
26
27 Pnm,
29
30 Tiff,
32
33 Tga,
35
36 Dds,
38
39 Bmp,
41
42 Ico,
44
45 Hdr,
47
48 OpenExr,
50
51 Farbfeld,
53
54 Avif,
56
57 Qoi,
59
60 #[cfg_attr(not(feature = "serde"), deprecated)]
62 #[doc(hidden)]
63 Pcx,
64}
65
66impl ImageFormat {
67 #[cfg(feature = "std")]
78 #[inline]
79 pub fn from_extension<S>(ext: S) -> Option<Self>
80 where
81 S: AsRef<OsStr>,
82 {
83 fn inner(ext: &OsStr) -> Option<ImageFormat> {
85 let ext = ext.to_str()?.to_ascii_lowercase();
86 ImageFormat::from_extension_str(&ext)
87 }
88
89 inner(ext.as_ref())
90 }
91
92 pub fn from_extension_str(ext: &str) -> Option<Self> {
94 Some(match ext {
96 "avif" => ImageFormat::Avif,
97 "jpg" | "jpeg" | "jfif" => ImageFormat::Jpeg,
98 "png" | "apng" => ImageFormat::Png,
99 "gif" => ImageFormat::Gif,
100 "webp" => ImageFormat::WebP,
101 "tif" | "tiff" => ImageFormat::Tiff,
102 "tga" => ImageFormat::Tga,
103 "dds" => ImageFormat::Dds,
104 "bmp" => ImageFormat::Bmp,
105 "ico" => ImageFormat::Ico,
106 "hdr" => ImageFormat::Hdr,
107 "exr" => ImageFormat::OpenExr,
108 "pbm" | "pam" | "ppm" | "pgm" | "pnm" => ImageFormat::Pnm,
109 "ff" => ImageFormat::Farbfeld,
110 "qoi" => ImageFormat::Qoi,
111 _ => return None,
112 })
113 }
114
115 #[cfg(feature = "std")]
128 #[inline]
129 pub fn from_path<P>(path: P) -> ImageResult<Self>
130 where
131 P: AsRef<Path>,
132 {
133 fn inner(path: &Path) -> ImageResult<ImageFormat> {
135 let exact_ext = path.extension();
136 exact_ext
137 .and_then(ImageFormat::from_extension)
138 .ok_or_else(|| {
139 let format_hint = match exact_ext {
140 None => ImageFormatHint::Unknown,
141 Some(os) => ImageFormatHint::PathExtension(os.into()),
142 };
143 ImageError::Unsupported(format_hint.into())
144 })
145 }
146
147 inner(path.as_ref())
148 }
149
150 pub fn from_mime_type<M>(mime_type: M) -> Option<Self>
161 where
162 M: AsRef<str>,
163 {
164 match mime_type.as_ref() {
165 "image/avif" => Some(ImageFormat::Avif),
166 "image/jpeg" => Some(ImageFormat::Jpeg),
167 "image/png" => Some(ImageFormat::Png),
168 "image/gif" => Some(ImageFormat::Gif),
169 "image/webp" => Some(ImageFormat::WebP),
170 "image/tiff" => Some(ImageFormat::Tiff),
171 "image/x-targa" | "image/x-tga" => Some(ImageFormat::Tga),
172 "image/vnd-ms.dds" => Some(ImageFormat::Dds),
173 "image/bmp" => Some(ImageFormat::Bmp),
174 "image/x-icon" | "image/vnd.microsoft.icon" => Some(ImageFormat::Ico),
175 "image/vnd.radiance" => Some(ImageFormat::Hdr),
176 "image/x-exr" => Some(ImageFormat::OpenExr),
177 "image/x-portable-bitmap"
178 | "image/x-portable-graymap"
179 | "image/x-portable-pixmap"
180 | "image/x-portable-anymap" => Some(ImageFormat::Pnm),
181 "image/x-qoi" => Some(ImageFormat::Qoi),
184 _ => None,
185 }
186 }
187
188 #[must_use]
209 pub fn to_mime_type(&self) -> &'static str {
210 match self {
211 ImageFormat::Avif => "image/avif",
212 ImageFormat::Jpeg => "image/jpeg",
213 ImageFormat::Png => "image/png",
214 ImageFormat::Gif => "image/gif",
215 ImageFormat::WebP => "image/webp",
216 ImageFormat::Tiff => "image/tiff",
217 ImageFormat::Tga => "image/x-targa",
219 ImageFormat::Dds => "image/vnd-ms.dds",
220 ImageFormat::Bmp => "image/bmp",
221 ImageFormat::Ico => "image/x-icon",
222 ImageFormat::Hdr => "image/vnd.radiance",
223 ImageFormat::OpenExr => "image/x-exr",
224 ImageFormat::Pnm => "image/x-portable-anymap",
226 ImageFormat::Qoi => "image/x-qoi",
229 ImageFormat::Farbfeld => "application/octet-stream",
231 #[allow(deprecated)]
232 ImageFormat::Pcx => "image/vnd.zbrush.pcx",
233 }
234 }
235
236 #[inline]
238 #[must_use]
239 pub fn can_read(&self) -> bool {
240 match self {
242 ImageFormat::Png => true,
243 ImageFormat::Gif => true,
244 ImageFormat::Jpeg => true,
245 ImageFormat::WebP => true,
246 ImageFormat::Tiff => true,
247 ImageFormat::Tga => true,
248 ImageFormat::Dds => false,
249 ImageFormat::Bmp => true,
250 ImageFormat::Ico => true,
251 ImageFormat::Hdr => true,
252 ImageFormat::OpenExr => true,
253 ImageFormat::Pnm => true,
254 ImageFormat::Farbfeld => true,
255 ImageFormat::Avif => true,
256 ImageFormat::Qoi => true,
257 #[allow(deprecated)]
258 ImageFormat::Pcx => false,
259 }
260 }
261
262 #[inline]
264 #[must_use]
265 pub fn can_write(&self) -> bool {
266 match self {
268 ImageFormat::Gif => true,
269 ImageFormat::Ico => true,
270 ImageFormat::Jpeg => true,
271 ImageFormat::Png => true,
272 ImageFormat::Bmp => true,
273 ImageFormat::Tiff => true,
274 ImageFormat::Tga => true,
275 ImageFormat::Pnm => true,
276 ImageFormat::Farbfeld => true,
277 ImageFormat::Avif => true,
278 ImageFormat::WebP => true,
279 ImageFormat::Hdr => true,
280 ImageFormat::OpenExr => true,
281 ImageFormat::Dds => false,
282 ImageFormat::Qoi => true,
283 #[allow(deprecated)]
284 ImageFormat::Pcx => false,
285 }
286 }
287
288 #[must_use]
298 pub fn extensions_str(self) -> &'static [&'static str] {
299 match self {
301 ImageFormat::Png => &["png"],
302 ImageFormat::Jpeg => &["jpg", "jpeg"],
303 ImageFormat::Gif => &["gif"],
304 ImageFormat::WebP => &["webp"],
305 ImageFormat::Pnm => &["pbm", "pam", "ppm", "pgm", "pnm"],
306 ImageFormat::Tiff => &["tiff", "tif"],
307 ImageFormat::Tga => &["tga"],
308 ImageFormat::Dds => &["dds"],
309 ImageFormat::Bmp => &["bmp"],
310 ImageFormat::Ico => &["ico"],
311 ImageFormat::Hdr => &["hdr"],
312 ImageFormat::OpenExr => &["exr"],
313 ImageFormat::Farbfeld => &["ff"],
314 ImageFormat::Avif => &["avif"],
316 ImageFormat::Qoi => &["qoi"],
317 #[allow(deprecated)]
318 ImageFormat::Pcx => &["pcx"],
319 }
320 }
321
322 #[inline]
324 #[must_use]
325 pub fn reading_enabled(&self) -> bool {
326 match self {
327 ImageFormat::Png => cfg!(feature = "png"),
328 ImageFormat::Gif => cfg!(feature = "gif"),
329 ImageFormat::Jpeg => cfg!(feature = "jpeg"),
330 ImageFormat::WebP => cfg!(feature = "webp"),
331 ImageFormat::Tiff => cfg!(feature = "tiff"),
332 ImageFormat::Tga => cfg!(feature = "tga"),
333 ImageFormat::Bmp => cfg!(feature = "bmp"),
334 ImageFormat::Ico => cfg!(feature = "ico"),
335 ImageFormat::Hdr => cfg!(feature = "hdr"),
336 ImageFormat::OpenExr => cfg!(feature = "exr"),
337 ImageFormat::Pnm => cfg!(feature = "pnm"),
338 ImageFormat::Farbfeld => cfg!(feature = "ff"),
339 ImageFormat::Avif => cfg!(feature = "avif"),
340 ImageFormat::Qoi => cfg!(feature = "qoi"),
341 #[allow(deprecated)]
342 ImageFormat::Pcx => false,
343 ImageFormat::Dds => false,
344 }
345 }
346
347 #[inline]
349 #[must_use]
350 pub fn writing_enabled(&self) -> bool {
351 match self {
352 ImageFormat::Gif => cfg!(feature = "gif"),
353 ImageFormat::Ico => cfg!(feature = "ico"),
354 ImageFormat::Jpeg => cfg!(feature = "jpeg"),
355 ImageFormat::Png => cfg!(feature = "png"),
356 ImageFormat::Bmp => cfg!(feature = "bmp"),
357 ImageFormat::Tiff => cfg!(feature = "tiff"),
358 ImageFormat::Tga => cfg!(feature = "tga"),
359 ImageFormat::Pnm => cfg!(feature = "pnm"),
360 ImageFormat::Farbfeld => cfg!(feature = "ff"),
361 ImageFormat::Avif => cfg!(feature = "avif"),
362 ImageFormat::WebP => cfg!(feature = "webp"),
363 ImageFormat::OpenExr => cfg!(feature = "exr"),
364 ImageFormat::Qoi => cfg!(feature = "qoi"),
365 ImageFormat::Hdr => cfg!(feature = "hdr"),
366 #[allow(deprecated)]
367 ImageFormat::Pcx => false,
368 ImageFormat::Dds => false,
369 }
370 }
371
372 pub fn all() -> impl Iterator<Item = ImageFormat> {
374 [
375 ImageFormat::Gif,
376 ImageFormat::Ico,
377 ImageFormat::Jpeg,
378 ImageFormat::Png,
379 ImageFormat::Bmp,
380 ImageFormat::Tiff,
381 ImageFormat::Tga,
382 ImageFormat::Pnm,
383 ImageFormat::Farbfeld,
384 ImageFormat::Avif,
385 ImageFormat::WebP,
386 ImageFormat::OpenExr,
387 ImageFormat::Qoi,
388 ImageFormat::Dds,
389 ImageFormat::Hdr,
390 #[allow(deprecated)]
391 ImageFormat::Pcx,
392 ]
393 .iter()
394 .copied()
395 }
396}
397
398#[cfg(test)]
399mod tests {
400 use std::collections::HashSet;
401 use std::path::Path;
402
403 use super::{ImageFormat, ImageResult};
404
405 #[test]
406 fn test_image_format_from_path() {
407 fn from_path(s: &str) -> ImageResult<ImageFormat> {
408 ImageFormat::from_path(Path::new(s))
409 }
410 assert_eq!(from_path("./a.jpg").unwrap(), ImageFormat::Jpeg);
411 assert_eq!(from_path("./a.jpeg").unwrap(), ImageFormat::Jpeg);
412 assert_eq!(from_path("./a.JPEG").unwrap(), ImageFormat::Jpeg);
413 assert_eq!(from_path("./a.pNg").unwrap(), ImageFormat::Png);
414 assert_eq!(from_path("./a.gif").unwrap(), ImageFormat::Gif);
415 assert_eq!(from_path("./a.webp").unwrap(), ImageFormat::WebP);
416 assert_eq!(from_path("./a.tiFF").unwrap(), ImageFormat::Tiff);
417 assert_eq!(from_path("./a.tif").unwrap(), ImageFormat::Tiff);
418 assert_eq!(from_path("./a.tga").unwrap(), ImageFormat::Tga);
419 assert_eq!(from_path("./a.dds").unwrap(), ImageFormat::Dds);
420 assert_eq!(from_path("./a.bmp").unwrap(), ImageFormat::Bmp);
421 assert_eq!(from_path("./a.Ico").unwrap(), ImageFormat::Ico);
422 assert_eq!(from_path("./a.hdr").unwrap(), ImageFormat::Hdr);
423 assert_eq!(from_path("./a.exr").unwrap(), ImageFormat::OpenExr);
424 assert_eq!(from_path("./a.pbm").unwrap(), ImageFormat::Pnm);
425 assert_eq!(from_path("./a.pAM").unwrap(), ImageFormat::Pnm);
426 assert_eq!(from_path("./a.Ppm").unwrap(), ImageFormat::Pnm);
427 assert_eq!(from_path("./a.pgm").unwrap(), ImageFormat::Pnm);
428 assert_eq!(from_path("./a.AViF").unwrap(), ImageFormat::Avif);
429 assert!(from_path("./a.txt").is_err());
430 assert!(from_path("./a").is_err());
431 }
432
433 #[test]
434 fn image_formats_are_recognized() {
435 use ImageFormat::*;
436 const ALL_FORMATS: &[ImageFormat] = &[
437 Avif, Png, Jpeg, Gif, WebP, Pnm, Tiff, Tga, Dds, Bmp, Ico, Hdr, Farbfeld, OpenExr,
438 ];
439 for &format in ALL_FORMATS {
440 let mut file = Path::new("file.nothing").to_owned();
441 for ext in format.extensions_str() {
442 assert!(file.set_extension(ext));
443 match ImageFormat::from_path(&file) {
444 Err(_) => panic!("Path {} not recognized as {:?}", file.display(), format),
445 Ok(result) => assert_eq!(format, result),
446 }
447 }
448 }
449 }
450
451 #[test]
452 fn all() {
453 let all_formats: HashSet<ImageFormat> = ImageFormat::all().collect();
454 assert!(all_formats.contains(&ImageFormat::Avif));
455 assert!(all_formats.contains(&ImageFormat::Gif));
456 assert!(all_formats.contains(&ImageFormat::Bmp));
457 assert!(all_formats.contains(&ImageFormat::Farbfeld));
458 assert!(all_formats.contains(&ImageFormat::Jpeg));
459 }
460
461 #[test]
462 fn reading_enabled() {
463 assert_eq!(cfg!(feature = "jpeg"), ImageFormat::Jpeg.reading_enabled());
464 assert_eq!(
465 cfg!(feature = "ff"),
466 ImageFormat::Farbfeld.reading_enabled()
467 );
468 assert!(!ImageFormat::Dds.reading_enabled());
469 }
470
471 #[test]
472 fn writing_enabled() {
473 assert_eq!(cfg!(feature = "jpeg"), ImageFormat::Jpeg.writing_enabled());
474 assert_eq!(
475 cfg!(feature = "ff"),
476 ImageFormat::Farbfeld.writing_enabled()
477 );
478 assert!(!ImageFormat::Dds.writing_enabled());
479 }
480}