# 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)