Crop scaled images to specific sizes in C#


[crop scaled images]

This example lets you crop scaled images to specific sizes in C#. The post Crop images to specific sizes in C# lets you drag a rectangle of a specified size around on an image to pick the part of the image that you want to save into a new file. I wrote that example to make it easier to create images with specific sizes. For example, Google Business is designed to favor images that have a 4:3 aspect ratio. That tool lets you pick an area that has the correct aspect ratio.

That example works pretty well, but lately I’ve been working with large images that don’t fit on the screen without scaling. The previous example does not scale images so it will only let you select areas that fit on the screen.

This example lets you crop scaled images so you can pick parts of the image that may not fit on the screen.

The basic approach used by this example is similar to the approach used by the previous one, but the differences are scattered throughout the code so I’m going to cover most of the new example’s more interesting parts in this post.

The Scale Menu

To use the program, invoke the File menu’s Open command to select a file. Enter the width and height of the area that you want to select in the text boxes.

Now you can use the Scale menu’s commands to set the image’s scale factor. The program lets you set the scale to 100%, 75%, 66%, 50%, 25%, and 15%. When you invoke one of those menu items, the following event handler executes.

private float ImageScale = 1f;

private void mnuScale_Click(object sender, EventArgs e)
{
    // Get the scale factor.
    ToolStripMenuItem menu_item =
        sender as ToolStripMenuItem;
    string scale_text =
        menu_item.Text.Replace("&", "").Replace("%", "");
    ImageScale = float.Parse(scale_text) / 100f;
    ShowScaledImage();

    // Display the new scale.
    mnuScale.Text = "Scale (" + menu_item.Text.Replace("&", "") + ")";

    // Check the selected menu item.
    foreach (ToolStripMenuItem item in mnuScale.DropDownItems)
    {
        item.Checked = (item == menu_item);
    }
}

This code gets the menu item that raised the event. It then gets the item’s text (as in &75%). It removes the & and % characters, parses the result, and divides by 100 to get the scale factor as a fraction (as in 0.75). It saves the result in the form-level variable ImageScale.

The code then calls the ShowScaledImage method described shortly. It then sets the Scale menu’s text to the selected menu item’s text with any & characters removed. The method finishes by looping through the items in the Scale menu and setting their Checked properties so the item that was selected is the only one that is checked.

Note that this approach makes it easy to add new scales to the Scale menu. Just add the new menu item with a numeric caption to the menu and set its Click event handler to this method. The code will automatically parse the new item’s text to get the scale factor. The new item is included in the Scale menu’s DropDownItems collection so the code will also check and uncheck it correctly.

ShowScaledImage

The following code shows the ShowScaledImage method.

private Bitmap OriginalImage = null;
private Bitmap ScaledImage = null;

// Display the scaled image.
private void ShowScaledImage()
{
    if (OriginalImage == null) return;
    int scaled_width = (int)(OriginalImage.Width * ImageScale);
    int scaled_height = (int)(OriginalImage.Height * ImageScale);
    ScaledImage = new Bitmap(scaled_width, scaled_height);
    using (Graphics gr = Graphics.FromImage(ScaledImage))
    {
        Point[] dest_points =
        {
            new Point(0, 0),
            new Point(scaled_width - 1, 0),
            new Point(0, scaled_height - 1),
        };
        Rectangle src_rect = new Rectangle(
            0, 0,
            OriginalImage.Width - 1,
            OriginalImage.Height - 1);
        gr.DrawImage(OriginalImage, dest_points, src_rect,
            GraphicsUnit.Pixel);
    }
    picImage.Image = ScaledImage;
    picImage.Visible = true;
    picImage.Refresh();
}

When you open a file, the program saves its image in variable OriginalImage. That code is fairly straightforward so I won’t show it here.

The ShowScaledImage method first checks OriginalImage to see if a file has been loaded and returns if it has not.

If you have loaded an image, the code multiplies the image’s dimensions by the scale factor to get the size of the scaled image. It then creates an image with that size and copies the original image onto it.

The method sets the picImage PictureBox control’s Image property to display the scaled image, displays the control, and refreshes it.

The Selection Rectangle

The program uses the variable ULCorner to keep track of the upper left corner of the selection rectangle.

private Point ULCorner = new Point(0, 0);

Note that the ULCorner value is in the original image’s unscaled coordinate system. Whenever the program needs to use this value to work with the scaled image displayed in the PictureBox, it will need to scale the coordinates.

The following SelectionRectangle method uses ULCorner to return a rectangle representing the selection area.

// Return the currently selected area.
private Rectangle SelectionRectangle(bool scaled)
{
    int x, y, width, height;

    // Get the desired dimensions.
    // If there's a problem, return a zero-size rectangle.
    if (!int.TryParse(txtWidth.Text, out width))
        return new Rectangle();
    if (!int.TryParse(txtHeight.Text, out height))
        return new Rectangle();
    x = ULCorner.X;
    y = ULCorner.Y;

    // Return the rectangle.
    if (scaled)
    {
        x = (int)(x * ImageScale);
        y = (int)(y * ImageScale);
        width = (int)(width * ImageScale);
        height = (int)(height * ImageScale);
    }
    return new Rectangle(x, y, width, height);
}

This code parses the selection rectangle’s width and height that you entered in the program’s text boxes. It also uses variable ULCorner to define the X and Y coordinates for the selection rectangle.

