lc/
image_utils.rs

1use anyhow::Result;
2use base64::{engine::general_purpose, Engine as _};
3use std::fs;
4use std::path::Path;
5
6/// Supported image formats
7#[derive(Debug, Clone, Copy)]
8pub enum ImageFormat {
9    Jpeg,
10    Png,
11    Gif,
12    WebP,
13}
14
15impl ImageFormat {
16    /// Detect image format from file extension
17    pub fn from_extension(ext: &str) -> Option<Self> {
18        match ext.to_lowercase().as_str() {
19            "jpg" | "jpeg" => Some(ImageFormat::Jpeg),
20            "png" => Some(ImageFormat::Png),
21            "gif" => Some(ImageFormat::Gif),
22            "webp" => Some(ImageFormat::WebP),
23            _ => None,
24        }
25    }
26
27    /// Get MIME type for the image format
28    pub fn mime_type(&self) -> &'static str {
29        match self {
30            ImageFormat::Jpeg => "image/jpeg",
31            ImageFormat::Png => "image/png",
32            ImageFormat::Gif => "image/gif",
33            ImageFormat::WebP => "image/webp",
34        }
35    }
36}
37
38/// Process an image file and return a data URL
39pub fn process_image_file(path: &Path) -> Result<String> {
40    // Check if file exists
41    if !path.exists() {
42        anyhow::bail!("Image file not found: {}", path.display());
43    }
44
45    // Detect image format from extension
46    let extension = path
47        .extension()
48        .and_then(|ext| ext.to_str())
49        .ok_or_else(|| anyhow::anyhow!("No file extension found"))?;
50
51    let format = ImageFormat::from_extension(extension)
52        .ok_or_else(|| anyhow::anyhow!("Unsupported image format: {}", extension))?;
53
54    // Read the image file
55    let image_data = fs::read(path)?;
56
57    // Check file size (limit to 20MB for most providers)
58    const MAX_SIZE: usize = 20 * 1024 * 1024; // 20MB
59    if image_data.len() > MAX_SIZE {
60        anyhow::bail!(
61            "Image file too large: {} bytes (max: {} bytes)",
62            image_data.len(),
63            MAX_SIZE
64        );
65    }
66
67    // Encode to base64
68    let base64_data = general_purpose::STANDARD.encode(&image_data);
69
70    // Create data URL
71    let data_url = format!("data:{};base64,{}", format.mime_type(), base64_data);
72
73    Ok(data_url)
74}
75
76/// Process an image from a URL
77pub fn process_image_url(url: &str) -> Result<String> {
78    // For now, just validate and return the URL
79    // In the future, we could download and process the image
80    if !url.starts_with("http://") && !url.starts_with("https://") {
81        anyhow::bail!("Invalid image URL: must start with http:// or https://");
82    }
83
84    Ok(url.to_string())
85}
86
87/// Process multiple image inputs (files or URLs)
88pub fn process_images(paths: &[String]) -> Result<Vec<String>> {
89    let mut processed_images = Vec::new();
90
91    for path_str in paths {
92        let processed = if path_str.starts_with("http://") || path_str.starts_with("https://") {
93            process_image_url(path_str)?
94        } else {
95            let path = Path::new(path_str);
96            process_image_file(path)?
97        };
98
99        processed_images.push(processed);
100    }
101
102    Ok(processed_images)
103}
104
105#[cfg(test)]
106mod tests {
107    use super::*;
108
109    #[test]
110    fn test_image_format_detection() {
111        assert!(matches!(
112            ImageFormat::from_extension("jpg"),
113            Some(ImageFormat::Jpeg)
114        ));
115        assert!(matches!(
116            ImageFormat::from_extension("JPEG"),
117            Some(ImageFormat::Jpeg)
118        ));
119        assert!(matches!(
120            ImageFormat::from_extension("png"),
121            Some(ImageFormat::Png)
122        ));
123        assert!(matches!(
124            ImageFormat::from_extension("gif"),
125            Some(ImageFormat::Gif)
126        ));
127        assert!(matches!(
128            ImageFormat::from_extension("webp"),
129            Some(ImageFormat::WebP)
130        ));
131        assert!(ImageFormat::from_extension("txt").is_none());
132    }
133
134    #[test]
135    fn test_mime_types() {
136        assert_eq!(ImageFormat::Jpeg.mime_type(), "image/jpeg");
137        assert_eq!(ImageFormat::Png.mime_type(), "image/png");
138        assert_eq!(ImageFormat::Gif.mime_type(), "image/gif");
139        assert_eq!(ImageFormat::WebP.mime_type(), "image/webp");
140    }
141}