ricecoder_images/
formats.rs1use crate::error::{ImageError, ImageResult};
4use std::path::Path;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum ImageFormat {
9 Png,
11 Jpeg,
13 Gif,
15 WebP,
17}
18
19impl ImageFormat {
20 pub fn detect_from_file(path: &Path) -> ImageResult<Self> {
22 let bytes = std::fs::read(path)?;
23 Self::detect_from_bytes(&bytes)
24 }
25
26 pub fn detect_from_bytes(bytes: &[u8]) -> ImageResult<Self> {
28 if bytes.len() < 4 {
29 return Err(ImageError::InvalidFile(
30 "File too small to be a valid image".to_string(),
31 ));
32 }
33
34 if bytes.starts_with(&[0x89, 0x50, 0x4E, 0x47]) {
36 return Ok(ImageFormat::Png);
37 }
38
39 if bytes.len() >= 3 && bytes.starts_with(&[0xFF, 0xD8, 0xFF]) {
41 return Ok(ImageFormat::Jpeg);
42 }
43
44 if bytes.starts_with(b"GIF") {
46 return Ok(ImageFormat::Gif);
47 }
48
49 if bytes.len() >= 12
51 && bytes.starts_with(b"RIFF")
52 && bytes[8..12] == *b"WEBP"
53 {
54 return Ok(ImageFormat::WebP);
55 }
56
57 Err(ImageError::InvalidFile(
58 "Unable to detect image format from file header".to_string(),
59 ))
60 }
61
62 pub fn as_str(&self) -> &'static str {
64 match self {
65 ImageFormat::Png => "png",
66 ImageFormat::Jpeg => "jpg",
67 ImageFormat::Gif => "gif",
68 ImageFormat::WebP => "webp",
69 }
70 }
71
72 pub fn validate_file(path: &Path, max_size_mb: u64) -> ImageResult<Self> {
83 if !path.exists() {
85 return Err(ImageError::InvalidFile(
86 "File does not exist".to_string(),
87 ));
88 }
89
90 let metadata = std::fs::metadata(path)?;
92 let size_mb = metadata.len() / (1024 * 1024);
93 if size_mb > max_size_mb {
94 return Err(ImageError::FileTooLarge {
95 size_mb: size_mb as f64,
96 });
97 }
98
99 Self::detect_from_file(path)
101 }
102
103 pub fn extract_metadata(path: &Path) -> ImageResult<(u32, u32)> {
105 let img = image::open(path).map_err(|e| {
106 ImageError::InvalidFile(format!("Failed to open image: {}", e))
107 })?;
108
109 Ok((img.width(), img.height()))
110 }
111}
112
113#[cfg(test)]
114mod tests {
115 use super::*;
116
117 #[test]
118 fn test_detect_png_format() {
119 let png_bytes = vec![0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A];
120 let format = ImageFormat::detect_from_bytes(&png_bytes).unwrap();
121 assert_eq!(format, ImageFormat::Png);
122 }
123
124 #[test]
125 fn test_detect_jpeg_format() {
126 let jpeg_bytes = vec![0xFF, 0xD8, 0xFF, 0xE0];
127 let format = ImageFormat::detect_from_bytes(&jpeg_bytes).unwrap();
128 assert_eq!(format, ImageFormat::Jpeg);
129 }
130
131 #[test]
132 fn test_detect_gif_format() {
133 let gif_bytes = b"GIF89a".to_vec();
134 let format = ImageFormat::detect_from_bytes(&gif_bytes).unwrap();
135 assert_eq!(format, ImageFormat::Gif);
136 }
137
138 #[test]
139 fn test_detect_webp_format() {
140 let mut webp_bytes = b"RIFF".to_vec();
141 webp_bytes.extend_from_slice(&[0x00, 0x00, 0x00, 0x00]);
142 webp_bytes.extend_from_slice(b"WEBP");
143 let format = ImageFormat::detect_from_bytes(&webp_bytes).unwrap();
144 assert_eq!(format, ImageFormat::WebP);
145 }
146
147 #[test]
148 fn test_invalid_format() {
149 let invalid_bytes = vec![0x00, 0x00, 0x00, 0x00];
150 let result = ImageFormat::detect_from_bytes(&invalid_bytes);
151 assert!(result.is_err());
152 }
153
154 #[test]
155 fn test_format_as_str() {
156 assert_eq!(ImageFormat::Png.as_str(), "png");
157 assert_eq!(ImageFormat::Jpeg.as_str(), "jpg");
158 assert_eq!(ImageFormat::Gif.as_str(), "gif");
159 assert_eq!(ImageFormat::WebP.as_str(), "webp");
160 }
161}