Wednesday, September 19, 2007

Feature or creature?

I sadly claimed at some point that this blog would feature the occasional snippet of software development. It's been a little lacking. Here's what I've been up to lately.

Let's play a little game called "Feature or creature?" It works like this - I'll describe a software problem and we'll try to decide if the behaviour was intentional (a feature) or a bug (a creature).

We have the ability to associate a scanned image to a radiology request. This is frequently used when a paper-based system collides with a computer-based system. For instance, Dr. Haywood writes up a request and sends the patient into the hospital. The hospital scans the piece of paper and it becomes available in the PACS client.

The sheets of paper are occasionally scanned sideways, or upside down, so we have the ability to rotate and flip them. We can also zoom them to fit the width or height of the display area, and we can invert them to make it easier to read.

Zooming in and out beyond the width/height of the display area is also supported. Except when it crashes. One of our testers reported that he was able to zoom in four times, but on the fifth zoom the application crashed. I tried it here, and of course it worked. When I tried it on his server, it crashed. I took a closer look at the request he was zooming. It was a jpeg image, 1760 x 2800 pixels. Quite large.

The stack trace from the crash was not much help.

Exception: System.ComponentModel.Win32Exception
Message: Not enough storage is available to process this command
Source: System.Windows.Forms
at System.Windows.Forms.DibGraphicsBufferManager.CreateCompatibleDIB(
IntPtr hdc, IntPtr hpal, Int32 ulWidth, Int32 ulHeight, IntPtr& ppvBits)
at System.Windows.Forms.DibGraphicsBufferManager.CreateBuffer(IntPtr src,
Int32 offsetX, Int32 offsetY, Int32 width, Int32 height)
at System.Windows.Forms.DibGraphicsBufferManager.AllocBuffer(
Graphics targetGraphics, IntPtr targetDC, Rectangle targetBounds)
at System.Windows.Forms.DibGraphicsBufferManager.AllocBufferInTempManager(
Graphics targetGraphics, IntPtr targetDC, Rectangle targetBounds)
at System.Windows.Forms.DibGraphicsBufferManager.AllocBuffer(
IntPtr target, Rectangle targetBounds)
at System.Windows.Forms.Control.WmPaint(Message& m)
at System.Windows.Forms.Control.WndProc(Message& m)
at System.Windows.Forms.ControlNativeWindow.OnMessage(Message& m)
at System.Windows.Forms.ControlNativeWindow.WndProc(Message& m)
at System.Windows.Forms.NativeWindow.Callback(
IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)

Looking at the code, I found something a little odd. We're showing the image in a PictureBox. Harkening back to my Visual Basic days, I remember that PictureBoxes are nasty little things, but sometimes you have no choice but to use them. Now, in the brave new .NET world, there really is no need. Why not just paint the image on a UserControl?

I quickly changed the zooming class to derive from UserControl instead of PictureBox, and the crash disappeared. All I was left with was a bit of flicker. That's easy to fix, I thought. I added these lines:
this.SetStyle(ControlStyles.DoubleBuffer |
ControlStyles.UserPaint |
ControlStyles.AllPaintingInWmPaint, true);


and boom! The crash was back. Now I head to Reflector, to see what the PictureBox is doing. Aha!

The PictureBox constructor is setting the DoubleBuffer style. I removed the style setting, and to get rid of the flicker, I added this to eat the background paint message:
protected override void OnPaintBackground(PaintEventArgs pevent)

Problem solved. I vote "creature" - a bug in the DoubleBuffer handling, down in the .NET code.

We now return to camping, curling and sick humour.


1 comment:

  1. Anonymous1:20 am

    Good work,I will try this