Image Editing & Processing Pipeline โ Developer Guide
Overview
The Image Editing & Processing Pipeline provides two complementary capabilities:
- Processing Pipeline Visualization โ Granular stage indicators during image uploads (Uploading โ Optimizing โ Thumbnails โ LQIP โ Background Removal โ Complete)
- Post-Upload Image Editing โ Client-side crop, rotate, flip, and resize tools via a modal editor that re-uploads through the existing presigned URL flow
All image transformations happen client-side using the Canvas API. The server's sharp pipeline handles final optimization (WebP conversion, thumbnail generation, LQIP) on re-upload via confirmUpload.
Architecture
graph LR
A["User clicks Edit"] --> B["ImageEditorModal opens"]
B --> C["Canvas API loads image"]
C --> D{Tool Selection}
D -->|Crop| E["CropOverlay.svelte"]
D -->|Rotate| F["rotateCanvas() / flipCanvas()"]
D -->|Resize| G["resizeCanvas()"]
E --> H["canvasToFile() โ JPEG"]
F --> H
G --> H
H --> I["uploadFile() with stages"]
I --> J["requestUploadUrl"]
J --> K["PUT to S3"]
K --> L["confirmUpload"]
L --> M["Server: sharp pipeline"]
M --> N["WebP + Thumbnails + LQIP"]
N --> O["Updated image in gallery"]
style B fill:#667eea,color:#fff
style M fill:#22c55e,color:#fff
Processing Pipeline Stages
The upload flow emits stage transitions for UI feedback. The backend processes these atomically during confirmUpload, but the frontend simulates transitions for a polished user experience.
| Stage |
Label |
Description |
UPLOADING |
Uploading to cloud |
File is being PUT to S3 via presigned URL |
OPTIMIZING |
Optimizing image |
Server converts to WebP, auto-rotates EXIF |
THUMBNAILS |
Generating thumbnails |
Server creates 3 sizes (150px, 300px, 600px) |
BLUR_PLACEHOLDER |
Creating placeholder |
Server generates 20px LQIP data URI |
BACKGROUND_REMOVAL |
Removing background |
AI model processes image (only if enabled) |
COMPLETE |
Complete |
All processing finished |
The pipeline indicator renders as a horizontal dot array inside the upload progress card:
- Green dot = completed stage
- Pulsing accent dot = active stage
- Gray dot = pending stage
Component Reference
ImageEditorModal
Full-featured modal image editor opened from the MediaUploader's edit button.
Props
| Prop |
Type |
Default |
Description |
src |
string |
โ |
Source image URL to edit |
filename |
string |
'edited-image' |
Filename for the exported file |
mediaId |
string? |
โ |
Optional media file ID for tracking |
maletId |
string? |
โ |
Malet ID for upload scoping |
onSave |
(newImage) => void |
โ |
Callback with new image data after re-upload |
onClose |
() => void |
โ |
Close callback |
| Tool |
Controls |
Implementation |
| Crop |
Aspect ratio presets (Free, 1:1, 4:3, 3:4, 16:9, 9:16), draggable selection with handles |
CropOverlay.svelte + cropCanvas() |
| Rotate |
90ยฐ CW, 90ยฐ CCW, Flip H, Flip V |
rotateCanvas() + flipCanvas() |
| Resize |
Width/Height inputs, aspect lock, percentage presets (25% โ 200%) |
resizeCanvas() + calculateAspectDimensions() |
CropOverlay
SVG-based interactive crop selection rendered over the image preview.
Props
| Prop |
Type |
Default |
Description |
containerWidth |
number |
โ |
Display width of the preview container |
containerHeight |
number |
โ |
Display height of the preview container |
imageWidth |
number |
โ |
Natural width of the source image |
imageHeight |
number |
โ |
Natural height of the source image |
aspectRatio |
number | null |
null |
Locked aspect ratio (null = free) |
oncrop |
(region) => void |
โ |
Callback with { x, y, width, height } in natural pixels |
Features
- 8 drag handles (corners + edges)
- Rule-of-thirds grid overlay
- Dark mask outside selection
- Dimension badge in natural pixels
- Touch support for mobile
- Constrained to canvas bounds
The existing MediaUploader component now includes:
| Feature |
Description |
| Pipeline indicators |
Dot-based stage visualization during uploads |
| Edit button |
โ๏ธ icon overlay on each image card (opens ImageEditorModal) |
| Drag-to-reorder |
Native HTML5 drag-and-drop for reordering images |
| Position badges |
Numbered badges on hover showing image order |
| Reorder hint |
"Drag to reorder" text when gallery has 2+ images |
Utility Functions
`imageProcessingUtils.ts`
| Function |
Signature |
Description |
getPipelineStages |
(removeBackground: boolean) => ProcessingStage[] |
Get ordered stage list |
getStageLabel |
(stage: ProcessingStage) => string |
Human-readable label |
getStageIcon |
(stage: ProcessingStage) => string |
Icon identifier |
estimateStageProgress |
(stage, removeBackground?) => number |
Progress percentage (0โ100) |
getStageIndex |
(stage, removeBackground?) => number |
Stage index in pipeline |
createCanvasFromImage |
(src: string) => Promise<{canvas, img}> |
Load image into canvas |
cropCanvas |
(canvas, x, y, w, h) => HTMLCanvasElement |
Crop to rectangle |
rotateCanvas |
(canvas, degrees) => HTMLCanvasElement |
Rotate by 90ยฐ increments |
flipCanvas |
(canvas, direction) => HTMLCanvasElement |
Flip H or V |
resizeCanvas |
(canvas, w, h) => HTMLCanvasElement |
Scale with bilinear interpolation |
canvasToFile |
(canvas, name, mime?, quality?) => Promise<File> |
Export to File object |
calculateAspectDimensions |
(origW, origH, newW?, newH?) => {w, h} |
Aspect-ratio-locked calculations |
Constants
| Constant |
Value |
Description |
MAX_EXPORT_DIMENSION |
4096 |
Maximum export dimension to prevent memory issues |
ASPECT_RATIO_PRESETS |
Array |
Crop aspect ratio options with labels |
File Reference
| File |
Purpose |
src/lib/utils/imageProcessingUtils.ts |
Pipeline stages, canvas utilities, dimension calculations |
src/lib/components/manage/ImageEditorModal.svelte |
Modal editor with crop/rotate/resize tools |
src/lib/components/manage/CropOverlay.svelte |
SVG-based interactive crop selection overlay |
src/lib/components/manage/MediaUploader.svelte |
Enhanced uploader with pipeline, editing, and reorder |
src/lib/media.ts |
Upload flow with onStageChange callback |
src/lib/queries/media.ts |
GraphQL mutations (requestUploadUrl, confirmUpload) |
tests/imageProcessing.test.ts |
Unit tests for pipeline stages and calculations |
e2e/image-editor.test.ts |
E2E tests for editor UI and pipeline indicators |
Integration Examples
Opening the Editor from Custom Code
<script lang="ts">
import ImageEditorModal from '$lib/components/manage/ImageEditorModal.svelte';
let showEditor = $state(false);
function handleSave(newImage: { id?: string; url: string; blurDataUrl?: string }) {
console.log('Edited image:', newImage.url);
showEditor = false;
}
</script>
{#if showEditor}
<ImageEditorModal
src="https://cdn.example.com/image.jpg"
filename="product-hero"
maletId="malet-123"
onSave={handleSave}
onClose={() => { showEditor = false; }}
/>
{/if}
Listening for Pipeline Stage Changes
import { uploadFile } from '$lib/media';
const result = await uploadFile(file, {
maletId: 'malet-123',
removeBackground: true,
onProgress: (percent) => console.log(`Upload: ${percent}%`),
onStageChange: (stage) => console.log(`Stage: ${stage}`)
});