Rotate images to straighten them in C#

[rotate images]

I’ve written a couple of examples that show how to rotate images. I made many of them because I wanted to adjust a picture I had taken that was slightly crooked. Those examples work well, but they can be fairly slow if the image is large so they’re hard to use to make small adjustments.

This example lets you rotate images by drawing a horizon line on the image to indicate where horizontal should be. The program them rotates the image to make that line horizontal.

To make it slightly easier to align objects in the picture, the program allows you to draw a grid on top of the image if you like.

The example performs three main tasks: line selection, image rotation, and drawing.

Line Selection

The program uses the following code to let you draw the horizon line on the image.

private float Angle = 0;
private Point StartPoint = new Point(50, 100);
private Point EndPoint = new Point(210, 100);
private bool Drawing = false;

// Let the use select an orientation line.
private void picImage_MouseDown(object sender, MouseEventArgs e)
{
    StartPoint = e.Location;
    EndPoint = e.Location;
    Drawing = true;
}

private void picImage_MouseMove(object sender, MouseEventArgs e)
{
    if (!Drawing) return;
    EndPoint = e.Location;
    picImage.Refresh();
}

private void picImage_MouseUp(object sender, MouseEventArgs e)
{
    Drawing = false;

    // Calculate the line's angle.
    int dx = EndPoint.X - StartPoint.X;
    int dy = EndPoint.Y - StartPoint.Y;
    double new_angle = Math.Atan2(dy, dx) * 180.0 / Math.PI;
    
    // Subtract this angle from the total angle so far.
    Angle -= (float)new_angle;

    // Make the new rotated image.
    DrawImage();
}

This is relatively straightforward code for drawing a line. Variable Angle stores the pictures current angle of rotation. The StartPoint and EndPoint variables keep track of the mouse’s position while you are drawing. The Drawing variable is true while you are drawing.

The MouseDown event handler starts the process. It saves the mouse’s location in the two point variables and sets Drawing = true.

The MouseMove event handler does nothing if Drawing is false. Otherwise it updates the position of EndPoint and refreshes the program’s PictureBox to draw the new line. The Paint event handler shown shortly draws the line.

The MouseUp event handler finishes the line. It sets Drawing = false to indicate that drawing is over.

The program then calculates the differences in X and Y coordinates between the line’s start and end points. It uses those differences to calculate the line’s angle. The Math.Atan2 method returns an angle in radians, so the code converts the angle into degrees.

The code then subtracts the line’s angle from the current value of Angle. That rotates the new horizon line so it is horizontal. The event MouseUp handler finishes by calling the DrawImage method to rotate the image that you have loaded.

Image Rotation

The most interesting part of the program is the following DrawImage method.

// Display the image rotated by the current amount.
private void DrawImage()
{
    if (OriginalBitmap == null)
    {
        picImage.Refresh();
        return;
    }

    // Get the image's dimensions.
    int wid = OriginalBitmap.Width;
    int hgt = OriginalBitmap.Height;

    // Make a Matrix representing the rotation.
    Matrix matrix = new Matrix();
    matrix.Rotate(Angle);

    // Rotate the image's corners to see
    // how large the rotated image must be.
    PointF[] points =
    {
        new PointF(0, 0),
        new PointF(wid, 0),
        new PointF(0, hgt),
        new PointF(wid, hgt),
    };
    matrix.TransformPoints(points);

    // Get the rotated bounds.
    float xmin = points[0].X;
    float xmax = xmin;
    float ymin = points[0].Y;
    float ymax = ymin;
    for (int i = 1; i < points.Length; i++)
    {
        if (xmin > points[i].X) xmin = points[i].X;
        if (xmax < points[i].X) xmax = points[i].X;
        if (ymin > points[i].Y) ymin = points[i].Y;
        if (ymax < points[i].Y) ymax = points[i].Y;
    }

    // Get the new image's dimensions.
    float new_wid = xmax - xmin;
    float new_hgt = ymax - ymin;

    // Add a translation to move the rotated image
    // to the center of the new bitmap.
    matrix.Translate(new_wid / 2, new_hgt / 2, MatrixOrder.Append);

    // Make the new bitmap.
    RotatedBitmap = new Bitmap((int)new_wid, (int)new_hgt);
    using (Graphics gr = Graphics.FromImage(RotatedBitmap))
    {
        gr.InterpolationMode = InterpolationMode.High;
        gr.Clear(Color.White);
        gr.Transform = matrix;

        // Draw the image centered at the origin.
        PointF[] dest_points =
        {
            new PointF(-wid / 2, -hgt / 2),
            new PointF(wid / 2, -hgt / 2),
            new PointF(-wid / 2, hgt / 2),
        };
        gr.DrawImage(OriginalBitmap, dest_points);
    }

    // Display the result.
    picImage.Image = RotatedBitmap;
}

