25

I need to convert some System.Drawing based code to use this .NET Core compatible library:

https://github.com/SixLabors/ImageSharp

The System.Drawing based code below resizes an image and crops of the edges, returning the memory stream to then be saved. Is this possible with the ImageSharp library?

private static Stream Resize(Stream inStream, int newWidth, int newHeight)
{
    var img = Image.Load(inStream);
    if (newWidth != img.Width || newHeight != img.Height)
    {
        var ratioX = (double)newWidth / img.Width;
        var ratioY = (double)newHeight / img.Height;
        var ratio = Math.Max(ratioX, ratioY);
        var width = (int)(img.Width * ratio);
        var height = (int)(img.Height * ratio);

        var newImage = new Bitmap(width, height);
        Graphics.FromImage(newImage).DrawImage(img, 0, 0, width, height);
        img = newImage;

        if (img.Width != newWidth || img.Height != newHeight)
        {
            var startX = (Math.Max(img.Width, newWidth) - Math.Min(img.Width, newWidth)) / 2;
            var startY = (Math.Max(img.Height, newHeight) - Math.Min(img.Height, newHeight)) / 2;
            img = Crop(img, newWidth, newHeight, startX, startY);
        }
    }

    var ms = new MemoryStream();
    img.Save(ms, ImageFormat.Jpeg);
    ms.Position = 0;
    return ms;
}

private static Image Crop(Image image, int newWidth, int newHeight, int startX = 0, int startY = 0)
{
    if (image.Height < newHeight)
        newHeight = image.Height;

    if (image.Width < newWidth)
        newWidth = image.Width;

    using (var bmp = new Bitmap(newWidth, newHeight, PixelFormat.Format24bppRgb))
    {
        bmp.SetResolution(72, 72);
        using (var g = Graphics.FromImage(bmp))
        {
            g.SmoothingMode = SmoothingMode.AntiAlias;
            g.InterpolationMode = InterpolationMode.HighQualityBicubic;
            g.PixelOffsetMode = PixelOffsetMode.HighQuality;
            g.DrawImage(image, new Rectangle(0, 0, newWidth, newHeight), startX, startY, newWidth, newHeight, GraphicsUnit.Pixel);

            var ms = new MemoryStream();
            bmp.Save(ms, ImageFormat.Jpeg);
            image.Dispose();
            var outimage = Image.FromStream(ms);
            return outimage;
        }
    }
}
Darren
  • 9,014
  • 2
  • 39
  • 50
  • 1
    Actually, I have found a sample that might help, probably I can drop the rounded corners part out and it will be similar: https://github.com/SixLabors/Samples/blob/master/ImageSharp/AvatarWithRoundedCorner/Program.cs – Darren Mar 22 '19 at 11:17

2 Answers2

34

Yeah, super easy.

using (var inStream = ...)
using (var outStream = new MemoryStream())
using (var image = Image.Load(inStream, out IImageFormat format))
{
    image.Mutate(
        i => i.Resize(width, height)
              .Crop(new Rectangle(x, y, cropWidth, cropHeight)));

    image.Save(outStream, format);
}

EDIT If you want to leave the original image untouched you can use the Clone method instead.

using (var inStream = ...)
using (var outStream = new MemoryStream())
using (var image = Image.Load(inStream, out IImageFormat format))
{
    var clone = image.Clone(
                    i => i.Resize(width, height)
                          .Crop(new Rectangle(x, y, cropWidth, cropHeight)));

    clone.Save(outStream, format);
}

You might even be able to optimize this into a single method call to Resize via the overload that accepts a ResizeOptions instance with `ResizeMode.Crop. That would allow you to resize to a ratio then crop off any excess outside that ratio.

James South
  • 10,147
  • 4
  • 59
  • 115
  • 1
    Awesome, looks like I can completely drop the original Resize and Crop methods I had. I was using them to save three different sizes. Will I need to reload from the 'instream' after each save, or if I am cropping **smaller each time** will I get a performance benefit by not reload loading? – Darren Mar 28 '19 at 10:05
  • I'm also wondering if ResizeMode.Crop is going to output the same image as Resize and then Crop? And if you have one of those 'buy me a coffee' pages to support? – Darren Mar 28 '19 at 10:31
  • 1
    I just used the backers link at the bottom of the github page (tiny amount but my site currently loses money), for anyone else: https://github.com/SixLabors/ImageSharp – Darren Mar 28 '19 at 10:50
  • 3
    Thanks! I've added an additional code example of how to clone the image and resize leaving the original untouched. That way you can load the image once and resize many times without loss of quality. `ResizeMode.Crop` should create the same output by default as long as the crop is centered though you can set the coordinates. – James South Mar 29 '19 at 03:12
  • Hi @JamesSouth, apologies for the necromancy. I've used more-or-less the same way to generate a thumbnail from an incoming image, except for the cloning part. But certain jpeg images get a distorted border on one side of the image once I resize them into thumbnails. I've come to learn that this has something to do with ICC color profile and the jpeg encoder, but could not find (nor understand) what exactly is causing this. Have you come across this? Could you please suggest a solution? TIA. – noobmaster007 Apr 27 '20 at 04:13
  • @noobmaster007 come visit our Gitter chatroom and we'll see what we can figure out from there. – James South Apr 27 '20 at 11:44
13

So here's relevent code after so far after converting to not use original methods:

using (var fullSizeStream = new MemoryStream())
using (var smallStream = new MemoryStream())
using (var thumbStream = new MemoryStream())
using (var reviewThumbStream = new MemoryStream())
using (var image = Image.Load(inStream))
{
    // Save original constrained
    var clone = image.Clone(context => context
        .Resize(new ResizeOptions
        {
            Mode = ResizeMode.Max,
            Size = new Size(1280, 1280)
        }));
    clone.Save(fullSizeStream, new JpegEncoder { Quality = 80 });

    //Save three sizes Cropped:
    var jpegEncoder = new JpegEncoder { Quality = 75 };
    clone = image.Clone(context => context
        .Resize(new ResizeOptions
        {
            Mode = ResizeMode.Crop,
            Size = new Size(277, 277)
        }));
    clone.Save(smallStream, jpegEncoder);

    clone = image.Clone(context => context
        .Resize(new ResizeOptions
        {
            Mode = ResizeMode.Crop,
            Size = new Size(100, 100)
        }));
    clone.Save(thumbStream, jpegEncoder);

    clone = image.Clone(context => context
        .Resize(new ResizeOptions
        {
            Mode = ResizeMode.Crop,
            Size = new Size(50, 50)
        }));
    clone.Save(reviewThumbStream, jpegEncoder);

    //...then I just save the streams to blob storage
}
Darren
  • 9,014
  • 2
  • 39
  • 50