Skip to main content

Batch Image Resizing in the Browser: Canvas, Blobs, ZIP

A single resize is one drawing call. A batch is a pipeline — and memory, not drawing, is where it breaks.

Updated 7 min readMarkdown

Batch Image Resizing in the Browser: Canvas, Blobs, ZIP

A single-image resizer is a drawing call. A batch resizer is a pipeline: read ten files, decode ten images, draw ten canvases, encode ten outputs, pack them into one archive, and do it all without uploading anything or freezing the tab. The drawing call is the easy part. Everything around it is where batch tools earn their keep — or fall over.

Disclosure: I built the free image resizer at https://toolkitloop.com/image-resizer, which runs entirely in the browser. This post is about the pipeline inside that kind of tool, not a tour of its buttons. The code below is illustrative, not a dump of the tool's source.

The pipeline, end to end

Strip a browser-local batch resizer to its skeleton and you get five stages:

  1. Read each file locally, with no network request anywhere in the chain.
  2. Decode it into something drawable — typically an image bitmap.
  3. Draw it onto a canvas at the target size, applying the Fit, Crop, or Pad geometry.
  4. Encode the canvas back into a file, in the chosen output format.
  5. Collect the outputs and offer one download — a ZIP when there are several.

Stages 1 and 5 are the ones users notice. Stage 1 is why local tools feel instant to start: there is no upload queue, no progress bar for a transfer that never happens. Stage 5 is why a batch tool lives or dies on its download story: ten separate downloads mean ten save dialogs, and most users will call that broken even if every resize was perfect. One ZIP is not a nice extra. It is the batch feature.

Geometry is decided before drawing

The geometry still applies, but in a batch it matters more, because one wrong mode choice is multiplied by ten. For a source of sw x sh and a target of tw x th:

function fitRect(sw, sh, tw, th, mode) {
  const scale = mode === 'crop'
    ? Math.max(tw / sw, th / sh)
    : Math.min(tw / sw, th / sh);
  const dw = sw * scale, dh = sh * scale;
  // Fit: draw at (dw, dh), centred, leaving empty space.
  // Crop: draw at (dw, dh), centred, letting edges overflow the canvas.
  // Pad: Fit, then fill the background first so the box ends up full.
  return { dw, dh };
}

Fit uses the smaller ratio, Crop the larger, Pad is Fit plus a background fill. The function is six lines. The product decision is whether the user sees and chooses the mode — in a batch, silently defaulting to Crop can ruin nine good photos with one assumption. Previewing the first result before committing the batch is the cheapest correctness check in the whole pipeline.

Memory: the real batch constraint

Batch resizing has a constraint that single-image resizing hides: memory. Each decoded image, each canvas, and each encoded blob costs memory at the same time if you hold them all. Ten phone photos decoded at full resolution can easily exceed what a tab should hold, especially on a low-memory device.

The standard answer is streaming discipline, not cleverness. Process images sequentially rather than decoding all ten up front. Release each source bitmap after its canvas is drawn. Revoke object URLs once they are no longer needed. Only accumulate the encoded outputs — which are much smaller than decoded bitmaps — until the ZIP is assembled. A pipeline that holds decoded bitmaps for the whole batch will work fine on a desktop with ten small images and fall over on a phone with ten large ones. The failure mode is instructive: it is not a wrong resize, it is a tab that slows down, then crashes, with no error message worth reading.

Sequential processing has a second benefit: it keeps the tab responsive. Decoding and encoding are the expensive steps, and yielding between images lets the browser paint progress instead of freezing until the ZIP appears. Users interpret a frozen tab as a broken tool long before they interpret a slow one that way.

Encoding is a second decision hiding inside the first

Resizing almost always re-encodes, and the output format changes the result in ways that look like resize bugs but are not. Three cases cover most reports:

  • JPEG to PNG: the file often gets much bigger, because PNG is lossless and photographic noise that JPEG had compressed away now has to be stored exactly.
  • PNG with transparency to JPEG: the transparency is gone, usually replaced by black or white, because JPEG has no alpha channel.
  • WebP as an output: often smaller than either for photographic content, but older tooling downstream may not accept it, so the "best" format depends on where the file goes next.

A batch tool should therefore treat format as a visible choice, not a hidden default, and it should warn rather than silently convert transparency away. The same applies to metadata: resized exports from a browser pipeline are created without original metadata where the pipeline supports it, which is a genuine privacy benefit — but "where supported" is doing real work in that sentence, and a tool that promises unconditional stripping is promising more than the platform guarantees.

Why no-upload changes the architecture

The no-upload property is not just a privacy slogan. It removes an entire class of infrastructure: no upload endpoint, no server-side image library, no storage bucket, no job queue, no download link expiry. The pipeline above runs on APIs that ship in the browser — file reading, canvas drawing, blob encoding, and client-side archive assembly.

The trade-off is equally structural. There is no server to retry a failed job, no stored project to resume, and no processing power beyond the user's device. A batch that would be trivial on a server with gigabytes of RAM must be disciplined about memory in a tab. That discipline — sequential processing, prompt release of bitmaps, accumulating only encoded outputs — is the difference between a batch tool that feels instant and one that feels fragile, even though both call the same drawing API.

A checklist for evaluating any batch resizer

If you are choosing or building one, these are the questions that actually discriminate:

  • Does it preview the first result before committing the batch, so a wrong mode choice costs one image instead of ten?
  • Does it download one ZIP, or ten separate files?
  • Does it process sequentially and release bitmaps, or hold everything decoded at once?
  • Is the output format a visible choice, with transparency loss called out before it happens?
  • Does it keep your originals untouched, so a bad batch is recoverable?

A tool that answers all five is doing the unglamorous work. The drawing call was never the hard part. The pipeline around it was.

Try the workflow on a real batch at https://toolkitloop.com/image-resizer — up to ten JPG, PNG, or WebP images, exact pixels or percentage, Fit, Crop, or Pad, one ZIP download, no upload and no account. And if you have built a batch pipeline yourself: where did it break first, memory or encoding? I would bet on memory, but I have been wrong about that before.

About the author

  • Founder of ToolkitLoop (toolkitloop.com) — 30 free online tools that run 100% in your browser. No sign-ups, no uploads, private by design.

No comments yet. Be the first to comment.