Search code examples
c#.netimage-processingimage-resizingsystem.drawing

How to reduce the size of image (in KB) on uploading and scaling?


I am uploading an image and reducing the dimensions. One of the reasons to do it is that I want also to reduce the size so it is better optimised and does not load long time.

But instead of downsizing the image has been enlarged despite the dimensions have halved.

This is my function:

    public Image ScaleProportionally(Image imgPhoto, int shortestEdge = 0)
    {
        float sourceWidth = imgPhoto.Width;
        float sourceHeight = imgPhoto.Height;
        float destHeight = 0;
        float destWidth = 0;
        int sourceX = 0;
        int sourceY = 0;
        int destX = 0;
        int destY = 0;

        if (shortestEdge > 0)
        {
            if(sourceWidth < sourceHeight)
            {
                destWidth = shortestEdge;
                destHeight = (float)(sourceHeight * shortestEdge / sourceWidth);
            }
            else
            {
                destWidth = (float)(shortestEdge * sourceWidth) / sourceHeight;
                destHeight = shortestEdge;
            }

            Bitmap bmPhoto = new Bitmap((int)destWidth, (int)destHeight,
                                        PixelFormat.Format32bppPArgb);
            bmPhoto.SetResolution(imgPhoto.HorizontalResolution, imgPhoto.VerticalResolution);

            Graphics grPhoto = Graphics.FromImage(bmPhoto);
            grPhoto.InterpolationMode = InterpolationMode.Low;
            grPhoto.CompositingQuality = CompositingQuality.AssumeLinear;

            grPhoto.DrawImage(imgPhoto,
                new Rectangle(destX, destY, (int)destWidth, (int)destHeight),
                new Rectangle(sourceX, sourceY, (int)sourceWidth, (int)sourceHeight),
                GraphicsUnit.Pixel);

            grPhoto.Dispose();

        return bmPhoto;

        }
        else
        {

            return imgPhoto;
        }
    }

I originally had this below for InterpolationMode and CompositingQuality but changing them as above didn't really reduced the size of scaled photo:

            grPhoto.InterpolationMode = InterpolationMode.HighQualityBicubic;
            grPhoto.CompositingQuality = CompositingQuality.HighSpeed;

Original image has 546KB while scaled has 1MB almost twice the size.

How to reduce the size on rescaling?

I am using .NET 4.5

EDIT

Save file function:

    public int CreateAndSaveMedia(HttpPostedFileBase file, string fileName, string mediaType, string caption, int folderId, Dictionary<string,string> additionalProperties = null)
    {
        // Create temp mediaitem folder
        string mediaDirectory = System.Web.HttpContext.Current.Server.MapPath($"~/media/");
        var path = Path.Combine(mediaDirectory, fileName);
        file.SaveAs(path);

        Image imgOriginal = Image.FromFile(path);

        //pass in whatever value you want
        Image imgActual = _imageService.ScaleProportionally(imgOriginal, 450);
        imgOriginal.Dispose();
        imgActual.Save(path);
        imgActual.Dispose();

        // Open file and assign media url to media content
        FileStream s = new FileStream(path, FileMode.Open);

        // Save media content
        IMedia media = _mediaService.CreateMedia(caption, folderId, mediaType);
        _mediaService.Save(media);
        media.SetValue("umbracoFile", Path.GetFileName(path), s);

        if (additionalProperties != null)
        {
            foreach (var itm in additionalProperties)
            {
                media.SetValue(itm.Key, itm.Value);
            }
        }

        _mediaService.Save(media);

        // Get media Id
        int mediaId = media.Id;

        s.Close();
        System.IO.File.Delete(path);

        return mediaId;
    }

EDIT

Following @Nyerguds's comment I specifically set the new image type to jpg and it made a total difference:

imgActual.Save(path, System.Drawing.Imaging.ImageFormat.Jpeg);

Thanks!


Solution

  • You're not setting a type when saving, which makes the Image.Save default to either the detected type of the original image, or to PNG if it can't save in the exact format detected from the input.

    The compression you get out of png depends a lot on what is in the image; png isn't very good at compressing complex images like photos, and resizing it to smaller size might in fact increase said complexity. Also, the .Net framework's png compression algorithms aren't as good as those in actual graphics manipulation programs.

    For reducing size, you may want to specifically re-save as jpeg.

    This can be done in two ways. The simple way is to just specify Jpeg as save type:

    imgActual.Save(path, ImageFormat.Jpeg);
    

    A more advanced method is to use the JPEG encoder. If you do this, you can set the save quality (inverse of compression rate, really) of the image, as percentage (values from 1 to 100). Unfortunately, though, there's no simple method to get it; you need to retrieve it by GUID by going over the full list of available encoders:

    Int32 quality = 80;
    ImageCodecInfo jpegEncoder = ImageCodecInfo.GetImageDecoders().First(c => c.FormatID == ImageFormat.Jpeg.Guid);
    EncoderParameters encparams = new EncoderParameters(1);
    encparams.Param[0] = new EncoderParameter(Encoder.Quality, quality);
    imgActual.Save(path, jpegEncoder, encparams);