Tuesday, September 06, 2011

Scaling images for web

 

Today I am going to divert a bit from the topic of my domain models series and try to write about what I am currently working on. I hope to continue the series in the following posts.

For the past 2 days I have been working on migrating a code responsible for image transformations (scaling to be precise) from the MakingWaves.Common library to Open Waves repository. Whenever I move stuff to Open Waves, I try to improve what we have and implement ideas we’ve had for a given feature but never got time to code them. Before I talk about the code itself, let’s see what problems need to be solved.

Resizing

Ok, so we have an image file (or a stream) and want to generate a resized version of the image.

System.Drawing (GDI+)

System.Drawing namespace has been around since the first version of the framework. It is a set of wrappers around GDI+ API.It has been used in many web application even thought, the documentation clearly says it is not supported for use in ASP.NET. The fact is, it works and works reasonably well. There are things to remember though. First, be very careful to dispose anything that should be disposed. Second, to achieve good results (image quality and performance) one needs to remember to set a couple of properties to just the right values. My favourite is imageAttributes.SetWrapMode(WrapMode.TileFlipXY) to avoid “ghosting” (1px frame) around the result image.

See this article for more details and this one for comparison of quality vs. performance for different settings.

WPF

It may be suppressing, but it is possible to use WPF to resize images in the server environment. Again, an article describing the details from Bertrand Le Roy. The performance is much better compared to GDI+, and the quality does not suffer. Two problems though: works only in full trust, and (according to Mr. Le Roy) it is again not supported in server scenarios (I could not find anything in MSDN to confirm this).

By the way, this is the method that EPiServer.ImageLibrary uses to process images for image editor in edit mode. So, if you want to use this method, don’t want to code it yourself and are working on an EPiServer site, go and read this entry from Mattias Lövström. The only problem is, that the API the component exposes makes it hard to maintain the aspect ratio of an image. Basically, they will let you scale the image to a given width and height, but first you will need to figure out the correct width/height ratio. I guess, when used in image editor that’s fine as the client-side code keeps a fixed aspect ratio when resizing an image, but when what you get is a random picture to resize, this becomes a problem.

The bad news is, that if you try to use System.Drawing.Image to first load an image, inspect Width and Height properties to compute the ratio, you’ll end up decoding the file which is a pretty heavy operation. It is possible that whatever you can gain by using WPF transformations you will lost by unnecessarily decoding the image. The good news is, that if you use WPF API to do the same, it will only load image metadata and will not decode the image (it is lazy in that matter).

WIC (Windows Imaging Component)

It is not a surprise that WPF imaging API is a wrapper for a piece of native code. This native code is called WIC. here is a chapter from MSDN showing the usage of the component. To use it from .NET you will need a managed wrapper. Once again I will send you to Tales from the Evil Empire blog for details on how to use the components. There you will also find a link to a ready to use managed wrapper.

Resizing summary

In theory, the only truly supported way is to use WIC directly (even though WPF does pretty much the same thing). In practice components from both System.Drawing (GDI) and System.Windows.Media (WPF) namespaces seem to work reliably on the server. System.Drawing has an advantage of working in a medium trust environment. At Making Waves, for quite a while, we have successfully used GDI for our resizing needs, but I figured, that since I am working on migrating this part of our frameworks I may as well implement the other two mechanisms and add support for plugging in EPiServer.ImageLibrary. Note: We use the same set of resizing components in non EPiServer projects, hence we need more then just EPiServer.ImageLibrary to cover our needs.

Transformations

In practice, in most cases, you will need transformations that maintain the aspect ratio of the original image. We use one of the following:

Scale to fit – resizes an image so it fits specified rectangle (width/height) without clipping. For example when fitting into a square, landscape images will be sized so their width matches the width of the square, while portrait images will be sized so their height matches the height of the square. This is the most often used transformation (galleries, thumbnails, user pictures, or whenever you want to avoid clipping the image).

Scale down to fit – same as the above but will only resize an image if it is larger then specified rectangle (width/height). This is for example useful if you want to scale images for display on mobile device, where any image wider then the width of the screen gets scaled down, but the ones that fit the display are not transformed.

Scale to fill – resizes an image so it fills the whole rectangle (width/height). Ultimately the image is scaled so it is large enough to cover the target area and centrally cropped to match the specified dimensions. Useful when a graphics design assumes that the picture covers the whole region of a page.

Other transformations that are not very popular but may be useful once in a while are stretch and crop.

End of part 1

I have not planned this but it appears this one is going to be a first post in the series about scaling images for web. Things I want to cover in the next post include:

  • Versioning of original images
  • Methods for serving transformed images
  • Caching strategies
  • OpenWaves.Web.Image control

A lot of the things I discussed here has already been implemented and is available in Open Waves repository. Even though this is still work in progress, I will be happy to hear any comments you might have about the code.

No comments: