Presenting images in a grid (mosaic) using Aspose.Imaging grid layout is a common requirement for image gallery layout .NET galleries, contact sheets, and dashboards. Aspose.Imaging for .NET provides everything you need to load images using Aspose.Imaging merge images, compose them on a canvas, and save a final composite—cleanly and cross-platform.

This guide shows how to build a reusable grid composer with rows/columns, outer padding, inter-cell spacing, background color (including transparent), and fit modes (contain/cover/stretch) plus alignment inside each cell.


Complete, Self-Contained Example

using System;
using System.Collections.Generic;
using System.IO;
using Aspose.Imaging;
using Aspose.Imaging.ImageOptions;

namespace ImagingGridDemo
{
    public enum FitMode { Contain, Cover, Stretch }
    public enum HAlign { Left, Center, Right }
    public enum VAlign { Top, Middle, Bottom }

    public sealed class GridOptions
    {
        // Required layout for create image grid .NET
        public int Rows { get; init; }
        public int Columns { get; init; }

        // Optional cell size. If null, computed from max source dimensions.
        public int? CellWidth { get; init; }
        public int? CellHeight { get; init; }

        // Spacing/padding
        public int Spacing { get; init; } = 8;     // space between cells
        public int Padding { get; init; } = 16;    // outer canvas padding

        // Drawing behavior
        public FitMode Fit { get; init; } = FitMode.Contain;
        public HAlign AlignX { get; init; } = HAlign.Center;
        public VAlign AlignY { get; init; } = VAlign.Middle;

        // Background
        public Color Background { get; init; } = Color.White;

        // Output
        public string OutputPath { get; init; } = "grid.png"; // encoder inferred by extension

        public void Validate()
        {
            if (Rows <= 0 || Columns <= 0)
                throw new ArgumentException("Rows and Columns must be positive.");
            if (Spacing < 0 || Padding < 0)
                throw new ArgumentException("Spacing and Padding cannot be negative.");
            if (!OutputPath.EndsWith(".png", StringComparison.OrdinalIgnoreCase) &&
                !OutputPath.EndsWith(".jpg", StringComparison.OrdinalIgnoreCase) &&
                !OutputPath.EndsWith(".jpeg", StringComparison.OrdinalIgnoreCase))
                throw new ArgumentException("OutputPath must end with .png or .jpg/.jpeg.");
        }
    }

    public static class ImageGridComposer
    {
        /// <summary>
        /// Builds a C# image mosaic grid create image grid .NET from the given image file paths.
        /// </summary>
        public static void Compose(IReadOnlyList<string> imagePaths, GridOptions options)
        {
            if (imagePaths is null || imagePaths.Count == 0)
                throw new ArgumentException("No input images provided.");
            options.Validate();

            // 1) Load the images
            var images = new List<Image>(imagePaths.Count);
            try
            {
            foreach (var path in imagePaths)
                images.Add(Image.Load(path));

            // 2) Determine cell size (if not supplied) from the max width/height of sources
            int cellW = options.CellWidth ?? GetMaxWidth(images);
            int cellH = options.CellHeight ?? GetMaxHeight(images);

            // 3) Compute canvas size
            int w = options.Padding * 2
                    + (options.Columns * cellW)
                    + ((options.Columns - 1) * options.Spacing);
            int h = options.Padding * 2
                    + (options.Rows * cellH)
                    + ((options.Rows - 1) * options.Spacing);

            // 4) Create canvas. Use PNG by default (lossless & supports transparency).
            ImageOptionsBase canvasOptions = options.OutputPath.EndsWith(".png", StringComparison.OrdinalIgnoreCase)
                ? new PngOptions()
                : new JpegOptions { Quality = 90 };

            using var canvas = Image.Create(canvasOptions, w, h);

            // If you want transparent background, set Background = Color.Transparent and save as PNG
            using var g = new Graphics(canvas);
            g.Clear(options.Background);

            // 5) Draw each image into its cell
            int idx = 0;
            for (int r = 0; r < options.Rows; r++)
            {
                for (int c = 0; c < options.Columns; c++)
                {
                    if (idx >= images.Count) break;

                    var img = images[idx++];
                    var cellRect = CellRect(r, c, cellW, cellH, options);

                    // Determine source and destination rectangles according to FitMode
                    GetDrawRects(img.Width, img.Height, cellRect, options, out Rectangle srcRect, out Rectangle dstRect);

                    // Draw scaled/cropped as needed
                    g.DrawImage(img, dstRect, srcRect);

                    // (Optional) draw cell borders for debugging:
                    // g.DrawRectangle(new Pen(Color.LightGray), cellRect);
                }
            }

            // 6) Save by extension
            SaveByExtension(canvas, options.OutputPath);
        }
        finally
        {
            foreach (var img in images)
                img.Dispose();
        }
    }

    private static int GetMaxWidth(List<Image> imgs)
    {
        int m = 1;
        foreach (var i in imgs) m = Math.Max(m, i.Width);
        return m;
    }

    private static int GetMaxHeight(List<Image> imgs)
    {
        int m = 1;
        foreach (var i in imgs) m = Math.Max(m, i.Height);
        return m;
    }

    private static Rectangle CellRect(int row, int col, int cellW, int cellH, GridOptions opt)
    {
        int x = opt.Padding + col * (cellW + opt.Spacing);
        int y = opt.Padding + row * (cellH + opt.Spacing);
        return new Rectangle(x, y, cellW, cellH);
    }

