use std::ffi::OsStr;
use std::path::Path;
use crate::error::{ImageError, ImageFormatHint, ImageResult};
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
pub enum ImageFormat {
Png,
Jpeg,
Gif,
WebP,
Pnm,
Tiff,
Tga,
Dds,
Bmp,
Ico,
Hdr,
OpenExr,
Farbfeld,
Avif,
Qoi,
#[cfg_attr(not(feature = "serde"), deprecated)]
#[doc(hidden)]
Pcx,
}
impl ImageFormat {
#[inline]
pub fn from_extension<S>(ext: S) -> Option<Self>
where
S: AsRef<OsStr>,
{
fn inner(ext: &OsStr) -> Option<ImageFormat> {
let ext = ext.to_str()?.to_ascii_lowercase();
Some(match ext.as_str() {
"avif" => ImageFormat::Avif,
"jpg" | "jpeg" | "jfif" => ImageFormat::Jpeg,
"png" | "apng" => ImageFormat::Png,
"gif" => ImageFormat::Gif,
"webp" => ImageFormat::WebP,
"tif" | "tiff" => ImageFormat::Tiff,
"tga" => ImageFormat::Tga,
"dds" => ImageFormat::Dds,
"bmp" => ImageFormat::Bmp,
"ico" => ImageFormat::Ico,
"hdr" => ImageFormat::Hdr,
"exr" => ImageFormat::OpenExr,
"pbm" | "pam" | "ppm" | "pgm" | "pnm" => ImageFormat::Pnm,
"ff" => ImageFormat::Farbfeld,
"qoi" => ImageFormat::Qoi,
_ => return None,
})
}
inner(ext.as_ref())
}
#[inline]
pub fn from_path<P>(path: P) -> ImageResult<Self>
where
P: AsRef<Path>,
{
fn inner(path: &Path) -> ImageResult<ImageFormat> {
let exact_ext = path.extension();
exact_ext
.and_then(ImageFormat::from_extension)
.ok_or_else(|| {
let format_hint = match exact_ext {
None => ImageFormatHint::Unknown,
Some(os) => ImageFormatHint::PathExtension(os.into()),
};
ImageError::Unsupported(format_hint.into())
})
}
inner(path.as_ref())
}
pub fn from_mime_type<M>(mime_type: M) -> Option<Self>
where
M: AsRef<str>,
{
match mime_type.as_ref() {
"image/avif" => Some(ImageFormat::Avif),
"image/jpeg" => Some(ImageFormat::Jpeg),
"image/png" => Some(ImageFormat::Png),
"image/gif" => Some(ImageFormat::Gif),
"image/webp" => Some(ImageFormat::WebP),
"image/tiff" => Some(ImageFormat::Tiff),
"image/x-targa" | "image/x-tga" => Some(ImageFormat::Tga),
"image/vnd-ms.dds" => Some(ImageFormat::Dds),
"image/bmp" => Some(ImageFormat::Bmp),
"image/x-icon" | "image/vnd.microsoft.icon" => Some(ImageFormat::Ico),
"image/vnd.radiance" => Some(ImageFormat::Hdr),
"image/x-exr" => Some(ImageFormat::OpenExr),
"image/x-portable-bitmap"
| "image/x-portable-graymap"
| "image/x-portable-pixmap"
| "image/x-portable-anymap" => Some(ImageFormat::Pnm),
"image/x-qoi" => Some(ImageFormat::Qoi),
_ => None,
}
}
#[must_use]
pub fn to_mime_type(&self) -> &'static str {
match self {
ImageFormat::Avif => "image/avif",
ImageFormat::Jpeg => "image/jpeg",
ImageFormat::Png => "image/png",
ImageFormat::Gif => "image/gif",
ImageFormat::WebP => "image/webp",
ImageFormat::Tiff => "image/tiff",
ImageFormat::Tga => "image/x-targa",
ImageFormat::Dds => "image/vnd-ms.dds",
ImageFormat::Bmp => "image/bmp",
ImageFormat::Ico => "image/x-icon",
ImageFormat::Hdr => "image/vnd.radiance",
ImageFormat::OpenExr => "image/x-exr",
ImageFormat::Pnm => "image/x-portable-anymap",
ImageFormat::Qoi => "image/x-qoi",
ImageFormat::Farbfeld => "application/octet-stream",
#[allow(deprecated)]
ImageFormat::Pcx => "image/vnd.zbrush.pcx",
}
}
#[inline]
#[must_use]
pub fn can_read(&self) -> bool {
match self {
ImageFormat::Png => true,
ImageFormat::Gif => true,
ImageFormat::Jpeg => true,
ImageFormat::WebP => true,
ImageFormat::Tiff => true,
ImageFormat::Tga => true,
ImageFormat::Dds => false,
ImageFormat::Bmp => true,
ImageFormat::Ico => true,
ImageFormat::Hdr => true,
ImageFormat::OpenExr => true,
ImageFormat::Pnm => true,
ImageFormat::Farbfeld => true,
ImageFormat::Avif => true,
ImageFormat::Qoi => true,
#[allow(deprecated)]
ImageFormat::Pcx => false,
}
}
#[inline]
#[must_use]
pub fn can_write(&self) -> bool {
match self {
ImageFormat::Gif => true,
ImageFormat::Ico => true,
ImageFormat::Jpeg => true,
ImageFormat::Png => true,
ImageFormat::Bmp => true,
ImageFormat::Tiff => true,
ImageFormat::Tga => true,
ImageFormat::Pnm => true,
ImageFormat::Farbfeld => true,
ImageFormat::Avif => true,
ImageFormat::WebP => true,
ImageFormat::Hdr => true,
ImageFormat::OpenExr => true,
ImageFormat::Dds => false,
ImageFormat::Qoi => true,
#[allow(deprecated)]
ImageFormat::Pcx => false,
}
}
#[must_use]
pub fn extensions_str(self) -> &'static [&'static str] {
match self {
ImageFormat::Png => &["png"],
ImageFormat::Jpeg => &["jpg", "jpeg"],
ImageFormat::Gif => &["gif"],
ImageFormat::WebP => &["webp"],
ImageFormat::Pnm => &["pbm", "pam", "ppm", "pgm", "pnm"],
ImageFormat::Tiff => &["tiff", "tif"],
ImageFormat::Tga => &["tga"],
ImageFormat::Dds => &["dds"],
ImageFormat::Bmp => &["bmp"],
ImageFormat::Ico => &["ico"],
ImageFormat::Hdr => &["hdr"],
ImageFormat::OpenExr => &["exr"],
ImageFormat::Farbfeld => &["ff"],
ImageFormat::Avif => &["avif"],
ImageFormat::Qoi => &["qoi"],
#[allow(deprecated)]
ImageFormat::Pcx => &["pcx"],
}
}
#[inline]
#[must_use]
pub fn reading_enabled(&self) -> bool {
match self {
ImageFormat::Png => cfg!(feature = "png"),
ImageFormat::Gif => cfg!(feature = "gif"),
ImageFormat::Jpeg => cfg!(feature = "jpeg"),
ImageFormat::WebP => cfg!(feature = "webp"),
ImageFormat::Tiff => cfg!(feature = "tiff"),
ImageFormat::Tga => cfg!(feature = "tga"),
ImageFormat::Bmp => cfg!(feature = "bmp"),
ImageFormat::Ico => cfg!(feature = "ico"),
ImageFormat::Hdr => cfg!(feature = "hdr"),
ImageFormat::OpenExr => cfg!(feature = "exr"),
ImageFormat::Pnm => cfg!(feature = "pnm"),
ImageFormat::Farbfeld => cfg!(feature = "ff"),
ImageFormat::Avif => cfg!(feature = "avif"),
ImageFormat::Qoi => cfg!(feature = "qoi"),
#[allow(deprecated)]
ImageFormat::Pcx => false,
ImageFormat::Dds => false,
}
}
#[inline]
#[must_use]
pub fn writing_enabled(&self) -> bool {
match self {
ImageFormat::Gif => cfg!(feature = "gif"),
ImageFormat::Ico => cfg!(feature = "ico"),
ImageFormat::Jpeg => cfg!(feature = "jpeg"),
ImageFormat::Png => cfg!(feature = "png"),
ImageFormat::Bmp => cfg!(feature = "bmp"),
ImageFormat::Tiff => cfg!(feature = "tiff"),
ImageFormat::Tga => cfg!(feature = "tga"),
ImageFormat::Pnm => cfg!(feature = "pnm"),
ImageFormat::Farbfeld => cfg!(feature = "ff"),
ImageFormat::Avif => cfg!(feature = "avif"),
ImageFormat::WebP => cfg!(feature = "webp"),
ImageFormat::OpenExr => cfg!(feature = "exr"),
ImageFormat::Qoi => cfg!(feature = "qoi"),
ImageFormat::Hdr => cfg!(feature = "hdr"),
#[allow(deprecated)]
ImageFormat::Pcx => false,
ImageFormat::Dds => false,
}
}
pub fn all() -> impl Iterator<Item = ImageFormat> {
[
ImageFormat::Gif,
ImageFormat::Ico,
ImageFormat::Jpeg,
ImageFormat::Png,
ImageFormat::Bmp,
ImageFormat::Tiff,
ImageFormat::Tga,
ImageFormat::Pnm,
ImageFormat::Farbfeld,
ImageFormat::Avif,
ImageFormat::WebP,
ImageFormat::OpenExr,
ImageFormat::Qoi,
ImageFormat::Dds,
ImageFormat::Hdr,
#[allow(deprecated)]
ImageFormat::Pcx,
]
.iter()
.copied()
}
}
#[cfg(test)]
mod tests {
use std::collections::HashSet;
use std::path::Path;
use super::{ImageFormat, ImageResult};
#[test]
fn test_image_format_from_path() {
fn from_path(s: &str) -> ImageResult<ImageFormat> {
ImageFormat::from_path(Path::new(s))
}
assert_eq!(from_path("./a.jpg").unwrap(), ImageFormat::Jpeg);
assert_eq!(from_path("./a.jpeg").unwrap(), ImageFormat::Jpeg);
assert_eq!(from_path("./a.JPEG").unwrap(), ImageFormat::Jpeg);
assert_eq!(from_path("./a.pNg").unwrap(), ImageFormat::Png);
assert_eq!(from_path("./a.gif").unwrap(), ImageFormat::Gif);
assert_eq!(from_path("./a.webp").unwrap(), ImageFormat::WebP);
assert_eq!(from_path("./a.tiFF").unwrap(), ImageFormat::Tiff);
assert_eq!(from_path("./a.tif").unwrap(), ImageFormat::Tiff);
assert_eq!(from_path("./a.tga").unwrap(), ImageFormat::Tga);
assert_eq!(from_path("./a.dds").unwrap(), ImageFormat::Dds);
assert_eq!(from_path("./a.bmp").unwrap(), ImageFormat::Bmp);
assert_eq!(from_path("./a.Ico").unwrap(), ImageFormat::Ico);
assert_eq!(from_path("./a.hdr").unwrap(), ImageFormat::Hdr);
assert_eq!(from_path("./a.exr").unwrap(), ImageFormat::OpenExr);
assert_eq!(from_path("./a.pbm").unwrap(), ImageFormat::Pnm);
assert_eq!(from_path("./a.pAM").unwrap(), ImageFormat::Pnm);
assert_eq!(from_path("./a.Ppm").unwrap(), ImageFormat::Pnm);
assert_eq!(from_path("./a.pgm").unwrap(), ImageFormat::Pnm);
assert_eq!(from_path("./a.AViF").unwrap(), ImageFormat::Avif);
assert!(from_path("./a.txt").is_err());
assert!(from_path("./a").is_err());
}
#[test]
fn image_formats_are_recognized() {
use ImageFormat::*;
const ALL_FORMATS: &[ImageFormat] = &[
Avif, Png, Jpeg, Gif, WebP, Pnm, Tiff, Tga, Dds, Bmp, Ico, Hdr, Farbfeld, OpenExr,
];
for &format in ALL_FORMATS {
let mut file = Path::new("file.nothing").to_owned();
for ext in format.extensions_str() {
assert!(file.set_extension(ext));
match ImageFormat::from_path(&file) {
Err(_) => panic!("Path {} not recognized as {:?}", file.display(), format),
Ok(result) => assert_eq!(format, result),
}
}
}
}
#[test]
fn all() {
let all_formats: HashSet<ImageFormat> = ImageFormat::all().collect();
assert!(all_formats.contains(&ImageFormat::Avif));
assert!(all_formats.contains(&ImageFormat::Gif));
assert!(all_formats.contains(&ImageFormat::Bmp));
assert!(all_formats.contains(&ImageFormat::Farbfeld));
assert!(all_formats.contains(&ImageFormat::Jpeg));
}
#[test]
fn reading_enabled() {
assert_eq!(cfg!(feature = "jpeg"), ImageFormat::Jpeg.reading_enabled());
assert_eq!(
cfg!(feature = "ff"),
ImageFormat::Farbfeld.reading_enabled()
);
assert!(!ImageFormat::Dds.reading_enabled());
}
#[test]
fn writing_enabled() {
assert_eq!(cfg!(feature = "jpeg"), ImageFormat::Jpeg.writing_enabled());
assert_eq!(
cfg!(feature = "ff"),
ImageFormat::Farbfeld.writing_enabled()
);
assert!(!ImageFormat::Dds.writing_enabled());
}
}