1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub enum ImageFormat {
6 Jpeg,
7 Png,
8 Bmp,
9 Tiff,
10}
11
12impl ImageFormat {
13 pub fn extension(&self) -> &'static str {
15 match self {
16 Self::Jpeg => "jpg",
17 Self::Png => "png",
18 Self::Bmp => "bmp",
19 Self::Tiff => "tiff",
20 }
21 }
22
23 pub fn ofd_format(&self) -> &'static str {
25 match self {
26 Self::Jpeg => "JPEG",
27 Self::Png => "PNG",
28 Self::Bmp => "BMP",
29 Self::Tiff => "TIFF",
30 }
31 }
32
33 pub fn mime_type(&self) -> &'static str {
35 match self {
36 Self::Jpeg => "image/jpeg",
37 Self::Png => "image/png",
38 Self::Bmp => "image/bmp",
39 Self::Tiff => "image/tiff",
40 }
41 }
42
43 pub fn detect(data: &[u8]) -> Option<Self> {
45 if data.len() < 4 {
46 return None;
47 }
48 if data[0] == 0xFF && data[1] == 0xD8 {
49 Some(Self::Jpeg)
50 } else if data[0..4] == [0x89, 0x50, 0x4E, 0x47] {
51 Some(Self::Png)
52 } else if data[0] == 0x42 && data[1] == 0x4D {
53 Some(Self::Bmp)
54 } else if (data[0] == 0x49 && data[1] == 0x49) || (data[0] == 0x4D && data[1] == 0x4D) {
55 Some(Self::Tiff)
56 } else {
57 None
58 }
59 }
60
61 pub fn from_extension(ext: &str) -> Option<Self> {
63 match ext.to_lowercase().as_str() {
64 "jpg" | "jpeg" => Some(Self::Jpeg),
65 "png" => Some(Self::Png),
66 "bmp" => Some(Self::Bmp),
67 "tif" | "tiff" => Some(Self::Tiff),
68 _ => None,
69 }
70 }
71}
72
73pub fn detect_image_dimensions(data: &[u8]) -> Option<(u32, u32)> {
76 let format = ImageFormat::detect(data)?;
77 match format {
78 ImageFormat::Png => detect_png_dimensions(data),
79 ImageFormat::Jpeg => detect_jpeg_dimensions(data),
80 ImageFormat::Bmp => detect_bmp_dimensions(data),
81 ImageFormat::Tiff => None, }
83}
84
85fn detect_png_dimensions(data: &[u8]) -> Option<(u32, u32)> {
86 if data.len() < 24 {
88 return None;
89 }
90 if &data[12..16] != b"IHDR" {
91 return None;
92 }
93 let w = u32::from_be_bytes([data[16], data[17], data[18], data[19]]);
94 let h = u32::from_be_bytes([data[20], data[21], data[22], data[23]]);
95 Some((w, h))
96}
97
98fn detect_jpeg_dimensions(data: &[u8]) -> Option<(u32, u32)> {
99 let mut i = 2; while i + 1 < data.len() {
102 if data[i] != 0xFF {
103 i += 1;
104 continue;
105 }
106 let marker = data[i + 1];
107 if marker == 0x00 || marker == 0xFF {
108 i += 1;
109 continue;
110 }
111 if (0xC0..=0xC3).contains(&marker) && i + 9 < data.len() {
112 let h = u16::from_be_bytes([data[i + 5], data[i + 6]]) as u32;
113 let w = u16::from_be_bytes([data[i + 7], data[i + 8]]) as u32;
114 return Some((w, h));
115 }
116 if i + 3 < data.len() {
117 let len = u16::from_be_bytes([data[i + 2], data[i + 3]]) as usize;
118 i += 2 + len;
119 } else {
120 break;
121 }
122 }
123 None
124}
125
126fn detect_bmp_dimensions(data: &[u8]) -> Option<(u32, u32)> {
127 if data.len() < 26 {
129 return None;
130 }
131 let w = u32::from_le_bytes([data[18], data[19], data[20], data[21]]);
132 let h_signed = i32::from_le_bytes([data[22], data[23], data[24], data[25]]);
133 let h = h_signed.unsigned_abs();
134 Some((w, h))
135}
136
137#[derive(Debug)]
139pub(crate) struct MediaDef {
140 pub id: u32,
141 pub format: ImageFormat,
142 pub file_name: String,
144}