Skip to main content
Glama

merge_images

Combine multiple images into one by arranging them horizontally, vertically, or in a grid layout with customizable spacing and background color.

Instructions

Merge multiple images into a single image by arranging them horizontally, vertically, or in a grid.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
input_pathsYesOrdered list of absolute paths to the images to merge (minimum 2)
layoutNoArrangement: horizontal (side by side), vertical (stacked), or grid (auto columns)horizontal
gapNoGap in pixels between images (default 0)
backgroundNoBackground/gap fill color as hex (default #ffffff)#ffffff
output_pathYesAbsolute path for the output file (format inferred from extension)

Implementation Reference

  • The handler function that performs the logic of merging multiple images based on input parameters (layout, gap, etc.) using the sharp library.
      async ({ input_paths, layout = "horizontal", gap = 0, background = "#ffffff", output_path }) => {
        try {
          // Load metadata for all images
          const metas = await Promise.all(input_paths.map(async (p) => {
            await fs.access(p);
            const meta = await sharp(p).metadata();
            return { path: p, width: meta.width, height: meta.height };
          }));
    
          // Parse background color
          let bg = { r: 255, g: 255, b: 255, alpha: 1 };
          const hex = background.replace('#', '');
          if (hex.length === 6) {
            bg = { r: parseInt(hex.slice(0,2),16), g: parseInt(hex.slice(2,4),16), b: parseInt(hex.slice(4,6),16), alpha: 1 };
          }
    
          let canvasW, canvasH, positions;
    
          if (layout === "horizontal") {
            canvasH = Math.max(...metas.map(m => m.height));
            canvasW = metas.reduce((sum, m) => sum + m.width, 0) + gap * (metas.length - 1);
            positions = [];
            let x = 0;
            for (const m of metas) {
              positions.push({ x, y: Math.floor((canvasH - m.height) / 2) });
              x += m.width + gap;
            }
          } else if (layout === "vertical") {
            canvasW = Math.max(...metas.map(m => m.width));
            canvasH = metas.reduce((sum, m) => sum + m.height, 0) + gap * (metas.length - 1);
            positions = [];
            let y = 0;
            for (const m of metas) {
              positions.push({ x: Math.floor((canvasW - m.width) / 2), y });
              y += m.height + gap;
            }
          } else {
            // grid: auto columns = ceil(sqrt(n))
            const cols = Math.ceil(Math.sqrt(metas.length));
            const rows = Math.ceil(metas.length / cols);
            const cellW = Math.max(...metas.map(m => m.width));
            const cellH = Math.max(...metas.map(m => m.height));
            canvasW = cols * cellW + gap * (cols - 1);
            canvasH = rows * cellH + gap * (rows - 1);
            positions = metas.map((m, i) => {
              const col = i % cols;
              const row = Math.floor(i / cols);
              return {
                x: col * (cellW + gap) + Math.floor((cellW - m.width) / 2),
                y: row * (cellH + gap) + Math.floor((cellH - m.height) / 2),
              };
            });
          }
    
          // Build composite layers
          const composites = await Promise.all(metas.map(async (m, i) => ({
            input: await sharp(m.path).toBuffer(),
            left: positions[i].x,
            top: positions[i].y,
          })));
    
          const outExt = path.extname(output_path).slice(1).toLowerCase() || "png";
          const formatMap = { jpg: "jpeg" };
          const fmt = formatMap[outExt] || outExt;
    
          await sharp({
            create: { width: canvasW, height: canvasH, channels: 4, background: bg },
          })
            .composite(composites)
            .toFormat(fmt)
            .toFile(output_path);
    
          const stat = await fs.stat(output_path);
          return {
            content: [{ type: "text", text: JSON.stringify({ success: true, output_path, width: canvasW, height: canvasH, size_bytes: stat.size }) }],
          };
        } catch (err) {
          return { isError: true, content: [{ type: "text", text: `Error: ${err.message}` }] };
        }
      }
    );
  • index.js:227-236 (registration)
    Registration of the "merge_images" tool with schema definitions for input arguments using Zod.
    server.tool(
      "merge_images",
      "Merge multiple images into a single image by arranging them horizontally, vertically, or in a grid.",
      {
        input_paths: z.array(z.string()).min(2).describe("Ordered list of absolute paths to the images to merge (minimum 2)"),
        layout: z.enum(["horizontal", "vertical", "grid"]).optional().default("horizontal").describe("Arrangement: horizontal (side by side), vertical (stacked), or grid (auto columns)"),
        gap: z.number().int().min(0).optional().default(0).describe("Gap in pixels between images (default 0)"),
        background: z.string().optional().default("#ffffff").describe("Background/gap fill color as hex (default #ffffff)"),
        output_path: z.string().describe("Absolute path for the output file (format inferred from extension)"),
      },

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/sonic0002/imagic-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server