If you follow me on twitter, you know that I am no fan of the System.Drawing namespace.  It’s a rush job thinly wrapped layer on top of GDI+.  A great deal is only partially implemented and there is quite a lot of undocumented behavior.  Today I hope to help a bit with this problem by running some tests on System.Drawing.dll v2.0.50727 and documenting the results.

 

Verifying the Stability of System.Drawing.Bitmap

First we must make sure our Bitmap class is stable with respect to System.Drawing.Imaging.PixelFormat as we will be using it to generate our images.  To test this I instantiated a Bitmap with each PixelFormat an then verified that the PixelFormat was the same as that which I specified.

  1: ForEachPixelFormat(pf => {
  2:     try
  3:     {
  4:         using (Image saveImg = new Bitmap(50, 50, pf))
  5:         {
  6:             if (saveImg.PixelFormat == pf) supported.Add(pf);
  7:             else unsupported.Add(pf);
  8:         }
  9:     }
 10:     catch { unsupported.Add(pf); }
 11: });

Supported by System.Drawing.Bitmap:
PixelFormat.Format16bppRgb555
PixelFormat.Format16bppRgb565
PixelFormat.Format24bppRgb
PixelFormat.Format32bppRgb
PixelFormat.Format1bppIndexed
PixelFormat.Format4bppIndexed
PixelFormat.Format8bppIndexed
PixelFormat.Format16bppArgb1555
PixelFormat.Format32bppPArgb
PixelFormat.Format16bppGrayScale
PixelFormat.Format48bppRgb
PixelFormat.Format64bppPArgb
PixelFormat.Format32bppArgb
PixelFormat.Format64bppArgb

Unsupported by System.Drawing.Bitmap:
PixelFormat.DontCare
PixelFormat.Max
PixelFormat.Indexed
PixelFormat.Gdi
PixelFormat.Alpha
PixelFormat.PAlpha
PixelFormat.Extended
PixelFormat.Canonical

So far so good.  All of the real PixelFormats are covered by the Bitmap class.

 

Per-Codec Roundtrip Stability

Next we will figure out which PixelFormat will survive a round trip.  To do this we will encode and decode with each of the codecs in every PixelFormat.  We can then compare the PixelFormat of the decoded image with our generated Bitmap. 

  1: [Test]
  2: [Explicit]
  3: public void CodecRoundTripStabilityTableBuilder()
  4: {
  5:     ForEachSupportedImageCodecInfo(info =>
  6:     {
  7:         List<String> stable = new List<String>();
  8:         List<String> unstable = new List<String>();
  9: 
 10:         ForEachPixelFormat(pf =>
 11:         {
 12:             try
 13:             {
 14:                 PixelFormat roundtripPf = GetDefaultPixelFormat(info, pf, 0);
 15:                 string outputString = String.Format("{0} -> {1}",
 16:                     "PixelFormat." + pf.ToString(),
 17:                     "PixelFormat." + roundtripPf.ToString());
 18: 
 19:                 if (pf == roundtripPf)
 20:                     stable.Add(outputString);
 21:                 else
 22:                     unstable.Add(outputString);
 23:             }
 24:             catch
 25:             {
 26:             }
 27:         });
 28: 
 29:         Debug.WriteLine("Stable: " + info.CodecName);
 30:         stable.ForEach(str => Debug.WriteLine(str));
 31: 
 32:         Debug.WriteLine("Unstable: " + info.CodecName);
 33:         unstable.ForEach(str => Debug.WriteLine(str));
 34:     });
 35: }
 36: 
 37: private PixelFormat GetRoundTripPixelFormat(ImageCodecInfo info, PixelFormat sourceFormat)
 38: {
 39:     using (MemoryStream ms = new MemoryStream())
 40:     using (Image saveImg = new Bitmap(50, 50, sourceFormat))
 41:     {
 42:         if (saveImg.PixelFormat != sourceFormat)
 43:             throw new Exception("saveImg.PixelFormat != sourceFormat");
 44: 
 45:         saveImg.Save(ms, info, null);
 46:         ms.Seek(0, SeekOrigin.Begin);
 47:         using (Image loadImg = Image.FromStream(ms))
 48:         {
 49:             return loadImg.PixelFormat;
 50:         }
 51:     }
 52: }

Stable: Built-in BMP Codec
PixelFormat.Format16bppRgb555 -> PixelFormat.Format16bppRgb555
PixelFormat.Format24bppRgb -> PixelFormat.Format24bppRgb
PixelFormat.Format32bppRgb -> PixelFormat.Format32bppRgb
PixelFormat.Format1bppIndexed -> PixelFormat.Format1bppIndexed
PixelFormat.Format4bppIndexed -> PixelFormat.Format4bppIndexed
PixelFormat.Format8bppIndexed -> PixelFormat.Format8bppIndexed
PixelFormat.Format64bppArgb -> PixelFormat.Format64bppArgb

Unstable: Built-in BMP Codec
PixelFormat.Format16bppRgb565 -> PixelFormat.Format32bppRgb
PixelFormat.Format16bppArgb1555 -> PixelFormat.Format32bppRgb
PixelFormat.Format32bppPArgb -> PixelFormat.Format32bppRgb
PixelFormat.Format16bppGrayScale -> PixelFormat.Format32bppRgb
PixelFormat.Format48bppRgb -> PixelFormat.Format32bppRgb
PixelFormat.Format64bppPArgb -> PixelFormat.Format64bppArgb
PixelFormat.Format32bppArgb -> PixelFormat.Format32bppRgb

