Skip to main content

use_image/
extension.rs

1use crate::format::ImageFormat;
2
3/// Detects an image format from a file extension.
4#[must_use]
5pub fn detect_image_format_from_extension(extension: &str) -> ImageFormat {
6    match normalize_extension(extension) {
7        Some("png") => ImageFormat::Png,
8        Some("jpg") => ImageFormat::Jpeg,
9        Some("webp") => ImageFormat::Webp,
10        Some("gif") => ImageFormat::Gif,
11        Some("svg") => ImageFormat::Svg,
12        Some("ico") => ImageFormat::Ico,
13        Some("bmp") => ImageFormat::Bmp,
14        Some("tiff") => ImageFormat::Tiff,
15        Some("avif") => ImageFormat::Avif,
16        _ => ImageFormat::Unknown,
17    }
18}
19
20/// Returns the canonical extension without a leading dot.
21#[must_use]
22pub const fn image_extension(format: ImageFormat) -> Option<&'static str> {
23    match format {
24        ImageFormat::Png => Some("png"),
25        ImageFormat::Jpeg => Some("jpg"),
26        ImageFormat::Webp => Some("webp"),
27        ImageFormat::Gif => Some("gif"),
28        ImageFormat::Svg => Some("svg"),
29        ImageFormat::Ico => Some("ico"),
30        ImageFormat::Bmp => Some("bmp"),
31        ImageFormat::Tiff => Some("tiff"),
32        ImageFormat::Avif => Some("avif"),
33        ImageFormat::Unknown => None,
34    }
35}
36
37/// Normalizes an image extension to a canonical lowercase value without a dot.
38#[must_use]
39pub fn normalize_extension(extension: &str) -> Option<&'static str> {
40    let trimmed = extension.trim();
41    let stripped = trimmed.trim_start_matches('.');
42
43    if stripped.is_empty() {
44        return None;
45    }
46
47    match stripped.to_ascii_lowercase().as_str() {
48        "png" => Some("png"),
49        "jpg" | "jpeg" => Some("jpg"),
50        "webp" => Some("webp"),
51        "gif" => Some("gif"),
52        "svg" => Some("svg"),
53        "ico" => Some("ico"),
54        "bmp" => Some("bmp"),
55        "tif" | "tiff" => Some("tiff"),
56        "avif" => Some("avif"),
57        _ => None,
58    }
59}
60
61/// Returns true when the input is a known image extension.
62#[must_use]
63pub fn is_image_extension(extension: &str) -> bool {
64    normalize_extension(extension).is_some()
65}
66
67/// Extracts a filename extension without changing its original case.
68#[must_use]
69pub fn extension_from_filename(filename: &str) -> Option<&str> {
70    let last_segment = filename.rsplit(['/', '\\']).next()?;
71    let candidate = last_segment
72        .split(['?', '#'])
73        .next()
74        .unwrap_or(last_segment);
75    let (stem, extension) = candidate.rsplit_once('.')?;
76
77    if stem.is_empty() || extension.is_empty() {
78        return None;
79    }
80
81    Some(extension)
82}