Server-side resizing with WPF: now with JPG

Un I’ve shown before how to generate thumbnails from ASP.NET server code using the WPF media APIs instead of GDI+ (which is unsupported when used in server code).

In the previous article, I’ve been generating the thumbnails as PNG files. The reason for that is that PNG is a lossless format and I wanted to isolate as few variables as possible that impacted output quality and performance. Adding JPEG artifacts and the variable of the quality setting would have just muddied the water.

One commenter (Victor) pointed out that the PNG API in WPF does not compress the output though. That is clearly a problem for a web application.

This is why I decided to write this follow-up post. The previous one is still useful for establishing that the decision to use WPF rather than GDI+ makes sense but I want to complement that with some JPEG comparison.

The algorithm that I’m going to use here is what I consider the best compromise between quality and speed for each technology. I chose HQ bicubic for GDI and “Fast WPF” for WPF. PNG generation will be our baseline, and I’ll generate JPG files for quality settings in increments of 5 from 50% (lower than 50% is just horribly bad). The set of images that I will resize is the same as in the previous post.

The code almost doesn’t change, except that we use a JpgBitmapEncoder that needs to be configured with the quality setting instead of a PngBitmapEncoder:

var targetEncoder = new JpegBitmapEncoder {
    QualityLevel = quality
};

Let’s look at the qualities for my two favorite, moiré-prone images:

Copenhagen_50 Copenhagen_55 Copenhagen_60
50% 55% 60%
Copenhagen_65 Copenhagen_70 Copenhagen_75
65% 70% 75%
Copenhagen_80 Copenhagen_85 Copenhagen_90
80% 85% 90%
Copenhagen_95 Copenhagen_100 copenhagen
95% 100% PNG
IMG_2565_50 IMG_2565_55 IMG_2565_60 IMG_2565_65
50% 55% 60% 65%
IMG_2565_70 IMG_2565_75 IMG_2565_80 IMG_2565_85
70% 75% 80% 85%
IMG_2565_90 IMG_2565_95 IMG_2565_100 IMG_2565
90% 95% 100% PNG

Even at 100%, JPEG doesn’t look as sharp, deep and saturated as PNG but none of those look tragically bad. This is because thumbnails are too small for the defects to jump to the eye. If you use magnification ([Windows] + [+] on Windows), you’ll see them.

The compression defects we are looking for are waves near high-contrast edges:Border artifacts

And zones, usually square, where the picture lacks definition and tends to be too flat to show the details of the original picture:Compression squares

The defects stop being too noticeable around 75% compression on these pictures. Of course, with this kind of lossless compression, it depends a lot on the pictures, so do your own tests and choose the compression that is the best compromise between quality and size for your images. Speaking of size, here’s a graph of size and time against compression level over resizing the same thirty 12 megapixels photos I used for the previous post:Resize Jpg bench results: size and time against compression levels.

The first thing to notice is that the time to compress almost does not depend on the compression level and is a little faster than PNG. Second, we also see that once more, WPF is more than twice as fast as GDI+. Third, the size of the resulting files grows reasonably with compression level up to around 85%, after which it rises faster.

There is a weird peak at 75% that I’m not sure how to explain. I did make some additional measurements around that value and it’s really just for 75%, not 76, not 74, and none of the other values. There seems to be something magical about 75% for some images so it might be a good idea to stay clear of that particular value.

Of course, even at 100%, thumbnails are still more than twice as small as uncompressed PNG but the sweet spot here seems to be between 76% and 90%, where most of the artifacts of compression stop being too visible on most images and yet the size doesn’t grow too fast. I’ll be using 85% myself.

Here are some of the sample images I used, at 85% so that you can compare with the previous PNG thumbnails:

IMG_2734_85 IMG_2744_85 IMG_2228_85
IMG_2235_85 IMG_2300_85 IMG_2305_85
IMG_2311_85 IMG_2317_85 IMG_2318_85
IMG_2325_85 IMG_2330_85 IMG_2332_85
IMG_2346_85 IMG_2351_85 IMG_2363_85
IMG_2398_85 IMG_2443_85 IMG_2445_85
IMG_2446_85 IMG_2452_85 IMG_2462_85
IMG_2504_85 IMG_2505_85 IMG_2525_85