If the method’s scaled parameter is true, the code multiples the X and Y coordinates and the dimensions by the scale factor stored in variable ImageScale.

The method then creates the rectangle and returns it.

The SelectionRectangle method is used in three places: in the Paint event handler, when moving the selection rectangle, and when saving the selected area into a new file. The following three sections describe those places.

The Paint Event Handler

PictureBox refreshes, the following Paint event handler uses the SelectionRectangle method.

// Draw the selection rectangle.
private void picImage_Paint(object sender, PaintEventArgs e)
{
    try
    {
        // Draw the selection rectangle.
        using (Pen pen = new Pen(Color.Red, 2))
        {
            Rectangle rect = SelectionRectangle(true);
            e.Graphics.DrawRectangle(pen, rect);

            pen.Color = Color.Yellow;
            pen.DashPattern = new float[] { 5, 5 };
            e.Graphics.DrawRectangle(pen, rect);
        }
    }
    catch
    {
    }
}

This code creates a thick red pen and draws the rectangle. It then changes the pen’s color to yellow, gives it a dash pattern, and redraws the rectangle. The result is a rad and yellow dashed rectangle.

Notice that the code passes the SelectionRectangle method the parameter true to indicate that the rectangle should be scaled. That makes the rectangle fit the scaled image that is displayed in the PictureBox.

Moving the Selection Rectangle

The program uses the PictureBox control’s MouseDown, MouseMove, and MouseUp event handlers to move the selection rectangle. The following code shows the MouseDown event handler.

// Start dragging.
private void picImage_MouseDown(object sender, MouseEventArgs e)
{
    // If the mouse is not inside the
    // selection rectangle, do nothing.
    Rectangle rect = SelectionRectangle(true);
    if (!rect.Contains(e.Location)) return;

    // Start the drag.
    LastPoint = e.Location;
    Dragging = true;
}

This code calls the SelectionRectangle method to get the scaled selection rectangle. It then checks whether the mouse has been pressed within the rectangle. If the mouse is outside of the rectangle, the method returns.

If the mouse is inside the selection rectangle, the code saves the mouse’s current location and sets Dragging to true.

When the mouse moves over the PictureBox, the following code executes.

// Continue dragging.
private void picImage_MouseMove(object sender, MouseEventArgs e)
{
    if (!Dragging) return;
    ULCorner.X += (int)((e.Location.X - LastPoint.X) / ImageScale);
    ULCorner.Y += (int)((e.Location.Y - LastPoint.Y) / ImageScale);
    LastPoint = e.Location;
    picImage.Refresh();
}

This code checks the Dragging variable and returns is no drag is in progress.

If a drag is in progress, the code subtracts the mouse’s last location from its current location to see how far the mouse has moved. Because that motion is on the scaled image displayed in the PictureBox, the code divides the distance by ImageScale to see how far the mouse has moved in unscaled coordinates. It then updates the ULCorner variable’s X and Y coordinates.

The method finishes by refreshing the picImage PictureBox so it can redraw the selection rectangle in its new location.

When you release the mouse button, the following event handler executes.

// Stop dragging.
private void picImage_MouseUp(object sender, MouseEventArgs e)
{
    Dragging = false;
}

This code simply sets Dragging to false to end the current drag.

Saving the Selected Area

When you invoke the File menu’s Save As command, the following code executes.

// Save the selected area.
private void mnuFileSaveAs_Click(object sender, EventArgs e)
{
    if (sfdImage.ShowDialog() == DialogResult.OK)
    {
        try
        {
            // Copy the selected area into a new Bitmap.
            Rectangle rect = SelectionRectangle(false);
            Bitmap bm = new Bitmap(rect.Width, rect.Height);
            using (Graphics gr = Graphics.FromImage(bm))
            {
                gr.DrawImage(OriginalImage, 0, 0, rect,
                    GraphicsUnit.Pixel);
            }

            // Save the new Bitmap.
            SaveImage(bm, sfdImage.FileName);
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.Message);
        }
    }
}

This code displays a SaveFileDialog to let you pick the file that should contain the selected part of the image. If you pick a file and click Save, the code calls the SelectionRectangle method to get the selection rectangle’s location and size. It creates a new Bitmap with that size and copies the original image onto it.

The code finishes by using the SaveImage method to save the bitmap into the file with the appropriate file format (JPG, PNG, BMP, etc.). For information on the SaveImage method, see my post Save images with an appropriate format depending on the file name’s extension in C#.

Conclusion

This example lets you crop scaled images to a given size. That’s a big improvement over the previous example when you’re working with large images, and it’s good enough for my current needs.

It would still be nice to be able to specify an aspect ratio and then drag the corners of the rectangle to change its size while preserving the aspect ratio, but I’ll leave that for another time. If you get that working, post a note in the comments below.

To learn about additional details, see the previous example. Download this example to experiment with it.


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.

2 Responses to Crop scaled images to specific sizes in C#

  1. Arne Laurene says:

    How should differing image resolutions be dealt with – e.g. screen 96 dpi versus image of some other dpi?

    • RodStephens says:

      Unfortunately I haven’t done much with that. The tools that C# gives us to manipulate pixel resolution are not very good and I suspect not well tested.

      You might try looking at this example:

      Change image resolution in C#

      Mostly, though, C# programs just use pixels and don’t worry about how many of them are in an inch.

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.