The smokesignal.events web application
at main 147 lines 5.1 kB view raw
1use image::GenericImageView; 2use image::{ImageFormat, imageops::FilterType}; 3 4use crate::image_errors::ImageError; 5 6/// Validate image data and ensure it's a valid image 7pub(crate) fn validate_image(data: &[u8], max_size: usize) -> Result<(), ImageError> { 8 if data.len() > max_size { 9 return Err(ImageError::SizeExceedsMaximum { 10 size: data.len(), 11 max: max_size, 12 }); 13 } 14 15 // Try to load the image to ensure it's valid 16 image::load_from_memory(data).map_err(|e| ImageError::InvalidImageData(e.to_string()))?; 17 18 Ok(()) 19} 20 21/// Process avatar image: validate 1:1 aspect ratio, resize to 400x400, convert to PNG 22pub(crate) fn process_avatar(data: &[u8]) -> Result<Vec<u8>, ImageError> { 23 // Load the image 24 let img = 25 image::load_from_memory(data).map_err(|e| ImageError::AvatarLoadFailed(e.to_string()))?; 26 27 let (width, height) = img.dimensions(); 28 29 // Validate 1:1 aspect ratio (allow 5% deviation) 30 let aspect_ratio = width as f32 / height as f32; 31 if (aspect_ratio - 1.0).abs() > 0.05 { 32 return Err(ImageError::InvalidAvatarAspectRatio { width, height }); 33 } 34 35 // Resize to 400x400 if needed 36 let resized = if width != 400 || height != 400 { 37 img.resize_exact(400, 400, FilterType::Lanczos3) 38 } else { 39 img 40 }; 41 42 // Convert to PNG 43 let mut png_buffer = std::io::Cursor::new(Vec::new()); 44 resized 45 .write_to(&mut png_buffer, ImageFormat::Png) 46 .map_err(|e| ImageError::AvatarEncodeFailed(e.to_string()))?; 47 48 Ok(png_buffer.into_inner()) 49} 50 51/// Process banner image: validate 16:9 aspect ratio, resize to 1600x900, convert to PNG 52pub(crate) fn process_banner(data: &[u8]) -> Result<Vec<u8>, ImageError> { 53 // Load the image 54 let img = 55 image::load_from_memory(data).map_err(|e| ImageError::BannerLoadFailed(e.to_string()))?; 56 57 let (width, height) = img.dimensions(); 58 59 // Validate 16:9 aspect ratio (allow 10% deviation) 60 let aspect_ratio = width as f32 / height as f32; 61 let expected_ratio = 16.0 / 9.0; 62 if (aspect_ratio - expected_ratio).abs() / expected_ratio > 0.10 { 63 return Err(ImageError::InvalidBannerAspectRatio { width, height }); 64 } 65 66 // Resize to 1600x900 if needed 67 let resized = if width != 1600 || height != 900 { 68 img.resize_exact(1600, 900, FilterType::Lanczos3) 69 } else { 70 img 71 }; 72 73 // Convert to PNG 74 let mut png_buffer = std::io::Cursor::new(Vec::new()); 75 resized 76 .write_to(&mut png_buffer, ImageFormat::Png) 77 .map_err(|e| ImageError::BannerEncodeFailed(e.to_string()))?; 78 79 Ok(png_buffer.into_inner()) 80} 81 82/// Process event header image: validate 3:1 aspect ratio, resize to 1500x500, convert to PNG 83pub(crate) fn process_event_header(data: &[u8]) -> Result<Vec<u8>, ImageError> { 84 // Load the image 85 let img = image::load_from_memory(data) 86 .map_err(|e| ImageError::EventHeaderLoadFailed(e.to_string()))?; 87 88 let (width, height) = img.dimensions(); 89 90 // Validate 3:1 aspect ratio (allow 10% deviation) 91 let aspect_ratio = width as f32 / height as f32; 92 let expected_ratio = 3.0 / 1.0; 93 if (aspect_ratio - expected_ratio).abs() / expected_ratio > 0.10 { 94 return Err(ImageError::InvalidEventHeaderAspectRatio { width, height }); 95 } 96 97 // Resize to 1500x500 if needed 98 let resized = if width != 1500 || height != 500 { 99 img.resize_exact(1500, 500, FilterType::Lanczos3) 100 } else { 101 img 102 }; 103 104 // Convert to PNG 105 let mut png_buffer = std::io::Cursor::new(Vec::new()); 106 resized 107 .write_to(&mut png_buffer, ImageFormat::Png) 108 .map_err(|e| ImageError::EventHeaderEncodeFailed(e.to_string()))?; 109 110 Ok(png_buffer.into_inner()) 111} 112 113/// Process event thumbnail image: validate 1:1 aspect ratio, resize within 512x512 to 1024x1024, convert to PNG 114pub(crate) fn process_event_thumbnail(data: &[u8]) -> Result<Vec<u8>, ImageError> { 115 // Load the image 116 let img = image::load_from_memory(data) 117 .map_err(|e| ImageError::ThumbnailLoadFailed(e.to_string()))?; 118 119 let (width, height) = img.dimensions(); 120 121 // Validate 1:1 aspect ratio (allow 5% deviation) 122 let aspect_ratio = width as f32 / height as f32; 123 if (aspect_ratio - 1.0).abs() > 0.05 { 124 return Err(ImageError::InvalidThumbnailAspectRatio { width, height }); 125 } 126 127 // Validate minimum size of 512x512 128 if width < 512 || height < 512 { 129 return Err(ImageError::ThumbnailTooSmall { width, height }); 130 } 131 132 // Resize to maximum 1024x1024 if larger, otherwise keep original size 133 let resized = if width > 1024 || height > 1024 { 134 img.resize_exact(1024, 1024, FilterType::Lanczos3) 135 } else { 136 // Keep original size if within bounds (512-1024) 137 img 138 }; 139 140 // Convert to PNG 141 let mut png_buffer = std::io::Cursor::new(Vec::new()); 142 resized 143 .write_to(&mut png_buffer, ImageFormat::Png) 144 .map_err(|e| ImageError::ThumbnailEncodeFailed(e.to_string()))?; 145 146 Ok(png_buffer.into_inner()) 147}