1use std::ffi::OsStr;
2use std::path::Path;
3
4use crate::error::{ImageError, ImageFormatHint, ImageResult};
5
6#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
9#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
10#[non_exhaustive]
11pub enum ImageFormat {
12 Png,
14
15 Jpeg,
17
18 Gif,
20
21 WebP,
23
24 Pnm,
26
27 Tiff,
29
30 Tga,
32
33 Dds,
35
36 Bmp,
38
39 Ico,
41
42 Hdr,
44
45 OpenExr,
47
48 Farbfeld,
50
51 Avif,
53
54 Qoi,
56
57 #[cfg_attr(not(feature = "serde"), deprecated)]
59 #[doc(hidden)]
60 Pcx,
61}
62
63impl ImageFormat {
64 #[inline]
75 pub fn from_extension<S>(ext: S) -> Option<Self>
76 where
77 S: AsRef<OsStr>,
78 {
79 fn inner(ext: &OsStr) -> Option<ImageFormat> {
81 let ext = ext.to_str()?.to_ascii_lowercase();
82 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 #[inline]
119 pub fn from_path<P>(path: P) -> ImageResult<Self>
120 where
121 P: AsRef<Path>,
122 {
123 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 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 "image/x-qoi" => Some(ImageFormat::Qoi),
174 _ => None,
175 }
176 }
177
178 #[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 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 ImageFormat::Pnm => "image/x-portable-anymap",
216 ImageFormat::Qoi => "image/x-qoi",
219 ImageFormat::Farbfeld => "application/octet-stream",
221 #[allow(deprecated)]
222 ImageFormat::Pcx => "image/vnd.zbrush.pcx",
223 }
224 }
225
226 #[inline]
228 #[must_use]
229 pub fn can_read(&self) -> bool {
230 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 #[inline]
254 #[must_use]
255 pub fn can_write(&self) -> bool {
256 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 #[must_use]
288 pub fn extensions_str(self) -> &'static [&'static str] {
289 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 ImageFormat::Avif => &["avif"],
306 ImageFormat::Qoi => &["qoi"],
307 #[allow(deprecated)]
308 ImageFormat::Pcx => &["pcx"],
309 }
310 }
311
312 #[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 #[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 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}