Sooner or later, you may find yourself in the position where you have to serialize and deserialize an object that is a member of one of your classes and of the type System.Drawing.Bitmap. Having a Bitmap as a member of a class is many times preferable over having the raw data be the member, as you only have to initialize the Bitmap once per instance, instead of per use.

You can try to serialize the class as is, and just throw a [Serializable] on top of the class, but you will more than likely run into the following problem:

ObjectDisposedException: Cannot access a closed Stream

This will occur if the original stream used to create the Bitmap is disposed. If you’re like me, you like to release unmanaged resources as soon as your done with them, which means I’m going to close that stream as soon as the Bitmap instance is loaded when I’m sync’ing up with a data source.

If you override ISerializable and try to achieve the serialization by creating a MemoryStream, and then calling the picture’s save message to save it to the stream?(in order to get the byte array), you will get the same error.

Luckily for you, I have another way for you to retrieve the byte contents of a Bitmap; read on for an explanation as to how.

Let’s say you have a class with a member of type Image named “Picture”. We’re going to implement ISerializable in the class and achieve our magic in the GetObjectData method and serialization constructor.

Basically, what we are going to do here is retrieve an object describing the attributes of the bitmap image (BitmapData), and then copy the bytes starting at the address of the first scan line in the bitmap and ending at the address corresponding to the size of the Bitmap’s raw data.

The BitmapData object is created by locking the Bitmap into system memory through the use of the LockBits method. We are able to calculate the size of the raw data of the Bitmap by taking the bitmap’s stride width (scan width) and multiplying it by the height of the image.

For the most part, we take the same steps whether we are serializing or deserializing.

First, we’ll take a look at GetObjectData, which is where we define how to actually serialize the data to disk.

protected virtual void GetObjectData(SerializationInfo info, StreamingContext context)
{
    Bitmap bitmap = Picture as Bitmap;

    BitmapData bitmapData =
        bitmap.LockBits(
            new Rectangle(new Point(), bitmap.Size),
            ImageLockMode.ReadOnly,
            PixelFormat.Format24bppRgb);

    int byteCount = bitmapData.Stride * bitmap.Height;

    byte[] bitmapBytes = new byte[byteCount];

    Marshal.Copy(bitmapData.Scan0, bitmapBytes, 0, byteCount);

    bitmap.UnlockBits(bitmapData);

    info.AddValue("Picture", bitmapBytes);

    info.AddValue("PictureHeight", bitmap.Height);

    info.AddValue("PictureWidth", bitmap.Width);
}

The reason we are adding the two extra height and width objects to the serialization store is because we need them during the deserialization process. There’s no need to have these as actual members of your class, so I can’t see a reason why this would be a issue.

Now, we’ll look over how we deserialize the data with the serialization constructor.

protected TheClass(SerializationInfo info, StreamingContext context)
{
    byte[] imageData = (byte[]) info.GetValue("Picture", typeof(byte[]));

    int height = (int) info.GetValue("PictureHeight", typeof(int));

    int width = (int) info.GetValue("PictureWidth", typeof(int));

    Bitmap bitmap = new Bitmap(width, height);

    BitmapData bitmapData =
        bitmap.LockBits(
            new Rectangle(new Point(), bitmap.Size),
            ImageLockMode.WriteOnly,
            PixelFormat.Format24bppRgb);

    Marshal.Copy(imageData, 0, bitmapData.Scan0, imageData.Length);

    bitmap.UnlockBits(bitmapData);

    Picture = bitmap;
}

You may wish to add a try-finally block in order to ensure UnlockBits is called in case something fails during the copying from the unmanaged array to the managed array.

That’s everything.

Matt Weber

I'm the founder of Bad Echo LLC, which offers consulting services to clients who need an expert in C#, WPF, Outlook, and other advanced .NET related areas. I enjoy well-designed code, independent thought, and the application of rationality in general. You can reach me at matt@badecho.com.

 Leave a Reply

(required)

(required)

You may use these HTML tags and attributes: <a href="" title="" rel=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

 
   
© 2012-2013 Matt Weber. All Rights Reserved. Terms of Use.