Select parts of a scaled image in a PictureBox with different SizeMode values in C#

Select parts of a scaled image

This example shows how you can select parts of a scaled image that has been stretched by a PictureBox.

The example Copy, cut, and paste parts of an image to the clipboard in C# works okay if the PictureBox has SizeMode value AutoSize or Normal, but it has trouble with other SizeMode values. The basic problem is that the PictureBox events give the mouse’s position in pixels on the screen but, depending on the SizeMode, the image may have been resized. That means the coordinates in pixels might not have the same positions as the corresponding location on the image.

For example, suppose the image has been scaled by a factor of 50% vertically and horizontally. Then the point with pixel coordinates (10, 30) lies over the point (20, 60) in the original unscaled image.

This example works much as the previous one does, so you should look at that example first so you can get the basic idea.

Whenever this example needs to use the mouse’s coordinates, it transforms those coordinates for the current SizeMode value. For example, the following code shows the program’s MouseDown event handler. The modified code is shown in blue.

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

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

The following code shows the ConvertCoordinates method.

// Convert the coordinates for the image's SizeMode.
private void ConvertCoordinates(PictureBox pic,
    out int X0, out int Y0, int x, int y)
{
    int pic_hgt = pic.ClientSize.Height;
    int pic_wid = pic.ClientSize.Width;
    int img_hgt = pic.Image.Height;
    int img_wid = pic.Image.Width;

    X0 = x;
    Y0 = y;
    switch (pic.SizeMode)
    {
        case PictureBoxSizeMode.AutoSize:
        case PictureBoxSizeMode.Normal:
            // These are okay. Leave them alone.
            break;
        case PictureBoxSizeMode.CenterImage:
            X0 = x - (pic_wid - img_wid) / 2;
            Y0 = y - (pic_hgt - img_hgt) / 2;
            break;
        case PictureBoxSizeMode.StretchImage:
            X0 = (int)(img_wid * x / (float)pic_wid);
            Y0 = (int)(img_hgt * y / (float)pic_hgt);
            break;
        case PictureBoxSizeMode.Zoom:
            float pic_aspect = pic_wid / (float)pic_hgt;
            float img_aspect = img_wid / (float)img_hgt;
            if (pic_aspect > img_aspect)
            {
                // The PictureBox is wider/shorter than the image.
                Y0 = (int)(img_hgt * y / (float)pic_hgt);

                // The image fills the height of the PictureBox.
                // Get its width.
                float scaled_width = img_wid * pic_hgt / img_hgt;
                float dx = (pic_wid - scaled_width) / 2;
                X0 = (int)((x - dx) * img_hgt / (float)pic_hgt);
            }
            else
            {
                // The PictureBox is taller/thinner than the image.
                X0 = (int)(img_wid * x / (float)pic_wid);

                // The image fills the height of the PictureBox.
                // Get its height.
                float scaled_height = img_hgt * pic_wid / img_wid;
                float dy = (pic_hgt - scaled_height) / 2;
                Y0 = (int)((y - dy) * img_wid / pic_wid);
            }
            break;
    }
}

First the method creates some variables to hold the width and height of the PictureBox and the image it holds. (It could get those values when needed directly from the PictureBox and its image, but the variables make the code a bit easier to read.)

The method then uses a switch statement to handle the SizeMode values separately.

If SizeMode is AutoSize or Normal, then the image isn’t scaled. That means the X and Y coordinates are correct, so the method leaves them alone.

If SizeMode is CenterImage, then the image is displayed at full scale and centered in the PictureBox. The code calculates the amount of empty space to the left and above the image in the PictureBox, and subtracts it from the X and Y coordinates to get the corresponding position over the image. (If the PictureBox is smaller than the image, then the amounts of empty space are negative.)

If SizeMode is StretchImage, then the image is stretched to fill the PictureBox. The code simply scales the X and Y coordinates by the same factor used to scale the image.

Finally, if SizeMode is Zoom, the image is stretched to be as large as possible without distorting it. For example, if the image is square and the PictureBox is tall and thin, then the image will fill the width of the PictureBox and there will be empty space above and below the image.

In this case, the code determines whether the PictureBox is relatively wider/shorter than the image. If it is, the code scales the location’s Y coordinate by the same amount the image is scaled vertically. It then calculates the scaled width of the image and uses that to find the amount of empty space to the image’s left. The code subtracts that amount from the X coordinate and then scales the result by the scale factor used to set the image’s resized width.

If the PictureBox is relatively tall/thin compared to the image, the code performs similar calculations switching the roles of the X and Y coordinates.


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

4 Responses to Select parts of a scaled image in a PictureBox with different SizeMode values in C#

  1. Tian says:

    error in the “zoom” case:

    float img_aspect = img_wid / (float)img_wid;
    should be
    float img_aspect = img_wid / (float)img_hgt;

    Thanks for the code. It helped me a lot.
    Regards

  2. Glassies Adamson says:

    Hi 🙂 I’m new to EMGU CV, and I’m wondering, if I can use this for other polygons or even 4-sided areas with irregular angles and lengths. If not, what can I do? Please help! BTW great tutorial 🙂

Leave a Reply

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