From b9169cb93973c10a8e84c2ed842619fd04f66d48 Mon Sep 17 00:00:00 2001 From: jason Date: Sun, 8 Mar 2026 16:14:57 -0500 Subject: [PATCH] Add live preview implementation guide --- docs/LIVE_PREVIEW_IMPLEMENTATION.md | 366 ++++++++++++++++++++++++++++ 1 file changed, 366 insertions(+) create mode 100644 docs/LIVE_PREVIEW_IMPLEMENTATION.md diff --git a/docs/LIVE_PREVIEW_IMPLEMENTATION.md b/docs/LIVE_PREVIEW_IMPLEMENTATION.md new file mode 100644 index 0000000..af2b91e --- /dev/null +++ b/docs/LIVE_PREVIEW_IMPLEMENTATION.md @@ -0,0 +1,366 @@ +# Live Preview Implementation Guide + +## Overview + +Live Preview is the #1 priority feature for PNGer. This guide outlines the implementation approach. + +## Goals + +1. **Instant Feedback**: Show preview within 100ms of parameter change +2. **Accurate Rendering**: Match final output as closely as possible +3. **Performance**: Don't block UI, handle large images efficiently +4. **Progressive**: Show low-quality preview immediately, high-quality after + +## Architecture + +### Approach: Hybrid Client + Server Preview + +``` +┌─────────────┐ +│ Upload │ +│ Image │ +└──────┬──────┘ + │ + v +┌─────────────────────────────────┐ +│ Client-Side Preview (Canvas) │ <-- Instant (< 100ms) +│ - Fast, approximate rendering │ +│ - Uses browser native resize │ +│ - Good for basic operations │ +└─────────┬───────────────────────┘ + │ + v +┌─────────────────────────────────┐ +│ Server Preview API (Optional) │ <-- Accurate (500ms-2s) +│ - Uses Sharp (same as export) │ +│ - Exact output representation │ +│ - Debounced to avoid spam │ +└─────────────────────────────────┘ +``` + +## Implementation Steps + +### Phase 1: Client-Side Preview (Quick Win) + +**Files to Modify:** +- `frontend/src/App.svelte` +- `frontend/src/lib/preview.ts` (new) + +**Key Features:** +1. Canvas-based image rendering +2. Debounced updates (300ms after parameter change) +3. Show original and preview side-by-side +4. Display file size estimate + +**Code Skeleton:** + +```typescript +// frontend/src/lib/preview.ts +export async function generateClientPreview( + file: File, + options: TransformOptions +): Promise { + return new Promise((resolve) => { + const img = new Image(); + const canvas = document.createElement('canvas'); + const ctx = canvas.getContext('2d')! + + img.onload = () => { + // Calculate dimensions + const { width, height } = calculateDimensions(img, options); + + canvas.width = width; + canvas.height = height; + + // Apply transforms + if (options.fit === 'cover') { + drawCover(ctx, img, width, height, options.position); + } else { + ctx.drawImage(img, 0, 0, width, height); + } + + // Apply filters (grayscale, blur, etc.) + applyFilters(ctx, options); + + // Convert to data URL + const quality = options.quality / 100; + const dataUrl = canvas.toDataURL(`image/${options.format}`, quality); + resolve(dataUrl); + }; + + img.src = URL.createObjectURL(file); + }); +} +``` + +**UI Updates:** + +```svelte + + + + +{#if file && previewUrl} +
+
+
+

Original

+ Original +

{formatFileSize(originalSize)}

+
+
+

Preview

+ Preview +

{formatFileSize(previewSize)}

+

+ {calculateSavings(originalSize, previewSize)} +

