Question

I'm having to fix a bug in some very old code that converts a Base64 string to a image using Memory Stream. Basically it loops through a list of images stored as base64 strings and converts them to images then draws them using ActiveReports.

The bug is that once it loads one image all following images will be a copy of the first image.

I found the code that is doing the conversion of string to image and immediately noticed it isn't disposing of the memory stream. If I wrap the memory stream in a using block I get a GDI exception. I'm guessing this is because the image isn't really read from memory yet or something, but I'd like to hear if anyone has a guess. Thanks in advance!

        byte[] oGraphic = null;
        try
        {
            oGraphic = Convert.FromBase64String(psGraphic);

            DataDynamics.ActiveReports.Picture oImg = new Picture();
            oImg.Top = this.Legend.Top + this.fTopFirst;
            oImg.Visible = true;
            oImg.Name = sLabelName;
            oImg.PictureAlignment = PictureAlignment.Center;

            oImg.Image = null;

            if (oGraphic != null)
            {
                var oStream = new MemoryStream(oGraphic);
                oImg.Image = System.Drawing.Image.FromStream(oStream);
                oImg.Height = Convert.ToSingle(oImg.Image.Height)/(oImg.Image.VerticalResolution);
                oImg.Width = Convert.ToSingle(oImg.Image.Width)/(oImg.Image.HorizontalResolution);
                oImg.SizeMode = SizeModes.Zoom;
                this.fGraphicHeight = oImg.Height;
                this.fGraphicWidth = oImg.Width;
                if (this.fConstantGraphic > this.fGraphicWidth)
                    oImg.Left = this.Legend.Left + this.fLeftFirst +
                                ((this.fConstantGraphic - this.fGraphicWidth)/2);
                else
                    oImg.Left = this.Legend.Left + this.fLeftFirst;

            }
            else
            {
                this.fGraphicHeight = 0f;
                this.fGraphicWidth = 0f;
            }

            this.GHMap.Controls.Add(oImg);
        }
        catch (Exception oE)
        {
            .....
        }

enter image description here

Was it helpful?

Solution 5

It turned out to be a problem with the way we were creating the image. Adding the code below fixed the problem.

   oImg.Image = System.Drawing.Image.FromStream(oStream);

   TO THIS

   oImg.Image = ImageFromBase64String(psGraphic);

   private Image ImageFromBase64String(string sBase64String)
        {
            using (var sStream = new MemoryStream(Convert.FromBase64String(sBase64String)))
            using (var iSourceImage = Image.FromStream(sStream))
            {
                return new Bitmap(iSourceImage);
            }
        }

OTHER TIPS

The only thing I can imagine is if there is a line of code missing from what you have here:

if (oGraphic == null) // missing line
    oGraphic = Convert.FromBase64String(psGraphic);

There is no reason for this byte[] to be declared outside of the try { } block. That array gets wrapped into a MemoryStream which is then wrapped into an Image. That image is attached to a brand new Picture which is added to a Picture collection.

What is it we don't see?

Here's another guess (I'll leave the first guess alone for posterity):

I'm not familiar with Active Reports, but it looks like you're setting the Top and PictureAlignment properties of the Picture object to the same value and adding more than one Picture. Is it possible they are all there, but one on top of each other? So the result is a single picture?

Guess #3 (one of these is going to get a checkmark, I just know it!)

Everything looks OK in the code provided, therefore the problem is somewhere else (though it's still entirely possible I'm wrong about it being OK).

Are you certain that psGraphic is different each time this code is executed?

The cause of the problem is that the Picture control is a single control instance on a single section. So you're just overwriting the image on this single control over and over.

If the only thing you want to see in this report is the images, then the best thing to do is use ActiveReports' Unbound mode and treat each image as another "record". See this walkthrough for an example of using unbound mode (see the DataInitialize and FetchData events for the meat of the matter).

Using unbound mode, ActiveReports will render the image one after the other in sections treating each image like a new record. The code would be something like the following (sorry I don't have ActiveReports handy at the moment so I can't check this code, but this should be pretty close. Let me know if you run into any problems and I'll clean it up in the morning):

In ActiveReports' ReportStart Event:

DataDynamics.ActiveReports.Picture oImg = new Picture();
oImg.Top = 0;
oImg.Visible = true;
oImg.Name = sLabelName;
oImg.PictureAlignment = PictureAlignment.Center;
// setting DataField "binds" the Picture control to get it's data from the MyImageField field which we'll initialize and bind in the events below
oImg.DataField = "MyImageField";
this.Sections["Detail"].Controls.Add(oImg);

In ActiveReports' DataInitialize Event:

this.Fields.Add("MyImageField");

In ActiveReports FetchData Event:

var imageBytes = Convert.FromBase64String(_imageStrings.Current); // I'm not sure where the base64 image strings come from, some I'm assuming you can put them in an enumerator field in the report like "_imageStrings" 
var imageStream = new MemoryStream(imageBytes);
var image = Image.FromStream(imageStream);
Fields["MyImageField"].Value = image;

// This tells ActiveReports if there are more records, and if it should raise the FetchData event again (allowing you to add another image).
eArgs.EOF = !_imageStrings.MoveNext();

If you need to resize the image control for each image, use the section's Format event for that. You could use something like the following:

In Detail_Format Event:

var pictureControl = this.Sections["Detail"].Controls["MyImageControl"] as DataDynamics.ActiveReports.Picture;
pictureControl.Width = Convert.ToSingle(pictureControl.Image.Width)/(pictureControl.Image.VerticalResolution);
pictureControl.Width = Convert.ToSingle(pictureControl.Image.Width)/(pictureControl.Image.HorizontalResolution);

Finally, ActiveReports will also just automatically bind to a set of POCO objects in an IEnumerable (or IList, I forget). So you could simply have a "MyImage" class with a property like "MyImage" and ActiveReports will read it and bind with it (you wouldn't have to write any code in DataInitialize and FetchData). I think you might also just be able to put the MemoryStream in there as the binding too and ActiveReports will read it, but I'm not positive on that.

BTW: The reason that GDI exception occurs when disposing the MemoryStream is because GDI attempts to just seek within that single MemoryStream for the image data rather than making a copy of it. So you'll need to supply each System.Drawing.Image instance with a new stream (don't worry MemoryStream will clean itself up when everything is released).

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top