Tales from the Evil Empire

Bertrand Le Roy's blog

News


Bertrand Le Roy


Add to Technorati Favorites Tales from the Evil Empire - Blogged

Blogs I read

My other stuff

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

Comments

Chris Houston said:

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

# January 22, 2010 5:02 AM

Bertrand Le Roy said:

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

# January 22, 2010 1:49 PM

Ryan Smith said:

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

# January 22, 2010 6:06 PM

Bertrand Le Roy said:

@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.

# January 22, 2010 6:14 PM

Ryan said:

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

# January 25, 2010 4:10 PM

Bertrand Le Roy said:

@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...

# January 25, 2010 4:48 PM

Ryan said:

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. :-)

# January 25, 2010 6:12 PM

Bertrand Le Roy said:

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

# January 25, 2010 6:27 PM

Tom said:

I recommend being VERY careful about using WPF on a production server.  According to people at MS, it wasn't designed for that role.  While fixes have been made, I really wonder if the scenario ever really graduated to supported (i.e. really tested by MS) status.  Don't just take my word for it:

forums.asp.net/.../1299963.aspx

blog.galasoft.ch/.../converting-and-customizing-xaml-to-png-with-server-side-wpf.aspx

connect.microsoft.com/.../ViewFeedback.aspx

But here's my own anecdote, since this was just so much fun:

In early 2009 I helped fix an ASP.NET based site that was working fine in a dev environment but failing in a production environment with real traffic.  The site would periodically be taken offline by IIS (due to too many repeated failures), requiring a manual restart of IIS!  (Or maybe just a recycle of the web application?  Can't remember for sure.)  It didn't take long to trace the problem to parts of the site that used WPF to render XAML to PNG.

We tried some fixes but ultimately the problem was fully resolved by banning use of WPF on the server.  Funny you should mention GDI+ being unsupported on the server, because the dev responsible for the WPF usage on the server rewrote his code to use System.Drawing (GDI+) instead, and stability problems disappeared completely.  I had suggested there might be a way to more safely use WPF on the server by isolating its use in a separate server-type process, maybe even on a separate machine eventually, but in this case he insisted on using System.Drawing instead...

It's risky to use a desktop-oriented technology in a server environment, no matter how convenient it may seem at first.  There are often nasty gotchas that WILL kill you (eventually) if you aren't totally on top of the situation.  Other examples include Office automation and desktop-intended SAPI (text-to-speech and/or speech recognition) APIs/engines.

Btw, if anyone has any links that more strongly suggest that server-side WPF is officially supported than the ones above, I would love to see them.

# January 25, 2010 9:25 PM

Tom said:

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" ... :)

# January 25, 2010 9:32 PM

Bertrand Le Roy said:

@Tom: I did not write these posts in isolation but have worked with people from the WPF team.

The links you provided all seem to use the part of WPF that would have to run in an STA thread, which is pretty much a non-starter for server use. What we're doing here is quite different and I'm certainly not saying you can or should use all parts of WPF on the server. I can and will triple-check with the team that what we're doing here is OK and supported.

Yes, really, any API on .NET is supported unless specified otherwise. There may be oversights (in which case the docs should be fixed), but you can call support and get help on any API, yes, really.

GDI+ is one of those APIs that is explicitly called out as not supported in the docs. We *know* of very real cases where it fails. Same thing for Office automation: support.microsoft.com/.../257757. As for SAPI, it's not managed code.

# January 26, 2010 12:56 AM

Tom said:

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. :)

# January 26, 2010 11:10 AM

Bertrand Le Roy said:

@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.

# January 26, 2010 1:53 PM

Bertrand Le Roy said:

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

# February 18, 2010 7:57 PM

ShadowChaser said:

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!

# May 5, 2010 12:22 PM
Leave a Comment

(required) 

(required) 

(optional)

(required)