The Ghost PixelFormat

There is a method, that given an input image file, saves its thumbnail to disk. The code works for, pretty much, all the images tested and then there’s one that makes it throw a System.OutOfMemoryException.

The method could be something like this:

private void LoadImage(string fileName)
{
  using (Image img = Image.FromFile(fileName))
  {
    Image thumbnail = new Bitmap(150, 200);
    using(var graphic = Graphics.FromImage(thumbnail))
    {
      var rectangle = new Rectangle(0, 0, 150, 200);
      graphic.DrawImage(img, rectangle);
      thumbnail.Save("Thumb_" + fileName);
    }
  }
}

I started debugging my code and it turns out the failing image used a CMYK color model (which is the wrong format for a web image) and this seems to be a somewhat random bug that depends on windows version. This was showing in the image properties where the property PixelFormat displayed a value of 8207, because this value is not one of the defined Enum values. I fixed the code by testing the PixelFormat property and when Enum.IsDefined(typeof(PixelFormat), img.PixelFormat) is false we fallback to a RGB color mode. Let’s leave it at that, it’s not this part that got me interested…

PixelFormat_blog

What really got me puzzled is how the Image object displayed a PixelFormat value of 8207. This didn’t make any sense. The value is not one of its defined values. So how could it be assuming this value? Does C# allow values outside of the defined Enum to be assigned to a enum object? Let’s find out:


enum GhostPixelFormat
{
 First = 1,
 Second = 2
}

static void Main(string[] args)
{
 GhostPixelFormat enumObject;
 if (Enum.TryParse("Second", out enumObject))
 {
   Console.WriteLine("Second was parsed to a valid GhostPixelFormat: " + enumObject);
 }
 if (!Enum.TryParse("Third", out enumObject))
 {
   Console.WriteLine("Third was not parsed to a valid GhostPixelFormat");
 }
 if (Enum.TryParse("1000", out enumObject))
 {
   Console.WriteLine("1000 was parsed to a valid GhostPixelFormat: " + enumObject);
 } 
 if (!Enum.IsDefined(typeof(GhostPixelFormat), "1000"))
 {
   Console.WriteLine("1000 is not defined in GhostPixelFormat Enum");
 }
 Console.ReadLine();
}

And the output is:

Second was parsed to a valid GhostPixelFormat: Second
Third was not parsed to a valid GhostPixelFormat
1000 was parsed to a valid GhostPixelFormat: 1000
1000 is not defined in GhostPixelFormat Enum

Even though we couldn’t parse the invalid Enum value “Third”, we could do it with an integer (1000) which is outside the defined values. On the other hand, the Enum.IsDefined() is false for 1000.

This is explained by looking at the way that Enums are implemented in .NET:

From Enum C# Reference:

“The enum keyword is used to declare an enumeration, a distinct type consisting of a set of named constants called the enumerator list. Every enumeration type has an underlying type, which can be any integral type except char. The default underlying type of the enumeration elements is int.”

Basically an enum is treated as the underlying type, so being an Int32, you can store any valid int value and the conversion will be valid. But even if this is true, do we want this? Well, most of the times, no!

From Enumeration Types:

“However, you should not do this because the implicit expectation is that an enum variable will only hold one of the values defined by the enum. To assign an arbitrary value to a variable of an enumeration type is to introduce a high risk for errors.”

Is it all bad? Not exactly, think of BitFlags for example, if enum conversions were explicit it wouldn’t be possible.

So, I’m definitely not the first to stumble on this, but at least now I found that ghost.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s