How to remove the previous image from memory?
Method for set image in images grid:
public async Task Show(KeyValuePair<Image, Storyboard> imageStoryboard)
{
if (isVisible)
{
return;
}
isVisible = true;
this.ImageComponent = imageStoryboard.Key;
// элемент показан сохраняем его страницу
Models.Page.Save(ImageData.Page, ImageData.NextPageLink);
BitmapImage _bitmapImage = await GetBitmapImage(ImageData.ThumbnailUrl);
// если высота неопределена вычисляем её
if (Height == 0)
{
PixelHeight = _bitmapImage.PixelHeight;
PixelWidth = _bitmapImage.PixelWidth;
CalcHeight();
}
// параметры изображения
ImageComponent.Width = App.Settings.side_size;
ImageComponent.Height = Height - MARGIN;
ImageComponent.Margin = new Thickness(0, MarginTop, 0, 0);
Grid.SetColumn(ImageComponent, Column);
ImageComponent.DataContext = ImageData;
ImageComponent.Source = _bitmapImage;
// анимация
imageStoryboard.Value.Stop();
imageStoryboard.Value.Begin();
}
I set new source ImageComponent.Source = _bitmapImage; but how remove previous image from memory?
When i load more and more images memory is lost and appliaction close. And when i load previous images they load is so so fasted.
This method for load image: https://pastebin.com/AWHpNCJU
You can't directly manipulate the memory, the application will periodically call the garbage cleanup GC.Collect to clean up unused resources. But this cleanup has a premise that the current object is no longer referenced. So please check if your image resources have not been released in time.
Here is the document about Garbage Collection and Performance.
You can remove image files from the image cache by setting all associated Image.Source values to null.
In order to optimize memory usage, try to use Uri as the source of the image instead of Stream, which is the content of the document:
To prevent images from being decoded more than once, assign image source property from Uniform Resource Identifier (URI) rather than using memory streams whenever you can. The XAML framework can associate the same Uniform Resource Identifier (URI) in multiple places with one decoded image, but it cannot do the same for multiple memory streams even if they contain the same data.
For this, some examples are provided in the documentation:
// use this
var bitmapImage = new BitmapImage();
myImage.Source = bitmapImage;
bitmapImage.UriSource = new URI("ms-appx:///static/posts/cool-image.png", UriKind.RelativeOrAbsolute);
// not this
var bitmapImage = new BitmapImage();
bitmapImage.UriSource = new URI("ms-appx:///static/posts/cool-image.png", UriKind.RelativeOrAbsolute);
myImage.Source = bitmapImage;
You also mentioned that memory will rise high when loading images. In this regard, you can use DecodePixelWidth/DecodePixelHeight to reduce the actual resolution of the image.
For example, if you get a 1080P image, but the size of the image control is only 100x100, if you do not modify the pixels of the image, it will cause unnecessary waste of resources. DecodePixelWidth/DecodePixelHeight can be used to limit the size of the image, which can effectively reduce the memory usage.
For more info on the Image class and performance, see Optimize image resources.
When you are trying the above methods and still can't effectively reduce your memory, please check the memory usage through Visual Studio's debugging tool, and take a snapshot to compare and see which objects have been released. This is a document about Use the Memory windows.
Best regards.
Related
I need to show the preview thumbnails of high resolution images in a control for user selection. I currently use ImageListView to load images.
This works fine for low to medium resolution images.But when it comes to showing thumbnails of very high resolution images there is a noticeable delay.Sample image can be downloaded from https://drive.google.com/open?id=1Qgu_aVXBiMlbHluJFU4fBvmFC45-E81C
The image size is around 5000x3000 pixels and size is around 12 MB.The issue can be replicated by using 1000 copies of this image.
The issue screen capture is uploaded here
https://giphy.com/gifs/ZEH3T3JTfN42OL3J1A
The images are loaded using a background worker
foreach (var f in filepaths)
{
imageListView1.Items.Add(f);
}
1. In order to solve this issue I tried resizing large resolution images and adding the resized image to ImageListView ... but for resizing there is a heavy time consumption and thumbnail generation is slow.
Bitmap x = UpdatedResizeImage2(new Bitmap(f), new Size(1000, 1000));
string q = Path.GetTempPath() + Path.GetFileName(f);
x.Save(Path.GetTempPath() + Path.GetFileName(f));
x.Dispose();
imageListView1.Items.Add(Path.GetTempPath() + Path.GetFileName(f));
2. I have also tried Image.CreateThumbnail Method but this is also quite slow.
Is there a better way to solve this issue?
I would suggest using image processing library such ImageMagick.
ImageMagick has optimized this feature and you have Magick.NET a nuget package for .NET.
It is simple and straight forward:
var file = new FileInfo(#"c:\temp\input.jpg");
using (MagickImage image = new MagickImage(file))
{
{
image.Thumbnail(new MagickGeometry(100, 100));
image.Write(#"C:\temp\thumbnail.jpg");
}
}
example I made:
Here is some documentation and references that might be useful:
https://imagemagick.org/Usage/thumbnails/#creation
http://www.imagemagick.org/Usage/thumbnails/
https://github.com/dlemstra/Magick.NET
https://www.smashingmagazine.com/2015/06/efficient-image-resizing-with-imagemagick/
https://devblogs.microsoft.com/dotnet/net-core-image-processing/
https://weblogs.asp.net/bleroy/resizing-images-from-the-server-using-wpf-wic-instead-of-gdi
Alternatives to System.Drawing for use with ASP.NET?
You could use WPF interop and use the DecodePixelWidth/Height properties. They use underlying Windows imaging layer technology ("Windows Imaging Component") to create an optimized thumbnail, saving lots of memory (and possibly CPU): How to: Use a BitmapImage (XAML)
You can also use WPF/WIC by code, with a code like this (adapted from this article The fastest way to resize images from ASP.NET. And it’s (more) supported-ish.. You just need to add a reference to PresentationCore and WindowsBase which shouldn't be an issue for a desktop app.
// needs System.Windows.Media & System.Windows.Media.Imaging (PresentationCore & WindowsBase)
public static void SaveThumbnail(string absoluteFilePath, int thumbnailSize)
{
if (absoluteFilePath == null)
throw new ArgumentNullException(absoluteFilePath);
var bitmap = BitmapDecoder.Create(new Uri(absoluteFilePath), BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.None).Frames[0];
int width;
int height;
if (bitmap.Width > bitmap.Height)
{
width = thumbnailSize;
height = (int)(bitmap.Height * thumbnailSize / bitmap.Width);
}
else
{
width = (int)(bitmap.Width * thumbnailSize / bitmap.Height);
height = thumbnailSize;
}
var resized = BitmapFrame.Create(new TransformedBitmap(bitmap, new ScaleTransform(width / bitmap.Width * 96 / bitmap.DpiX, height / bitmap.Height * 96 / bitmap.DpiY, 0, 0)));
var encoder = new PngBitmapEncoder();
encoder.Frames.Add(resized);
var thumbnailFilePath = Path.ChangeExtension(absoluteFilePath, thumbnailSize + Path.GetExtension(absoluteFilePath));
using (var stream = File.OpenWrite(thumbnailFilePath))
{
encoder.Save(stream);
}
}
Otherwise there are lots of tools out there like MagicScaler, FreeImage ImageSharp, ImageMagick, Imazen, etc. Most were written for ASP.NET/Web server scenarios (for which WPF is officially not supported but works, read the article) and are also cross-platform which you don't seem to need. I'm not sure they're generally faster or use less memory than builtin Windows technology, but you should test all this in your context.
PS: otherwise there's no magic bullet, bigger images take more time.
There's also NetVips, the C# binding for libvips.
It's quite a bit quicker than Magick.NET: between 3x and 10x faster, depending on the benchmark.
Thumbnailing is straightforward:
using NetVips;
var image = Image.Thumbnail("some-image.jpg", 128);
image.WriteToFile("x.jpg");
There's an introduction in the documentation.
Most of answers approach is to resize bitmap and then save it. Its a bit offcourse slow, specially if you say very high resolution.
Why not use existing thumbnail created by windows explorer ? This is fastest way of all (specially if you use smaller thumbnails).
//https://stackoverflow.com/a/1751610
using Microsoft.WindowsAPICodePack.Shell;
var shellFile = ShellFile.FromFilePath(pathToYourFile); Bitmap
Image image = shellFile.Thumbnail.LargeBitmap;
Nuget : https://www.nuget.org/packages/WindowsAPICodePack-Shell (around 600KB)
Note: Its same as others, if thumbnail arent cached already.
I use OpenFileDialog to get the path of an image and then set it to my image source property imgSelected.Source = new BitmapImage(new Uri(filenameSteg, UriKind.Absolute)); in a WPF application.
The thing is, I need to open that file again later but I can't open it since the file is being used by another process ( System.IO.IOException -> The process cannot access the file pathToFile because it is being used by another process.).
The code that needs to access it later it as follow :
bitmapSource = JpegBitmapDecoder.Create(File.Open(filenameSteg, FileMode.Open),
BitmapCreateOptions.None, BitmapCacheOption.OnLoad).Frames[0];
This bitmapSource is used to give that image to a WriteableBitmap and from there I go through the pixels.
Is there any way to dispose of a File opened with an OpenFileDialog ?
I tried to cast to to IDisposable, to use a using block and so on but this thing is persistent.
EDIT :
1 - I tried this (#ctacke answer) :
using (var stream = File.Open(filenameSteg, FileMode.Open)){
bitmapSource = JpegBitmapDecoder.Create(stream, BitmapCreateOptions.None,
BitmapCacheOption.OnLoad).Frames[0];}
But it still gives me the error about the process being already in used by another process because even though it will be disposed after, I still am trying to open the same file (filenameSteg) than I opened in the OpenFileDialog. (Or at least, that's how I see it.)
2 - I then tried this (based on #ctacke recommended link:
using (FileStream fileStream = new FileStream(filenameSteg+1, FileMode.Create)){
MemoryStream memoryStream = new MemoryStream();
BitmapImage bi = new BitmapImage();
byte[] fileBytes = File.ReadAllBytes(filenameSteg);
memoryStream.Write(fileBytes, 0, fileBytes.Length);
memoryStream.Position = 0;
bi.BeginInit();
bi.StreamSource = memoryStream;
bi.EndInit();
bitmapSource = bi;}
Note : Notice that I here ask for filenameSteg +1. That is because I wanted to test the rest of my method so I create a copy the file and simply added a 1 to its name. That being said, when using filenameSteg for real, it gave me the same error about being already in used which I again suspect is that I still am asking to open the same image that was previously opened in the OpenFileDialog.
3 - I thought of another approach which does not require me to dispose the opened image :
When I open the image for the first time in the OpenFileDialog, I store the byte array of the image in a variable so I could create the WriteableBitmap using the BitmapFactory and the byte array.
// This is in the OpenFileDialog. It is where I stock the image "pixels" in a byte array.
bytesArrayImage = File.ReadAllBytes(filenameSteg);
//And then later when I needed the WriteableBitmap, I used the byte array and the BitmapFactory
//imgSelected being the Image containing the image I opened in the OpenFileDialog, I used it's
//width and height
wb = BitmapFactory.New(Convert.ToInt32(imgSelected.Width),
Convert.ToInt32(imgSelected.Height)).FromByteArray(bytesArrayImage);
The problem with this approach is that, some pictures works fine and I can use the byte array to create the WriteableBitmap and go through it's pixels but in other cases it gives me an AccessViolationException stating : Attempted to read or write protected memory. This is often an indication that other memory is corrupt.. In other words, trying to bypass the dispose problem got me into another problem.
You should release the original image, something like this:
if(imgSelected.Source != null)
{
imgSelected.Source.Dispose;
}
imgSelected.Source = new BitmapImage(new Uri(filenameSteg, UriKind.Absolute));
Next, File.Open returns a stream, which you need to explicitly release.
using(var stream = File.Open(filenameSteg, FileMode.Open))
{
var bitmapSource = JpegBitmapDecoder.Create(stream,
BitmapCreateOptions.None, BitmapCacheOption.OnLoad).Frames[0];
}
See also: Load a BitmapSource and save using the same name in WPF -> IOException
How can I convert a System.Drawing.Bitmap to GDK# Image so that I can set to the image widget.
I have tried this...
System.Drawing.Bitmap b = new Bitmap (1, 1);
Gdk.Image bmp = new Gdk.Image (b);
UPDATE:
Bitmap bmp=new Bitmap(50,50);
Graphics g=Graphics.FromImage(bmp);
System.Drawing.Font ff= new System.Drawing.Font (System.Drawing.FontFamily.GenericMonospace, 12.0F, FontStyle.Italic, GraphicsUnit.Pixel);
g.DrawString("hello world",ff,Brushes.Red,new PointF(0,0));
MemoryStream ms = new MemoryStream ();
bmp.Save (ms, ImageFormat.Png);
Gdk.Pixbuf pb= new Gdk.Pixbuf (ms);
image1.Pixbuf=pb;
Exception:
System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> GLib.GException: Unrecognized image file format
at Gdk.PixbufLoader.Close()
at Gdk.PixbufLoader.InitFromStream(Stream stream)
at Gdk.PixbufLoader..ctor(Stream stream)
at Gdk.Pixbuf..ctor(Stream stream)
One ugly, but working, way is to store the bitmap as a PNG in a MemoryStream.
To save the Bitmap, you can use the Save method:
b.Save(myMemoryStream, ImageFormat.Png);
That was easy enough. Loading the PNG data into the Gdk# Pixbuf is also rather easy; you can use the appropriate constructor:
Pixbuf pb = new Gdk.Pixbuf(myMemoryStream);
You may need to reset the memory stream so the reading position is at the start of the stream before creating the Pixbuf.
A word of caution: I do not consider this the best, or even a "good" solution. Transferring data between two object-oriented data structures by serializing and deserializing the data has a certain code smell to it. I genuinely hope someone else can come up with a better solution.
EDIT: As for the used libraries: This answer uses only plain GDI+ (System.Drawing.Bitmap) and Gdk# (Gdk.Pixbuf). Note that a Gtk.Image is a widget that displays a Gdk.Pixbuf. As such, Gtk.Image is the equivalent of Windows Forms' PictureBox, whereas Gdk.Pixbuf is roughly equivalent to Windows Forms' System.Drawing.Bitmap.
EDIT2: After testing your code, I have found that there are three additional preconditions to ensure before you can run your minimum example:
As suspected above, you must reset the stream position to the beginning of the after saving your Bitmap and before loading your Pixbuf: ms.Position = 0;
You must compile the application for x86 CPUs.
You must invoke Gtk.Application.Init(); before you do anything with Pixbuf.
You may draw in Gtk# like in Winforms. For this you must obtain System.Drawing.Graphics object and then you may draw lines, images and text on it. You may do it like this: 1. Create new Widget. 2. Subscribe on ExposeEvent. 3. On event handler write some code:
protected void OnExposeEvent(object o, ExposeEventArgs e)
{
Gdk.Window window = e.Event.Window;
using (System.Drawing.Graphics graphics =
Gtk.DotNet.Graphics.FromDrawable(window))
{
// draw your stuff here...
graphics.DrawLine(new System.Drawing.Pen(System.Drawing.Brushes.Black), 0, 0, 30, 40);
}
}
Also you need to add reference on gtk-dotnet.dll.
try this ....
Gdk.Pixbuf pixbufImage = mew Gdk.Pixbuf(#"images/test.png");
Gtk.Image gtkImage = new Gtk.Image(pixbufImage);
Gdk.Image gdkImage = gtkImage.ImageProp;
I simply cannot load Silverlight images synchronously. ImageOpened is all well and good but it doesn't really help me if if I have 20 textures to load BEFORE the app is allowed to execute! You cannot use threads as it causes multiple cross domain / cross thread exceptions. I have solved it but I am v curious as to how anyone else has tackled this.
My requirement is to load a jpeg / png / whatever into a pixel array, as i say, asynchronous options are a no go as I NEED the pixels before I start rendering.
Help!
you didn't say, from where you load a jpeg / png / whatever. If from resource, you can try to load first to BitmapImage, but not by UriSource property. Just use a method SetSource() - for me it loads the image immediately.
For example:
using System.Windows.Media.Imaging;
using System.Windows.Resources;
BitmapImage bmp = new BitmapImage();
Uri uri = new Uri("/SilverlightApp1;component/Resources/foto.jpg", UriKind.Relative);
StreamResourceInfo sri = Application.GetResourceStream(uri);
bmp.SetSource(sri.Stream);
Image image = new Image();
image.Source = bmp;
I am using this C# code to access an image file in order to read metadata from it.
BitmapSource img = BitmapFrame.Create(uri);
Unfortunately the image file specified by uri becomes locked until the program ends. How do I prevent the image from being locked?
maybe this could help ?
edit
BitmapSource img = BitmapFrame.Create(uri,BitmapCreateOptions.None,BitmapCacheOption.OnLoad);
BitmapCreateOptions.None = default option
BitmapCacheOption.OnLoad = Caches the entire image into memory at load time. All requests for image data are filled from the memory store.
from here
If you want to be able to delete/change the file immediately afterwards, read the whole file into memory, and then give it the MemoryStream instead. For example:
MemoryStream data = new MemoryStream(File.ReadAllBytes(file));
BitmapSource bitmap = BitmapFrame.Create(data);
You can also use generic stream:
Stream stream = File.OpenRead(filename);
Bitmap template = new Bitmap(stream); // or (Bitmap) Bitmap.FromStream(stream)
stream.Close();