UPDATE: I contacted the WPF team to have the final word on whether this is supported. Unfortunately, it's not, and the documentation is being updated accordingly. I apologize about any confusion this may have caused. We're looking at ways to make that story more acceptable in the future.

UPDATE 2: for an approach that scales better, try this.

My benchmark code:
http://weblogs.asp.net/blogs/bleroy/Samples/ImageResizeBenchmarkJpg.zip

The code for the JPG thumbnail handlers:
http://weblogs.asp.net/blogs/bleroy/Samples/SupportedResizeJpg.zip

The previous article:
http://weblogs.asp.net/bleroy/archive/2009/12/10/resizing-images-from-the-server-using-wpf-wic-instead-of-gdi.aspx

14 Comments

  • Hi,

    I just wondered if you have managed to get this to work in Medium Trust mode as I've been told by a friend on twitter (@drobar) that it won't work under medium trust.

    Cheers,

    Chris

  • @Chris: no, it won't work, which is utterly stupid as the equivalent APIs using GDI+ will work just fine under medium trust.

  • Looks like your cache check conditional is backwards...I had to set nocache=true in order for it to use the cache.

    Or I'm drunk.

    Ryan

  • @Ryan: I'm afraid you're right. I mean, you might be drunk as well but that doesn't make you wrong. Fixing the code.

  • This is awesome stuff Bertrand. Where might I start looking for examples to add say a textual watermark or something to the image during the resizing?

    Drunk? Don't mind if I do! (glug)

    Ryan

  • @Ryan: I'd love to blog that next. But it is, let's say, not optimally easy... Which is one more reason to blog it ;) I doubt you'll find that sort of stuff anywhere yet. Amazingly, very little has been written so far about using WPF on the server. Here's to hoping these posts can start a trend...

  • I've been searching around...you're right...not a lot.

    I'd be willing to advance as much as one American dollar to the cause if it would help. :-)

  • @Ryan: Thanks. No, I don't think $1 will help much currently. Euros?

  • Er, read through your previous post about this and the comments... I guess my comment still stands, I would just add a "really?" in response to your comment "*any* API in .NET is supported on the server unless specified otherwise" ... :)

  • Bertrand, thanks for the reply. It does make sense to me that what you're doing here could be safe even if other uses of WPF may not be. I still worry about developers coming to wrong conclusions about the safety of using different parts of WPF in a server environment, but I realize that's a tough problem to solve and happens with other APIs too. I'll be following this issue with interest. Thanks for writing about it!

    Btw, when it comes to being supported, I think different people are probably interested in different aspects of that. Personally I care very little about being able to call support and get help. I care that whatever MS or third-party code I'm using works - now and more importantly in the future - and it seems more likely to work if someone at MS spends time thinking about it and making sure it works. Thinking a little about how I've gotten this info in the past, beyond docs, blogs, and forums, samples have probably been the most useful way. Imagine if WPF shipped with one or more samples showing how to safely use it in a server environment! That would be a pretty strong indicator. Of course, even samples can be wrong or get broken/deprecated. :)

  • @Tom: all good points. I may follow-up with a post clearly delineating the supported from the unsupported.
    I understand what you're saying about what supported means to you. In its time, GDI was marked unsupported exactly because that testing had not been done. And sure enough, problems were discovered. In principle, supported means that it's been tested and also that you can call support. No matter what amount of testing is done, there are always unforeseen scenarios. In those cases, you can call support and they will work on finding you an acceptable workaround, or in some cases the product will get patched. In some cases the patch is private until the next version, in some cases it's public.
    In any case, support is a pretty expensive thing, and the declaration that something is supported already is a strong indicator that we're committed to make it work for you.
    I hope this helps.

  • I updated the post to reflect the current supportability of this.

  • I can confirm that this flat-out does not work on a production server. We used it for thumbnail generation and we would randomly have images show up blank - the WPF team confirmed that it's not supported.

    Beware!

  • @ShadowChaser: you should try http://weblogs.asp.net/bleroy/archive/2010/05/03/the-fastest-way-to-resize-images-from-asp-net-and-it-s-more-supported-ish.aspx

Comments have been disabled for this content.