Skip to main content

rosetta_squint_decode/
decode.rs

1use crate::bmp::decode_bmp;
2use crate::gif::decode_gif;
3use crate::heic::decode_heic;
4use crate::png::decode_png;
5use crate::tiff::decode_tiff;
6use crate::error::{DecodeError, DecodeErrorKind};
7use crate::types::{DecodedImage, Format};
8use crate::webp::decode_webp;
9
10/// Decode auto-detects the format from magic bytes and decodes.
11pub fn decode(bytes: &[u8]) -> Result<DecodedImage, DecodeError> {
12    let fmt = detect_format(bytes).ok_or_else(|| {
13        DecodeError::new(DecodeErrorKind::UnsupportedFormat, None, "")
14    })?;
15    match fmt {
16        Format::Bmp => decode_bmp(bytes),
17        Format::Gif => decode_gif(bytes),
18        Format::Heic => decode_heic(bytes),
19        Format::Jpeg => crate::jpeg::decode_jpeg(bytes),
20        Format::Png => decode_png(bytes),
21        Format::Tiff => decode_tiff(bytes),
22        Format::Webp => decode_webp(bytes),
23        other => Err(DecodeError::new(DecodeErrorKind::UnsupportedFormat, Some(other), "")),
24    }
25}
26
27/// Returns the Format if the magic bytes match, else None.
28pub fn detect_format(bytes: &[u8]) -> Option<Format> {
29    if bytes.len() < 2 {
30        return None;
31    }
32    if bytes[0] == 0x42 && bytes[1] == 0x4D {
33        return Some(Format::Bmp);
34    }
35    if bytes.len() >= 8
36        && bytes[0] == 0x89
37        && bytes[1] == 0x50
38        && bytes[2] == 0x4E
39        && bytes[3] == 0x47
40        && bytes[4] == 0x0D
41        && bytes[5] == 0x0A
42        && bytes[6] == 0x1A
43        && bytes[7] == 0x0A
44    {
45        return Some(Format::Png);
46    }
47    // GIF87a or GIF89a
48    if bytes.len() >= 6
49        && bytes[0] == b'G'
50        && bytes[1] == b'I'
51        && bytes[2] == b'F'
52        && bytes[3] == b'8'
53        && (bytes[4] == b'7' || bytes[4] == b'9')
54        && bytes[5] == b'a'
55    {
56        return Some(Format::Gif);
57    }
58    // JPEG: starts with FF D8 (SOI marker); third byte FF is typical but not required for detection
59    if bytes.len() >= 2
60        && bytes[0] == 0xFF
61        && bytes[1] == 0xD8
62    {
63        return Some(Format::Jpeg);
64    }
65    // WebP: RIFF????WEBP (bytes 0-3 = "RIFF", bytes 8-11 = "WEBP")
66    if bytes.len() >= 12
67        && bytes[0..4] == *b"RIFF"
68        && bytes[8..12] == *b"WEBP"
69    {
70        return Some(Format::Webp);
71    }
72    // TIFF: little-endian (II + 0x002A) or big-endian (MM + 0x2A00)
73    if bytes.len() >= 4
74        && ((bytes[0] == 0x49 && bytes[1] == 0x49 && bytes[2] == 0x2A && bytes[3] == 0x00)
75            || (bytes[0] == 0x4D && bytes[1] == 0x4D && bytes[2] == 0x00 && bytes[3] == 0x2A))
76    {
77        return Some(Format::Tiff);
78    }
79    // HEIC: ISO Base Media File (bytes 4-8 == "ftyp"), brand at bytes 8-12 must be
80    // a HEVC/HEIF brand; AVIF brand is explicitly excluded (v1 scope).
81    if bytes.len() >= 12 && &bytes[4..8] == b"ftyp" {
82        let brand = &bytes[8..12];
83        match brand {
84            b"avif" | b"avis" => return None, // AVIF — not supported in v1
85            b"heic" | b"heix" | b"mif1" | b"msf1" | b"hevc" | b"hevx" => {
86                return Some(Format::Heic)
87            }
88            _ => {} // unknown brand → fall through (not detected)
89        }
90    }
91    None
92}
93
94/// Returns the list of formats this port can decode.
95pub fn supported_formats() -> Vec<Format> {
96    vec![Format::Bmp, Format::Png, Format::Gif, Format::Jpeg, Format::Webp, Format::Tiff, Format::Heic]
97}