Skip to content

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 →