Make a hexagonal montage of pictures in C#

[hexagonal montage]

This example combines techniques from several earlier posts to make a hexagonal montage. It uses the techniques demonstrated in the post Draw a hexagonal grid in C# to define the grid and to make positions on the form two and from grid rows and columns. It uses clipping techniques used by the post Clip an image to a polygon in C# to clip images to the hexagons. Finally it uses the general approach demonstrated by the post Make an improved diagonal picture montage in C# to make the hexagonal montage.

This is a fairly long example so I won’t describe all of the techniques described in those earlier posts. See those posts for the details.

This example defines a static Hex class to contain methods that work with hexagons. Those methods include methods that convert points to and from locations in the grid, draw the grid, get points defining a hexagon, and draw pictures inside a hexagon.

However I will briefly explain what happens when you click on the program’s picGrid PictureBox.

Selecting Images

The program defines the following Hexagon class to keep track of the image that should be drawn in a particular hexagon.

public class Hexagon
{
    public int Row, Column;
    public Bitmap Picture;
    public string FileName;
    public Hexagon(int row, int column, Bitmap picture, string file_name)
    {
        Row = row;
        Column = column;
        Picture = picture;

        // Remove the file path.
        FileInfo file_info = new FileInfo(file_name);
        FileName = file_info.Name;
    }
}

When you click on the picGrid PictureBox, the following code executes.

// Add the clicked hexagon to the Hexagons list.
private void picGrid_MouseClick(object sender, MouseEventArgs e)
{
    // Get the row and column clicked.
    int row, col;
    Hex.PointToHex(e.X, e.Y, HexHeight, out row, out col);

    // Remove any existing record for this cell.
    RemoveHexagon(row, col);

    // Let the user select a new picture.
    if (ofdFile.ShowDialog() == DialogResult.OK)
    {
        Bitmap bm = LoadBitmapUnlocked(ofdFile.FileName);
        Hexagons.Add(new Hexagon(row, col, bm, ofdFile.FileName));
    }

    // Redraw.
    picGrid.Refresh();
}

This event handler uses the Hex class’s static PointToHex method to find the row and column of the point that you clicked in the hexagonal grid. It then calls the RemoveHexagon method described shortly to remove any existing hexagon that is currently at that position.

Next the code displays an OpenFileDialog to let you select an image file. If you pick a file, the code uses it to create a new Hexagon object for the selected row and column and containing the image that you selected. The code finishes by refreshing the PictureBox to redraw the grid.

The following code shows the RemoveHexagon method.

// Remove the Hexagon at this position if there is one.
private void RemoveHexagon(int row, int col)
{
    int index = FindHexagon(row,col);
    if (index >= 0) Hexagons.RemoveAt(index);
}

The RemoveHexagon method calls the following FindHexagon method to find the index of the hexagon at the clicked row and column, if there is already a hexagon there. If the method finds a hexagon at that position, the RemoveHexagon method removes it from the Hexagons list.

// Find the Hexagon at this position if there is one.
private int FindHexagon(int row, int col)
{
    for (int i = 0; i < Hexagons.Count; i++)
    {
        if ((Hexagons[i].Row == row) &&
            (Hexagons[i].Column == col))
                return i;
    }
    return -1;
}

This method simply loops through the objects in the Hexagons list and returns the index of any object that has the desired row and column. If it finds no such object, the method returns -1.

Drawing the Grid

The picGrid PictureBox control’s Paint event handler calls the following DrawGrid method to draw the grid.

private void DrawGrid(Graphics gr, int xmax, int ymax)
{
    gr.Clear(picBackgroundColor.BackColor);
    gr.SmoothingMode = SmoothingMode.AntiAlias;

    // Draw the selected hexagons.
    float xmin = BorderThickness / 2f;
    foreach (Hexagon hexagon in Hexagons)
    {
        PointF[] points = Hex.HexToPoints(HexHeight,
            hexagon.Row, hexagon.Column, xmin, xmin);

        if (points[3].X > xmax) continue;
        if (points[4].Y > ymax) continue;

        Hex.DrawImageInPolygon(gr,
            Hex.HexToPoints(HexHeight,
                hexagon.Row, hexagon.Column, xmin, xmin),
                hexagon.Picture);
    }

    // Draw the grid.
    using (Pen pen = new Pen(picBorderColor.BackColor, BorderThickness))
    {
        Hex.DrawHexGrid(gr, pen,
            xmin, xmax, xmin, ymax, HexHeight);
    }
}

