Skip to content

Photo Merge — Java 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 Java implementation — the same logic the interactive tool runs, in a shareable, citable form.

// photo-merge — Java port: side-by-side merge cell layout.

import java.util.ArrayList;
import java.util.List;

/** 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. */
record ImageSize(int width, int height) {}

/** Where one image lands on the merged canvas. */
record Placement(double scale, int x, int y, int w, int h) {}

record MergedLayout(int canvasW, int canvasH, List<Placement> placements) {}

public class PhotoMerge {

    /** 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.
     *  Math.round rounds half up, matching JS exactly. */
    static MergedLayout mergeLayout(List<ImageSize> images, int gap) {
        int n = images.size();
        int cellW = 0, cellH = 0;
        for (ImageSize img : images) {
            cellW = Math.max(cellW, img.width());
            cellH = Math.max(cellH, img.height());
        }

        List<Placement> placements = new ArrayList<>(n);
        for (int i = 0; i < n; i++) {
            ImageSize img = images.get(i);
            double scale = Math.min(
                    cellW / (double) img.width(),
                    cellH / (double) img.height());
            int w = (int) Math.round(img.width() * scale);
            int h = (int) Math.round(img.height() * scale);
            placements.add(new Placement(
                    scale,
                    gap + i * (cellW + gap) + (int) Math.round((cellW - w) / 2.0),
                    gap + (int) Math.round((cellH - h) / 2.0),
                    w, h));
        }
        return new MergedLayout(
                n * cellW + (n + 1) * gap, cellH + 2 * gap, placements);
    }

    public static void main(String[] args) {
        // 800x600 + 600x800 + 1024x768 with a 12px gap -> 3120x824 canvas.
        MergedLayout layout = mergeLayout(List.of(
                new ImageSize(800, 600),
                new ImageSize(600, 800),
                new ImageSize(1024, 768)), 12);
        System.out.printf("canvas %dx%d%n", layout.canvasW(), layout.canvasH());
        for (int i = 0; i < layout.placements().size(); i++) {
            Placement p = layout.placements().get(i);
            System.out.printf("image %d: (%d, %d) %dx%d scale=%.4f%n",
                    i, p.x(), p.y(), p.w(), p.h(), p.scale());
        }
    }
}

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 →