Before to start I would like to save a few words. Initially I wanted to write post about scalable image action result for ASP.Net MVC. But after that I realized that it will be more useful if I will expand the post and show how to make similar tasks using several ways (client-side and server-side) . So this post will not be ASP.Net MVC specific – it will also contain information general for web development. This is the end of little preface, lets start.
Most of web developers in their practice worked with images. And also many of developers encountered with problem of correct scaling of the images in their web sites. But what is the problem? Suppose that we have e-commerce application and want to display some products on the web site. Also there is a list of categories in left so users are able to choose products of what category they want to see. For example go to http://www.amazon.com and select some category. Probably you will see something like this (I selected more or less neutral Kids toys category, so please don’t treat it as advertisement :) ):
I intentionally added arrows which show size of the products. As you can see all images have approximately the same width and height. It makes overall page structure more esthetic and user friendly. But in real life product pictures may have very different proportions, and it is very is insufficient for business if company staff responsible for adding content in application will arrange all product images manually. So how this process can be automated?
In order to achieve such result you can use several approaches:
- Static images – images are resized during upload and stored on external image server or database.
- Server side. Images are scaled dynamically on server (using query string parameters for example which specify width and height).
In this post I will briefly overview all mentioned methods. But at first lets summarize our task. We have different product images (with different aspect ratio) and we want to display product icons in similar divs for example (all parent divs have the same size, but their size is less then size of real product image) with preserving image proportions:
Algorithm is the following. We need to determine minimal of two ratios and then scale image using this coefficient:
As said above we assume that image size is greater then div size (k < 1), i.e. we consider size decreasing. This algorithm preserves aspect ratio (because width and height are multiplied on the same coefficient) and allows to adjust image size to size of parent div (as we used minimal of two ratios it ensures that image will fit div).
Ok now lets return to the methods which can be used to implement this algorithm. How it is solved on Amazon? Lets just see properties of the images using firebug for example. We will see that it is simple images located on one of the Amazon’s image server (e.g. http://ecx.images-amazon.com/images/I/41zku-TbXmL._SL120_.jpg). Also real image’s width and height are specified in <img /> attributes – this is double check in order to ensure that it will be displayed properly in all browsers:
So this is one of the approaches – every time when new product is added into catalog, its icon is automatically generated in uploaded into image server (keeping in mind their turnovers I don’t think Amazon staff makes it manually).
This is pretty good approach. It has minimal impact on performance as most of current browsers cache static images very well. Also we shouldn’t worry about images scaling as they were already scaled for proper size during product creation. But there is also another side: it is not so flexible and adaptable for layout changing. What if we will redesign of pages and will need to change images height or width? One of the solution – we can use css styles to adjust images size, i.e. specify css class for images which will keep original proportions. E.g. if original proportions were 10:8 we can specify:
(Scale images using em units is out of the scope of this article). But this approach will work well when you need to decrease image size (most of browsers minimize pictures with acceptable quality):
For tests I used IE 8. Opera 10.6, FF 3.0.19, Chrome 5.0.375.70. As you can see almost all browsers minimized picture pretty well (IE quality is the not very good although). But what happens if you will increase image size using css. Lets see:
Here we slightly increased images using css – and quality was lost in all browsers (and it becomes worst when image size is increased). You can see typical smoothness in all pictures (from all images above I think Chrome has best quality). I think it is not very reasonable to consciously make quality worse if we have picture of big size which can be decreased (as we saw size decreasing works better). What options do we have for such situation? One options – we can resize all images to new static size again which will not save us from similar problems in future. So it is not very good approach if we have millions of pictures and layout is changed. Another approach is to make pictures scaling dynamic.
Another way to implement algorithm mentioned above is to use css. You can specify static width in css and height: auto (or vice versa):
In this case you will have all your images with the same width, but height will be computed using original proportions:
Which is apparently not what we want because as described above we want to fit product images into parent divs with the same aspect ratio. There are useful css properties which we can apply: max-width and max-height. Lets see result if we will apply the following css class to our image:
And it works well in all browsers I checked (IE, Opera, FF, Chrome). Ok, this method is working and can be used on your site. But what if you will also need scaled images when css is not supported, e.g. you want to generate pdf or image file dynamically with catalog of your products. For this case we need to have scaled images with predefined size in order to add them in our graphic catalog. In order to do it we should consider another method: server side scaling.
First of all I will prepare some infrastructure for our example. I will assume that images are stored in content database (ProductImage table with foreign key on Product table) which is mapped on the following model (e.g. by Fluent NHibernate):
(PersistentObject<T> is layer super class which contains T Id property and overrides Equal method in order to equal objects by Id). Using DDD approach we will have separate repository for our images:
(IRepository<TEntity, TKey>is also base interface for repositories which is instantiated by model type and key type). Ok we have a model. Now we need custom action result – ImageResult in order to be able to return images from controllers (see http://blog.maartenballiauw.be/post/2008/05/ASPNET-MVC-custom-ActionResult.aspx):
In order to display ImageResult on our views we can use ImageExtensions from MVC futures, but it uses non strongly-typed approach. Magic strings approach is not very good, instead it is better to have possibility to write something like this:
I used the same idea as in post mentioned above and added several overridden methods for convenience (including possibility to specify attributes using anonymous type):
We could use helper class TagBuilder from MVC futures, but for our example I decided to use simple StringBuilder in order to keep it independent.
Now we have a model and infrastructure for displaying. Lets see how we can define ProductImageController:
As you can see ProductImageController has 2 methods: View and ViewResized. 1st method just retrieves image from database by id and returns ImageResult (custom action result, see below). 2nd method ViewResized returns scaled image by using specified maxWidth and maxHeight parameters.
In order to implement ViewResized method we can use approach described here:
As I said earlier we are interesting currently only in size decreasing. That’s why we have a check that specified maxWidth and maxHeight are less than original size and if not – return original image. It will also help us to prevent malicious users from requesting action with big width and height specified in order to occupy a lot of memory on web server.
Having this infrastructure we can add images into views by the following code:
In real world applications you can consider adding caching into this code in order to avoid database querying each time when image is requested from view. Also if you use http modules don’t forget that modules are initialized and used per web application. It means that by default your http modules will be executed each time when any resource (including images) are requested. In order to avoid performance impact consider use separate web server or at least web application (by converting regular folder in IIS with your images into virtual folder with separate web.config).
This is all I wanted to say about scaling images in ASP.Net MVC applications. I also hope that this information will be useful for web developers who work with other technologies.