Zoom and crop a picture in C#

[crop a picture]

This program lets the user zoom in and then click and drag to crop a picture. When you zoom in, the selection rectangle snaps to positions between the image’s enlarged pixels so it’s easy to tell exactly which pixels are selected.

The program is largely similar to the one in the post Crop a picture in C#, but it must address a few new issues.

The program uses the following class-level variables to keep track of its images.

// The original image.
private Bitmap OriginalImage;

// The currently cropped image.
private Bitmap CroppedImage;

// The currently scaled cropped image.
private Bitmap ScaledImage;

// The cropped image with the selection rectangle.
private Bitmap DisplayImage;
private Graphics DisplayGraphics;

These variables are:

  • OriginalImage – Holds the originally loaded image.
  • CroppedImage – This is the original image after being cropped by the user. The user can then crop it again if desired.
  • ScaledImage – This is the cropped image scaled.
  • DisplayImage – This is the image visible on the form. It’s the same as ScaledImage except during selection it includes the selection rectangle.
  • DisplayGraphics – This is a Graphics object attached to the DisplayImage.

The two biggest differences between this program and the previous one are how the program creates all of these images and how it draws its selection rectangle on the scaled image.

The following method shows how the program creates its images.

// Make the scaled cropped image.
private void MakeScaledImage()
{
    int wid = (int)(ImageScale * (CroppedImage.Width));
    int hgt = (int)(ImageScale * (CroppedImage.Height));
    ScaledImage = new Bitmap(wid, hgt);
    using (Graphics gr = Graphics.FromImage(ScaledImage))
    {
        Rectangle src_rect = new Rectangle(0, 0,
            CroppedImage.Width, CroppedImage.Height);
        Rectangle dest_rect = new Rectangle(0, 0, wid, hgt);
        gr.PixelOffsetMode = PixelOffsetMode.Half;
        gr.InterpolationMode = InterpolationMode.NearestNeighbor;
        gr.DrawImage(CroppedImage, dest_rect, src_rect,
            GraphicsUnit.Pixel);
    }

    DisplayImage = ScaledImage.Clone() as Bitmap;
    if (DisplayGraphics != null) DisplayGraphics.Dispose();
    DisplayGraphics = Graphics.FromImage(DisplayImage);

    picCropped.Image = DisplayImage;
    picCropped.Visible = true;
}

Before this method is called, the program sets the ImageScale variable to the desired scale and it creates the CroppedImage.

This method calculates the size of the ScaledImage. It creates rectangles to represent the areas of the CroppedImage and the ScaledImage, sets the Graphics object’s PixelOffsetMode and InterpolationMode properties (see the post Use PixelOffsetMode in C#), and draws the CroppedImage onto the ScaledImage. The method finishes by cloning the image and displaying it.

The second major change is in the way the program draws its selection rectangle. The event handlers are roughly the same, but this program snaps the rectangle’s edges so they lie between the image’s enlarged pixels.

The following code shows the program’s MouseDown event handler.

private void picCropped_MouseDown(object sender, MouseEventArgs e)
{
    Drawing = true;

    StartPoint = RoundPoint(e.Location);

    // Draw the area selected.
    DrawSelectionBox(StartPoint);
}

This is the same as the previous program’s version except it calls the following RoundPoint method to snap the starting point so it lies between the enlarged pixels.

// Round the point to the nearest unscaled pixel location.
private Point RoundPoint(Point point)
{
    int x = (int)(ImageScale * (int)(point.X / ImageScale));
    int y = (int)(ImageScale * (int)(point.Y / ImageScale));
    return new Point(x, y);
}

The RoundPoint method simply divides the point’s coordinates by the scale factor ImageScale. It converts the result into an integer and then multiplies by ImageScale again. As a result, the X and Y coordinates are multiples of ImageScale so they lie between the scaled pixels.

The program’s only remaining interesting detail is how the program converts the scaled selection area into an unscaled version in the following MouseUp event handler.

private void picCropped_MouseUp(object sender, MouseEventArgs e)
{
    if (!Drawing) return;
    Drawing = false;

    // Crop.
    // Get the selected area's dimensions.
    int x = (int)(Math.Min(StartPoint.X, EndPoint.X) / ImageScale);
    int y = (int)(Math.Min(StartPoint.Y, EndPoint.Y) / ImageScale);
    int width = (int)(Math.Abs(StartPoint.X - EndPoint.X) / ImageScale);
    int height = (int)(Math.Abs(StartPoint.Y - EndPoint.Y) / ImageScale);

    if ((width == 0) || (height == 0))
    {
        MessageBox.Show("Width and height must be greater than 0.");
        return;
    }

    Rectangle source_rect = new Rectangle(x, y, width, height);
    Rectangle dest_rect = new Rectangle(0, 0, width, height);

    // Copy that part of the image to a new bitmap.
    Bitmap new_image = new Bitmap(width, height);
    using (Graphics gr = Graphics.FromImage(new_image))
    {
        gr.DrawImage(CroppedImage, dest_rect, source_rect,
            GraphicsUnit.Pixel);
    }
    CroppedImage = new_image;

    // Display the new scaled image.
    MakeScaledImage();
}

The key steps (highlighted in blue) divide the selection rectangle’s bounds by ImageScale to map back to the original image’s unscaled coordinates.

The method then copies the selected area into a new image, saves it in CroppedImage, and calls MakeScaledImage to display the scaled version.

Download the program to see additional 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 graphics, image processing and tagged , , , , , , , , , , , . Bookmark the permalink.

Leave a Reply

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