    /// <summary>
    /// Computes source and destination rectangles for DrawImage based on fit & alignment.
    /// </summary>
    private static void GetDrawRects(
        int srcW, int srcH, Rectangle cell, GridOptions opt,
        out Rectangle srcRect, out Rectangle dstRect)
    {
        srcRect = new Rectangle(0, 0, srcW, srcH);

        if (opt.Fit == FitMode.Stretch)
        {
            // Stretch to fill the entire cell.
            dstRect = cell;
            return;
        }

        // Aspect ratios
        double imgAR = (double)srcW / srcH;
        double cellAR = (double)cell.Width / cell.Height;

        if (opt.Fit == FitMode.Contain)
        {
            // Scale down so the whole image fits inside the cell (letterboxing possible).
            double scale = (imgAR > cellAR)
                ? (double)cell.Width / srcW
                : (double)cell.Height / srcH;

            int drawW = (int)Math.Round(srcW * scale);
            int drawH = (int)Math.Round(srcH * scale);

            int dx = AlignX(cell.X, cell.Width, drawW, opt.AlignX);
            int dy = AlignY(cell.Y, cell.Height, drawH, opt.AlignY);

            dstRect = new Rectangle(dx, dy, drawW, drawH);
            return;
        }

        // FitMode.Cover:
        // Crop source to match cell aspect, then scale to fill cell completely.
        if (opt.Fit == FitMode.Cover)
        {
            if (imgAR > cellAR)
            {
                // Image too wide; crop left/right
                int newSrcW = (int)Math.Round(srcH * cellAR);
                int sx = (srcW - newSrcW) / 2;
                srcRect = new Rectangle(sx, 0, newSrcW, srcH);
            }
            else
            {
                // Image too tall; crop top/bottom
                int newSrcH = (int)Math.Round(srcW / cellAR);
                int sy = (srcH - newSrcH) / 2;
                srcRect = new Rectangle(0, sy, srcW, newSrcH);
            }

            dstRect = cell; // fill the cell exactly
            return;
        }

        // Default fallback (shouldn't hit)
        dstRect = cell;
    }

    private static int AlignX(int cellX, int cellW, int drawW, HAlign ax)
    {
        return ax switch
        {
            HAlign.Left   => cellX,
            HAlign.Center => cellX + (cellW - drawW) / 2,
            HAlign.Right  => cellX + (cellW - drawW),
            _             => cellX
        };
    }

    private static int AlignY(int cellY, int cellH, int drawH, VAlign ay)
    {
        return ay switch
        {
            VAlign.Top    => cellY,
            VAlign.Middle => cellY + (cellH - drawH) / 2,
            VAlign.Bottom => cellY + (cellH - drawH),
            _             => cellY
        };
    }

    private static void SaveByExtension(Image image, string outputPath)
    {
        var ext = Path.GetExtension(outputPath).ToLowerInvariant();
        ImageOptionsBase opts = ext switch
        {
            ".jpg" or ".jpeg" => new JpegOptions { Quality = 90 },
            ".png"            => new PngOptions(),
            _                 => new PngOptions()
        };
        image.Save(outputPath, opts);
    }
}

// Demo usage
public static class Program
{
    public static void Main()
    {
        var inputs = new List<string>
        {
            "img1.jpg", "img2.png", "img3.jpg",
            "img4.jpg", "img5.png", "img6.jpg"
        };

        var opts = new GridOptions
        {
            Rows = 2,
            Columns = 3,
            // Leave CellWidth/CellHeight null to auto-fit to largest source,
            // or set fixed cell size (e.g., 640x480):
            // CellWidth = 640, CellHeight = 480,

            Spacing = 10,
            Padding = 20,
            Fit = FitMode.Contain,         // Contain | Cover | Stretch
            AlignX = HAlign.Center,        // used by Contain
            AlignY = VAlign.Middle,        // used by Contain
            Background = Color.White,      // use Color.Transparent with PNG for transparent canvas
            OutputPath = "grid.png"
        };

        ImageGridComposer.Compose(inputs, opts);
        Console.WriteLine("Grid created: " + opts.OutputPath);
    }
}

}


---

## Step-by-Step Guide 

1. **Load Inputs**
   `Image.Load(path)` for each file. Keep them in memory until the composite is saved, then dispose.

2. **Cell Size**

   * If not provided, compute `cellWidth` and `cellHeight` as the **max** width/height across sources.
   * Or force a specific cell size for uniform grids.

3. **Canvas Size**

width = 2Padding + ColumnscellW + (Columns-1)Spacing height = 2Padding + Rows*cellH + (Rows-1)*Spacing


4. **Create Canvas**
`Image.Create(new PngOptions(), width, height)` (or `JpegOptions` if you want lossy, smaller files).
Clear background: `g.Clear(Color.White)` or `Color.Transparent` (PNG).

5. **Place Each Image**

* Compute the **cell rectangle** for (row, column).
* For **Contain**: scale to fit within cell, then align (Left/Center/Right, Top/Middle/Bottom).
* For **Cover**: crop source to the cell aspect, then fill the cell completely.
* For **Stretch**: fill cell regardless of aspect ratio.

6. **Save**
Choose encoder by output extension:

* `.png` → `new PngOptions()`
* `.jpg`/`.jpeg` → `new JpegOptions { Quality = 90 }`

---

## Best Practices

* **Transparency**: For transparent backgrounds, use `Color.Transparent` and save as PNG.
* **Very Large Grids**: Watch total pixel count to avoid OOM; process in pages if needed.
* **Mixed DPI/Color Types**: Let Aspose handle conversions implicitly, or standardize ahead of time if your pipeline demands it.
* **Validation**: Ensure `Rows * Columns >= images.Count` (or decide how to handle overflow gracefully).
* **Deterministic Output**: Fix `CellWidth/CellHeight` to enforce uniform tiles, especially for UI thumbnails.

More in this category