Developer Docs

Image Editing & Processing Pipeline โ€” Developer Guide

Overview

The Image Editing & Processing Pipeline provides two complementary capabilities:

  1. Processing Pipeline Visualization โ€” Granular stage indicators during image uploads (Uploading โ†’ Optimizing โ†’ Thumbnails โ†’ LQIP โ†’ Background Removal โ†’ Complete)
  2. 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

Usage in MediaUploader

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

Tools

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

MediaUploader Enhancements

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}`)
});