Friday, June 12, 2009

WPF destroys images when resizing them

This is the bug I was reminded of, which caused me to decide it was time to start a blog. Two years ago, I wrote my first WPF project. Basically, it's a program that gives you access to high quality images, which you can zoom in on, etc. Now, because of issues with WPF memory management ( which I'll cover at a later date ), we decided to save memory, by loading images as requested, then resizing them to the size of the screen. We'd reload at a bigger size if the user zoomed. What we found was, some of our images would become distorted sometimes when we zoomed ( which involved reloading the image at a new size ). WPF has a control for scaling images, however, it keeps the full size image in memory, making it useless for our purpose ( it just wasn't what it was for ). So, we resize the image the way the docs tell you to. Something like this:


fullHeight = bi.PixelHeight;
fullWidth = bi.PixelWidth;
bi = new BitmapImage();
bi.BeginInit();
if (heightBasedAR)
{
bi.DecodePixelHeight = (int)Math.Min(height, fullHeight);
}
else
{
bi.DecodePixelWidth = (int)Math.Min(width, fullWidth);
}
bi.StreamSource = new MemoryStream(fileBuffer);
bi.EndInit();

As I said, this worked sometimes, and others, would destroy the image. We have illustrations in the program which are PNGs, and photos which are JPEGs. I am not sure if it was the photographic content, or the JPEG format that made the latter ones get distorted, but I have a feeling we changed some images to PNG and that helped, so I'd say that I believe it's the format that is the issue. In any case, it would only happen sometimes, but by experimenting, I proved that the issue was that it would only happen for certain sizes of image, that is, the same image would consistently break, if you found a new size that would break it.


I created a sample app and sent to Microsoft, it produces the following output:




As compared to my original image, which looked like this:




It actually took me three emails back and forth of 'I don't see a problem', before the Microsoft guy accepted that image 1 does NOT look like image 2. This seemed to me to be a pretty major bug, and it was duly reported and went on the pile. Two years later, I run the latest version of WPF and .NET ( 3.5 SP1 ), and my program still destroys this image, which is how I got that screenshot.

The only solution I could find, was to revert to GDI+ to do my image resizing. So, our application does something like this every time we load an image:

int scaledWidth = (int)Math.Round(fullWidth * scale);
int scaledHeight = (int)Math.Round(fullHeight * scale);
System.Drawing.Size newSize = new System.Drawing.Size(scaledWidth, (int)height);
using (System.Drawing.Bitmap bp = new System.Drawing.Bitmap(img, newSize))
{
using (MemoryStream ms2 = new MemoryStream())
{
bp.Save(ms2, System.Drawing.Imaging.ImageFormat.Png);
bi = new BitmapImage();
bi.BeginInit();
bi.CacheOption = BitmapCacheOption.OnLoad;
bi.StreamSource = ms2;
bi.EndInit();
}
}

So, GDI+ reloads the image at a new size, then saves it to a memory stream, and from there we read the stream into a WPF BitmapImage. Hardly neat, but it works, and WPF still does not.

8 comments:

  1. Bah. Those two images look the same... ;-P

    ReplyDelete
  2. You're not squinting hard enough, clearly they are the same picture. I see dog in picture one. I see dog in picture 2. No problem there.

    ReplyDelete
  3. You're right. I need to tell all my clients to squint more :P

    ReplyDelete
  4. Both dogs look like they have serious health issues anyway - missing fur, transparent skin...

    ReplyDelete
  5. I just encountered this problem. XP only. Only affects white (which for me was a killer, since I'm dealing with documents with a white background.)

    Simple solution: Do not use DecodePixelHeight/Width. Instead, add an extra BitmapTransform, with a ScaleTransform set appropriately.

    ReplyDelete
  6. The second image has the intensities inverted. What was bright in the original is darker in the second, and vice versa.

    Have you noticed the WPF Image control hogging way more memory than the size of the image when zooming in?

    I just finished a consulting gig at a company that does live cell imaging and they were having tremendous problems with overlaying 2-3 (64Mb each channel, 8Kx8K 1byte pixels) gray scale images to create a color image. They used pixel shader and WPF Image control and when zooming in the RAM usage would go from 2-3 GB to 4.5-6GB on a 8GB machine.

    I've had developed a custom WPF control at home and showed them how it only used the same memory as the original image no matter what zoom level and would draw up to 200,000 overlays (outlines around identified blobs) on top of the image channels. With 3 image channels I'm rendering at 30-60Hz depending on the monitor size.

    ReplyDelete
  7. got any updates for this issue? we're encountering the same thing.

    ReplyDelete
  8. http://brucezhou1986.wordpress.com/2010/12/21/improve-displaying-quality/

    ReplyDelete