I'm resizing heaps of images to 1000x1000 thumbnails in parallel and running out of memory very quickly. (Performance profiler puts me at 3GB of used memory after about 3 minutes)
Originally I was using Image.FromFile() but doing some research, I found that Image.FromStream() is the way to go. I think I have the appropriate using statements, something somewhere is still keeping stuff in memory and the GC isn't clearing resources as expected.
It seems like there's an issue with GDI+ keeping the handles open, but I can't seem to find an appropriate solution for my case.
Questions:
Am I doing something completely wrong?
If not, is there a better way to Dispose() of the stream / image / ResizedImage so I'm not eating up all the resources, while still maintaining speedy parallel operations?
If GDI+ is the problem and is keeping unmanaged resources alive, how do I correct the issue?
Code
List<FileInfo> files contains ~300 valid JPG images, each JPG ~2-4mb
Caller
public void Execute()
{
Parallel.ForEach(Files, (file) =>
{
Resize.ResizeImage(file.FullName);
}
);
}
Execute() calls a Parallel.Foreach()..
Resize Class
public static class Resize
{
public static void ResizeImage(string fileName)
{
ResizeImage(fileName, 1000, 1000, true);
}
public static void ResizeImage(string fileName, int newHeight, int newWidth, bool keepAspectRatio = true)
{
string saveto = Path.GetDirectoryName(fileName) + #"\Alternate\" + Path.GetFileName(fileName);
try
{
using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read))
{
using (Image ImageFromStream = Image.FromStream(fs))
{
var ImageSize = new Size(newHeight, newWidth);
if (keepAspectRatio)
{
int oWidth = ImageFromStream.Width;
int oHeight = ImageFromStream.Height;
double pWidth = ((double)ImageSize.Width / (double)oWidth);
double pHeight = ((double)ImageSize.Height / (double)oWidth);
double percent;
if (pHeight < pWidth)
percent = pHeight;
else
percent = pWidth;
newWidth = (int)(oWidth * percent);
newHeight = (int)(oHeight * percent);
}
else
{
newWidth = ImageSize.Width;
newHeight = ImageSize.Height;
}
var ResizedImage = new Bitmap(newWidth, newHeight);
using (Graphics gfxHandle = Graphics.FromImage(ResizedImage))
{
gfxHandle.InterpolationMode = InterpolationMode.HighQualityBicubic;
gfxHandle.DrawImage(ImageFromStream, 0, 0, newWidth, newHeight);
if (!Directory.Exists(Path.GetDirectoryName(saveto))) { Directory.CreateDirectory(Path.GetDirectoryName(saveto)); }
ResizedImage.Save(saveto, ImageFormat.Jpeg);
}
ResizedImage.Dispose();
ResizedImage = null;
}
}
}
catch (Exception ex)
{
Debug.WriteLine(string.Format("Exception: {0}", ex.Message));
}
}
This explanation of parallelism points out that my Parallel.ForEach() was basically creating an overabundance of new tasks because it was waiting on disk access. At about the 5 minute mark, and about when the exception was thrown, there were ~160 threads. Reducing the degree of parallelism limits the amount of threads created, and the number of images waiting in memory to finish loading or writing to the disk before falling out of scope and being disposed of. Setting MaxDegreeOfParallelism = 2 seemed to be the sweet spot for networked disk access and reduced my thread count to around 25, and increased CPU utilization to about 35% (Up from 17-24%, due to GC blocking threads, and CPU overhead from too many threads)
public void Execute()
{
//Parallel.ForEach(Files, (file) =>
//{
// Resize.ResizeImage(file.FullName);
//}
//);
Parallel.ForEach(Files, new ParallelOptions() { MaxDegreeOfParallelism = 2 }, (file) => { Resize.ResizeImage(file.FullName); } );
}
Thanks #ZacFaragher.
Related
I'm implementing a small imageviewer, unfortunately I'am facing a memory leak.
Following is my loading routine.
public BitmapSource getImage(string fileName, double width, double height)
{
FileStream s = File.Open(fileName, FileMode.Open);
Image i = Image.FromStream(s, false, false);
double iWidth = i.Width;
double iHeight = i.Height;
i.Dispose();
s.Close();
BitmapImage tmpImage = new BitmapImage();
tmpImage.BeginInit();
tmpImage.CacheOption = BitmapCacheOption.OnLoad;
tmpImage.UriSource = new Uri(fileName);
if (iWidth > iHeight)
{
tmpImage.DecodePixelWidth = (int)width;
}
else
{
tmpImage.DecodePixelHeight = (int)height;
}
tmpImage.EndInit();
return tmpImage;
}
This is how I call the loader
private void whenArrowKeyPressed(int index)
{
CurrentImage = fh.getImage(fileList[index], 1920, 1080);
}
CurrentImage is a property, which is bound to a WPF ViewBox.
Any Ideas?
I also tried to read from StreamSource, with the same effect.
Only issue I could see is you are not disposing you FileStream. BitmapImage is not Disposable and it will release its memory if there are no references to it.
How did you find there is a memory leak? It’s recommended to use a profiling tool. Garbage collector doesn't release memory immediately something goes out of scope, it waits until memory usages exceeds certain thresholds (usually when Gen 0 going to exceed the threshold). So, you will see some memory increase and it will release memory only after garbage collector executes.
Apparently you are not using a profiling tool. In this case if you want to check if there is any memory leak, you can manually execute GC.Collect and wait for finalization before you get memory reading. Keep in mind that you don’t have to execute GC.Collect manually as it will occur automatically in an optimized way when required.
public BitmapSource getImage(string fileName, double width, double height)
{
using(FileStream s = File.Open(fileName, FileMode.Open))
using(Image i = Image.FromStream(s, false, false))
{
double iWidth = i.Width;
double iHeight = i.Height;
}
BitmapImage tmpImage = new BitmapImage();
tmpImage.BeginInit();
tmpImage.CacheOption = BitmapCacheOption.OnLoad;
tmpImage.UriSource = new Uri(fileName);
if (iWidth > iHeight)
{
tmpImage.DecodePixelWidth = (int)width;
}
else
{
tmpImage.DecodePixelHeight = (int)height;
}
tmpImage.EndInit();
return tmpImage;
}
private void whenArrowKeyPressed(int index)
{
CurrentImage = fh.getImage(fileList[index], 1920, 1080);
// Remove this once you finish testing.
GC.Collect();
GC.WaitForPendingFinalizers();
}
I have the following code
int oswidth = 0;
int osheight = 0;
if (comboBox3.SelectedIndex == 0)
{
oswidth = Convert.ToInt32(textBox5.Text.ToString());
osheight = Convert.ToInt32(textBox6.Text.ToString());
}
else if (comboBox3.SelectedIndex == 1)
{
oswidth = 38 * Convert.ToInt32(textBox5.Text.ToString());
osheight = 38 * Convert.ToInt32(textBox6.Text.ToString());
}
Bitmap oldimg = new Bitmap(pictureBox3.Image);
Bitmap objBitmap = new Bitmap(oldimg, new Size(oswidth, osheight));
objBitmap.Save(pictureBox3.ImageLocation.ToString(), ImageFormat.Jpeg);
The problem is when the selected index is 0 it works fine
but when the selected index is 1 i get a error "Parameter is not valid."
i tried different images but same error. is it the multiply by 32 thing
The Parameter is not valid error message when trying to create a Bitmap usually means that you are trying to allocate too much memory to it. The bitmap requires bit-depth*width*height/8 bytes of contiguous memory, and there just isn't enough available to satisfy that.
In this case, it looks like it's because you're multiplying its dimensions by 38 (and therefore multiplying the size in memory by 38^2).
You could utilize the following method:
private static void ResizeImage(string file, double vscale, double hscale, string output)
{
using(var source = Image.FromFile(file))
{
var width = (int)(source.Width * vscale);
var height = (int)(source.Height * hscale);
using(var image = new Bitmap(width, height, PixelFormat.Format24bppRgb))
using(var graphic = Graphics.FromImage(image))
{
graphic.SmoothingMode = SmoothingMode.AntiAlias;
graphic.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphic.PixelOffsetMode = PixelOffsetMode.HighQuality;
graphic.DrawImage(source, new Rectangle(0, 0, width, height));
image.Save(output);
}
}
}
You can tailor this however you'd like, but it should meet your needs.
Important: The reason vscale and hscale are separate is to not follow scaling. You can easily combine them so you can scale accordingly. The other thing to remember, is your using a value of 32. Try using a value of .32 which will treat it more like a percent, which will scale. Also it won't increase the memory drastically causing your error.
My app has huge memory leak and memory usage spiked from 40MB to 700MB after 1 day running. I'm coding with C# and I don't know how to stop leak. I suspect below 2 functions are main culprit.
SendLiveVideo: It captures from camera and send via socket.
ResizeImage: In order to save bandwidth it resizes image before I send out via socket.
My app calls 3-rd party library( LibImage, Libcapture) capture image from old webcams (4 cameras) and stream as video to list of LAN connected clients.
I already assigned null values to my *variables within finally statement upon exit of Sendvideo function in order to release memory, but it seems not working.
Do I need to use Dispose method instead?
this.timer_stream0.Interval = 30; /*I capture image and send to client this every 30 milseconds make it as video stream to human eye*/
this.timer_stream0.Tick += new System.EventHandler(this.timer_stream0_Tick);
private void timer_stream0_Tick(object sender, EventArgs e)
{
ThreadPool.QueueUserWorkItem(SendLiveVideo, 0);
ThreadPool.QueueUserWorkItem(SendLiveVideo, 1);
ThreadPool.QueueUserWorkItem(SendLiveVideo, 2);
ThreadPool.QueueUserWorkItem(SendLiveVideo, 3);
}
void SendLiveVideo(object state)
{
LibImage image=new LibImage();
LibCapture capture_camera=new LibCapture();
int cam = (int)state;
System.Collections.ArrayList m_workerSocketList_Video =
ArrayList.Synchronized(new System.Collections.ArrayList());
switch (cam)
{
case 0:
{
image = image0;
capture_camera = capture_camera0;
m_workerSocketList_Video = m_workerSocketList_Video0;
break;
}
case 1:
{
image = image1;
capture_camera = capture_camera1;
m_workerSocketList_Video = m_workerSocketList_Video1;
break;
}
case 2:
{
image = image2;
capture_camera = capture_camera2;
m_workerSocketList_Video = m_workerSocketList_Video2;
break;
}
default:
{
image = image3;
capture_camera = capture_camera3;
m_workerSocketList_Video = m_workerSocketList_Video3;
break;
}
}
byte[] imageData=new byte[1000 * 1000 * 10];
try
{
try
{
capture_camera.Capture(image);
}
catch
{
// objData = null;
imageData = null;
return;
}
imageData = image.SaveToMem(PNG);
var stream = new MemoryStream(imageData);
{
var bitmap = new Bitmap(stream);
bitmap = ResizeImage(bitmap, new Size(150, 112));
//stream = null;
MemoryStream streamnew = new MemoryStream();
bitmap.Save(streamnew, ImageFormat.Png);
imageData = streamnew.ToArray();
bitmap = null;
streamnew = null;
}
Socket workerSocket = null;
for (int i = 0; i < m_workerSocketList_Video.Count; i++)
{
workerSocket = (Socket)m_workerSocketList_Video[i];
if (workerSocket != null)
{
if (SocketConnected(workerSocket))
{
workerSocket.Send(imageData);
}
}
}
}
catch (SocketException se)
{
// MessageBox.Show(se.Message);
return;
}
finally
{
image = null;
capture_camera = null;
imageData = null;
m_workerSocketList_Video = null;
}
}
private Bitmap ResizeImage(Bitmap imgToResize, Size size)
{
try
{
int sourceWidth = imgToResize.Width;
int sourceHeight = imgToResize.Height;
float nPercent = 0;
float nPercentW = 0;
float nPercentH = 0;
nPercentW = ((float)size.Width / (float)sourceWidth);
nPercentH = ((float)size.Height / (float)sourceHeight);
if (nPercentH < nPercentW)
nPercent = nPercentH;
else
nPercent = nPercentW;
int destWidth = (int)(sourceWidth * nPercent);
int destHeight = (int)(sourceHeight * nPercent);
Bitmap b = new Bitmap(destWidth, destHeight, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
b.SetResolution(200, 200);
Graphics g = Graphics.FromImage((Image)b);
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.CompositingQuality = CompositingQuality.HighSpeed;
g.SmoothingMode = SmoothingMode.HighSpeed;
g.DrawImage(imgToResize, 0, 0, destWidth, destHeight);
g.Dispose();
return b;
}
Try taking a look at my SO answer here.
Basically, it is an approach of how to find the origin of the leak.
This is practically a copy-paste:
Open a memory profiler. I use perfmon. This article has some material about setting perfmon and #fmunkert also explains it rather well.
Locate an area in the code that you suspect that it is likely that the leak is in that area. This part is mostly depending on you having good guesses about the part of the code that is responsible for the issue.
Push the Leak to the extreme: Use labels and "goto" for isolating an area / function and repeat the suspicious code many times (a loop will work to. I find goto more convenient for this matter).
In the loop I have used a breakpoint that halted every 50 hits for examining the delta in the memory usage. Of course you can change the value to feet a noticeable leak change in your application.
If you have located the area that causes the leak, the memory usage should rapidly spike. If the Memory usage does not spike, repeat stages 1-4 with another area of code that you suspect being the root cause. If it does, continue to 6.
In the area you have found to be the cause, use same technique (goto + labels) to zoom in and isolate smaller parts of the area until you find the source of the leak.
Note that the down sides of this method are:
If you are allocating an object in the loop, it's disposal should be also contained in the loop.
If you have more than one source of leak, It makes it harder to spot (yet still possible)
I would use the
using(Graphics g = Graphics.FromImage((Image)b))
{
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.CompositingQuality = CompositingQuality.HighSpeed;
g.SmoothingMode = SmoothingMode.HighSpeed;
g.DrawImage(imgToResize, 0, 0, destWidth, destHeight);
}
return b;
pattern on each type which implements the IDisposable, this way you can't forget to dispose anything?
you have to use the using keyword whenever an IDisposable instance is used.
using (MemoryStream streamnew = new MemoryStream())
// The same goes for your bitmap instance !
you have to check on LibImage and LibCapture's implementations and the native libraries they're wrapping if it's the case.
there is no need to instantiate all those three variables image, capture_camera and m_workerSocketList_Video since you're initializing them in your switch statement unless. This will lead to significant memory loss if the classes are native wrappers around libraries that poorly manage the memory they use.
With this line of code,
imageData = image.SaveToMem(PNG);
you don't need to initiate the imageData array if the SaveToMem function returns the right buffer !
I'm having issues opening multiple image files from the users desktop and then converting those images to a scaled down size which then gets displayed on the UI (after all the converting is done). I can't find what the issue is exactly but what I've observed is that there seems to be a 5 second limit between hitting the "Open" button on the "OpenFileDialog" box control and how much time I have to read those File(s). I've used 6 files ranging in size of 9-11MB, and in another case I've used 50 1-2MB files and in all cases the process will read up until 5 seconds have expired. It never fails on the same image either so the image isn't causing the issue which would further make me believe its not a file count issue. If I test this process with only a few small sized files it happens under 1 second and there is not failure and I see all images on the UI. That is why I'm guessing its a timing issue. I know silverlight has a security exception between when the user interacts with a control (button) and how much time can elapse before displaying the "OpenFileDialog" box but this time limit seems to be different but I can't find any documentation.
Here is the code I'm using. It seems to be a pretty common recipe used everywhere but posting for completeness. The error happens on the line
var bitmap = new WriteableBitmap(bitmapImage);
The reason it fails is because the bitmapImage pixelWidth/Height == 0. Here is the full code.
private const int MaxPixelSize = 500;
public byte[] Convert(FileInfo fileInfo, FileTypes fileType, DateTime startTime)
{
byte[] result = null;
using (var stream = fileInfo.OpenRead())
{
DateTime EndTime = DateTime.Now;
if (fileType == FileTypes.JPG || fileType == FileTypes.BMP || fileType == FileTypes.PNG)
{
var bitmapImage = new BitmapImage();
bitmapImage.SetSource(stream);
double scaleX = 1;
double scaleY = 1;
if (bitmapImage.PixelWidth > MaxPixelSize)
{
scaleX = MaxPixelSize / (double)bitmapImage.PixelWidth;
}
if (bitmapImage.PixelHeight > MaxPixelSize)
{
scaleY = MaxPixelSize / (double)bitmapImage.PixelHeight;
}
var scale = Math.Min(scaleX, scaleY);
var bitmap = new WriteableBitmap(bitmapImage);
var resizedBitmap = bitmap.Resize((int)((double)bitmapImage.PixelWidth * scale), (int)((double)bitmapImage.PixelHeight * scale), WriteableBitmapExtensions.Interpolation.Bilinear);
using (var scaleStream = new MemoryStream())
{
var encoder = new JpegEncoder();
var image = resizedBitmap.ToImage();
encoder.Encode(image, scaleStream);
result = scaleStream.GetBuffer();
}
}
else
{
result = new byte[stream.Length];
stream.Read(result, 0, (int)stream.Length);
}
}
return result;
}
Any help or suggestions are welcomed.
Thanks,
Dean
if bitmapImage.ImageOpened event is executed, you can get valid pixelWidth and height.
when bitmapImage.SetSource(stream) is excuted, this event will be invoked.
I'm trying to thumb an image as fast as possible regardless of the usage of resources to be used in my ImageList and listview and this is currently how i'm doing it but it seems to be slow:
public Image toThumbs(string file, int width, int height)
{
image = null;
aspectRatio = 1;
fullSizeImg = null;
try
{
fullSizeImg = Image.FromFile(file);
float w = fullSizeImg.Width;
float h = fullSizeImg.Height;
aspectRatio = w / h;
int xp = width;
int yp = height;
if (fullSizeImg.Width > width && fullSizeImg.Height > height)
{
if ((float)xp / yp > aspectRatio)
{
xp = (int)(yp * aspectRatio);
}
else
{
yp = (int)(xp / aspectRatio);
}
}
else if (fullSizeImg.Width != 0 && fullSizeImg.Height != 0)
{
xp = fullSizeImg.Width;
yp = fullSizeImg.Height;
}
image = new Bitmap(width, height);
graphics = Graphics.FromImage(image);
graphics.FillRectangle(Brushes.White, ((width - xp) / 2), (height - yp), xp, yp);
graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
graphics.DrawImage(fullSizeImg, new Rectangle(((width - xp) / 2), (height - yp), xp, yp));
graphics.Dispose();
fullSizeImg.Dispose();
}
catch (Exception)
{
image = null;
}
return image;
}
I'm not sure if the computation is the one that is slowing down the thumbnailing or maybe the classes itself that are being used are slow, if that is the case then what other alternatives can be use maybe a different computation or i need to import other classes or is there a third party libraries that can be used or i need to do a dll import or something? Please help me.
Edit: Just found a solution here
http://www.vbforums.com/showthread.php?t=342386
it extracts a thumbnail from a file
without reading the whole file. I was able to reduce the time about 40% when i used this.
Your calculations happen in fractions of a second. The call to DrawImage is most likely the slowest part of this (as that one is doing the scaling).
If you're needing this thumbnail image exactly once then I don't see much room for improvement here. If you're calling that method on the same image more than once, you should cache the thumbnails.
I use this mechanism which seems to be very fast.
BitmapFrame bi = BitmapFrame.Create(new Uri(value.ToString()), BitmapCreateOptions.DelayCreation, BitmapCacheOption.OnDemand);
// If this is a photo there should be a thumbnail image, this is VERY fast
if (bi.Thumbnail != null)
{
return bi.Thumbnail;
}
else
{
// No thumbnail so make our own (Not so fast)
BitmapImage bi2 = new BitmapImage();
bi2.BeginInit();
bi2.DecodePixelWidth = 100;
bi2.CacheOption = BitmapCacheOption.OnLoad;
bi2.UriSource = new Uri(value.ToString());
bi2.EndInit();
return bi2;
}
Hope this helps.
Out of curiosity, have you tried the GetThumbnailImage method on System.Drawing.Bitmap? It might at least be worth comparing to your current implementation.
This may seem like to much of an obvious answer but have you tried just using Image.GetThumbnailImage()?
You don't get as much control over the quality of the result but if speed is your main concern?
Your thumbnail extraction that gets you the big speed up relies on the image having a thumbnail already embedded in it.
To speed up the original you might find that changing:-
graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
to
graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.Low;
Might help.