back to portfolio
Case Study 05  ·  batch image tool

BatchPhotos Online, framing every photo, all at once

A free web tool that lets you upload dozens of photos at once, apply a branded template overlay to every image, and export the entire batch as individual PNGs or a single ZIP file, in seconds. Built because I watched a workmate apply overlays, frames, and watermarks to photos one by one for Facebook posts.

Role
Sole engineer, product, design, build, deployment
Stack
JavaScript, Canvas API, TUS (resumable uploads), ZIP export
Status
batchphotos.online, live, free to use

The starting line

I was at the office when I noticed a workmate editing photos for Facebook posts. The workflow was simple and painfully manual: open a photo, paste an overlay, resize it, position the frame, add a watermark, save, close, open the next one. Repeat for every image in the batch.

The work was repetitive and time-consuming, especially when handling large batches of images. The kind of task that makes you think there has to be a better way, and then you realize the better way doesn't exist yet. So I built it.

What it needed to do

The core idea was straightforward: upload multiple photos, apply the same treatment to all of them, and export everything at once. But the specifics mattered.

Architecture in one picture

flowchart TB
    subgraph browser["Browser (Client)"]
        direction LR
        upload["Upload (TUS)"]
        canvas["Canvas Rendering"]
        zip["ZIP Export"]
    end

    upload --> canvas --> zip

    style browser fill:#ece9e2,stroke:#bdb5a6,color:#2b2b25
    style upload fill:#ece9e2,stroke:#bdb5a6,color:#2b2b25
    style canvas fill:#ece9e2,stroke:#bdb5a6,color:#2b2b25
    style zip fill:#ece9e2,stroke:#bdb5a6,color:#2b2b25
        

The key architectural decision: everything runs in the browser. No uploaded image ever touches a server for processing. The Canvas API handles compositing, the browser handles memory, and the ZIP is assembled client-side using a streaming library. The server only stores the files, it never sees pixels.

Resumable uploads with TUS

The first version used standard multipart uploads. It worked for small photos. It failed for large batches on slow connections, a single dropped connection meant re-uploading everything from the start.

The fix was the TUS protocol, the open standard for resumable file uploads. When a user selects 50 photos, each one uploads in chunks. If the connection drops at chunk 7 of 12, the next request resumes at chunk 8 instead of starting over. On slow or unstable connections, this is the difference between a tool that works and a tool that frustrates.

// Upload flow
1. User selects photos
2. Each photo gets a TUS upload session
3. Chunks resume on connection drop
4. Server stores originals in object storage
5. Client-side Canvas renders the final output

Canvas-based image processing

The rendering pipeline runs entirely in the browser. When the user clicks export, for each photo the client:

  1. Loads the original image into an offscreen Canvas element.
  2. Applies the editing adjustments, exposure, contrast, highlights, shadows, clarity, dehaze, vignette, using pixel-level Canvas operations.
  3. Composites the overlay template on top, respecting portrait and landscape orientation.
  4. Resizes to the target dimensions if specified.
  5. Exports the Canvas content as a PNG blob.

Running this on the client means zero server costs for processing, instant preview of every change, and no waiting in a queue. The tradeoff is that the browser's memory is the ceiling, but for the batch sizes this tool targets (dozens, not thousands), the browser handles it comfortably.

Auto orientation detection

Photos come in all orientations. A portrait photo needs a different template placement than a landscape one. The tool detects each image's orientation from its EXIF data and dimensions, then applies the matching template variant automatically. The user doesn't have to think about it, they upload a mixed batch and get consistent results.

Lightroom-style editing

The editing panel gives users fine-grained control over the final output. Exposure, contrast, highlights, shadows, clarity, dehaze, vignette, and split toning for highlights and shadows independently. There are also 17 built-in presets across 5 categories for users who want quick results without manual tweaking.

The editing state is reactive, every slider change re-renders the preview canvas in real time. Users see exactly what they'll get before they export.

ZIP batch export with progress

Exporting 50 photos as individual PNGs means 50 separate downloads. That's not a workflow, that's a chore. The ZIP export bundles everything into a single download with a progress indicator showing which photo is being processed and how many are left.

The ZIP is assembled streaming, as each photo finishes rendering on the Canvas, it gets appended to the ZIP immediately. The user sees progress incrementing in real time instead of waiting for all 50 to render before anything downloads.

What broke, or surprised me

Memory limits on large batches

The first version tried to render all photos simultaneously. The browser crashed on batches larger than about 20 images. The fix was sequential rendering, process one photo at a time, flush the Canvas, move to the next. Slower in theory, but it actually completes.

Template alignment across orientations

Portrait and landscape photos need different template placements, but users upload mixed batches. I initially tried a single template with auto-scaling, which produced misaligned frames on edge cases. The solution was separate template variants per orientation, uploaded once and applied automatically based on each image's dimensions.

Large file uploads on mobile

Mobile users uploading high-resolution photos from their camera roll hit upload timeouts more often than desktop users. TUS solved the resumability problem, but I also added client-side compression as an option, downscale to a configurable maximum dimension before upload. Most users don't need 24-megapixel originals for a Facebook post.

What it does today

What I'd do differently next time

What I take away

The most valuable thing about this project is not the image processing or the upload pipeline. It's that it started from watching someone do boring, repetitive work and thinking I can fix that. The technical decisions, client-side rendering, TUS uploads, streaming ZIP export, all served one goal: make a tedious task disappear.

Need a custom tool for your workflow? I build web apps that automate repetitive work. Open to remote roles + select contracts.
Start a conversation