Specifying the size of an ImageResult

In this recipe, we will create another custom ActionResult. This one will also render an image, but in this case we will allow the user to set the size of the image.

Getting ready

Nothing is required to get this recipe running other than writing some quick code. Due to the changes of how this recipe works, we won't actually be building off of the last recipe.

How to do it...

  1. Let's start by creating a new ASP.NET MVC project.
  2. In the models folder, we create a new class and name it ImageResizeResult. Then, we set this new class to inherit from ActionResult.

    ImageResizeResult.cs:

    public class ImageResizeResult : ActionResult
    {
    
  3. At the top of the class we are going to specify four private variables for use in our class. These variables will be populated via the constructor when this ActionResult is put to work.

    ImageResizeResult.cs:

    private string _path;
    private int _width;
    private int _maximumHeight;
    private bool _noZooming;
    
  4. Remember that, if you want to pass data into the custom ActionResult, you will need to do it through the constructor. For that reason, we will define the constructor next. In our constructor, we will take in the name of the file that is requested (just the filename), which will allow us to hide the location of our files and (if we need to) move them too. We will also take in the height and width of the image that should be returned. We will also allow the requestor to specify whether or not the image can be zoomed (made bigger than the original). Notice that we are using a new .NET 4 optional parameter!

    ImageResizeResult.cs:

    public ImageResizeResult(string fileName, int width, int maximumHeight, bool noZooming = false)
    {
    //put this path reference in a config file somewhere!
    _path = @"{path to files that can be loaded}" + fileName;
    _width = width;
    _maximumHeight = maximumHeight;
    _noZooming = noZooming;
    }
    
  5. Next, we are going to reuse a snippet of code from the last recipe that will allow us to determine the content type (except that we are returning ImageFormat instead of String) of the image that we are playing with, so that it is rendered correctly on the client. This code interrogates the file extension of the file that was requested and uses a simple switch statement to determine the ImageFormat.

    ImageResizeResult.cs:

    private ImageFormat GetImageFormatFromFile()
    {
    //get extension from path to determine contentType
    string[] parts = _path.Split('.'),
    string extension = parts[parts.Length - 1];
    ImageFormat imageFormat;
    switch (extension.ToLower())
    {
    case "jpeg":
    case "jpg":
    imageFormat = ImageFormat.Jpeg;
    break;
    case "gif":
    imageFormat = ImageFormat.Gif;
    break;
    default:
    throw new NotImplementedException(extension + "not handled");
    }
    return imageFormat;
    }
    
  6. Now we can create a method to resize an image. In this case, we are expecting the users to pass in only the filename that they are interested in—which means that we need to do some checking to make sure that the image that was requested actually exists on the filesystem. If it does, then we load the image using the Image.FromFile helper method in the Image class.

    ImageResizeResult.cs:

    public Image ResizeImage(string imagePathToResize)
    {
    Image fullsizeImage;
    //check for file
    if (File.Exists(_path))
    {
    fullsizeImage = Image.FromFile(_path);
    }
    else
    {
    throw new FileNotFoundException(_path);
    }
    //load the image from the file system
    fullsizeImage = Image.FromFile(_path);
    
  7. Now we have to perform a little hackery to make sure that the image is loaded instead of the image's internal thumbnail.

    ImageResizeResult.cs:

    // hack to prevent the internal thumbnail from being used!
    fullsizeImage.RotateFlip(RotateFlipType.Rotate180FlipNone);
    fullsizeImage.RotateFlip(RotateFlipType.Rotate180FlipNone);
    
  8. Next, we will implement a feature that allows the request to control whether we resize the image to larger than the original (zoom). In some cases, it might be inappropriate to enlarge an image. Enlargements frequently cause distortions (though the .NET graphics libraries have gotten much better at this).

    ImageResizeResult.cs:

    // can we zoom this image?
    if (_noZooming)
    {
    if (fullsizeImage.Width <= _width)
    {
    _width = fullsizeImage.Width;
    }
    }
    
  9. Now we are going to make sure that we don't distort our image by setting the width and zoom to whatever was entered. Instead, we are going to maintain the aspect ratio of the image that we are working with. This means that if we have a square original image, we won't allow the request to turn it into a rectangle there by distorting the image.

    ImageResizeResult.cs:

    // determine new height
    int newHeight = fullsizeImage.Height * _width / fullsizeImage.Width;
    if (newHeight > _maximumHeight)
    {
    // Resize with height instead
    _width = fullsizeImage.Width * _maximumHeight / fullsizeImage.Height;
    newHeight = _maximumHeight;
    }
    
  10. Now that we have all the information we need, we can create a new instance of Image by using another helper method—GetThumbnailImage. Then we will dispose of the instance of the original image. And finally, we will return the resized image.

    ImageResizeResult.cs:

    Image newImage = fullsizeImage.GetThumbnailImage(_width, newHeight, null, IntPtr.Zero);
    //dispose of the in memory original
    fullsizeImage.Dispose();
    return newImage;
    }
    
  11. Now we can build the ExecuteResult method, which is our hook into the MVC framework. In this method, we will perform all the orchestration that allows us to set the type of image we are returning in the response stream, resize the image, and then write the image down to the client (this part is very much like the last recipe). Pay close attention to where we dispose of the image that we created!

    ImageResizeResult.cs:

    public override void ExecuteResult(ControllerContext context)
    {
    byte[] buffer = new byte[4096];
    HttpResponseBase response = context.HttpContext.Response;
    //no context? stop processing
    if (context == null)
    throw new ArgumentNullException("context");
    //set files content type
    response.ContentType = "image/" + GetImageFormatFromFile().ToString();
    //get the resized image
    Image resizedImage = ResizeImage(_path);
    MemoryStream ms = new MemoryStream();
    resizedImage.Save(ms, GetImageFormatFromFile());
    MemoryStream imageStream = new MemoryStream(ms.ToArray());
    ImageResult sizeImageResult sizespecifyingwhile (true)
    {
    int read = imageStream.Read(buffer, 0, buffer.Length);
    if (read == 0)
    break;
    response.OutputStream.Write(buffer, 0, read);
    }
    response.End();
    ms.Dispose();
    imageStream.Dispose();
    }
    
  12. At this point, our ActionResult should be ready for use. For that reason, open up the HomeController and add a new ActionResult named GetImage. This ActionResult will return our new ImageResizeResult. In this action, we will allow the requester to pass in the requested image name, the width and height, and whether or not they want to allow zooming. However, we are going to use the new optional parameter functionality for the zooming option and give it a default value of false.

    HomeController.cs:

    public ImageResizeResult GetImage(string image, int width, int height, bool noZooming = false)
    {
    return new ImageResizeResult(image, width, height, noZooming);
    }
    
  13. Now you can run your solution and browse directly to this action specifying all the appropriate url parameters, and you should get an image that you can resize till your heart is content!
    How to do it...
  14. With this working as expected, we can add some code to the Index view that exercises our resizing abilities. We will add three images to our Index.aspx view. The first one will show that we can take an image, which is normally 200x200, and blow it up to 250x250. The next example will show that we don't allow distortion of the image by trying to set the image's width to 75 and the height to 200. And the last example will show that we can control distortion in another way by not allowing zooming.

    Index.aspx:

    <p>
    <img src="/home/GetImage?image=me.jpg&width=250&height=250" />
    <img src="/home/GetImage?image=me.jpg&width=75&height=200" />
    <img src= "/home/GetImage?image=me.jpg&width=1000&height=1000&noZooming=true" />
    </p>
    
    How to do it...

How it works...

This example really isn't all that different from our last recipe, in that we created a class that implemented ActionResult. We did add a few additional parameters to allow the users to have more control over the process. And then we added some image resize code.

There's more...

The nice thing about this is that you have a generic image-resizing handler. Also, if all of your images go through either this or the previous recipe, you have a path that you can add to your image processing fairly easily. Something you might want to add down the road is that when an image is requested for the first time in a given size, you might save it to the file system. Then, when it is requested the next time, you might serve that file from the file system rather than attempting a resize every time. Or you might toss the resized file into a cache of some form and load it straight out of memory the next time. The key here is that you have control over how your images are served.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset