1

I have a form (using MVC2) which has an image-upload script, but the rules for the final image stored on the server are pretty strict. I can force the file to the dimensions I want but it always ends up exceeding the file-size required... so I can allow a sub-200k image but once my code has processed it ends up slightly bigger.

These are the rules I have to adhere to:

  • Photographs should be in colour
  • The permitted image types for the photograph are .JPG or .GIF
  • The maximum size of the image is 200kb
  • The dimensions of the photograph on the badge will be 274 pixels (wide) x 354 pixels (high) @ 200dpi (depth of pixels per inch)

This is what I have currently:

[HttpPost]
public ActionResult ImageUpload(HttpPostedFileBase fileBase)
{
    ImageService imageService = new ImageService();

    if (fileBase != null  && fileBase.ContentLength > 0 && fileBase.ContentLength < 204800 && fileBase.ContentType.Contains("image/"))
    {
            string profileUploadPath = "~/Resources/images";

            Path.GetExtension(fileBase.ContentType);
            var newGuid = Guid.NewGuid();
            var extension = Path.GetExtension(fileBase.FileName);

            if (extension.ToLower() != ".jpg" && extension.ToLower() != ".gif") // only allow these types
            {
                return View("WrongFileType", extension);
            }

            EncoderParameters encodingParameters = new EncoderParameters(1);
            encodingParameters.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, 70L); // Set the JPG Quality percentage

            ImageCodecInfo jpgEncoder = imageService.GetEncoderInfo("image/jpeg");
            var uploadedimage = Image.FromStream(fileBase.InputStream, true, true);

            Bitmap originalImage = new Bitmap(uploadedimage);
            Bitmap newImage = new Bitmap(originalImage, 274, 354);

            Graphics g = Graphics.FromImage(newImage);
            g.InterpolationMode = InterpolationMode.HighQualityBilinear;
            g.DrawImage(originalImage, 0, 0, newImage.Width, newImage.Height);

            var streamLarge = new MemoryStream();
            newImage.Save(streamLarge, jpgEncoder, encodingParameters);

            var fileExtension = Path.GetExtension(extension);
            var ImageName = newGuid + fileExtension;

            newImage.Save(Server.MapPath(profileUploadPath) + ImageName);
            //newImage.WriteAllBytes(Server.MapPath(profileUploadPath) + ImageName, streamLarge.ToArray());

            originalImage.Dispose();
            newImage.Dispose();
            streamLarge.Dispose();

            return View("Success");
        }
    return View("InvalidImage");
}

Just to add: The images are going off to print on a card so the DPI is important. But I realise that 200k is not a lot for a printed image.. none of these are my business rules! As it stands with this code an image uploaded that is pretty much 200k, ends up costing 238k(ish)

beebul
  • 993
  • 1
  • 16
  • 37

3 Answers3

3

It's very difficult to calculate the size of a jpeg in advance. Having said that, you don't need to compress it much.

Let's just look at some metrics:

274 * 354 = 96996 pixels. If you have 8 bits per pixel and 3 colour channels (i.e. 24bit colour) then you have:

274* 354 * 8 * 3 = 2,327,904 bits = 290988 bytes = 284.17 kb.

200 / 284.17 ~ 0.70.

You only need to reduce it to 70% of its original size.

Sadly, it's at this point we get to the limit of my knowledge in this area! But I reckon that by saving as a jpeg it will be in the right size range anyway, even if saving at the highest quality setting.

I would guess at setting the quality to 70 and see what happens.

EDIT: DPI settings

Apparently you only need to change the EXIF data. See this answer: https://stackoverflow.com/a/4427411/234415

Community
  • 1
  • 1
