Use a rubber band box to let the user select an area in a picture in C#


rubber band box

The following shows the program’s startup code.

private Bitmap m_OriginalImage = null;
private int X0, Y0, X1, Y1;
private bool SelectingArea = false;
private Bitmap SelectedImage = null;
private Graphics SelectedGraphics = null;

// Save the original image.
private void Form1_Load(object sender, EventArgs e)
{
    m_OriginalImage = new Bitmap(picImage.Image);
    this.KeyPreview = true;
}

The variables X0, Y0, X1, and Y1 store the mouse coordinates as you select a region. The form’s Load event handler saves a copy of the picImage PictureBox‘s image. It also sets the form’s KeyPreview property to true so the program can tell when you press Esc to cancel the area selection.

The program uses three event handlers to display the rubber band box: MouseDown, MouseMove, and MouseUp.

When you press the mouse down, the following MouseDown event handler starts selecting an area.

// Start selecting an area.
private void picImage_MouseDown(object sender, MouseEventArgs e)
{
    // Save the starting point.
    SelectingArea = true;
    X0 = e.X;
    Y0 = e.Y;

    // Make the selected image.
    SelectedImage = new Bitmap(m_OriginalImage);
    SelectedGraphics = Graphics.FromImage(SelectedImage);
    picImage.Image = SelectedImage;
}

This code sets SelectingArea to true so the program knows it is selecting an area. It then saves the mouse’s current coordinates and makes a new copy of the PictureBox‘s original image. (As you move the mouse, it will draw the rubber band box on this image.) The code creates a Graphics object associated with the image and makes the PictureBox display it.

When the user moves the mouse over the picture, the following MouseMove event handler continues selecting the area.

// Continue selecting an area.
private void picImage_MouseMove(object sender, MouseEventArgs e)
{
    // Do nothing if we're not selecting an area.
    if (!SelectingArea) return;

    // Generate the new image with the selection rectangle.
    X1 = e.X;
    Y1 = e.Y;

    // Copy the original image.
    SelectedGraphics.DrawImage(m_OriginalImage, 0, 0);

    // Draw the selection rectangle.
    using (Pen select_pen = new Pen(Color.Red))
    {
        select_pen.DashStyle = DashStyle.Dash;
        Rectangle rect = MakeRectangle(X0, Y0, X1, Y1);
        SelectedGraphics.DrawRectangle(select_pen, rect);
    }

    picImage.Refresh();
}

First, if the program isn’t currently selecting an area, the event handler exits.

Next the code saves the mouse’s new location. It copies the original image onto the SelectedImage to erase the previous rubber band box. It then draws a new rubber band box with a red dashed pen.

The Rectangle structure’s constructor cannot handle negative widths and heights, so you can’t simply create the Rectangle to draw as in the following code.

// This doesn't work.
Rectangle rect = new Rectangle(X0, Y0, X1 - X0, Y1 - Y0);

To avoid this issue, the MouseMove event handler calls the following MakeRectangle method to create the Rectangle.

// Return a Rectangle with these points as corners.
private Rectangle MakeRectangle(int x0, int y0, int x1, int y1)
{
    return new Rectangle(
        Math.Min(x0, x1),
        Math.Min(y0, y1),
        Math.Abs(x0 - x1),
        Math.Abs(y0 - y1));
}

When you release the mouse, the following MouseUp event handler finishes selecting the area.

// Finish selecting the area.
private void picImage_MouseUp(object sender, MouseEventArgs e)
{
    if (!SelectingArea) return;
    SelectingArea = false;
    SelectedImage = null;
    SelectedGraphics = null;
    picImage.Image = m_OriginalImage;
    picImage.Refresh();

    // Convert the points into a Rectangle.
    Rectangle rect = MakeRectangle(X0, Y0, X1, Y1);
    if ((rect.Width > 0) && (rect.Height > 0))
    {
        // Display the Rectangle.
        MessageBox.Show(rect.ToString());
    }
}

This code sets SelectingArea to false so future mouse movement is ignored. The code restores the PictureBox‘s original image and refreshes it.

The event handler finishes by converting the selected points into a Rectangle and displaying it’s properties. (In my next blog entry, I’ll explain how to do something more interesting with the selected area: copy that part of the image to the clipboard.)

The final piece of code is the following KeyPress event handler.

// If the user presses Escape, cancel.
private void Form1_KeyPress(object sender, KeyPressEventArgs e)
{
    if (e.KeyChar == 27)
    {
        if (!SelectingArea) return;
        SelectingArea = false;

        // Stop selecting.
        SelectedImage = null;
        SelectedGraphics = null;
        picImage.Image = m_OriginalImage;
        picImage.Refresh();
    }
}

If you press Esc while selecting an area, this code cancels the selection.


Download Example   Follow me on Twitter   RSS feed




This entry was posted in drawing, graphics and tagged , , , , , , , , , , . Bookmark the permalink.

8 Responses to Use a rubber band box to let the user select an area in a picture in C#

  1. Mike Ross says:

    An elegant solution to a glaring gap in GDI+. Thanks for sharing!

  2. Rob says:

    Hi –

    I realize this post is older, but it does come up on the first page of a google search when looking for rubberband selection and C#.

    I downloaded your code and it is an nice solution where gdi+ fails us.

    I do have a suggestion for you though.

    How would you feel about drawing the rubberand selection rectangle using the picture boxes paint event and the graphics object that is passed to the event arguments of the paint event handler. Then you don’t have to keep track of any of the bitmaps.

    Your mouseup, mousedown and mousemove event handlers would be as they are, where you track the mouse location, but you wouldn’t need to paint in any of the mouse event handlers.

    Thanks,

    Rob

  3. Rod Stephens says:

    That’s a good idea but I’m not sure it will work. When I try it, the Paint event doesn’t remove previous versions of the selection rectangle so I get a whole series of selection rectangles. I think you need to “clear” the PictureBox to the original image before you draw the new rectangle. You can do that by using DrawImage but then you may as well do it in the MouseMove event handler so Paint doesn’t need to be cluttered up with it (and presumably Paint executes more than MouseMove).

    If you know a way around that, let me know.

  4. Nealz says:

    I have created a PictureBox derived control implementing similar functionality. All painting is done in the overloaded Paint event. Works just fine. Have not had any problems with multiple rectangles.

  5. Pingback: Copy, cut, and paste parts of an image to the clipboard in C# -

  6. Pingback: Pixellate parts of an image in C# - C# HelperC# Helper

  7. Tom says:

    How about selecting non rectangular shapes ? how to do that ?

    non uniform shape copy and pasting ?

Leave a Reply

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