···9797 #[error("error-smokesignal-blob-37 No header file provided")]
9898 NoHeaderFile,
9999100100+ /// Error when no thumbnail file is provided in the upload.
101101+ ///
102102+ /// This error occurs when the expected thumbnail file field is missing
103103+ /// from the multipart form data.
104104+ #[error("error-smokesignal-blob-47 No thumbnail file provided")]
105105+ NoThumbnailFile,
106106+100107 /// Error from image processing operations.
101108 ///
102109 /// This error wraps `ImageError` and is automatically converted from
···8080 Ok(png_buffer.into_inner())
8181}
82828383-/// Process event header image: validate 16:9 aspect ratio, resize to 1600x900, convert to PNG
8383+/// Process event header image: validate 3:1 aspect ratio, resize to 1500x500, convert to PNG
8484pub(crate) fn process_event_header(data: &[u8]) -> Result<Vec<u8>, ImageError> {
8585 // Load the image
8686 let img = image::load_from_memory(data)
···88888989 let (width, height) = img.dimensions();
90909191- // Validate 16:9 aspect ratio (allow 10% deviation)
9191+ // Validate 3:1 aspect ratio (allow 10% deviation)
9292 let aspect_ratio = width as f32 / height as f32;
9393- let expected_ratio = 16.0 / 9.0;
9393+ let expected_ratio = 3.0 / 1.0;
9494 if (aspect_ratio - expected_ratio).abs() / expected_ratio > 0.10 {
9595 return Err(ImageError::InvalidEventHeaderAspectRatio { width, height });
9696 }
97979898- // Resize to 1600x900 if needed
9999- let resized = if width != 1600 || height != 900 {
100100- img.resize_exact(1600, 900, FilterType::Lanczos3)
9898+ // Resize to 1500x500 if needed
9999+ let resized = if width != 1500 || height != 500 {
100100+ img.resize_exact(1500, 500, FilterType::Lanczos3)
101101 } else {
102102 img
103103 };
···110110111111 Ok(png_buffer.into_inner())
112112}
113113+114114+/// Process event thumbnail image: validate 1:1 aspect ratio, resize within 512x512 to 1024x1024, convert to PNG
115115+pub(crate) fn process_event_thumbnail(data: &[u8]) -> Result<Vec<u8>, ImageError> {
116116+ // Load the image
117117+ let img = image::load_from_memory(data)
118118+ .map_err(|e| ImageError::ThumbnailLoadFailed(e.to_string()))?;
119119+120120+ let (width, height) = img.dimensions();
121121+122122+ // Validate 1:1 aspect ratio (allow 5% deviation)
123123+ let aspect_ratio = width as f32 / height as f32;
124124+ if (aspect_ratio - 1.0).abs() > 0.05 {
125125+ return Err(ImageError::InvalidThumbnailAspectRatio { width, height });
126126+ }
127127+128128+ // Validate minimum size of 512x512
129129+ if width < 512 || height < 512 {
130130+ return Err(ImageError::ThumbnailTooSmall { width, height });
131131+ }
132132+133133+ // Resize to maximum 1024x1024 if larger, otherwise keep original size
134134+ let resized = if width > 1024 || height > 1024 {
135135+ img.resize_exact(1024, 1024, FilterType::Lanczos3)
136136+ } else {
137137+ // Keep original size if within bounds (512-1024)
138138+ img
139139+ };
140140+141141+ // Convert to PNG
142142+ let mut png_buffer = std::io::Cursor::new(Vec::new());
143143+ resized
144144+ .write_to(&mut png_buffer, ImageFormat::Png)
145145+ .map_err(|e| ImageError::ThumbnailEncodeFailed(e.to_string()))?;
146146+147147+ Ok(png_buffer.into_inner())
148148+}
+41-3
src/image_errors.rs
···8484 #[error("error-smokesignal-image-9 Failed to load event header image: {0}")]
8585 EventHeaderLoadFailed(String),
86868787- /// Error when event header aspect ratio is not 16:9.
8787+ /// Error when event header aspect ratio is not 3:1.
8888 ///
8989 /// This error occurs when an event header image doesn't have the required
9090- /// 16:9 aspect ratio (allowing 10% deviation).
9191- #[error("error-smokesignal-image-10 Event header must have 16:9 aspect ratio: got {width}:{height}")]
9090+ /// 3:1 aspect ratio (allowing 10% deviation).
9191+ #[error("error-smokesignal-image-10 Event header must have 3:1 aspect ratio: got {width}:{height}")]
9292 InvalidEventHeaderAspectRatio {
9393 /// The width of the image in pixels.
9494 width: u32,
···102102 /// and processing the event header image.
103103 #[error("error-smokesignal-image-11 Failed to encode event header as PNG: {0}")]
104104 EventHeaderEncodeFailed(String),
105105+106106+ /// Error when loading a thumbnail image fails.
107107+ ///
108108+ /// This error occurs when attempting to load image data for thumbnail
109109+ /// processing, typically due to invalid format or corrupted data.
110110+ #[error("error-smokesignal-image-12 Failed to load thumbnail image: {0}")]
111111+ ThumbnailLoadFailed(String),
112112+113113+ /// Error when thumbnail aspect ratio is not 1:1.
114114+ ///
115115+ /// This error occurs when a thumbnail image doesn't have the required
116116+ /// square aspect ratio (allowing 5% deviation).
117117+ #[error("error-smokesignal-image-13 Thumbnail must have 1:1 aspect ratio: got {width}:{height}")]
118118+ InvalidThumbnailAspectRatio {
119119+ /// The width of the image in pixels.
120120+ width: u32,
121121+ /// The height of the image in pixels.
122122+ height: u32,
123123+ },
124124+125125+ /// Error when thumbnail image is too small.
126126+ ///
127127+ /// This error occurs when a thumbnail image is smaller than the minimum
128128+ /// required size of 512x512 pixels.
129129+ #[error("error-smokesignal-image-14 Thumbnail must be at least 512x512: got {width}x{height}")]
130130+ ThumbnailTooSmall {
131131+ /// The width of the image in pixels.
132132+ width: u32,
133133+ /// The height of the image in pixels.
134134+ height: u32,
135135+ },
136136+137137+ /// Error when encoding a thumbnail as PNG fails.
138138+ ///
139139+ /// This error occurs when the PNG encoding process fails after resizing
140140+ /// and processing the thumbnail image.
141141+ #[error("error-smokesignal-image-15 Failed to encode thumbnail as PNG: {0}")]
142142+ ThumbnailEncodeFailed(String),
105143}