Stable: Built-in JPEG Codec
PixelFormat.Format24bppRgb -> PixelFormat.Format24bppRgb

Unstable: Built-in JPEG Codec
PixelFormat.Format16bppRgb555 -> PixelFormat.Format24bppRgb
PixelFormat.Format16bppRgb565 -> PixelFormat.Format24bppRgb
PixelFormat.Format32bppRgb -> PixelFormat.Format24bppRgb
PixelFormat.Format1bppIndexed -> PixelFormat.Format24bppRgb
PixelFormat.Format4bppIndexed -> PixelFormat.Format24bppRgb
PixelFormat.Format8bppIndexed -> PixelFormat.Format24bppRgb
PixelFormat.Format16bppArgb1555 -> PixelFormat.Format24bppRgb
PixelFormat.Format32bppPArgb -> PixelFormat.Format24bppRgb
PixelFormat.Format48bppRgb -> PixelFormat.Format24bppRgb
PixelFormat.Format64bppPArgb -> PixelFormat.Format24bppRgb
PixelFormat.Format32bppArgb -> PixelFormat.Format24bppRgb
PixelFormat.Format64bppArgb -> PixelFormat.Format24bppRgb

Stable: Built-in GIF Codec
PixelFormat.Format8bppIndexed -> PixelFormat.Format8bppIndexed

Unstable: Built-in GIF Codec
PixelFormat.Format16bppRgb555 -> PixelFormat.Format8bppIndexed
PixelFormat.Format16bppRgb565 -> PixelFormat.Format8bppIndexed
PixelFormat.Format24bppRgb -> PixelFormat.Format8bppIndexed
PixelFormat.Format32bppRgb -> PixelFormat.Format8bppIndexed
PixelFormat.Format1bppIndexed -> PixelFormat.Format8bppIndexed
PixelFormat.Format4bppIndexed -> PixelFormat.Format8bppIndexed
PixelFormat.Format16bppArgb1555 -> PixelFormat.Format8bppIndexed
PixelFormat.Format32bppPArgb -> PixelFormat.Format8bppIndexed
PixelFormat.Format48bppRgb -> PixelFormat.Format8bppIndexed
PixelFormat.Format64bppPArgb -> PixelFormat.Format8bppIndexed
PixelFormat.Format32bppArgb -> PixelFormat.Format8bppIndexed
PixelFormat.Format64bppArgb -> PixelFormat.Format8bppIndexed

Stable: Built-in TIFF Codec
PixelFormat.Format24bppRgb -> PixelFormat.Format24bppRgb
PixelFormat.Format1bppIndexed -> PixelFormat.Format1bppIndexed
PixelFormat.Format4bppIndexed -> PixelFormat.Format4bppIndexed
PixelFormat.Format8bppIndexed -> PixelFormat.Format8bppIndexed
PixelFormat.Format32bppArgb -> PixelFormat.Format32bppArgb

Unstable: Built-in TIFF Codec
PixelFormat.Format16bppRgb555 -> PixelFormat.Format24bppRgb
PixelFormat.Format16bppRgb565 -> PixelFormat.Format24bppRgb
PixelFormat.Format32bppRgb -> PixelFormat.Format32bppArgb
PixelFormat.Format16bppArgb1555 -> PixelFormat.Format32bppArgb
PixelFormat.Format32bppPArgb -> PixelFormat.Format32bppArgb
PixelFormat.Format48bppRgb -> PixelFormat.Format24bppRgb
PixelFormat.Format64bppPArgb -> PixelFormat.Format32bppArgb
PixelFormat.Format64bppArgb -> PixelFormat.Format32bppArgb

Stable: Built-in PNG Codec
PixelFormat.Format24bppRgb -> PixelFormat.Format24bppRgb
PixelFormat.Format1bppIndexed -> PixelFormat.Format1bppIndexed
PixelFormat.Format4bppIndexed -> PixelFormat.Format4bppIndexed
PixelFormat.Format48bppRgb -> PixelFormat.Format48bppRgb
PixelFormat.Format32bppArgb -> PixelFormat.Format32bppArgb
PixelFormat.Format64bppArgb -> PixelFormat.Format64bppArgb

Unstable: Built-in PNG Codec
PixelFormat.Format16bppRgb555 -> PixelFormat.Format32bppArgb
PixelFormat.Format16bppRgb565 -> PixelFormat.Format32bppArgb
PixelFormat.Format32bppRgb -> PixelFormat.Format32bppArgb
PixelFormat.Format8bppIndexed -> PixelFormat.Format32bppArgb
PixelFormat.Format16bppArgb1555 -> PixelFormat.Format32bppArgb
PixelFormat.Format32bppPArgb -> PixelFormat.Format32bppArgb
PixelFormat.Format64bppPArgb -> PixelFormat.Format64bppArgb

These results are quite disturbing as it seems there is no way to really know what your output format will be without a huge table.  Some might suggest using Encoder.ColorDepth to control the output but other tests I’ve run show it only works for the TIFF codec and will only cause the PixelFormat to be converted to Format24bppRgb and Format32bppArgb.

 

Conclusion

The one glaring problem with this test is that when the PixelFormat is unstable we don’t know if the cause was the encoder or decoder.  I would hope that the decoders wouldn’t change the PixelFormats of images they read but with System.Drawing you can never be sure.  I have quickly glanced at some file output from this test and it seems to indicate that decoding is generally stable.  Although, I haven’t yet done anything systematic.

If you don’t want to deal with all of this garbage I suggest you give our imaging toolkit a try.  We work hard every day at cutting through these types of time wasting problems.