Skip to content

Photo Merge — TypeScript source

Combine photos side by side, stacked, or in a grid — uniform cells, adjustable gap, one PNG out. Runs entirely in your browser.

This is the TypeScript implementation — the same logic the interactive tool runs, in a shareable, citable form.

// Photo merge — display snippet. The live tool adds stacked/grid layouts and
// gap control; canvas-bound, so no Go twin.
export async function mergeSideBySide(files: File[], gap = 12): Promise<Blob> {
  const bitmaps = await Promise.all(files.map((f) => createImageBitmap(f)));
  const cellH = Math.max(...bitmaps.map((b) => b.height));
  const cellW = Math.max(...bitmaps.map((b) => b.width));
  const canvas = document.createElement('canvas');
  canvas.width = bitmaps.length * cellW + (bitmaps.length + 1) * gap;
  canvas.height = cellH + 2 * gap;
  const ctx = canvas.getContext('2d')!;
  ctx.fillStyle = '#0f172a';
  ctx.fillRect(0, 0, canvas.width, canvas.height);

  bitmaps.forEach((bmp, i) => {
    const scale = Math.min(cellW / bmp.width, cellH / bmp.height);
    const w = Math.round(bmp.width * scale);
    const h = Math.round(bmp.height * scale);
    ctx.drawImage(
      bmp,
      gap + i * (cellW + gap) + Math.round((cellW - w) / 2),
      gap + Math.round((cellH - h) / 2),
      w,
      h,
    );
  });

  return new Promise<Blob>((resolve, reject) =>
    canvas.toBlob((b) => (b ? resolve(b) : reject(new Error('encode failed'))), 'image/png'),
  );
}

Also available in 8 other languages

Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →