My Question: Is there a way to load images into a BitmapImage that will not take up a tremendous amount of memory and the images can still be deleted? Read below for more details:
I have a class PhotoCollection : ObservableCollection<Photo>{ }, where the Photo class creates a BitmapImage Object:
PhotoCollection Class:
public class PhotoCollection : ObservableCollection<Photo>
{
...Stuff in here...
}
Photo Class:
public class Photo
{
public Photo(string path)
{
_path = path;
_source = new Uri(path);
BitmapImage tmp = new BitmapImage();
tmp.BeginInit();
tmp.UriSource = _source;
tmp.CacheOption = BitmapCacheOption.None;
tmp.DecodePixelWidth = 200;
tmp.DecodePixelHeight = 200;
tmp.EndInit();
BitmapImage tmp2 = new BitmapImage();
tmp2.BeginInit();
tmp2.UriSource = _source;
tmp2.CacheOption = BitmapCacheOption.None;
tmp2.EndInit();
_image = BitmapFrame.Create(tmp2, tmp);
_metadata = new ExifMetadata(_source);
}
public BitmapFrame _image;
public BitmapFrame Image { get { return _image; } set { _image = value; } }
...More Property Definitions used to support the class
}
When I drag and drop images on my computer into a listbox the photos are loaded into the PhotoCollection of Photos and displayed within a listbox(Thanks to Binding). If I drop 50MBs of photos my program takes up ~50MBs of memory.
The problem I have is I need to later delete these photos from the folder. To do this I must unload or dispose of the photos in memory first, because BitmapImage locks the files. I cannot figure out how to do this.
I thought after finding this similar StackOverFlow Question that all my problems were solved. Implementing the code from the StackOverFlow's Question:
public class Photo
{
public Photo(string path)
{
BitmapImage tmp = new BitmapImage();
BitmapImage tmp2 = new BitmapImage();
tmp = LoadImage(_path);
tmp2 = LoadImage(_path);
...
}
private BitmapImage LoadImage(string myImageFile)
{
BitmapImage myRetVal = null;
if (myImageFile != null)
{
BitmapImage image = new BitmapImage();
using (FileStream stream = File.OpenRead(myImageFile))
{
image.BeginInit();
image.CacheOption = BitmapCacheOption.OnLoad;
image.StreamSource = stream;
image.EndInit();
}
myRetVal = image;
}
return myRetVal;
}
...
}
There was just one HUGE issue with implementing a FileStream to load the images into the BitMapImage object. My memory use skyrocketed! Like 50MBs of photos took up 1GB of memory and took 10 times longer to load:
Link to Image
To reiterate my Question: Is there a way to load images into a BitmapImage that will not take up a tremendous amount of memory and the images can still be deleted?
Thanks so much! ^_^
You can set the DecodePixelWidth and DecodePixelHeight properties of the BitmapImage to tell it to load fewer pixels into memory.
Related
I have to get a BitmapSource from an Image and for this I use an extension method like this:
public static BitmapSource ToBitmapSource(this Image image)
{
LogEx.FunctionEnter();
if (image == null)
throw new ArgumentNullException("image");
BitmapImage bitmapImage = null;
using (MemoryStream memory = new MemoryStream())
{
image.Save(memory, ImageFormat.Jpeg);
memory.Position = 0;
bitmapImage = new BitmapImage();
bitmapImage.BeginInit();
bitmapImage.StreamSource = memory;
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
bitmapImage.EndInit();
}
LogEx.FunctionLeave("Rendered image as BitmapSource");
return bitmapImage;
}
If I now release the handle of this and dispose the original Image it stays in the memory, even after calling the GC by hand multiple times. I tested to use files instead of streams with this little piece of code:
string filename = $"c:\\temp\\{page}.jpg";
if (File.Exists(filename))
{
File.Delete(filename);
}
_highResPageImages[page].Save(filename, ImageFormat.Jpeg);
Uri uri = new Uri(filename);
BitmapImage source = new BitmapImage();
source.BeginInit();
source.UriSource = uri;
source.CacheOption = BitmapCacheOption.OnLoad;
source.EndInit();
Document.PageImage = source;
// Ducument.PageImage = _highResPageImages[page].ToBitmapSource();
And even if there is also an OnLoad, it gets disposed when the handle is released. So it must be something with the MemoryStream. But what? I tried this WrapperStream I found somewhere else and to use the Freeze() Method of BitmapImage, but both to no a avail. The problem is, that I cannot cache the images on the drive of the customer (even if it wouldn't cost a huge amount of time to do so) and I get the Image from another DLL, so I cannot change it beforehand. Has someone else an idea?
Edit: I use WPF and the value of the handle is being used in a Binding for display. Maybe that matters somehow. Or that I use BitmapSource in the Binding and handle and not the original BitmapImage.
Try this in ToBitmapSource:
...
bitmapImage.StreamSource = null;
return bitmapImage
Hello! The problem is? that I've got a multipage Tiff file to show, and I use
BitmapFrame.Thumbnail property to show small size thumbnail of every frame(page) of my multipage Tiff file. But< for some reason? the property returns null. Please, give step by step description, of how this should be done?
I've already tried to create my own BitmapSource thumbnail with this method:
public static BitmapImage GetThumbnail(BitmapFrame bitmapFrame)
{
try
{
JpegBitmapEncoder encoder = new JpegBitmapEncoder();
MemoryStream memorystream = new MemoryStream();
BitmapImage tmpImage = new BitmapImage();
encoder.Frames.Add(bitmapFrame);
encoder.Save(memorystream);
tmpImage.BeginInit();
tmpImage.CacheOption = BitmapCacheOption.OnLoad;
tmpImage.StreamSource = new MemoryStream(memorystream.ToArray());
File.WriteAllBytes( $"{Path.GetTempFileName()}.jpg", memorystream.ToArray());
tmpImage.UriSource = new Uri($"{Path.GetTempFileName()}.jpg");
tmpImage.DecodePixelWidth = 80;
tmpImage.DecodePixelHeight = 120;
tmpImage.EndInit();
memorystream.Close();
return tmpImage;
}
catch (Exception ex)
{
return null;
throw ex;
}
}
then I convert the result to BitmapSource and create a list of BitmapFrames using:
List<BitmapFrame> tiffImageList = new List<BitmapFrame>();
tiffImageList.Add(new TiffImage() { index = imageIndex, image = BitmapFrame.Create(frame, (BitmapSource)GetThumbnail(frame))});
In the end I try to get property, but it returns null:
foreach (var tiffImage in tiffImageList)
{
Image image = new Image();
image.Source = tiffImage.image.Thumbnail;
}
I ran into a similar issue, modifying with the SDK PhotoViewerDemo example. Some valid jpg's are shown as white square thumbnails.
I think I found why the question code does not work. Ivan's question provides a correct constructor of BitmapFrame, but the functions have to create a BitmapSource, not a BitmapImage.
C# BitmapFrame.Thumbnail property is null for some images
I got it working with the function provided in that topic, using Ivan's call to the constructor, using the two bitmapsource arguments.
Code in the SDK example I now use is..
private BitmapSource CreateBitmapSource(Uri path)
{
BitmapImage bmpImage = new BitmapImage();
bmpImage.BeginInit();
bmpImage.UriSource = path;
bmpImage.EndInit();
return bmpImage;
}
private BitmapSource CreateThumbnail(Uri path)
{
BitmapImage bmpImage = new BitmapImage();
bmpImage.BeginInit();
bmpImage.UriSource = path;
bmpImage.DecodePixelWidth = 120;
bmpImage.EndInit();
return bmpImage;
}
// it has to be plugged in here,
public Photo(string path)
{
Source = path;
_source = new Uri(path);
// replaced.. Image = BitmapFrame.Create(_source);
// with this:
Image = BitmapFrame.Create(CreateBitmapSource(_source),CreateThumbnail(_source));
Metadata = new ExifMetadata(_source);
}
I am working on a project that requires me to get bunch of images and display them as a video. Which means, I will update Image control of WPF 30 times a second.
So far no luck.
BitmapImage img = BitmapToImageSource((Bitmap)image);
this.image_box.Dispatcher.Invoke(() =>
{
//this.image_box.Source.Freeze();
this.image_box.Source = img;
});
BitmapImage BitmapToImageSource(Bitmap bitmap)
{
using (MemoryStream memory = new MemoryStream())
{
bitmap.Save(memory, System.Drawing.Imaging.ImageFormat.Bmp);
memory.Position = 0;
BitmapImage bitmapimage = new BitmapImage();
bitmapimage.BeginInit();
bitmapimage.StreamSource = memory;
bitmapimage.CacheOption = BitmapCacheOption.OnLoad;
bitmapimage.EndInit();
return bitmapimage;
}
}
image_box is the image control (wpf).
So, I was expecting this code to update image and give an illusion of video being played. Whereas it does nothing. I see waiting cursor on the window.
Edited ---- More Info ----
I created a simple program to check if images can be changed in image control. But the results are same. I see waiting cursor and no image.
private void Window_Loaded(object sender, RoutedEventArgs e)
{
ImageSource s1;
ImageSource s2;
s1 = new BitmapImage(new Uri(#"IMAGE_PATH", UriKind.Absolute));
s2 = new BitmapImage(new Uri(#"IMAGE_PATH", UriKind.Absolute));
while (true)
{
try
{
if (image.Source == s1)
image.Source = s2;
else
image.Source = s1;
System.Threading.Thread.Sleep(1000);
}
catch(Exception ex)
{
MessageBox.Show("Error");
}
}
}
Although I knew this fact, but still made the same mistake.
We are not allowed to update GUI in a loop. If we want to do so, we need to create a separate thread.
I'm pretty new to displaying images in WPF forms, and i'm having trouble when it comes to converting and assigning an Image source for my GUI.
System.Drawing.Image testImg = ImageServer.DownloadCharacterImage(charID, ImageServer.ImageSize.Size128px);
byte[] barr = imgToByteArray(testImg);
CharImage.Source = ByteToImage(barr);
public byte[] imgToByteArray(System.Drawing.Image testImg)
{
using (MemoryStream ms = new MemoryStream())
{
testImg.Save(ms,System.Drawing.Imaging.ImageFormat.Jpeg);
return ms.ToArray();
}
}
public System.Drawing.Image ByteToImage(byte[] barr)
{
MemoryStream ms = new MemoryStream(barr);
System.Drawing.Image returnImage = System.Drawing.Image.FromStream(ms);
return returnImage;
}
So i take in an image (JPEG) from the EVE Online C# API Library and then try to convert it to a byte array and back to a proper image. However i always get this error: "Cannot Implicitly convert type 'System.Drawing.Image' to 'System.Windows.Media.ImageSource'" I'm completely dumbfounded on how to solve this.
One possible solution is to save the image files (for example, .jpg) as WPF embedded resource and then use the following code snippet to get BitmapImage:
Listing 1. Get BitmapImage from EmbeddedResource
private string GetAssemblyName()
{
try { return Assembly.GetExecutingAssembly().FullName.Split(',')[0]; }
catch { throw; }
}
private BitmapImage GetEmbeddedBitmapImage(string pathImageFileEmbedded)
{
try
{
// compose full path to embedded resource file
string _fullPath = String.Concat(String.Concat(GetAssemblyName(), "."), pathImageFileEmbedded);
BitmapImage _bmpImage = new BitmapImage();
_bmpImage.BeginInit();
_bmpImage.StreamSource = Assembly.GetExecutingAssembly().GetManifestResourceStream(_fullPath);
_bmpImage.EndInit();
return _bmpImage;
}
catch { throw; }
finally { }
}
Correspondingly, set the Source property of the WPF Image control (for example, Image1) to that BitmapImage returned by function:
Image1.Source = GetEmbeddedBitmapImage(_strEmbeddedPath);
Note: you should reference the following:
using System.Windows.Media.Imaging;
using System.Reflection;
Another possible solution is to get the BitmapImage from image file using Uri object as shown in the following code snippet (Listing 2):
Listing 2. Get BitmapImage from File (use Uri)
private BitmapImage GetBitmapImageFromFile(string ImagePath)
{
Uri BitmapUri;
StreamResourceInfo BitmapStreamSourceInfo;
try
{
// Convert stream to Image.
BitmapImage bi = new BitmapImage();
BitmapUri = new Uri(ImagePath, UriKind.Relative);
BitmapStreamSourceInfo = Application.GetResourceStream(BitmapUri);
bi.BeginInit();
bi.StreamSource = BitmapStreamSourceInfo.Stream;
bi.EndInit();
return bi;
}
catch { throw; }
}
Hope this may help.
System.Drawing.Image is WinForms, not WPF. Your ByteToImage method should return BitmapSource instead.
The probably easiest way to create a BitmapSource from a byte array is BitmapFrame.Create:
public BitmapSource ByteArrayToImage(byte[] buffer)
{
using (var stream = new MemoryStream(buffer))
{
return BitmapFrame.Create(stream,
BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
}
}
You would assign the return value of the above method to the Source property of an Image control:
image.Source = ByteArrayToImage(barr);
Ik in windows forms I could add images in resources and then change the images as users click on an event handler not sure whats changed in Xaml but I cant figure it out.
private void guessClick(object sender, RoutedEventArgs e)
{
wrongGuesses++;
hangmanPicture.Image = hangmanImage[wrongGuesses];
}
if I just put hangmanPicture = hangmanImage[wrongGuesses];
I get can not convert. I don't understand why its trying to convert anything.
if your hangmanImage array is an array of ImageSource or BitmapImage, you can use it like this:
private void guessClick(object sender, RoutedEventArgs e)
{
wrongGuesses++;
hangmanPicture.Source = hangmanImage[wrongGuesses];
}
Otherwise, you have to convert anything in hangmanImage into ImageSource or BitmapImage.
If it's Bitmap you can use below converter before that code:
public static BitmapImage ConvertToBitmapImageFromBitmap(Bitmap bitmap)
{
using(var memory = new MemoryStream())
{
BitmapImage bitmapImage;
bitmap.Save(memory, ImageFormat.Png);
memory.Position = 0;
bitmapImage = new BitmapImage();
bitmapImage.BeginInit();
bitmapImage.StreamSource = memory;
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
bitmapImage.EndInit();
return bitmapImage;
}
}
So, then it will be like this:
hangmanPicture.Source = ConvertToBitmapImageFromBitmap(hangmanImage[wrongGuesses]);