yesman
yesman

Reputation: 7829

Crop an image starting from its center in C#

Following this answer, I made the following code for resizing an image while keeping the aspect ratio correct:

private static Bitmap ResizeImage(Image image, int? width, int? height)
   {
       var dimensions = GetImageDimensions(image.Width, image.Height, width, height);

       var destRect = new Rectangle(0, 0, dimensions.Item1, dimensions.Item2);
       var destImage = new Bitmap(dimensions.Item1, dimensions.Item2);

       destImage.SetResolution(image.HorizontalResolution, image.VerticalResolution);

       using (var graphics = Graphics.FromImage(destImage))
       {
           graphics.CompositingMode = CompositingMode.SourceCopy;
           graphics.CompositingQuality = CompositingQuality.HighQuality;
           graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
           graphics.SmoothingMode = SmoothingMode.HighQuality;
           graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;

           using (var wrapMode = new ImageAttributes())
           {
               wrapMode.SetWrapMode(WrapMode.TileFlipXY);
               graphics.DrawImage(image, destRect, 0, 0, image.Width, image.Height, GraphicsUnit.Pixel, wrapMode);
           }
       }

       return destImage;
   }

I made the below method for getting the dimensions corrrect:

public static Tuple<int, int> GetImageDimensions(int originalWidth, int originalHeight, int? width, int? height)
    {
        double widthRatio = (double)originalHeight / (double)originalWidth;
        double heighthRatio = (double)originalWidth / (double)originalHeight;
        int newWidth = originalWidth;
        int newHeight = originalHeight;

        if (width.HasValue && !height.HasValue && width.Value <= originalWidth)
        {
            newWidth = width.Value;
            newHeight = (int)(width.Value * widthRatio);
        }
        else if (!width.HasValue && height.HasValue && height.Value <= originalHeight)
        {
            newHeight = height.Value;
            newWidth = (int)(height.Value * heighthRatio);
        }

        return new Tuple<int, int>(newWidth, newHeight);
    }

My problem: In case a user fills in both a new width and a new height, instead of resizing the image I want to crop it (starting from the center pixel of the original image). See this image:

enter image description here

If I have an image that was originally 1000x1000 pixels (red in the image), and I pass it to my method along with a new with and height (both 500), I want to return a cropped image (green in the image). However, no matter what parameter I change in the ResizeImage method, I can't seem to crop the image. In the call to graphics.DrawImage, I've tried changing all of the parameters, but I can't seem to get the result as described in the image above. What's going on?

Upvotes: 1

Views: 2292

Answers (2)

Thorsten Dittmar
Thorsten Dittmar

Reputation: 56697

First of all I suggest a change to your GetImageDimensions method - it should also return whether to resize or to crop:

public static Tuple<int, int, bool> GetImageDimensions(int originalWidth, int originalHeight, int? width, int? height)
{
    double widthRatio = (double)originalHeight / (double)originalWidth;
    double heighthRatio = (double)originalWidth / (double)originalHeight;
    int newWidth = originalWidth;
    int newHeight = originalHeight;
    bool cropping = false;

    if (width.HasValue && !height.HasValue && width.Value <= originalWidth)
    {
        newWidth = width.Value;
        newHeight = (int)(width.Value * widthRatio);
    }
    else if (!width.HasValue && height.HasValue && height.Value <= originalHeight)
    {
        newHeight = height.Value;
        newWidth = (int)(height.Value * heighthRatio);
    }
    else
    {
        cropping = true;
    }

    return new Tuple<int, int>(newWidth, newHeight, cropping);
}

Now, basically what you need to crop the image the way you want is the new dimensions and the location. Assuming that your GetImageDimensions method returns the correct dimensions, you can calculate the location as follows:

var newDimensions = GetImageDimensions(...);

// We need to detect whether to resize or to crop
bool mustCrop = newDimensions.Item3;

// Initialize your SOURCE coordinates
int x = 0;
int y = 0;
int w = image.Width;
int h = image.Height;

// Adjust if we want to crop
if (mustCrop)
{
    x = (image.Width - newDimensions.Item1) / 2;
    y = (image.Height - newDimensions.Item2) / 2;
    w = newDimensions.Item1;
    h = newDimensions.Item2;
}

Now you create a new bitmap for the destination image with the new dimensions. After that, you can draw into the new image providing both the location and the new size as the source coordinates:

graphics.DrawImage(image, 0, 0, new Rectangle(x, y, w, h), GraphicsUnit.Pixel);

This should handle both scaling and cropping. So your ResizeImage method would look like this:

private static Bitmap ResizeImage(Image image, int? width, int? height)
{
   var dimensions = GetImageDimensions(image.Width, image.Height, width, height);

   // We need to detect whether to resize or to crop
   bool mustCrop = newDimensions.Item3;

   // Initialize your SOURCE coordinates. By default, we copy
   // then entire image, resizing it
   int x = 0;
   int y = 0;
   int w = image.Width;
   int h = image.Height;

   // Adjust if we want to crop
   if (mustCrop)
   {
       x = (image.Width - newDimensions.Item1) / 2;
       y = (image.Height - newDimensions.Item2) / 2;
       w = newDimensions.Item1;
       h = newDimensions.Item2;
   }

   var destImage = new Bitmap(dimensions.Item1, dimensions.Item2);
   destImage.SetResolution(image.HorizontalResolution, image.VerticalResolution);

   using (var graphics = Graphics.FromImage(destImage))
   {
       graphics.CompositingMode = CompositingMode.SourceCopy;
       graphics.CompositingQuality = CompositingQuality.HighQuality;
       graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
       graphics.SmoothingMode = SmoothingMode.HighQuality;
       graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;

       using (var wrapMode = new ImageAttributes())
       {
           wrapMode.SetWrapMode(WrapMode.TileFlipXY);
           graphics.DrawImage(image, new Point(0,0), new Rectangle(x, y, w, h), GraphicsUnit.Pixel, wrapMode);
       }
   }

   return destImage;

}

Please note: I have not tried this. You may to fine-tune things, but you get the picture...

Upvotes: 2

mahesh sharma
mahesh sharma

Reputation: 1018

You can use the Image class of System.Drawing.Image namespace which return the image in expected radio.

For example :

Image image = Image.FromFile(path); // actual image
Image thumb = image.GetThumbnailImage(150, 150, () => false, IntPtr.Zero); //cropped image with height 150 and width 150 

Upvotes: 3

Related Questions