Selected work

Case study / Browser / WebAssembly / accessibility

Image Converter

How do you make batch image conversion private and usable within the browser's memory and interface limits?

A released browser image converter that uses a WebAssembly codec and separate workers while keeping files on the device.

Project type
software
Contribution
Built the browser interface, processing pipeline, and release
Status
v1.0.0 released and deployed
Context
Independent browser product
Image Converter interface with a completed sample image and WebP output settings
Browser interface with a sample image conversion

Key decision: Keep source Files, conversion, and completed Blobs in the browser. The deployed Vite application is static and has no upload API.

Start here

Overview

The engineering problem

Image conversion must decode large, sometimes malformed files while the interface keeps accepting input, explaining failures, and offering usable downloads. In a browser, the same device supplies the codec memory, queue storage, and responsive UI.

Contribution

I combined a React queue, content-based format detection, worker-hosted ImageMagick WebAssembly, bounded batch processing, accessible controls, and a static Vercel release.

What exists now

Image Converter has a public v1.0.0 release and a live Vercel deployment. Its source and automated checks show the codec, worker, accessibility, and resource decisions behind a shipped browser product.

Inspect the work

Engineering detail

Constraints

  • Image bytes stay in the browser. The Vite app has no upload API, account, database, or server processing path.
  • Source files, decoded pixels, retained outputs, and ZIP assembly compete for browser memory.
  • The advertised format matrix must reflect what the distributed codec actually reads and writes.
  • Keyboard access, clear error feedback, narrow layouts, and browser differences remain part of the product.

Architecture at a glance

Picker, drop, and paste feed content sniffing and a React queue on the main thread. The queue sends transferable bytes to a conversion worker, which loads ImageMagick WebAssembly on first inspection. Completed Blobs return to the application for individual download or pass to a separate, on-demand ZIP worker. There is no file upload path.

Image Converter browser boundaries

File input passes through content-based validation into React queue state. A conversion worker uses the lazily loaded ImageMagick WebAssembly codec and returns output to the interface. Completed output can download directly or be assembled by a separate ZIP worker. All stages run on the user's device.

Text explanation

  • File input: The picker, drop zone, and clipboard paste provide local File objects.
  • Validation: Content signatures identify inputs. File, batch, dimension, and settings limits are checked before conversion and again at the worker boundary.
  • UI and queue: React owns files, settings, job status, progress, completed Blobs, and object URL cleanup.
  • Download: The interface creates individual downloads and receives a finished ZIP for batch download.
  • Codec worker: A module worker inspects and converts one job at a time. Cancellation terminates and recreates it.
  • WASM codec: ImageMagick WebAssembly decodes, transforms, and encodes the supported format matrix after lazy loading.
  • ZIP worker: A separate worker starts on demand and packages eligible completed Blobs with ZIP STORE.

Connections: File input to Validation (local files); Validation to UI and queue (accepted jobs); UI and queue to Codec worker (inspect and convert); Codec worker to WASM codec (decode and encode); Codec worker to UI and queue (progress and output bytes); UI and queue to ZIP worker (completed Blobs); UI and queue to Download (individual files); ZIP worker to Download (archive).

Boundaries: Main thread; Worker threads.

Annotations: Browser only. No upload path.

Key decisions

Decision 01

Where should image processing happen?

Uploading source images would add a server and transfer private files before conversion could begin.

Selected
Keep source Files, conversion, and completed Blobs in the browser. The deployed Vite application is static and has no upload API.
Alternatives
Upload files to a server-side conversion service
Trade-off
The user's device supplies CPU and memory, so large or difficult files may fail even within configured limits.
Consequence
The product can offer conversion without an account or server processing, while making browser resource constraints explicit.

Decision 02

Which codec should support the advertised format matrix?

Native browser encoders do not provide the complete input and output matrix, including HEIC input and TIFF and ICO output.

Selected
Put @imagemagick/magick-wasm behind a module worker and load its hashed WASM asset when inspection first needs it. Advertise only formats verified against the distributed codec.
Alternatives
Combine smaller format-specific @jsquash codecs and additional decoders; Add a separate HEIC decoder
Trade-off
The broad codec brings a large initial download and WebAssembly memory cost.
Consequence
One adapter handles orientation, resize, alpha flattening, metadata stripping, and encoding across the verified formats. The codec integration test checks output signatures and HEIC input.

Decision 03

How should expensive conversion and archive work interact with the interface?

Inspection, encoding, and ZIP creation can take long enough to interfere with controls and progress feedback on the main thread.

Selected
Transfer image bytes to a codec worker for inspection and sequential conversion. Start a separate ZIP worker only when a batch archive is requested. Terminate and recreate the codec worker on cancellation.
Alternatives
Run codec and ZIP work on the main UI thread; Keep an archive worker running throughout the session
Trade-off
Worker protocols, transferable buffers, cancellation, and cleanup add coordination code. ZIP assembly temporarily duplicates output data.
Consequence
React can continue reporting job state and errors while workers perform heavy work, and archive creation is limited to eligible completed outputs.

Decision 04

What limits keep a browser batch practical?

Compressed images can expand sharply in memory, and retaining originals, previews, outputs, WASM state, and an archive compounds usage.

Selected
Cap the queue at 30 files, each source at 50 MiB, decoded and output images at 40 megapixels, output axes at 8192 px, retained sources and outputs at 150 MiB each, and ZIP inputs at 75 MiB. Convert sequentially and revoke unused object URLs.
Alternatives
Accept unbounded batches and dimensions; Convert queued files concurrently
Trade-off
Some legitimate large batches require smaller groups or individual downloads, and the caps cannot guarantee success on every device.
Consequence
The interface can reject known unsafe requests early, while the worker and codec adapter check their own boundaries before encoding.

Reliability and quality

Unit and component checks
Vitest covers queue and file utilities, detection, resize and validation rules, worker clients, ZIP rules, React components, and application states including errors and reconversion.
Codec integration
The distributed ImageMagick WASM is exercised against advertised encoders, file signatures and dimensions, HEIC decoding, EXIF orientation, transparency fill, corrupted input, and unsafe resize requests.
Browser and accessibility checks
Playwright tests the production build in desktop and mobile Chromium, Firefox, and WebKit. Scenarios include conversion and download, ZIP naming, keyboard order, invalid input, narrow reflow, themes, and axe checks at several states.
Delivery gates
GitHub Actions runs format, lint, type, Vitest, build, bundle-size, and multi-browser Playwright checks on pushes to main and pull requests. Vercel serves the static build with configured security headers.

Trace the implementation

What next

The next useful evidence would be measured conversion and memory behaviour across representative devices. The published roadmap treats offline support and any server processing as separate future work.

Scope of the evidence

Limitations

  • Browser memory remains device dependent. Compressed images and the WASM runtime can exhaust a tab despite the 30-file, byte, dimension, and ZIP guardrails.
  • The first inspection fetches a large, lazily loaded WASM asset. AVIF encoding can be slow, and offline use is unsupported.
  • Animation and multipage content is reduced to the first frame or page. SVG input, animated output, and multipage preservation are unsupported. HEIC/HEIF is input only and ICO is output only.
  • Output metadata and colour profiles can change across formats. The app does not offer source previews for TIFF or HEIC, and clipboard read access requires permission.