[hexagonal montage]

This code loops through the Hexagon objects in the Hexagons list. For each object, it uses the Hex class’s HexToPoints method to get points that define the object’s hexagon. The HexToPoints method returns the points in the order shown in the picture on the right, so the code looks at the points with indices 3 and 4 to determine whether the hexagon lies partly outside of the PictureBox. If the hexagon does not fit, the code skips it.

If the hexagon fits, the code calls the Hex.DrawImageInPolygon method to draw the hexagon’s image.

After it has drawn all of the images, the code calls the Hex.DrawHexGrid method to draw the hexagonal outlines.

Loading Many Files

The program includes one other interesting technique that I want to discuss. If you open the File menu and select Open Files in Directory, then the program executes the following code.

// Load the files from a directory.
private void mnuFileOpenDirectoryFiles_Click(object sender, EventArgs e)
{
    if (ofdDirectoryFiles.ShowDialog() == DialogResult.OK)
    {
        // Get a list of the files in the directory.
        FileInfo info = new FileInfo(ofdDirectoryFiles.FileName);
        DirectoryInfo dir_info = info.Directory;
        List<FileInfo> file_infos = new List<FileInfo>();
        foreach (FileInfo file_info in dir_info.GetFiles())
        {
            string ext = file_info.Extension.ToLower().Replace(".", "");
            if ((ext == "bmp") || (ext == "png") ||
                (ext == "jpg") || (ext == "jpeg") ||
                (ext == "gif") || (ext == "tiff"))
            {
                file_infos.Add(file_info);
            }
        }

        // Calculate the number of rows and columns.
        int num_rows = (int)Math.Sqrt(file_infos.Count);
        int num_cols = num_rows;
        if (num_rows * num_cols < file_infos.Count)
            num_cols++;
        if (num_rows * num_cols < file_infos.Count)
            num_rows++;

        // Load the files.
        Hexagons = new List<Hexagon>();
        int index = 0;
        for (int row = 0; row < num_rows; row++)
        {
            for (int col = 0; col < num_cols; col++)
            {
                string name = file_infos[index].Name;
                string full_name = file_infos[index].FullName;
                Bitmap bm = LoadBitmapUnlocked(full_name);
                Hexagons.Add(new Hexagon(row, col, bm, name));

                index++;
                if (index >= file_infos.Count) break;
            }
            if (index >= file_infos.Count) break;
        }

        picGrid.Refresh();
    }
}

This code displays an OpenFileDialog to let you select a file in a directory that contains images. If you select a file, the code gets a DirectoryInfo object representing the file’s directory. It calls that object’s GetFiles method to loop through the file in the directory and adds those that are image files to the file_infos list.

Next the code calculates a reasonable number of rows and columns to hold the files’ images. It starts by making the number of rows and columns equal to the square root of the number of images truncated to an integer. If that doesn’t give enough room, the method adds an extra column. If that still doesn’t give enough room, the method adds another row.

The code then loops through the rows and columns. For each position in the grid, the program loads the next image file from the file_infos list and creates a Hexagon object for that position.

After it finishes loading all of the files into Hexagon objects, the code refreshes the picGrid PictureBox to show the new grid.

Conclusion

That’s about all there is to the example that’s new. There are lots of other details such as how the program loads images without locking their files, how the program saves the resulting montage, how the Hex class calculates the positions of grid cells, and how the program displays the name of the image file under mouse. See the previous examples and download this example to see those details.


Download Example   Follow me on Twitter   RSS feed   Donate




About RodStephens

Rod Stephens is a software consultant and author who has written more than 30 books and 250 magazine articles covering C#, Visual Basic, Visual Basic for Applications, Delphi, and Java.
This entry was posted in drawing, graphics, image processing, tools and tagged , , , , , , , , , , , , , , . Bookmark the permalink.

1 Response to Make a hexagonal montage of pictures in C#

  1. Sinan says:

    Mr. Rod, Thank you so much for this well detailed study.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.