I wrote a WPF app that should swap (fast) between a large set of images (600+, 190Kb average size), but I'm finding some difficulties.
private int appendImages(Canvas c, int start, int end)
{
int tot = 0;
for (int i = start; i < end; i++)
{
BitmapImage bi = new BitmapImage();
bi.BeginInit();
//bi.CacheOption = BitmapCacheOption.OnLoad;
bi.UriSource = new Uri(appFolder+#"/"+imgFolder+"/"+filename(i)+".jpg");
bi.EndInit();
Image img = new Image
{
Width = imgWidth,
Height = imgHeight,
Source = bi,
Name = name(i),
Visibility = i == startImg ? Visibility.Visible : Visibility.Hidden
};
c.Children.Add(img);
tot++;
}
}
Apparently the inizialization is fine, but if I try to swap the images like this:
private void changeImageTo(int n)
{
Image img = findImage(n);
Image old = findImage(prevImg);
if (img != null)
{
img.Visibility = Visibility.Visible;
if (old != null && old != img)
old.Visibility = Visibility.Hidden;
prevImg = n;
}
}
..then the app shows the first 200/300 images (depending on the sources I use), and the others are just empty/blank (i can see the canvas underneath).
I suspect it's a memory issue, but I'm not really sure what causes it.
By the way, if I uncomment the commented line (BitmapCacheOption.OnLoad) sometimes I get a vshost error when launching the app.
Any help would be MUCH appreciated, since I couldn't find anything useful browsing around.
Thanks in advance!
It looks like you're loading all the images at once, and putting them into WinForms/WPF controls. That is a very bad idea with that many images, as each one takes resources even if it's not shown.
Rough back of the envelope calculation, assuming 640x480 images, 24bpp being the native GDI+ format, shows a bit over 2gb for loading all the images at once, and that would, of course, increase exponentially with image size.
What I would do instead, is have only one Image. Move the actual image loading code into your changeImageTo function, build the file name based on n, and set the loaded image to the Image there.
Related
I'm trying to create a gif out of some Bitmap images and it takes a really long time for it to load. I followed the example on the library github page for this.
Here is my code:
public void SaveAsGif(Stream stream, ICollection<Bitmap> images, float fps, bool loop)
{
ICollection<IMagickImage> magickImages = new System.Collections.ObjectModel.Collection<IMagickImage>();
float exactDelay = 100 / fps;
foreach (Bitmap bitmap in images)
{
MagickImage image = new MagickImage(bitmap);
image.AnimationDelay = (int) exactDelay;
if (!loop)
{
image.AnimationIterations = 1;
}
magickImages.Add(image);
}
using (MagickImageCollection collection = new MagickImageCollection(magickImages))
{
QuantizeSettings settings = new QuantizeSettings();
settings.Colors = 256;
collection.Quantize(settings);
collection.Optimize();
collection.Write(stream, MagickFormat.Gif);
}
}
I tested and the conversion of the images from bitmap to MagickImages doesn't take very long, at most 5 seconds. And my images are about 4000x3000.
All the images have the same size. Writing to stream doesn't take long either. Any way to improve the timing?
If I understand your question correctly, the slowness is in displaying the GIF, not generating it. If that is the case, try resizing the image prior to calling the Add method.
e.g.:
image.Resize(400,300);
The questions in SO, for example this one or this one have discussed many reason for this exception, but none could help me to find out my problem, and I still cannot figure out why this is happening.
I'm having this error, when trying to save (code is below). And the strangest thing I've got is that - this error is occurring for random images (let's say "random" for now, because I don't know exact problem yet). Only for particular images, out of thousands, I got this problem.
Here is the one successful and one unsuccessful image when trying.
1. 2009-05-23_00.51.39.jpg
Image dimensions: 768x1024
Size: 87KB
Bit depth: 24
Color representation: sRGB
2. 2009-05-23_00.52.50.jpg
Image dimensions: 768x1024
Size: 335KB
Bit depth: 24
Color representation: sRGB
For image #1 the process is going smoothly, with no errors. However, for image #2, I keep getting this error. As you see the images are almost the same. Both are .jpg, same dimensions, and most of the properties are just same.
Here is the method.
public static Stream GetImageBytes(Stream fileStream, int maxWidth, int maxHeight, bool showCopyRight, string link, int fillColor = 0xFFFFFF)
{
var img = Image.FromStream(fileStream);
if (maxHeight != 0 && img.Height > maxHeight)
ResizeByHeight(ref img, maxHeight, fillColor.ToString("X6"));
if (maxWidth != 0 && img.Width > maxWidth)
ResizeByWidth(ref img, maxWidth, fillColor.ToString("X6"));
if (showCopyRight)
img = ApplyWatermark(ref img, "watermark.png", link);
var ms = new MemoryStream();
//problem is here
img.Save(ms, ImageFormat.Jpeg);
img.Dispose();
return ms;
}
Edit after comments.
Ok, here is the Watermark method
private static Bitmap ApplyWatermark(ref Image img, string watermarkPathPng, string link) {
const int x = 0;
var y = img.Height - 20;
string watermarkText;
if (img.Width <= 220) {
watermarkText = "shorter copy";
} else {
if (img.Width < 500)
watermarkText = "longer copy";
else
watermarkText = "full copy " + link;
}
var bp = new Bitmap(img);
img.Dispose();
using (var gr = Graphics.FromImage(bp)) {
var watermark = Image.FromFile(watermarkPathPng);
gr.DrawImage(watermark, x, y, watermark.Width, watermark.Height);
watermark.Dispose();
gr.DrawString(watermarkText, new Font(new FontFamily("Batang"), 13, FontStyle.Regular, GraphicsUnit.Pixel), Brushes.Black, 5, y + 5);
}
return bp;
}
The reason why I've used Bitmap inside this method is that I had Indexed Pixels problem before and that solved the issue.
Try copying the image rather than operating directly on the one you loaded. Often gdi errors are caused by you trying to change something that you think you "own" when the loader thinks it owns it.
As it only happens for some images, check the logic in your three processing methods - perhaps the failure only occurs if you need to do a vertical resize, in which case look for a bug in that method. Our maybe it's images that have to be processed by more than one of your methods.
It is also suspicious that you return img and pass it by reference in ApplyWatermark(). Check your logic here.
As I've not a great knowledge of GDI+, I'm not fully sure what has happened, but changing the following code line:
var ms = new MemoryStream();
//problem is here
img.Save(ms, ImageFormat.Jpeg);
img.Dispose();
return ms;
to this,
var ms = new MemoryStream();
var bit = new Bitmap(img);
img.Dispose();
bit.Save(ms, ImageFormat.Jpeg);
bit.Dispose();
return ms;
Worked well, so I just create a Bitmap from the image, then save the Bitmap to the Memory. Now this function is working well for all the images.
My program has a lot of small images (Image controls are small, not the images themselves) and by saying a lot I mean more than 500. These images are generated asynchronously and then assigned to the Image controls, which were initialized before.
Basically my code does the following:
filename = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, string.Format("{0}.JPG", Guid.NewGuid().GetHashCode().ToString("x2")));
converter.ConvertPdfPageToImage(filename, i);
//Fire the ThumbnailCreated event
onThumbnailCreated(filename, (i - 1));
There is no memory leak in code that creates the images, I have the following code:
string[] files = Directory.GetFiles("C:\\Users\\Daniel\\Pictures", "*.jpg");
for(int i=0; i<files.Length; i++){
onThumbnailCreated(files[i], i);
}
Still the problem persists.
This happens in the event handler method:
void Thumbnails_ThumbnailCreated(ThumbnailCreatedEventArgs e, object sender)
{
//Since we generate the images async, we need to use Invoke
this.parent.Dispatcher.Invoke(new SetImageDelegate(SetImage), e.Filename, e.PageNumber);
}
private void SetImage(string filename, int pageNumber)
{
BitmapImage bitmap = new BitmapImage();
bitmap.BeginInit();
//I am trying to make the Image control use as less memory as possible
//so I prevent caching
bitmap.CacheOption = BitmapCacheOption.None;
bitmap.UriSource = new Uri(filename);
bitmap.EndInit();
//We set the bitmap as the source for the Image control
//and show it to the user
this.images[pageNumber].Source = bitmap;
}
With 468 images the program uses about 1Gb of memory and then runs out of it at all. Is my task even possible to achieve using WPF or is the number of images too high? Maybe there is something wrong with my code?
Thanks in advance
You should freeze these images and set their width (or height) to that will be actually used in the application if possible:
// ...
bitmap.DecodePixelWidth = 64; // "displayed" width, this improves memory usage
bitmap.EndInit();
bitmap.Freeze();
this.images[pageNumber].Source = bitmap;
Try this:
private void SetImage(string filename, int pageNumber)
{
using (BitmapImage bitmap = new BitmapImage())
{
bitmap.BeginInit();
//I am trying to make the Image control use as less memory as possible
//so I prevent caching
bitmap.CacheOption = BitmapCacheOption.None;
bitmap.UriSource = new Uri(filename);
bitmap.EndInit();
this.images[pageNumber].Source = bitmap;
}
}
That will dispose of your bitmaps when you're done with them.
It may be the event handlers that are causing your memory leaks.
See this SO question:
Why and How to avoid Event Handler memory leaks?
private void LoadImageList()
{
string filepath = Application.StartupPath + #"\Content\Textures\Tiles\PlatformTiles.png";
Bitmap tileSheet = new Bitmap(filepath);
int tilecount = 0;
for (int y = 0; y < tileSheet.Height / TileGrid.TileHeight; y++)
{
for (int x = 0; x < tileSheet.Width / TileGrid.TileWidth; x++)
{
Bitmap newBitmap = tileSheet.Clone(
new System.Drawing.Rectangle(
x * TileGrid.TileWidth,
y * TileGrid.TileHeight,
TileGrid.TileWidth,
TileGrid.TileHeight),
System.Drawing.Imaging.PixelFormat.DontCare);
imgListTiles.Images.Add(newBitmap);
string itemName = "";
if (tilecount == 0)
{
itemName = "Empty";
}
if (tilecount == 1)
{
itemName = "White";
}
listTiles.Items.Add(new ListViewItem(itemName, tilecount++));
}
}
}
All I did was update PlatformTiles.png with a newer one and the next time I ran the program it doesn't load. I placed a breakpoint at int tilecount = 0; and it doesn't ever reach it. Every thing after it doesn't load either. Any ideas?
Is that Bitmap referring to System.Drawing.Bitmap? If so your code has a memory leak. You're creating hundreds (thousands?) of Bitmap objects without disposing them. Each Bitmap object encapsulates a GDI Bitmap surface which must be explicitly disposed. Use a C# using(Bitmap bmp) { } block to ensure they're disposed when they fall out of scope, or ensure that you dispose of the bitmaps when the parent class object is disposed.
This may be related to your problem (generally it's a bad idea to create too many bitmaps in case you hit the GDI Object limit).
I got rid of the string and replaced filepatth, Bitmap tileSheet = new Bitmap(filepath);, with the path of the image on my hard-drive and it worked. Solution, but could use some explaining.
If the file is part of your content project and it's Build Action is set to Compile, the file that's in that folder at run time does not have a .png extension so the Bitmap will never be created. Content assets have a .xnb extension as they've been compiled to a binary format. Change the Build Action to None and the Copy to Output Direction to Copy if newer
I have a problem with out of memory when I'm trying load a few images into one picturebox.
public void button2_Click(object sender, EventArgs e)
{
FolderBrowserDialog dialog = new FolderBrowserDialog();
dialog.ShowDialog();
string selected = dialog.SelectedPath;
string[] imageFileList = Directory.GetFiles(selected);
int iCtr = 0,zCtr = 0;
foreach(string imageFile in imageFileList)
{
if (Image.FromFile(imageFile) != null)
{
Image.FromFile(imageFile).Dispose();
}
PictureBox eachPictureBox = new PictureBox();
eachPictureBox.Size = new Size(100,100);
// if (iCtr % 8 == 0)
//{
// zCtr++;
// iCtr = 0;
//}
eachPictureBox.Location = new Point(iCtr * 100 + 1, 1);
eachPictureBox.Image = Image.FromFile(imageFile);
iCtr++;
panel1.Controls.Add(eachPictureBox);
}
}`enter code here`
if (Image.FromFile(imageFile) != null)
{
Image.FromFile(imageFile).Dispose();
}
Bad. You're loading the image from the file, checking to see if the result is null...then loading it again into a new result so that you can dispose it. While the latter portion is silly, it isn't harmful. The first portion is, however, as the resulting Image is never properly disposed of (if/when the GC collects it, the finalizer on the Image type should dispose of the unmanaged resources, but this is not a wise thing to rely on).
Incidentally, Image.FromFile will never return null. If it cannot read the image, then it will throw an OutOfMemoryException.
The code also appears to do nothing, since there's no else block and nothing meaningful is done in the if block.
My guess is that your OutOfMemoryException is coming from the fact that one or more of the files in that directory is stored in a corrupted or unsupported image format, or isn't an image at all.
Try replacing the code in your foreach with this:
try
{
Image image = Image.FromFile(imageFile);
PictureBox eachPictureBox = new PictureBox();
eachPictureBox.Size = new Size(100,100);
eachPictureBox.Location = new Point(iCtr * 100 + 1, 1);
eachPictureBox.Image = Image.FromFile(imageFile);
iCtr++;
panel1.Controls.Add(eachPictureBox);
}
catch(OutOfMemoryException) { } // skip the file
The picture box internally holds a reference to the bitmap that you place in it. Unless you get rid of the picture box, it's holding a reference to every bitmap you load into it.
Something you have to consider that regardless of the type of picture stored on disk, when you open it for display the picture will become a bitmap and require 4 bytes per displayed pixel.
Your code seems to suggest an attempt at a thumbnail operation. You are in fact loading 70 files into memory and regardless of the display size, in memory they will be very large.
For example let's say you have 70 jpegs at 32bit color depth and say 1920x1080 pixels in size. Your memory requirement to load that many images all at once then is:
70 pics x 1920 pixels x 1080 pixels x 4 bytes/pixel = 580,608,000 bytes!
And that's a fairly low estimate.
You could consider loading many fewer pictures or trying for a real thumbnailing solution.