Arrange images on the corners of a polygon in C#


[arrange images]

This post shows a way to arrange images on top of a background image as shown in the image above.

How to Use the Program

Enter the names of the background and foreground image files, or use the File menu’s Foreground Image and Background Image commands to select the files. Next, enter the final size that you want the composite image to have. The program will use an area with this size from the upper left corner of the background image to produce the final result, so make sure this image is at least as large as the values you enter here. If the background image is too small, parts of the result image will be blank. (You could modify the program to scale the background image if necessary.)

Now enter the width that you want the foreground image to have. The program scales that image uniformly so it calculates an appropriate image height from the width you enter.

Next enter a radius to determine the distance between the center of the result image and the centers of the foreground images. Finally enter the number of copies of the foreground image that you want to display. For example, if you enter 6, then the images are placed on the corners of a hexagon.

How the Program Works

The program is relatively straightforward. The basic idea is to make an angle theta loop around a circle, incrementing by the number of radians between the vertices of the polygon. For example, if we’re building a hexagon, then the values of theta are 2π/6 radians apart. For each value of theta, the program finds a point in the direction of theta from the origin at the distance given by the radius value. It then adds that point’s coordinates to the center of the image to center the result. (This is a common approach for doing something to points on a circle. For example, you could connect the points to draw the polygon.)

As it generates its points, the program draws copies of the foreground image at them.

To simplify the discussion somewhat, I’ll describe the program’s code in two pieces. The following code shows how the first piece, which prepares the foreground image.

private void btnGo_Click(object sender, EventArgs e)
{
    try
    {
        Bitmap fg_image = new Bitmap(txtFgImage.Text);
        Bitmap bg_image = new Bitmap(txtBgImage.Text);
        int width = int.Parse(txtWidth.Text);
        int height = int.Parse(txtHeight.Text);
        int fg_width = int.Parse(txtFgWidth.Text);
        int radius = int.Parse(txtRadius.Text);
        int num_images = int.Parse(txtNumImages.Text);

        // Scale the foreground image.
        float scale = fg_width / (float)fg_image.Width;
        int fg_height = (int)(fg_image.Height * scale);
        fg_image = new Bitmap(fg_image, new Size(fg_width, fg_height));

This code gets the images and the parameters that you entered. It then scales the foreground image. To do that, it sets variable scale equal to the foreground image width that you entered divided by the foreground image’s actual width. It then uses that scale factor to calculate the appropriate foreground image height.

The program then sets variable fg_image to a new Bitmap. It passes the constructor the original foreground image and the new size that it should have. This is perhaps the easiest way to resize an image.

The following shows the rest of the code that produces the result image. This piece draws the background image and then draws the foreground image on top of it.

        // Draw the final result.
        Bitmap bm = new Bitmap(width, height);
        using (Graphics gr = Graphics.FromImage(bm))
        {
            gr.InterpolationMode = InterpolationMode.High;
            gr.DrawImage(bg_image, 0, 0);
            int cx = width / 2;
            int cy = height / 2;
            int rx = fg_width / 2;
            int ry = fg_height / 2;
            RectangleF src_rect =
                new RectangleF(0, 0, fg_width, fg_height);

            double theta = -Math.PI / 2;
            double dtheta = 2 * Math.PI / num_images;
            for (int i = 0; i < num_images; i++)
            {
                float x = (float)(radius * Math.Cos(theta));
                float y = (float)(radius * Math.Sin(theta));
                PointF[] dest_points =
                {
                    new PointF(cx + x - rx, cy + y - ry),
                    new PointF(cx + x + rx, cy + y - ry),
                    new PointF(cx + x - rx, cy + y + ry),
                };
                gr.DrawImage(fg_image, dest_points, src_rect, GraphicsUnit.Pixel);
                theta += dtheta;
            }

            // Redraw the left side of the first image.
            src_rect =
                new RectangleF(0, 0, fg_width / 2f, fg_height);

            theta = -Math.PI / 2;
            {
                float x = (float)(radius * Math.Cos(theta));
                float y = (float)(radius * Math.Sin(theta));
                PointF[] dest_points =
                {
                    new PointF(cx + x - rx, cy + y - ry),
                    new PointF(cx + x, cy + y - ry),
                    new PointF(cx + x - rx, cy + y + ry),
                };
                gr.DrawImage(fg_image, dest_points, src_rect, GraphicsUnit.Pixel);
            }
        }
        picResult.Image = bm;
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
}

This code creates a bitmap with the final size that you entered. It makes an associated Graphics object and draws the background image onto it. The only parameters that the code passes into the DrawImage method are the image and the coordinates (0, 0) were the image’s upper left corner should be drawn. The image is drawn at full scale and truncated if it cannot fit on the bitmap.

Next the code calculates the coordinates of the center of the bitmap. It also makes a RectangleF sized to fit the foreground image.

The code then makes variable theta loop through angles around a circle. It starts at angle π/2 so the first image is directly above the center of the bitmap.

For each angle, the code calculates the point (x, y) distance radius from the origin. It adds those values to the coordinates at the center of the result image. It also adds +/-rx and +/-ry to find the corners where the new copy of the foreground image should be placed. Finally the code calls the Graphics object’s DrawImage method to draw a copy of the foreground image in that position.

If the program left it at that, the first image would be below the last one as shown in the following picture.


[arrange images]

To make the final image lies partly below the first one, the code redraws the left side of the first image again. Note that this doesn’t work if the images are so close together that images before the last one also overlap with the first image.

Conclusion

This example lets you arrange images at the corners of a polygon. You may not need this in a line-of-business application, but it produces an interesting effect that you may find useful. It’s also an worthwhile exercise in image processing.

Note that this program does not rotate the copies of the foreground image. You can see this if you look closely at the images above. It’s easier to see in the following image.


[arrange images]

If you want the images to rotate so they all have their bottoms closer to the center of the result, you can use techniques described in my earlier post Draw an image spiral in C#.


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 graphics, image processing, mathematics and tagged , , , , , , , , , . Bookmark the permalink.

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.