I created a simple example to test the image control's memory handling.
On a dockpanel there are two buttons ("Load" and "Reset") and an image control.
In the code behind, the event handlers of the buttons look like this:
private void LoadButton_Click(object sender, RoutedEventArgs e)
{
var dlg = new OpenFileDialog
{
Title = "Open image file",
DefaultExt = ".tif",
Filter = "",
Multiselect = false,
InitialDirectory = "D:\\testimages"
};
dlg.ShowDialog();
string file = dlg.FileName;
if (!string.IsNullOrEmpty(file))
{
if (this.img.Source != null)
{
this.img.Source = null;
this.img.UpdateLayout();
GC.Collect();
}
var bi = new BitmapImage();
bi.BeginInit();
bi.CacheOption = BitmapCacheOption.OnLoad;
bi.UriSource = new Uri(file);
bi.EndInit();
bi.Freeze();
this.img.Source = bi;
}
else
{
this.img.Source = null;
this.img.UpdateLayout();
GC.Collect();
}
}
private void ResetButton_Click(object sender, RoutedEventArgs e)
{
this.img.Source = null;
this.img.UpdateLayout();
GC.Collect();
}
When I load the first image, the memory usage increases. Hitting the Reset-button, the memory is released correctly. So far this looks like correct behaviour.
But if I do not "reset", but load another image, the memory usage increases.
"Reset" does only release the memory of the latest image.
How can I make sure, that the memory of previously loaded images gets released when loading the next image?
The images I use are app. 4000 x 1000 px with a resolution of 300dpi.
Related
Here's my problem.
I'm loading a few BitmapImages in a BlockingCollection
public void blockingProducer(BitmapImage imgBSource)
{
if (!collection.IsAddingCompleted)
collection.Add(imgBSource);
}
the loading happens in a backgroungwork thread.
private void worker_DoWork(object sender, DoWorkEventArgs e)
{
String filepath; int imgCount = 0;
for (int i = 1; i < 10; i++)
{
imgCount++;
filepath = "Snap";
filepath += imgCount;
filepath += ".bmp";
this.Dispatcher.BeginInvoke(new Action(() =>
{
label1.Content = "Snap" + imgCount + " loaded.";
}), DispatcherPriority.Normal);
BitmapImage imgSource = new BitmapImage();
imgSource.BeginInit();
imgSource.UriSource = new Uri(filepath, UriKind.Relative);
imgSource.CacheOption = BitmapCacheOption.OnLoad;
imgSource.EndInit();
blockingProducer(imgSource);
}
}
debugging this part of the code everything looks okay, the problem comes now ...
after finishing loading the images I want to show them in UI one by one. I'm using a dispatcher to do so but I always get the message telling me that the called Thread can not access the object because it belongs to a different Thread.
public void display(BlockingCollection<BitmapImage> results)
{
foreach (BitmapImage item in collection.GetConsumingEnumerable())
{
this.Dispatcher.BeginInvoke(new Action(() =>
{
this.dstSource.Source = item;
Thread.Sleep(250);
}), DispatcherPriority.Background);
}
}
debug accuses that the error is here
this.dstSource.Source = item;
I'm trying everything but cant find out what’s wrong. Anyone has any idea?
You have to call Freeze after loading the images in order to make them accessible to other threads:
BitmapImage imgSource = new BitmapImage();
imgSource.BeginInit();
imgSource.UriSource = new Uri(filepath, UriKind.Relative);
imgSource.CacheOption = BitmapCacheOption.OnLoad;
imgSource.EndInit();
imgSource.Freeze(); // here
As far as I have understood the BitmapCacheOption.OnLoad flag, it is only effective when a BitmapImage is loaded from a stream. The Remarks section in BitmapCacheOption says:
Set the CacheOption to BitmapCacheOption.OnLoad if you wish to close a
stream used to create the BitmapImage. The default OnDemand cache
option retains access to the stream until the image is needed, and
cleanup is handled by the garbage collector.
A BitmapImage created from a Uri may be loaded asynchronously (see the IsDownloading property). Consequently, Freeze may not be callable on such a BitmapImage, as downloading may still be in progress after EndInit. I guess it nevertheless works in your case because you are loading BitmapImages from file Uris, which seems to be done immediately.
To avoid this potential problem you may just create the BitmapImage from a FileStream:
var imgSource = new BitmapImage();
using (var stream = new FileStream(filepath, FileMode.Open))
{
imgSource.BeginInit();
imgSource.StreamSource = stream;
imgSource.CacheOption = BitmapCacheOption.OnLoad;
imgSource.EndInit();
imgSource.Freeze();
}
For the further future readers, here is the code I used to fix my problem.
public void display(BlockingCollection<BitmapImage> collection)
{
if (collection.IsCompleted || collection.Count != 0)
{
BitmapImage item = collection.Take();
this.Dispatcher.BeginInvoke(new Action(() =>
{
this.dstSource.Source = item;
}), DispatcherPriority.Normal);
}
else
{
dispatcherTimer.Stop();
}
}
public void dispatcherTimer_Tick(object sender, EventArgs e)
{
display(collection);
}
public void configureDispatcherTimer()
{
dispatcherTimer.Tick += new EventHandler(dispatcherTimer_Tick);
TimeSpan interval = TimeSpan.FromMilliseconds(150);
dispatcherTimer.Interval = interval;
}
My application loads a lot of images in a BackgroundWorker to stay usable. My Image control is bound to a property named "ImageSource". If this is null it's loaded in the background and raised again.
public ImageSource ImageSource
{
get
{
if (imageSource != null)
{
return imageSource;
}
if (!backgroundImageLoadWorker.IsBusy)
{
backgroundImageLoadWorker.DoWork += new DoWorkEventHandler(bw_DoWork);
backgroundImageLoadWorker.RunWorkerCompleted +=
new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
backgroundImageLoadWorker.RunWorkerAsync();
}
return imageSource;
}
}
void bw_DoWork(object sender, DoWorkEventArgs e)
{
bitmap = new BitmapImage();
bitmap.BeginInit();
try
{
bitmap.CreateOptions = BitmapCreateOptions.DelayCreation;
bitmap.DecodePixelWidth = 300;
MemoryStream memoryStream = new MemoryStream();
byte[] fileContent = File.ReadAllBytes(imagePath);
memoryStream.Write(fileContent, 0, fileContent.Length);
memoryStream.Position = 0;
bitmap.StreamSource = memoryStream;
}
finally
{
bitmap.EndInit();
}
bitmap.Freeze();
e.Result = bitmap;
}
void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
BitmapSource bitmap = e.Result as BitmapSource;
if (bitmap != null)
{
Dispatcher.CurrentDispatcher.BeginInvoke(
(ThreadStart)delegate()
{
imageSource = bitmap;
RaisePropertyChanged("ImageSource");
}, DispatcherPriority.Normal);
}
}
This is all well so far but my users can change the images in question. They choose a new image in an OpenDialog, the old image file is overwritten with the new and ImageSource is raised again which loads the new image with the same filename again:
public string ImagePath
{
get { return imagePath; }
set
{
imagePath= value;
imageSource = null;
RaisePropertyChanged("ImageSource");
}
}
On some systems the overwriting of the old file results in an exception:
"a generic error occured in GDI+" and "The process cannot access the file..."
I tried a lot of things like loading with BitmapCreateOptions.IgnoreImageCache and BitmapCacheOption.OnLoad. This raised Exceptions when loading them:
Key cannot be null.
Parameter name: key
If I try this without the BackgroundWorker on the UI thread it works fine. Am I doing something wrong? Isn't it possible to load the images in the background while keeping the files unlocked?
Well, it seems all of the above works. I simplified the example for the question and somehow on the way lost the problem.
The only difference I could see in my code is that the loading of the image itself was delegated to a specific image loader class which somehow created the problem. When I removed this dependency the errors disappeared.
I can successfully load the following Bitmap like this and display it within an Image control on the view.
var bitmapImage = new BitmapImage
{
UriSource =
new Uri("../Images/Test.JPG", UriKind.Relative)
};
However as soon as I add this line to create a WriteableBitmap out of the bitmap,
var w = new WriteableBitmap(bitmapImage);
I get a Runtime error at the line above: "Object reference not set to an instance of an object."
It seems the BitmapImage creation is delayed, could that be? How should I fix this?
Update:
I am now trying this but the openImage seems never to be hit. (even without trying to make it synchronous, it still fails) What is wrong here?
var image = new BitmapImage();
image.ImageOpened += (sender, args) => resetEventBitmap.Set();
image.ImageFailed += (o, eventArgs) =>
{
resetEventBitmap.Set();
throw eventArgs.ErrorException;
};
image.CreateOptions = BitmapCreateOptions.IgnoreImageCache;
image.UriSource = uri;
resetEventBitmap.WaitOne();
Thanks,
Reference:
http://www.blog.ingenuitynow.net/Silverlight+Creating+A+WriteableBitmap+From+A+Uri+Source.aspx
Basically, bitmap image has a dependency property "CreateOptions" which, by default, is set to "DelayCreation". This causes the bitmap to be delayed for rendering until after it's needed. Hence, this causes our "object reference not set to an instance of an object" error. To fix this, we have to break the bitmap creation out of the writeablebitmap constructor, change this option, and then put it back in. In vb.net this looks like:
Dim tmpUri As New Uri(yourpath.ToString)
Dim bmp As New BitmapImage
bmp.CreateOptions = BitmapCreateOptions.None
bmp.UriSource = tmpUri
Dim wb As New WriteableBitmap(bmp)
BitmapImage _classField;
void LoadImageFunction()
{
_classField = new BitmapImage();
_classField.ImageOpened += new EventHandler<RoutedEventArgs>(bi_ImageOpened);
_classField.ImageFailed += new EventHandler<ExceptionRoutedEventArgs>(bi_ImageFailed);
//sorry.. totally forgot about order :)
_classField.UriSource = new Uri("../some/uri", UriKind.Relative);
}
void bi_ImageFailed(object sender, ExceptionRoutedEventArgs e)
{
//something has happend
throw e.ErrorException;
}
void bi_ImageOpened(object sender, RoutedEventArgs e)
{
//image is loaded.. now we can work with it..
var w = new WriteableBitmap(_classField);
}
img1 = new BitmapImage(new Uri("/PrjName;component/Images/image01.jpg", UriKind.RelativeOrAbsolute));
img2 = new BitmapImage(new Uri("/PrjName;component/Images/image02.jpg", UriKind.RelativeOrAbsolute));
img1.CreateOptions = BitmapCreateOptions.None;
img2.CreateOptions = BitmapCreateOptions.None;
img1.ImageOpened += new EventHandler<RoutedEventArgs>(img1_ImageOpened);
img2.ImageOpened += new EventHandler<RoutedEventArgs>(img2_ImageOpened);
void img2_ImageOpened(object sender, RoutedEventArgs e)
{
load2 = true;
}
void img1_ImageOpened(object sender, RoutedEventArgs e)
{
load1 = true;
}
private void LayoutRoot_Loaded(object sender, RoutedEventArgs e)
{
while (!load1 && !load2)
{ }
WriteableBitmap x = new WriteableBitmap(img1);
WriteableBitmap y = new WriteableBitmap(img2);
}
This should work. it did for me..! It makes it a lil' complicated, but that's how it works!
I was added picture as a children to layer called "canvas". By the following code:
if (addChild)
{
Image i = new Image();
BitmapImage src = new BitmapImage();
src.BeginInit();
src.UriSource = new Uri(path, UriKind.Absolute);
src.EndInit();
i.Source = src;
i.Width = 200;
i.IsManipulationEnabled = true;
double rotAngle = Rand.GetRandomDouble(-3.14/4, 3.14/4);
i.RenderTransform = new MatrixTransform(Math.Cos(rotAngle), -Math.Sin(rotAngle),
Math.Sin(rotAngle), Math.Cos(rotAngle), Rand.GetRandomDouble(0, this.Width - i.Width), Rand.GetRandomDouble(0, this.Height - i.Width));
canvasImages.Add(i);
canvas.Children.Add(i);
Canvas.SetZIndex(i, canvas.Children.Count-1);
addedFiles.Add(path);
maxZ++;
}
Here is the problem. I'm trying to make an event called "canvas_TouchDown" which can detect the specify picture when I touched it so that it will get the center of that image object.
List<Image> canvasImages = new List<Image>();
private void canvas_TouchDown(object sender, TouchEventArgs e)
{
foreach (Image canvasImage in canvasImages)
{
if (canvasImage.AreAnyTouchesCaptured == true)
{
System.Diagnostics.Debug.WriteLine("I found image that you touch");
}
}
}
However, there is nothing happened. I also try to use PersistId property but it doesn't work. Have any suggestion?
Regard,
C.Porawat
If you are adding the image to the canvas, touching it and expecting the canvas to receive the touch you will be disappointed. You should either listen to "touch down" on the image or "preview touch down" on the canvas.
I'm trying to delete a Image file in WPF, but WPF locks the file.
<Image Source="C:\person.gif" x:Name="PersonImage">
<Image.ContextMenu>
<ContextMenu>
<MenuItem Header="Delete..." x:Name="DeletePersonImageMenuItem" Click="DeletePersonImageMenuItem_Click"/>
</ContextMenu>
</Image.ContextMenu>
</Image>
And the Click handler just looks like this:
private void DeletePersonImageMenuItem_Click(object sender, RoutedEventArgs e)
{
System.IO.File.Delete(#"C:\person.gif");
}
But, when I try to delete the file it is locked and cannot be removed.
Any tips on how to delete the file?
My application Intuipic deals with this by using a custom converter that frees the image resource. See the code here.
Using code behind, you can use the cache option BitmapCacheOption.OnLoad:
BitmapImage bi = new BitmapImage();
bi.BeginInit();
bi.CacheOption = BitmapCacheOption.OnLoad;
bi.CreateOptions = BitmapCreateOptions.IgnoreImageCache;
bi.UriSource = new Uri(PathToImage);
bi.EndInit();
PersonImage.Source = bi;
Because I struggled to find it, I will add that if you want to replace the deleted image dynamically, you need to add the argument BitmapCreateOptions.IgnoreImageCache.
First Remove it from the PersonImage control then delete the image. Hope that will help.
As you have assigned to the control in source, and remove it without unassigning the control source.
PersonImage.Source = null;
System.IO.File.Delete(#"C:\person.gif");
hope that will help.
The most easiest way to do this will be, creating a temporary copy of your image file and using it as a source.. and then at end of your app, deleting all temp files..
static List<string> tmpFiles = new List<string>();
static string GetTempCopy(string src)
{
string copy = Path.GetTempFileName();
File.Copy(src, copy);
tmpFiles.Add(copy);
return copy;
}
static void DeleteAllTempFiles()
{
foreach(string file in tmpFiles)
{
File.Delete(file);
}
}
Image caching in WPF also can be configured to do this, but for some reason my various attempts failed and we get unexpected behaviour like not being able to delete or refresh the image etc, so we did this way.
Do not attach the physical file to the object.
BitmapImage bmp;
static int filename = 0;
static string imgpath = "";
private void saveButton_Click(object sender, RoutedEventArgs e)
{
try
{
filename = filename + 1;
string locimagestored = Directory.GetParent(Assembly.GetExecutingAssembly().Location).ToString();
locimagestored = locimagestored + "\\StoredImage\\";
imgpath = locimagestored + filename + ".png";
webCameraControl.GetCurrentImage().Save(imgpath);
Bitmap bitmap = new Bitmap(#imgpath);
IntPtr hBitmap = bitmap.GetHbitmap();
ImageSource wpfBitmap = Imaging.CreateBitmapSourceFromHBitmap(hBitmap, IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions()); ;
if (filename == 1)
{
imgSavedOnline0.Source = wpfBitmap;
bitmap.Dispose();
}
else if (filename == 2)
{
imgSavedOnline1.Source = wpfBitmap;
bitmap.Dispose();
}
else if (filename == 3)
{
imgSavedOnline2.Source = wpfBitmap;
bitmap.Dispose();
}
else if (filename == 4)
{
imgSavedOnline3.Source = wpfBitmap;
bitmap.Dispose();
}
System.IO.DirectoryInfo di2 = new DirectoryInfo(locimagestored);
foreach (FileInfo file in di2.GetFiles())
{
file.Delete();
}
}
catch (Exception ex)
{
textBox1.Text = ex.Message;
}
}