+
+
+
+{/if} +``` + +### Phase 2: Server Preview API (Accurate) + +**Files to Modify:** +- `backend/src/routes/image.ts` + +**New Endpoint:** + +```typescript +// POST /api/preview (returns base64 or temp URL) +router.post( + "/preview", + upload.single("file"), + async (req, res): Promise => { + // Same processing as /transform + // But return as base64 data URL or temp storage URL + // Max preview size: 1200px (for performance) + + const previewBuffer = await image.toBuffer(); + const base64 = previewBuffer.toString('base64'); + + res.json({ + preview: `data:image/${format};base64,${base64}`, + size: previewBuffer.length, + dimensions: { width: metadata.width, height: metadata.height } + }); + } +); +``` + +**Benefits:** +- Exact rendering (uses Sharp like final output) +- Shows actual file size +- Handles complex operations client can't do + +**Trade-offs:** +- Slower (network round-trip) +- Server load (mitigate with rate limiting) + +### Phase 3: Progressive Loading + +**Enhancement**: Show low-quality preview first, then high-quality + +```typescript +// Generate two previews: +// 1. Immediate low-res (client-side, 200px max) +// 2. Delayed high-res (server-side, full resolution) + +async function generateProgressivePreview() { + // Step 1: Fast low-res + const lowRes = await generateClientPreview(file, { + ...options, + width: Math.min(options.width || 200, 200), + height: Math.min(options.height || 200, 200) + }); + previewUrl = lowRes; // Show immediately + + // Step 2: High-res from server (debounced) + const highRes = await fetchServerPreview(file, options); + previewUrl = highRes; // Replace when ready +} +``` + +## File Size Estimation + +```typescript +function estimateSize(dataUrl: string): number { + // Base64 data URL size (approximate) + const base64Length = dataUrl.split(',')[1].length; + return Math.ceil((base64Length * 3) / 4); // Convert base64 to bytes +} + +function formatFileSize(bytes: number): string { + if (bytes < 1024) return `${bytes} B`; + if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`; + return `${(bytes / (1024 * 1024)).toFixed(2)} MB`; +} + +function calculateSavings(original: number, preview: number): string { + const diff = original - preview; + const percent = ((diff / original) * 100).toFixed(1); + if (diff > 0) return `↓ ${formatFileSize(diff)} saved (${percent}%)`; + if (diff < 0) return `↑ ${formatFileSize(-diff)} larger (${Math.abs(Number(percent))}%)`; + return 'Same size'; +} +``` + +## UI/UX Considerations + +### Layout Options + +**Option A: Side-by-Side** +``` +┌──────────────┬──────────────┐ +│ Original │ Preview │ +│ │ │ +│ [Image] │ [Image] │ +│ 2.4 MB │ 450 KB │ +│ 1920x1080 │ 800x600 │ +└──────────────┴──────────────┘ +``` + +**Option B: Slider Compare** +``` +┌────────────────────────────┐ +│ [<──── Slider ────>] │ +│ Original │ Preview │ +│ │ │ +└────────────────────────────┘ +``` + +**Option C: Tabs** +``` +┌─ Original ─┬─ Preview ─────┐ +│ │ +│ [Image] │ +│ │ +└─────────────────────────────┘ +``` + +**Recommendation**: Start with Option A (simplest), add Option B later for detail comparison. + +## Performance Optimizations + +### 1. Debouncing +```typescript +function debounce any>( + func: T, + wait: number +): (...args: Parameters) => void { + let timeout: ReturnType; + return (...args: Parameters) => { + clearTimeout(timeout); + timeout = setTimeout(() => func(...args), wait); + }; +} +``` + +### 2. Image Downsampling +- Preview max size: 1200px (retina displays) +- Original size only for final download +- Reduces memory usage and processing time + +### 3. Worker Thread (Advanced) +- Offload canvas operations to Web Worker +- Keeps UI responsive during processing + +```typescript +// preview.worker.ts +self.onmessage = async (e) => { + const { file, options } = e.data; + const preview = await generatePreview(file, options); + self.postMessage({ preview }); +}; +``` + +## Testing Plan + +### Unit Tests +- [ ] `calculateDimensions()` with various aspect ratios +- [ ] `formatFileSize()` edge cases +- [ ] `debounce()` timing + +### Integration Tests +- [ ] Preview updates on parameter change +- [ ] Preview matches final output (within tolerance) +- [ ] Large image handling (> 10MB) +- [ ] Multiple format conversions + +### Manual Tests +- [ ] Mobile responsiveness +- [ ] Slow network simulation +- [ ] Various image formats (PNG, JPEG, WebP) +- [ ] Edge cases (1x1px, 10000x10000px) + +## Rollout Strategy + +### Step 1: Feature Flag +```typescript +// Enable via environment variable +const ENABLE_PREVIEW = import.meta.env.VITE_ENABLE_PREVIEW === 'true'; +``` + +### Step 2: Beta Testing +- Deploy to staging environment +- Gather user feedback +- Monitor performance metrics + +### Step 3: Gradual Rollout +- Enable for 10% of users +- Monitor error rates +- Full rollout if stable + +## Success Metrics + +- **User Engagement**: Time spent on page increases +- **Conversion**: More downloads completed +- **Performance**: Preview renders in < 500ms (p95) +- **Accuracy**: Preview matches output 95%+ of time +- **Satisfaction**: User feedback positive + +## Future Enhancements + +- [ ] Before/after slider with drag handle +- [ ] Zoom on preview (inspect details) +- [ ] Multiple preview sizes simultaneously +- [ ] A/B comparison (compare 2-4 settings) +- [ ] Preview history (undo/redo preview) +- [ ] Export preview settings as preset + +--- + +**Estimated Effort**: 2-3 days for Phase 1 (client preview) +**Complexity**: Medium +**Impact**: ⭐⭐⭐⭐⭐ (Highest) + +**Next Steps**: +1. Create feature branch `feature/live-preview` +2. Implement client-side preview +3. Add UI components +4. Test thoroughly +5. Merge to main +6. Deploy and monitor \ No newline at end of file