Photo Merge — Ruby 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 Ruby implementation — the same logic the interactive tool runs, in a shareable, citable form.
# photo-merge — Ruby port: side-by-side merge cell layout.
module PhotoMerge
# 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.
Image = Struct.new(:width, :height)
# Where one image lands on the merged canvas.
Placement = Struct.new(:scale, :x, :y, :w, :h) do
def to_s = "(#{x}, #{y}) #{w}x#{h} scale=#{scale.round(4)}"
end
# 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. Float#round
# rounds half away from zero, which matches JS Math.round for the
# positive values used here.
def self.merge_layout(images, gap = 12)
cell_w = images.map(&:width).max
cell_h = images.map(&:height).max
n = images.length
placements = images.each_with_index.map do |img, i|
scale = [cell_w.to_f / img.width, cell_h.to_f / img.height].min
w = (img.width * scale).round
h = (img.height * scale).round
Placement.new(
scale,
gap + i * (cell_w + gap) + ((cell_w - w) / 2.0).round,
gap + ((cell_h - h) / 2.0).round,
w,
h,
)
end
[n * cell_w + (n + 1) * gap, cell_h + 2 * gap, placements]
end
end
# 800x600 + 600x800 + 1024x768 with a 12px gap -> 3120x824 canvas.
canvas_w, canvas_h, placements =
PhotoMerge.merge_layout(
[PhotoMerge::Image.new(800, 600),
PhotoMerge::Image.new(600, 800),
PhotoMerge::Image.new(1024, 768)],
12,
)
puts "canvas #{canvas_w}x#{canvas_h}"
placements.each_with_index { |p, i| puts "image #{i}: #{p}" }
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 →