-2

How to shrink an image to a target size in bytes in C#? Specifically, I am looking to take an image uploaded to an ASP.NET full framework application and shrink it so that it is less or equal to a maximum number of bytes (e.g. 1 MB). I am OK with the dimensions of the image being shrunk as well and can accept some loss of image quality.

Bryan Bedard
  • 2,619
  • 1
  • 23
  • 22
  • _"that one because it is closed."_ -- yup...for a reason. First of all, "good way" is opinion based, and very much sensitive to specific requirements. Second, there's nothing in the question that indicates what the _specific_ problem is. The question you posted here is pretty much identical to the one that was closed. Of course, there are already answers to that poorly-presented question too. If you think the question shouldn't have been closed, you can vote to open it again, and if it gets opened again, you can post an answer. – Peter Duniho May 24 '21 at 00:45
  • Thanks Peter. I don't see how to vote to open the other question. I do have the ability to edit the question, are you suggesting that I edit the other question to make it more focused then post my answer there? – Bryan Bedard May 24 '21 at 00:56
  • You may be able to flag the question for reopening...I'm not sure. I don't remember what the flag options are. You certainly could edit the question to improve it. But I'll point out that you'd have to a) make sure you stay within the intent of the original author, while b) making a much less-opinion-based/vaguely-stated question than either of these questions have been. – Peter Duniho May 24 '21 at 02:03
  • I removed "good way" from my question in the last edit and elaborated on the requirements. It's a specific question with a specific answer. – Bryan Bedard May 24 '21 at 03:37
  • It's still too vague. As stated now, given that you _"can accept some loss of image quality"_, it's not clear at all why you don't just reencode the image as JPEG at a lower quality to achieve the size you want. Conversely, it's trivial to resize an image to a size that's guaranteed to be less than 1 MB. And the question still isn't appreciably different from the duplicate. Please read [ask]. – Peter Duniho May 24 '21 at 06:45

1 Answers1

-1

If you have access to System.Drawing (.NET Full Framework) then this code works. This algorithm is a bit naive because it simply keeps shrinking the dimensions of the image by 10% until the target size is reached or a maximum number of attempts has been exceeded. It could be improved and made less brute force.

using System.Drawing;
using System.IO;

public static class ImgUtil
{
    /// <summary>
    /// Attempt to shrink an image down to a target size in bytes. If the input image is 
    /// already small enough, it will be returned unaltered. If the image needs to be 
    /// shrunk, the output image will be smaller in terms of width/height but the width/height
    /// ratio will be preserved. There can be some loss of quality. The method attempts to
    /// shrink the dimensions of he image incrementally until the number of bytes is equal or
    /// less than the target. There is a maximum number of attempts. If the maximum number of
    /// attempts is reached, the output image may be larger than the target size.
    /// </summary>
    /// <param name="imageBytes">>Byte array containing the image to be shrunk.</param>
    /// <param name="targetSizeBytes">Target size in bytes.</param>
    /// <param name="maxAttempts">Maximum number of shrink attempts, reducing the dimensions by 10% each attempt.</param>
    /// <returns></returns>
    public static byte[] ShrinkImage(byte[] imageBytes, int targetSizeBytes, int maxAttempts = 10)
    {
        using (var ms = new MemoryStream(imageBytes))
        using (var img = Image.FromStream(ms))
        {
            var attempts = 1;
            var newWidth = img.Width;
            var newHeight = img.Height;
            while ((imageBytes.Length > targetSizeBytes) && (attempts <= maxAttempts))
            {
                // Shrink by 10%
                newWidth = (int)(newWidth * 0.9);
                newHeight = (int)(newHeight * 0.9);

                using (var bitmap = new Bitmap(img, new Size(newWidth, newHeight)))
                using (var outputMs = new MemoryStream())
                {
                    bitmap.Save(outputMs, img.RawFormat);
                    imageBytes = outputMs.ToArray();
                }

                attempts++;
            }
        }

        return imageBytes;
    }
}
Bryan Bedard
  • 2,619
  • 1
  • 23
  • 22
  • 2
    This is absolutely dreadful, you're leaking native memory for no reason. – Blindy May 23 '21 at 15:34
  • 2
    1) memory leaks because of missing `using` directives, 2) unneccessary loss of image quality by creating a shrunk version of a shrunk version of a shrunk version instead of using the original image as source for every step. (also: you can just calculate newHeight the _exact_ same way as newWidth) – Franz Gleichmann May 23 '21 at 15:35
  • 2
    And if you want to use the raw image format you can just math it out. The question is about compressed images. And what is that region doing there? Where's the stream disposes? – Blindy May 23 '21 at 15:35
  • Thanks for the feedback. I updated the code block based on the suggestions. Blindy/Franz, I did forget about wrapping the IDisposables in usings when I was working through the code. I also took out the use of HBitmap which wasn't necessary. Franz, good suggestion about using the original image each resize. Blindy, I agree there is a way to "math it out" but I'm not sure how to do it because compression comes into play. If you can elaborate on what you are thinking that would be welcome. – Bryan Bedard May 24 '21 at 00:46