Skip to main content

use_image/
magic.rs

1use crate::format::ImageFormat;
2
3const PNG_SIGNATURE: &[u8] = b"\x89PNG\r\n\x1a\n";
4const JPEG_SIGNATURE: &[u8] = &[0xFF, 0xD8, 0xFF];
5
6fn is_webp(bytes: &[u8]) -> bool {
7    bytes.len() >= 12 && &bytes[..4] == b"RIFF" && &bytes[8..12] == b"WEBP"
8}
9
10fn is_tiff(bytes: &[u8]) -> bool {
11    matches!(
12        bytes,
13        [0x49, 0x49, 0x2A, 0x00, ..]
14            | [0x4D, 0x4D, 0x00, 0x2A, ..]
15            | [0x49, 0x49, 0x2B, 0x00, ..]
16            | [0x4D, 0x4D, 0x00, 0x2B, ..]
17    )
18}
19
20fn is_avif(bytes: &[u8]) -> bool {
21    if bytes.len() < 16 || bytes.get(4..8) != Some(b"ftyp") {
22        return false;
23    }
24
25    let search_end = bytes.len().min(32);
26
27    bytes[8..search_end]
28        .chunks(4)
29        .any(|chunk| chunk == b"avif" || chunk == b"avis")
30}
31
32pub(crate) fn looks_like_svg(bytes: &[u8]) -> bool {
33    if bytes.is_empty() {
34        return false;
35    }
36
37    let sniff_len = bytes.len().min(512);
38    let text = String::from_utf8_lossy(&bytes[..sniff_len]);
39    let text = text.strip_prefix('\u{feff}').unwrap_or(&text);
40    let lowered = text.to_ascii_lowercase();
41    let trimmed = lowered.trim_start();
42
43    trimmed.starts_with("<svg") || (trimmed.starts_with("<?xml") && trimmed.contains("<svg"))
44}
45
46/// Detects an image format from common magic bytes without fully parsing the file.
47#[must_use]
48pub fn detect_image_format_from_bytes(bytes: &[u8]) -> ImageFormat {
49    if bytes.starts_with(PNG_SIGNATURE) {
50        return ImageFormat::Png;
51    }
52
53    if bytes.starts_with(JPEG_SIGNATURE) {
54        return ImageFormat::Jpeg;
55    }
56
57    if bytes.starts_with(b"GIF87a") || bytes.starts_with(b"GIF89a") {
58        return ImageFormat::Gif;
59    }
60
61    if is_webp(bytes) {
62        return ImageFormat::Webp;
63    }
64
65    if bytes.starts_with(&[0x00, 0x00, 0x01, 0x00]) {
66        return ImageFormat::Ico;
67    }
68
69    if bytes.starts_with(b"BM") {
70        return ImageFormat::Bmp;
71    }
72
73    if is_tiff(bytes) {
74        return ImageFormat::Tiff;
75    }
76
77    if is_avif(bytes) {
78        return ImageFormat::Avif;
79    }
80
81    if looks_like_svg(bytes) {
82        return ImageFormat::Svg;
83    }
84
85    ImageFormat::Unknown
86}