npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

image-grid-combine

v1.0.0

Published

Combine multiple images into a single grid image with the Canvas API. Dependency-free, works in any modern browser.

Readme

image-grid-combine

Combine several images into one grid image using the Canvas API.

No dependencies, no build step, no server round-trip — the images are composited in the browser and you get back a Blob or a data URL.

npm install image-grid-combine

Usage

import { combineToBlob } from 'image-grid-combine';

const blob = await combineToBlob({
  sources: [fileA, fileB, fileC, fileD], // File, Blob, URL, <img>, canvas, ImageBitmap
  columns: 2,
  gap: 12,
  padding: 24,
  background: '#0b0b0f',
  fit: 'cover',
});

preview.src = URL.createObjectURL(blob);

Every source is accepted in whatever shape you already have it: a File from an <input type="file">, a Blob from fetch, a URL string, an HTMLImageElement, a canvas, or an ImageBitmap. They are loaded in parallel and drawn in the order you pass them.

API

combineToBlob(options) => Promise<Blob>

Composites the images and resolves with an encoded Blob. This is the usual entry point — hand the blob to URL.createObjectURL, a FormData, or a download link.

combineToDataURL(options) => Promise<string>

Same thing, but resolves with a data: URL.

combineToCanvas(options) => Promise<HTMLCanvasElement | OffscreenCanvas>

Returns the raw canvas if you want to keep drawing on it. Uses OffscreenCanvas when the browser has it, so it also runs inside a worker.

Options

| Option | Type | Default | Notes | | --- | --- | --- | --- | | sources | ImageSource[] | — | Required, at least one image. | | columns | number | square-ish | Columns per row. Left unset, the grid stays as square as the image count allows. | | gap | number | 0 | Pixels between cells. | | padding | number | 0 | Pixels around the outside of the grid. | | background | string \| null | '#ffffff' | Any CSS colour. null leaves the gaps transparent (PNG only). | | cellSize | number \| {width, height} | auto | Pin the cell size. Left unset, cells take the size of the largest source so nothing is upscaled. | | fit | 'cover' \| 'contain' \| 'stretch' | 'cover' | How each image fills its cell. | | maxWidth | number | 2048 | Upper bound used when the cell size is auto. | | format | string | 'image/png' | Blob/data URL output only. | | quality | number | 0.92 | Blob/data URL output only; ignored for PNG. |

fit modes

  • cover (default) fills the cell and crops the overflow — no letterboxing, no distortion. Good for photo grids where cells must line up.
  • contain fits the whole image inside the cell and letterboxes the rest, so nothing is ever cropped.
  • stretch fills the cell exactly, aspect ratio be damned.

Layout helpers

The geometry is separated from the drawing, so you can compute a layout without touching a canvas — useful for previews, drag-and-drop targets, or running the same maths in a worker or on the server.

import { autoCellSize, computeGridLayout, fitRect } from 'image-grid-combine/layout';

const { cellWidth, cellHeight, columns } = autoCellSize(
  [{ width: 1600, height: 900 }, { width: 900, height: 1600 }],
  { gap: 8, maxWidth: 1200 },
);

const layout = computeGridLayout({ count: 2, columns, cellWidth, cellHeight, gap: 8 });
// layout.cells -> [{ index, x, y, width, height }, ...]

const rect = fitRect({ sourceWidth: 1600, sourceHeight: 900, boxWidth: 600, boxHeight: 600 });
// rect -> { sx, sy, sw, sh, dx, dy, dw, dh } ready for ctx.drawImage

computeGridLayout and fitRect are pure functions with no DOM access, and they are covered by the test suite:

npm test

Notes and limits

  • Everything happens client-side. Images loaded from another origin need CORS headers, otherwise the canvas is tainted and encoding throws — the library sets crossOrigin="anonymous" for you on URL sources, but the server still has to agree.
  • Very large grids are bounded by maxWidth (2048px by default) because browsers cap canvas dimensions, and Safari's cap is the lowest of the mainstream engines.
  • SVG sources rasterise at their intrinsic size.

Related

  • Lumfuse — a browser-based AI image combiner for when you would rather not write code: drop in two or more photos and merge them with AI blending, no install and no signup.

License

MIT