UPDATE: Dynamically Resizing Your ASP NET MVC Images

A while back, I used a technique for dynamically resizing images. Today, I fix an issue my readers found when loading the main page.

Written by Jonathan "JD" Danylko • Last Updated: • MVC •
small fighting figure

Back in 2016, I wrote a post about dynamically resizing your ASP.NET MVC images.

I thought this was a great way to save storage with a hosting company. Resize the original image, send it back to the browser, and call it a day.

However, there was an issue with the code.

When rendering the home page, images wouldn't display on the screen. It simply displayed a blank spot. Not even a partial image would appear.

No images on DanylkoWeb

Hmm...not cool.

While looking through the Chrome Dev Tools, I examined the Network tab and found out the images would return an HTTP status of 200, but with only a content size of zero.

THAT would explain the empty images.

There were a couple of thoughts running through my head:

  1. I was using an old class for loading an image (WebImage). Could this have an affect on the blanks images? After reading through the description, I found out the class isn't thread-safe.
  2. Was I doing too much with the WebImage class where it couldn't keep up with the processing of each image as requested?

Maybe I analyzed it too much back in 2016 and needed a simple approach.

Time to Refactor!

A Simpler Approach

I needed to take a step back and look at this a different way.

  • ImageResult should be easier to use.
  • Instead of using the WebImage, I thought I'd use the Image class (from System.Drawing, not System.Windows.Controls).
  • The Controller should be smaller instead of what we had previously.

So let's focus on the ImageResult first.

ActionResults\ImageResult.cs

public class ImageResult : ActionResult
{
    private readonly Image _image;

    public ImageResult(Image image)     {         _image = image;     }
    public override void ExecuteResult(ControllerContext context)     {         HttpResponseBase response = context.HttpContext.Response;         response.ContentType = "image/jpeg";         try         {             _image.Save(response.OutputStream, ImageFormat.Jpeg);         }         finally         {             _image.Dispose();         }     } }

For my needs, I need the picture on the main page to be a JPG so I save the image to the OutputStream as a JPG.

Compare this to our previous version and it's definitely easier on the eyes.

Image Manipulation Extension Methods

How's that for a mouthful?

Basically, we need something to constrain either the width or height when resizing the image.

If we want to constrain the image to a certain height or width, we need a way to shrink it to the appropriate size without the image looking squished.

public static class ImageExtensions
{
    public static Image BestFit(this Image image, int height, int width)
    {
        return image.Height > image.Width 
            ? image.ConstrainProportions(height, Dimensions.Height) 
            : image.ConstrainProportions(width, Dimensions.Width);
    }
    public static Image ConstrainProportions(this Image imgPhoto, int size, Dimensions dimension)
    {
        var sourceWidth = imgPhoto.Width;
        var sourceHeight = imgPhoto.Height;
        var sourceX = 0;
        var sourceY = 0;
        var destX = 0;
        var destY = 0;
        float nPercent;
 
        switch (dimension)
        {
            case Dimensions.Width:
                nPercent = (float)size / (float)sourceWidth;
                break;
            default:
                nPercent = (float)size / (float)sourceHeight;
                break;
        }
 
        int destWidth = (int)(sourceWidth * nPercent);
        int destHeight = (int)(sourceHeight * nPercent);
 
        Bitmap bmPhoto = new Bitmap(destWidth, destHeight, PixelFormat.Format24bppRgb);
        bmPhoto.SetResolution(imgPhoto.HorizontalResolution, imgPhoto.VerticalResolution);
 
        Graphics grPhoto = Graphics.FromImage(bmPhoto);
        grPhoto.InterpolationMode = InterpolationMode.HighQualityBicubic;
        grPhoto.CompositingMode = CompositingMode.SourceCopy;
        grPhoto.CompositingQuality = CompositingQuality.HighQuality;
        grPhoto.SmoothingMode = SmoothingMode.HighQuality;
        grPhoto.PixelOffsetMode = PixelOffsetMode.HighQuality;
        
        try
        {
            grPhoto.DrawImage(imgPhoto,
                new Rectangle(destX, destY, destWidth, destHeight),
                new Rectangle(sourceX, sourceY, sourceWidth, sourceHeight),
                GraphicsUnit.Pixel
            );
        }
        finally
        {
            grPhoto.Dispose();
        }
        return bmPhoto;
    }
}

It doesn't matter if we use width or height, we still need a percentage to shrink the image down to our specifications.

Once we have our dimensions, we can copy the image into a new image for the browser.

The Final Touch

The only thing remaining is the controller.

Since we don't want to change our HTML, we'll keep the Thumbnail signature the same and update the implementation details with our new ImageResult.

[ETag, OutputCache(Duration = 3600, VaryByParam = "filename")]
public ActionResult Thumbnail(string filename)
{
    var partialName = $"~{new Uri(filename).PathAndQuery}";

    using (Image image = Image.FromFile(Server.MapPath(partialName)))     {         return new ImageResult(image.BestFit(291, 285));     } }

With this change in place, the Image class seems to be a better alternative than the WebImage.

In addition to fixing this issue, I now have the start of an image manipulation extension library.

Conclusion

First, thanks go out to my readers for letting me know about this issue. By letting me know about issues like this makes it a win-win for everyone. I (try to) fix the issue and let my audience know about it so they can benefit from the solution as well.

Second, after about a week in "production," this technique seems to fix my issue with blank images.

Finally, this brings me to a couple of questions for my readers:

  • How could you make a unit test for this scenario?
  • Would you use the Parallel library for testing the Controller action?

I understand this would be considered more of an integration test, but if done properly, it would tell me whether the images were generated and returned correctly.

Thoughts?

Do you manipulate images on the fly? Is this a similar approach? Post your comments below and let's discuss.

Did you like this content? Show your support by buying me a coffee.

Buy me a coffee  Buy me a coffee
Picture of Jonathan "JD" Danylko

Jonathan Danylko is a web architect and entrepreneur who's been programming for over 25 years. He's developed websites for small, medium, and Fortune 500 companies since 1996.

He currently works at Insight Enterprises as an Principal Software Engineer Architect.

When asked what he likes to do in his spare time, he replies, "I like to write and I like to code. I also like to write about code."

comments powered by Disqus