Tom Chantler
  • 14,753
  • 4
  • 48
  • 53
  • To be conservative you should assume k=1000, not 1024. – Mark Ransom Dec 13 '11 at 19:15
  • Fair point; I am showing my age I think! In which case it would be `68.7%`, but I don't think the quality settings relate to file size percentages anyway. – Tom Chantler Dec 13 '11 at 19:19
  • 1
    If you were old enough you'd remember that `k` had meaning long before the computer industry tried a different definition. I always get amused by the folks claiming a conspiracy by the disk manufacturers because their size definitions are larger than the OS; to me it's always been clear that the fault lies with the OS. And you're correct, the JPEG quality setting does not specify an absolute compression ratio. – Mark Ransom Dec 13 '11 at 19:29
  • I'm 35 and I've been around computers since I was three (Commodore Pet), so to me `k` has always meant `1024` (and then I've had to remember that it's `1000` when dealing with SI units and stuff)... :-) – Tom Chantler Dec 13 '11 at 19:31
  • I found this helpful so thanks Dommer. My first computer was a Sinclair Spectrum, 48k :) – beebul Dec 15 '11 at 09:03
  • No worries. Imagine trying to learn to type on a Sinclair Spectrum. You can only do about one word per minute :-D – Tom Chantler Dec 15 '11 at 13:07
2

You should experiment with the JPEG quality setting. You currently have it set to 90, 80 might be sufficient and will result in a smaller file.

Mark Ransom
  • 299,747
  • 42
  • 398
  • 622
1

I see some problems with the code:

  • You are using GetThumbnailImage to create a thumbnail, but that is not intended for such large thumbnails. It works up to about 120x120 pixels. If the image has an embedded thumbnail, that will be used instead of scaling down the full image, so you will be scaling up a smaller image, with obvious quality problems.
  • You are saving the thumbnail to a memory stream, which you then just throw away.
  • You are saving the thumbnail to file without specifying the encoder, which means that it will either be saved as a low compressed JPEG image or a PNG image, that's why you get a larger file size.
  • You never dispose the uploadedImage object.

Note: The resolution (PPI/DPI) has no relevance when you display images on the web.

Guffa
  • 687,336
  • 108
  • 737
  • 1,005
  • Yeah I've removed (unsuccessfully) some other elements from that code which did stuff that wasn't relevant for this project. Also the images are going off to print on a card so the DPI is important. But I realise that 200k is not a lot for a printed image.. none of these are my business rules! :) – beebul Dec 13 '11 at 19:34
  • 1
    @beebul, DPI is important when printing but it's usually determined by the application or driver, not the field embedded in the file. As a rule of thumb you should worry about the pixel dimensions and ignore DPI entirely, as it's already been used to calculate the required pixel dimensions. – Mark Ransom Dec 13 '11 at 19:41
  • Actually the files do end up as a png. How can I force them to jpg... I I think that might be the solution.. – beebul Dec 13 '11 at 19:43
  • 1
    @beebul: That is because you are calling the `Save` method with only the file name, no encoder. If you already have saved the thumbnail to the memory stream, you should just save that to a file: `File.WriteAllBytes(Server.MapPath(profileUploadPath) + ThumbnailName, streamThumbnail.ToArray());`. – Guffa Dec 13 '11 at 19:48
  • @beebul: It's been available since .NET 2.0. It's in the `System.IO` namespace, so you would need `using System.IO;` at the top of the file. If you right click on the method name in Visual Studio, it should give you the option to add the `using` statement for you. – Guffa Dec 14 '11 at 09:16
  • Hi I'm using that in my class but I still get a red WriteAllBytes on this line newImage.WriteAllBytes(Server.MapPath(profileUploadPath) + ImageName, streamLarge.ToArray()); -- I've changed the code posted up above. Thanks. Dave – beebul Dec 14 '11 at 10:12
  • The `WriteAllBytes` method is not in the `Image` class, it's a static method in the `File` class. – Guffa Dec 14 '11 at 10:26
  • Hi Guffa I managed to get that to work and it gives me a tiny jpg now so I can actually increase the compression rate :) Thanks for all replies! All have been helpful.. – beebul Dec 14 '11 at 13:19