# ADR-058: Image Optimization Strategy ## Status Accepted ## Context The v1.2.0 media upload feature requires decisions about image size limits, optimization, and validation. Based on user requirements: - 4 images maximum per note (confirmed) - No drag-and-drop reordering needed (display order is upload order) - Image optimization desired - Optional caption field for each image (accessibility) Research was conducted on: - Web image best practices (2024) - IndieWeb implementation patterns - Python image processing libraries - Storage implications for single-user CMS ## Decision ### Image Limits We will enforce the following limits: 1. **Count**: Maximum 4 images per note 2. **File Size**: Maximum 10MB per image 3. **Dimensions**: Maximum 4096x4096 pixels 4. **Formats**: JPEG, PNG, GIF, WebP only ### Optimization Strategy We will implement **automatic resizing on upload**: 1. **Resize Policy**: - Images larger than 2048 pixels (longest edge) will be resized - Aspect ratio will be preserved - Original quality will be maintained (no aggressive compression) - EXIF orientation will be corrected 2. **Rejection Policy**: - Files over 10MB will be rejected (before optimization) - Dimensions over 4096x4096 will be rejected - Invalid formats will be rejected - Corrupted files will be rejected 3. **Processing Library**: Use **Pillow** for image processing ### Database Schema Updates Add caption field to `note_media` table: ```sql CREATE TABLE note_media ( id INTEGER PRIMARY KEY, note_id INTEGER NOT NULL, media_id INTEGER NOT NULL, display_order INTEGER NOT NULL DEFAULT 0, caption TEXT, -- Optional caption for accessibility created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (note_id) REFERENCES notes(id) ON DELETE CASCADE, FOREIGN KEY (media_id) REFERENCES media(id) ON DELETE CASCADE, UNIQUE(note_id, media_id) ); ``` ## Rationale ### Why 10MB file size limit? - Generous for high-quality photos from modern phones - Prevents storage abuse on single-user instance - Reasonable upload time even on slower connections - Matches or exceeds most social platforms ### Why 4096x4096 max dimensions? - Covers 16-megapixel images (4000x4000) - Sufficient for 4K displays (3840x2160) - Prevents memory issues during processing - Larger than needed for web display ### Why resize to 2048px? - Optimal balance between quality and performance - Retina-ready (2x scaling on 1024px display) - Significant file size reduction - Matches common social media limits - Preserves quality for most use cases ### Why Pillow over alternatives? - De-facto standard for Python image processing - Fastest for basic resize operations - Minimal dependencies - Well-documented and stable - Sufficient for our needs (resize, format conversion, EXIF) ### Why automatic optimization? - Better user experience (no manual intervention) - Consistent output quality - Storage efficiency - Faster page loads - Users still get good quality ### Why no thumbnail generation? - Adds complexity for minimal benefit - Modern browsers handle image scaling well - Single-user CMS doesn't need CDN optimization - Can be added later if needed ## Consequences ### Positive - Automatic optimization improves performance - Generous limits support high-quality photography - Captions improve accessibility - Storage usage remains reasonable - Fast processing with Pillow ### Negative - Users cannot upload raw/unprocessed images - Some quality loss for images over 2048px - No manual control over optimization - Additional processing time on upload ### Neutral - Requires Pillow dependency - Images stored at single resolution - No progressive enhancement (thumbnails) ## Alternatives Considered ### Alternative 1: No Optimization Accept images as-is, no processing. - **Pros**: Simpler, preserves originals - **Cons**: Storage bloat, slow page loads, memory issues ### Alternative 2: Strict Limits (1MB, 1920x1080) Match typical web recommendations. - **Pros**: Optimal performance, minimal storage - **Cons**: Too restrictive for photography, poor UX ### Alternative 3: Generate Multiple Sizes Create thumbnail, medium, and full sizes. - **Pros**: Optimal delivery, responsive images - **Cons**: Complex implementation, 3x storage, overkill for single-user ### Alternative 4: Client-side Resizing Resize in browser before upload. - **Pros**: Reduces server load - **Cons**: Inconsistent quality, browser limitations, poor UX ## Implementation Notes 1. **Validation Order**: - Check file size (reject if >10MB) - Check MIME type (accept only allowed formats) - Load with Pillow (validates file integrity) - Check dimensions (reject if >4096px) - Resize if needed (>2048px) - Save optimized version 2. **Error Messages**: - "File too large. Maximum size is 10MB" - "Invalid image format. Accepted: JPEG, PNG, GIF, WebP" - "Image dimensions too large. Maximum is 4096x4096" - "Image appears to be corrupted" 3. **Pillow Configuration**: ```python # Preserve quality during resize image.thumbnail((2048, 2048), Image.Resampling.LANCZOS) # Correct EXIF orientation ImageOps.exif_transpose(image) # Save with original quality image.save(output, quality=95, optimize=True) ``` 4. **Caption Implementation**: - Add caption field to upload form - Store in `note_media.caption` - Use as alt text in HTML - Include in Microformats markup ## References - [MDN Web Performance: Images](https://developer.mozilla.org/en-US/docs/Web/Performance/images) - [Pillow Documentation](https://pillow.readthedocs.io/) - [Web.dev Image Optimization](https://web.dev/fast/#optimize-your-images) - [Twitter Image Specifications](https://developer.twitter.com/en/docs/twitter-api/v1/media/upload-media/uploading-media/media-best-practices)