If you have not yet loaded an image, then the form-level variable OriginalBitmap is null. In that case, the method simply returns without doing anything.

If OriginalImage is not null, the program needs to figure out how large the rotated image will be. To do that, it gets the image’s dimensions. It then makes a Matrix representing a rotation through the angle Angle. It uses the dimensions to make points representing the image’s corners and puts them in an array. The program then uses the Matrix to rotate the points and loops through them to find their minimum and maximum X and Y coordinates. From those values it calculates the dimensions of the rotated image.

So far the Matrix represents a rotation. The code adds a translation to move the origin (0, 0) so it is centered over the image when placed so it’s upper left corner is at the origin.

The program then makes a new bitmap with the required size and creates an associated Graphics object. It sets the Graphics object’s Transform property to the Matrix so any drawing the object does is automatically rotated and translated.

Next the program draws the original image that you loaded centered at the origin. The Graphics object automatically rotates and translates it so it is centered on the new bitmap.

The method finishes by displaying the new bitmap in the picImage control.

Drawing

The following Paint event handler draws the horizon line and alignment grid.

// Draw the horizon line and alignment grid.
private void picImage_Paint(object sender, PaintEventArgs e)
{
    if (Drawing)
    {
        e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
        e.Graphics.DrawLine(Pens.Yellow, StartPoint, EndPoint);
        using (Pen pen = new Pen(Color.Red))
        {
            pen.DashPattern = new float[] { 5, 5 };
            e.Graphics.DrawLine(pen, StartPoint, EndPoint);
        }
    }

    if (mnuFormatDrawGrid.Checked)
    {
        const int dx = 50;
        int xmax = picImage.ClientSize.Width;
        int ymax = picImage.ClientSize.Height;
        for (int x = 0; x < xmax; x += dx)
            e.Graphics.DrawLine(Pens.Silver, x, 0, x, ymax);
        for (int y = 0; y < ymax; y += dx)
            e.Graphics.DrawLine(Pens.Silver, 0, y, xmax, y);
    }
}

The program only draws the horizon line while you are drawing it, so the code checks the Drawing variable to see if you are drawing that line. If Drawing is true, the code draws a line between StartPoint and EndPoint in yellow. It then creates a dashed red pen and draws the line again. The result is a dashed red and yellow line that’s fairly easy to see no matter what color the image is behind the line.

The Format menu’s Draw Grid item has its CheckOnClick property set to true, so that item checks and unchecks itself automatically when you select it at run time. The Paint event handler checks the menu item’s Checked property to see if it is currently checked. If the menu item is checked, the program uses two loops to draw vertical and horizontal lines on the picture.

Recall that the DrawImage method set the picImage control’s Image property to the rotated bitmap. When the Paint event runs, the control’s image has been reset to show the bitmap. When Paint draws the horizon line and the grid, those are automatically drawn on top of the image.

Conclusion

Those are the example’s most interesting pieces, but there are still some extra details such as how the program loads and saves images. Download the 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 algorithms, graphics, image processing, transformations 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.