Convert a byte[] to Image without using a MemoryStream - c#

I am having a problem exporting SQL images to files. I first initialize a List. MyRecord is a class with GraphicName, and Graphic properties. When I try to go through the list and save MyRecord.Graphic to disk I get a first chance exception of type 'System.ObjectDisposedException'. I realize this is because when I converted the bytes from the database to an Image I used a using statement with the MemoryStream. I can not use the using statement and it all works, but I am worried about memory usage / memory leaks on up to 6,000 records. Is there another way to convert the bytes to an image or is there a better design to do this?
... prior code
using (SqlDataReader reader = sqlCommand.ExecuteReader())
{
while (reader.Read())
{
MyRecord record = new MyRecord();
record.GraphicId = reader["GRAPHIC_ID"].ToString();
record.Graphic = !reader.IsDBNull(reader.GetOrdinal("IMAGE")) ? GetImage((byte[])reader["IMAGE"]) : null;
records.Add(record);
}
... more code
private Image GetImage(byte[] rawImage)
{
using (System.IO.MemoryStream ms = new System.IO.MemoryStream(rawImage))
{
Image image = Image.FromStream(ms);
return image;
}
}

You shouldn't use a using statement with a stream that will be passed to Image.FromStream, as the Image class is basically responsible for the stream from then on. From the documentation:
You must keep the stream open for the lifetime of the Image.
Just change your code to:
private Image GetImage(byte[] rawImage)
{
var stream = new MemoryStream(rawImage);
return Image.FromStream(stream);
}
... but then make sure you dispose of your Image objects later. That will dispose of the stream, allowing the memory to be garbage collected. Then there shouldn't be any memory leaks - but you need to work out whether you can really load all 6000 images into memory at a time anyway.
(If you don't dispose of the Image objects, they're likely to be finalized anyway at some point - but it would be better to dispose of them deterministically.)

Related

Why Image.Save() in C# does not always give the same serialized result and how to serialize an image in a deterministic way?

I serialize images using the following code:
public static string SerializeImage(Image image)
{
using (MemoryStream memoryStream = new MemoryStream())
{
image.Save(memoryStream, image.RawFormat);
return Convert.ToBase64String(memoryStream.ToArray());
}
}
and deserialize the images by doing the following
public static Image DeserializeImage(string serializedImage)
{
byte[] imageAsBytes = Convert.FromBase64String(serializedImage);
using (MemoryStream memoryStream = new MemoryStream(imageAsBytes, 0, imageAsBytes.Length))
{
memoryStream.Write(imageAsBytes, 0, imageAsBytes.Length);
return Image.FromStream(memoryStream, true);
}
}
If I have an image and does
string serializedImage1 = SerializeImage(image);
Image deserializedImage = DeserializeImage(serializedImage1);
string serializedImage2 = SerializeImage(deserializedImage );
Then
serializedImage1 == serializedImage2;
as expected. But it is not always the case.
If I serialize an image on Process 1, and then redeserialize and reserialize it on Process 2, then the result of the reserialization on Process 2 is not the same as on the Process 1. Everything works, but a few bytes in the beginning of the serialization are different.
Worst, if I do the same thing on 2 different dll (or thread, I'm not sure), it seems the serialization result is not the same too. Again, the serialization/deserialization works, but a few bytes are different.
The image the first time is loaded with the following function :
public static Image GetImageFromFilePath(string filePath)
{
var uri = new Uri(filePath);
var bitmapImage = new BitmapImage(uri);
bitmapImage.Freeze();
using (var memoryStream = new MemoryStream())
{
var pngBitmapEncoder = new PngBitmapEncoder();
pngBitmapEncoder.Frames.Add(BitmapFrame.Create(bitmapImage));
pngBitmapEncoder.Save(memoryStream);
Image image = Image.FromStream(memoryStream);
return image;
}
}
Note however that it happens even if the image is loaded twice with the DeserializeImage() function.
The tests I have done are with ImageFormat.Jpeg and ImageFormat.Png.
First question, why it does this ? I would have expected the result to be always the same, but I suppose some salt is used when doing the Image.Save().
Second question : I want to have a deterministic way to serialize an image, keeping the image format intact. The goal is to save the image in a DB and also to compare serialized images to know if it already exists in the system where this function is used.
Well, I discovered while trying to solve this that a png or jpeg Image object inside C# has some metadata associated to it and doing what I was doing is just not a reliable way to compare images.
The solution I used was derived from this link
https://insertcode.wordpress.com/2014/05/13/compare-content-of-two-files-images-in-c/
So what I do finally is save the images inside the system with the SerializeImage(Image image) function previously described, and when I want to consume it I deserialize it with the DeserializeImage(string serializedImage) function previously described. But when I want to compare images I use the following functions
public static bool ImagesAreEqual(Image image1, Image image2)
{
string image1Base64Bitmap = GetImageAsBase64Bitmap(image1);
string image2Base64Bitmap = GetImageAsBase64Bitmap(image2);
return image1Base64Bitmap.Equals(image2Base64Bitmap);
}
public static string GetImageAsBase64Bitmap(Image image)
{
using (var memoryStream = new MemoryStream())
{
using (var bitmap = new Bitmap(image))
{
bitmap.Save(memoryStream, System.Drawing.Imaging.ImageFormat.Bmp);
}
return Convert.ToBase64String(memoryStream.ToArray());
}
}
That convert the image to raw bitmap before comparing them.
This does a perfect job for me in all my needed cases : the formats of the images are saved/restored correctly, and I can compare them between them to check if they are the same without having to bother with the possibly different serialization.

C# bitmaps causes memory leak

I have a simple application, which renders images and prints them out. In order to make it easy and convenient to design, I have used custom Control.
Now, I have a simple control of size 1800 x 2400 (yeah, pretty big):
public class PhotoControl : UserControl
{
public PhotoControl()
{
InitializeComponent();
}
}
And an extension class, which uses it for generating images:
public static class PhotoProcessor
{
private static readonly PhotoControl PhotoForm = new PhotoControl(); // Single instance
public static Image GenerateImage(this Photo photo)
{
PhotoForm.SetPhoto(photo); // Apply a photo
Image result;
using (Bitmap b = new Bitmap(PhotoForm.Width, PhotoForm.Height))
{
Rectangle r = new Rectangle(0, 0, b.Width, b.Height);
PhotoForm.DrawToBitmap(b, r); // Draw control to bitmap
using (MemoryStream ms = new MemoryStream())
{
b.Save(ms, ImageFormat.Png); // Save bitmap as PNG
result = Image.FromStream(ms);
}
}
// GC.Collect();
// GC.WaitForPendingFinalizers();
return result; // return
}
Now, I try to generate 30 photos using this:
myPhoto.GenerateImage().Save(#"Output1.png", ImageFormat.Png);
myPhoto.GenerateImage().Save(#"Output2.png", ImageFormat.Png);
I do not store the reference, I just save an image and expect that GC will collect these images after saving to a file.
It will take about 2 GB of memory, and finally throw an exception:
An unhandled exception of type 'System.OutOfMemoryException' occurred in System.Drawing.dll
Additional information: Out of memory.
If I look at Visual Studio diagnostic tools, it looks like:
Let's take a snapshot, and look at the contents of the heap, we will see that there are many MemoryStreams:
What causes MemoryStream to produce a memory leak? As far as I know, using() generates Dispose() call, which should take care of it.
P.S. If I remove comment and call GC.Collect(); GC.WaitForPendingFinalizers();, then it takes 2-3 times less memory, but it still grows - ~200 images will still kill an application.
Changing the image saving to the following:
Image img;
img = myPhoto.GenerateImage();
img.Save(#"Output1.png", ImageFormat.Png);
img.Dispose();
img = myPhoto.GenerateImage();
img.Save(#"Output2.png", ImageFormat.Png);
img.Dispose();
will produce the same result as using GC.Collect();. It will also take less memory, but not eliminate memory leakage.
Image implements IDisposable and thus must be disposed manually. You're currently returning an Image from GenerateImage but not disposing it after saving.

A generic error occurred in GDI+ again

I understand, what this message means (need to do Dispose for unmanaged resources), but really don't understand why it happens in my case:
System.Drawing.Image imgAnimaha, imgNoanimaha;
using (System.IO.Stream file = thisExe.GetManifestResourceStream("WindowsApplication1.img.noanimaha135.gif"))
{
using (System.Drawing.Image img = Image.FromStream(file))
{
imgNoanimaha = (System.Drawing.Image)img.Clone();
}
}
using (System.IO.Stream file = thisExe.GetManifestResourceStream("WindowsApplication1.img.animaha135.gif"))
{
using (System.Drawing.Image img = Image.FromStream(file))
{
imgAnimaha = (System.Drawing.Image)img.Clone();
}
}
pbDiscovery.Image = imgAnimaha;
In this case I get "A generic error occurred in GDI+" Why and how to solve?
PS. If I write the following:
pbDiscovery.Image = imgNoanimaha;
It works correctly.
I really don't understand where and which unmanaged resource is not disposed...
The problem is that Image.Clone(), as in:
using (System.Drawing.Image img = Image.FromStream(file))
{
imgAnimaha = (System.Drawing.Image)img.Clone();
}
... does not create a deep copy of the image. It creates a copy of all the header information, but not the actual pixel data (it just points to the original pixel data). The original (and only) pixel data is disposed along with the original img object when the using goes out of scope.
So the question then becomes, what is the point of the using here? I would suggest there is none. Read the image into a System.Drawing.Image object and keep it alive as long as you need the pixel data (e.g. as long as the Image will need to be redrawn) and only Dispose it after it does not need to be displayed again.

OutOfMemory exception when loading an image in .Net

Im loading an image from a SQL CE db and then trying to load that into a PictureBox.
I am saving the image like this:
if (ofd.ShowDialog() == DialogResult.OK)
{
picArtwork.ImageLocation = ofd.FileName;
using (System.IO.FileStream fs = new System.IO.FileStream(ofd.FileName, System.IO.FileMode.Open))
{
byte[] imageAsBytes = new byte[fs.Length];
fs.Read(imageAsBytes, 0, imageAsBytes.Length);
thisItem.Artwork = imageAsBytes;
fs.Close();
}
}
and then saving to the Db using LINQ To SQL.
I load the image back like so:
using (FileStream fs = new FileStream(#"C:\Temp\img.jpg", FileMode.CreateNew ,FileAccess.Write ))
{
byte[] img = (byte[])encoding.GetBytes(ThisFilm.Artwork.ToString());
fs.Write(img, 0, img.Length);
}
but am getting an OutOfMemoryException. I have read that this is a slight red herring and that there is probably something wrong with the filetype, but i cant figure what.
Any ideas?
Thanks
picArtwork.Image = System.Drawing.Bitmap.FromFile(#"C:\Temp\img.jpg");
Based on the code you provided it seems like you are treating the image as a string, it might be that data is being lost with the conversion from byte[] to string and string to byte[].
I am not familiar with SQL CE, but if you can you should consider treating the data as a byte[] and not encoding to and from string.
I base my assumption in this line of code
byte[] img = (byte[])encoding.GetBytes(ThisFilm.Artwork.ToString());
The GDI has the bad behavior of throwing an OOM exception whenever it is not capable of understanding a certain image format. Use Irfanview to see what kind of image that really is, it is likely GDI can't handle it.
My advice would be to not use a FileStream to load images unless you need access to the raw bytes. Instead, use the static methods provided in Bitmap or Image objects:
Image img = Image.FromFile(#"c:\temp\img.jpg")
Bitmap bmp = Bitmap.FromFile(#"c:\temp\img.jpg")
To save the file use .Save method of the Image or Bitmap object:
img.Save(#"c:\temp\img.jpg")
bmp.Save(#"c:\temp\img.jpg")
Of course we don't know what type ArtWork is. Would you care to share that information with us?

Why am I getting "the process cannot access the file * because it is being used by another process" with this code?

I'm trying to convert bmp files in a folder to jpg, then delete the old files. The code works fine, except it can't delete the bmp's.
DirectoryInfo di = new DirectoryInfo(args[0]);
FileInfo[] files = di.GetFiles("*.bmp");
foreach (FileInfo file in files)
{
string newFile = file.FullName.Replace("bmp", "jpg");
Bitmap bm = (Bitmap)Image.FromFile(file.FullName);
bm.Save(newFile, ImageFormat.Jpeg);
}
for (int i = 0; i < files.Length; i++)
files[i].Delete();
The files aren't being used by another program/process like the error indicates, so I'm assuming the problem is here. But to me the code seems fine, since I'm doing everything sequentially. This is all that there is to the program too, so the error can't be caused by code elsewhere.
Try wrap bitmaps with using:
using (Bitmap bm = (Bitmap)Image.FromFile(file.FullName))
{
bm.Save(newFile, ImageFormat.Jpeg);
}
This will dispose bitmap objects just after they were saved.
The best way to solve the issue with Image.FromFile wherein it leaves file handles open is to use Image.FromStream instead.
using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read))
{
using (Image original = Image.FromStream(fs))
{
...
Using an explicit Dispose(), a using() statement or setting the value to null doesn't solve the issue until a garbage collection happens (and forcing a garbage collection to happen is generally a bad idea).
public static Image LoadImage( string fileFullName )
{
Stream fileStream = File.OpenRead( fileFullName );
Image image = Image.FromStream( fileStream );
// PropertyItems seem to get lost when fileStream is closed to quickly (?); perhaps
// this is the reason Microsoft didn't want to close it in the first place.
PropertyItem[] items = image.PropertyItems;
fileStream.Close();
foreach ( PropertyItem item in items )
{
image.SetPropertyItem( item );
}
return image;
}
The Bitmap object encapsulates a file handle. You must call Dispose on the bitmap object after you have called Save.
You're seeing a problem because those Bitmap objects haven't been garbage collected yet.
After bm.Save, you should release your bitmap object. Try adding bm.Dispose(); after bm.Save(newFile, ImageFormat.Jpeg)
From MSDN:
Always call Dispose before you release
your last reference to the Image.
Otherwise, the resources it is using
will not be freed until the garbage
collector calls the Image object's
Finalize method.
I am not sure what you are doing with these images after then, but sometimes you will need also to set the image reference to null then call GC.Collect().

Categories