Skip to content

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++ (C++17) port: side-by-side merge cell layout.

#include <algorithm>
#include <cmath>
#include <cstdint>
#include <iostream>
#include <vector>

// 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.
struct ImageSize {
    int width{};
    int height{};
};

// Where one image lands on the merged canvas.
struct Placement {
    double scale{};
    int x{}, y{}, w{}, h{};
};

struct MergedLayout {
    int canvas_w{};
    int canvas_h{};
    std::vector<Placement> placements;
};

// 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. std::lround
// (half away from zero) matches JS Math.round for positive values.
MergedLayout merge_layout(const std::vector<ImageSize>& images, int gap) {
    const auto n = static_cast<int>(images.size());
    int cell_w = 0, cell_h = 0;
    for (const auto& img : images) {
        cell_w = std::max(cell_w, img.width);
        cell_h = std::max(cell_h, img.height);
    }

    MergedLayout layout;
    layout.canvas_w = n * cell_w + (n + 1) * gap;
    layout.canvas_h = cell_h + 2 * gap;
    layout.placements.reserve(images.size());

    for (int i = 0; i < n; i++) {
        const double scale = std::min(cell_w / static_cast<double>(images[i].width),
                                      cell_h / static_cast<double>(images[i].height));
        const int w = static_cast<int>(std::lround(images[i].width * scale));
        const int h = static_cast<int>(std::lround(images[i].height * scale));
        layout.placements.push_back({
            scale,
            gap + i * (cell_w + gap) + static_cast<int>(std::lround((cell_w - w) / 2.0)),
            gap + static_cast<int>(std::lround((cell_h - h) / 2.0)),
            w,
            h,
        });
    }
    return layout;
}

int main() {
    // 800x600 + 600x800 + 1024x768 with a 12px gap -> 3120x824 canvas.
    const auto layout = merge_layout({{800, 600}, {600, 800}, {1024, 768}}, 12);
    std::cout << "canvas " << layout.canvas_w << "x" << layout.canvas_h << '\n';
    for (auto i = 0u; i < layout.placements.size(); i++) {
        const auto& p = layout.placements[i];
        std::cout << "image " << i << ": (" << p.x << ", " << p.y << ") "
                  << p.w << 'x' << p.h << " scale=" << p.scale << '\n';
    }
    return 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 →