Photo Merge — C# 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 C# implementation — the same logic the interactive tool runs, in a shareable, citable form.
// photo-merge — C# port: side-by-side merge cell layout.
using System;
using System.Collections.Generic;
using System.Linq;
// A decoded image's dimensions. In the browser tool the bitmap is
// drawImage'd into its letterboxed cell; the geometry here is the
// deterministic part of that pipeline.
readonly struct ImageSize
{
public ImageSize(int width, int height) => (Width, Height) = (width, height);
public int Width { get; }
public int Height { get; }
}
// Where one image lands on the merged canvas.
readonly struct Placement
{
public Placement(double scale, int x, int y, int w, int h) =>
(Scale, X, Y, W, H) = (scale, x, y, w, h);
public double Scale { get; }
public int X { get; }
public int Y { get; }
public int W { get; }
public int H { get; }
}
static class PhotoMerge
{
// JS Math.round rounds half up; Math.Round(away-from-zero) matches
// it for the positive coordinates and dimensions used here.
static int Round(double v) => (int)Math.Round(v, MidpointRounding.AwayFromZero);
// Mirrors mergeSideBySide in typescript.ts: cellW/cellH are the
// largest input dimensions, the canvas is n cells + (n + 1) gaps
// wide and one cell + 2 gaps tall, and each image is scaled by
// min(cellW / w, cellH / h) and centered inside its cell.
public static (int CanvasW, int CanvasH, List<Placement> Placements)
MergeLayout(IReadOnlyList<ImageSize> images, int gap = 12)
{
int n = images.Count;
int cellW = images.Max(i => i.Width);
int cellH = images.Max(i => i.Height);
var placements = new List<Placement>(n);
for (int i = 0; i < n; i++)
{
double scale = Math.Min(
cellW / (double)images[i].Width,
cellH / (double)images[i].Height);
int w = Round(images[i].Width * scale);
int h = Round(images[i].Height * scale);
placements.Add(new Placement(
scale,
gap + i * (cellW + gap) + Round((cellW - w) / 2.0),
gap + Round((cellH - h) / 2.0),
w, h));
}
return (n * cellW + (n + 1) * gap, cellH + 2 * gap, placements);
}
}
class Program
{
static void Main()
{
// 800x600 + 600x800 + 1024x768 with a 12px gap -> 3120x824 canvas.
var (canvasW, canvasH, placements) = PhotoMerge.MergeLayout(
new[] { new ImageSize(800, 600), new ImageSize(600, 800), new ImageSize(1024, 768) });
Console.WriteLine($"canvas {canvasW}x{canvasH}");
for (int i = 0; i < placements.Count; i++)
{
var p = placements[i];
Console.WriteLine($"image {i}: ({p.X}, {p.Y}) {p.W}x{p.H} scale={p.Scale:0.####}");
}
}
}
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 →