Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • World
  • Users
  • Groups
Skins
  • Light
  • Cerulean
  • Cosmo
  • Flatly
  • Journal
  • Litera
  • Lumen
  • Lux
  • Materia
  • Minty
  • Morph
  • Pulse
  • Sandstone
  • Simplex
  • Sketchy
  • Spacelab
  • United
  • Yeti
  • Zephyr
  • Dark
  • Cyborg
  • Darkly
  • Quartz
  • Slate
  • Solar
  • Superhero
  • Vapor

  • Default (No Skin)
  • No Skin
Collapse
Code Project
  1. Home
  2. General Programming
  3. WPF
  4. Converting a byte[] to a ImageSource means huge memory leak

Converting a byte[] to a ImageSource means huge memory leak

Scheduled Pinned Locked Moved WPF
csharpwpfvisual-studiodata-structuresperformance
16 Posts 4 Posters 30 Views 1 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • S Offline
    S Offline
    Starwer
    wrote on last edited by
    #1

    Hi Geeks ! I bang my head on what looked to be a ridiculously trivial problem... In a sentence: I need to display a image which is in a form of byte[] (array of byte) in a WPF ItemsControl. 'Sounds simple enough right ? I could find tens of solutions on the web, all similarly making use of a MemoryStream assigned to an BitmapImage. See snippet bellow in C# 10:

    	public static ImageSource? BitmapFromRaw(byte\[\]? imageData)
    	{
    		if (imageData == null) return null;
    		var image = new BitmapImage();
            var mem = new MemoryStream(imageData);
    		//mem.Position = 0;
    		image.BeginInit();
    		//image.CreateOptions = BitmapCreateOptions.PreservePixelFormat;
    		//image.CacheOption = BitmapCacheOption.None;
    		//image.UriSource = null;
    		image.StreamSource = mem;
    		image.EndInit();
            //mem.Close();
            //mem.Dispose();
            image.Freeze();
    
            return image;
    	}
    

    At first it looks like it works fine. The images converted this way could be displayed in an Image WPF control. BUT: this leads to a big memory leak. Looking at the memory usage in Visual Studio it shows that neither the BitmapImage nor the MemoryStream get freed from memory. From the lines commented out, you could see I've already tried to tweak this with different options... no success. Here is the smallest code I could make to reproduce the problem: MainWindow.xaml

    L Richard DeemingR S 3 Replies Last reply
    0
    • S Starwer

      Hi Geeks ! I bang my head on what looked to be a ridiculously trivial problem... In a sentence: I need to display a image which is in a form of byte[] (array of byte) in a WPF ItemsControl. 'Sounds simple enough right ? I could find tens of solutions on the web, all similarly making use of a MemoryStream assigned to an BitmapImage. See snippet bellow in C# 10:

      	public static ImageSource? BitmapFromRaw(byte\[\]? imageData)
      	{
      		if (imageData == null) return null;
      		var image = new BitmapImage();
              var mem = new MemoryStream(imageData);
      		//mem.Position = 0;
      		image.BeginInit();
      		//image.CreateOptions = BitmapCreateOptions.PreservePixelFormat;
      		//image.CacheOption = BitmapCacheOption.None;
      		//image.UriSource = null;
      		image.StreamSource = mem;
      		image.EndInit();
              //mem.Close();
              //mem.Dispose();
              image.Freeze();
      
              return image;
      	}
      

      At first it looks like it works fine. The images converted this way could be displayed in an Image WPF control. BUT: this leads to a big memory leak. Looking at the memory usage in Visual Studio it shows that neither the BitmapImage nor the MemoryStream get freed from memory. From the lines commented out, you could see I've already tried to tweak this with different options... no success. Here is the smallest code I could make to reproduce the problem: MainWindow.xaml

      L Offline
      L Offline
      Lost User
      wrote on last edited by
      #2

      You're not doing any "disposing"; that's why you have a memory leak. You need to keep a reference to the memory stream you create, then in the next go around, you check if that reference is not null; if it isn't, you dispose it before creating another stream and running with that. Your other dispose attempts were (probably) premature.

      "Before entering on an understanding, I have meditated for a long time, and have foreseen what might happen. It is not genius which reveals to me suddenly, secretly, what I have to say or to do in a circumstance unexpected by other people; it is reflection, it is meditation." - Napoleon I

      S 1 Reply Last reply
      0
      • L Lost User

        You're not doing any "disposing"; that's why you have a memory leak. You need to keep a reference to the memory stream you create, then in the next go around, you check if that reference is not null; if it isn't, you dispose it before creating another stream and running with that. Your other dispose attempts were (probably) premature.

        "Before entering on an understanding, I have meditated for a long time, and have foreseen what might happen. It is not genius which reveals to me suddenly, secretly, what I have to say or to do in a circumstance unexpected by other people; it is reflection, it is meditation." - Napoleon I

        S Offline
        S Offline
        Starwer
        wrote on last edited by
        #3

        Thanks for your reply Gerry! In view of your suggestion, I've added the following lines just before the ToyList.Clear() call:

        foreach (var item in ToyList)
        {
        if (item.Data is BitmapImage img)
        {
        img.StreamSource.Dispose();
        }
        }

        Unfortunatly, the problem remains: there is no freeing up of those MemoryStream...

        L 1 Reply Last reply
        0
        • S Starwer

          Thanks for your reply Gerry! In view of your suggestion, I've added the following lines just before the ToyList.Clear() call:

          foreach (var item in ToyList)
          {
          if (item.Data is BitmapImage img)
          {
          img.StreamSource.Dispose();
          }
          }

          Unfortunatly, the problem remains: there is no freeing up of those MemoryStream...

          L Offline
          L Offline
          Lost User
          wrote on last edited by
          #4

          Well, your byte[] is a managed resource; so you might try .Dispose( true ); otherwise, I would use the debugger to follow the stream after the Dispose.

            // Summary:
            //     Releases the unmanaged resources used by the System.IO.MemoryStream class and
            //     optionally releases the managed resources.
            //
            // Parameters:
            //   disposing:
            //     true to release both managed and unmanaged resources; false to release only unmanaged
            //     resources.
            protected override void Dispose( bool disposing );
          

          "Before entering on an understanding, I have meditated for a long time, and have foreseen what might happen. It is not genius which reveals to me suddenly, secretly, what I have to say or to do in a circumstance unexpected by other people; it is reflection, it is meditation." - Napoleon I

          S 1 Reply Last reply
          0
          • L Lost User

            Well, your byte[] is a managed resource; so you might try .Dispose( true ); otherwise, I would use the debugger to follow the stream after the Dispose.

              // Summary:
              //     Releases the unmanaged resources used by the System.IO.MemoryStream class and
              //     optionally releases the managed resources.
              //
              // Parameters:
              //   disposing:
              //     true to release both managed and unmanaged resources; false to release only unmanaged
              //     resources.
              protected override void Dispose( bool disposing );
            

            "Before entering on an understanding, I have meditated for a long time, and have foreseen what might happen. It is not genius which reveals to me suddenly, secretly, what I have to say or to do in a circumstance unexpected by other people; it is reflection, it is meditation." - Napoleon I

            S Offline
            S Offline
            Starwer
            wrote on last edited by
            #5

            As the Dispose(bool) is a protected method, I couldn't use it directly in this context. Moreover, the MemoryStream documentation indicates that calling Dispose() doesn't do anything and is not necessary... Looks like it's going the wrong way... Another test I did is using the Image without ItemsCollection, directly:

            This show some errors on the output console, but the Image gets display and surprisingly the BitmapImage and MemoryStream objects get freed/disposed properly. So it really looks like the problem is dependent to the use of ItemsCollection...

            1 Reply Last reply
            0
            • S Starwer

              Hi Geeks ! I bang my head on what looked to be a ridiculously trivial problem... In a sentence: I need to display a image which is in a form of byte[] (array of byte) in a WPF ItemsControl. 'Sounds simple enough right ? I could find tens of solutions on the web, all similarly making use of a MemoryStream assigned to an BitmapImage. See snippet bellow in C# 10:

              	public static ImageSource? BitmapFromRaw(byte\[\]? imageData)
              	{
              		if (imageData == null) return null;
              		var image = new BitmapImage();
                      var mem = new MemoryStream(imageData);
              		//mem.Position = 0;
              		image.BeginInit();
              		//image.CreateOptions = BitmapCreateOptions.PreservePixelFormat;
              		//image.CacheOption = BitmapCacheOption.None;
              		//image.UriSource = null;
              		image.StreamSource = mem;
              		image.EndInit();
                      //mem.Close();
                      //mem.Dispose();
                      image.Freeze();
              
                      return image;
              	}
              

              At first it looks like it works fine. The images converted this way could be displayed in an Image WPF control. BUT: this leads to a big memory leak. Looking at the memory usage in Visual Studio it shows that neither the BitmapImage nor the MemoryStream get freed from memory. From the lines commented out, you could see I've already tried to tweak this with different options... no success. Here is the smallest code I could make to reproduce the problem: MainWindow.xaml

              Richard DeemingR Offline
              Richard DeemingR Offline
              Richard Deeming
              wrote on last edited by
              #6

              Remove the finalizer from the ToyItem class. That class doesn't hold any unmanaged resources, so the only effect of adding a finalizer is to prolong the lifetime of the instance until the GC's finalizer thread runs. If you want to have the stream disposed of when the item is removed, have ToyItem implement IDisposable:

              internal sealed class ToyItem : INotifyPropertyChanged, IDisposable
              {
              ...

              public void Dispose()
              {
                  if (Data is BitmapImage image)
                  {
                      image.StreamSource.Dispose();
                  }
                  
                  Data = null;
              }
              

              }

              Unfortunately, the ObservableCollection<T> class doesn't dispose of items when they are removed. You can create a custom collection class to do that:

              public class DisposableObservableCollection<T>
              : ObservableCollection<T>, IDisposable
              where T : IDisposable
              {
              public DisposableObservableCollection(List<T> list) : base(list)
              {
              }

              public DisposableObservableCollection(IEnumerable<T> collection) : base(collection)
              {
              }
              
              public DisposableObservableCollection()
              {
              }
              
              protected override void SetItem(int index, T item)
              {
                  T oldItem = this\[index\];
                  base.SetItem(index, item);
                  oldItem?.Dispose();
              }
              
              protected override void RemoveItem(int index)
              {
                  T item = this\[index\];
                  base.RemoveItem(index);
                  item?.Dispose();
              }
              
              protected override void ClearItems()
              {
                  List itemsToDispose = Items.Where(i => i != null).ToList();
                  base.ClearItems();
                  foreach (T item in itemsToDispose)
                  {
                      item.Dispose();
                  }
              }
              

              }

              Then you can use that collection in your viewmodel:

              ObservableCollection<ToyItem> ToyList = new DisposableObservableCollection<ToyItem>();


              "These people looked deep within my soul and assigned me a number based on the order in which I joined." - Homer

              "These people looked deep within my soul and assigned me a number based on the order in which I joined" - Homer

              S 1 Reply Last reply
              0
              • Richard DeemingR Richard Deeming

                Remove the finalizer from the ToyItem class. That class doesn't hold any unmanaged resources, so the only effect of adding a finalizer is to prolong the lifetime of the instance until the GC's finalizer thread runs. If you want to have the stream disposed of when the item is removed, have ToyItem implement IDisposable:

                internal sealed class ToyItem : INotifyPropertyChanged, IDisposable
                {
                ...

                public void Dispose()
                {
                    if (Data is BitmapImage image)
                    {
                        image.StreamSource.Dispose();
                    }
                    
                    Data = null;
                }
                

                }

                Unfortunately, the ObservableCollection<T> class doesn't dispose of items when they are removed. You can create a custom collection class to do that:

                public class DisposableObservableCollection<T>
                : ObservableCollection<T>, IDisposable
                where T : IDisposable
                {
                public DisposableObservableCollection(List<T> list) : base(list)
                {
                }

                public DisposableObservableCollection(IEnumerable<T> collection) : base(collection)
                {
                }
                
                public DisposableObservableCollection()
                {
                }
                
                protected override void SetItem(int index, T item)
                {
                    T oldItem = this\[index\];
                    base.SetItem(index, item);
                    oldItem?.Dispose();
                }
                
                protected override void RemoveItem(int index)
                {
                    T item = this\[index\];
                    base.RemoveItem(index);
                    item?.Dispose();
                }
                
                protected override void ClearItems()
                {
                    List itemsToDispose = Items.Where(i => i != null).ToList();
                    base.ClearItems();
                    foreach (T item in itemsToDispose)
                    {
                        item.Dispose();
                    }
                }
                

                }

                Then you can use that collection in your viewmodel:

                ObservableCollection<ToyItem> ToyList = new DisposableObservableCollection<ToyItem>();


                "These people looked deep within my soul and assigned me a number based on the order in which I joined." - Homer

                S Offline
                S Offline
                Starwer
                wrote on last edited by
                #7

                Hi Richard, Thanks a lot for this very complete answer. I've removed the finalizer and implemented the IDisposable as you suggested. I like the DisposableObservableCollection proposal. It looks very neat this way. Unfortunatly this doesn't solve the problem. The leak is just as bad as before, with Image/BitmapImage and MemoryStream never being freed. It has probabably something to do with the fact that the MemoryStream.Dispose() doesn't do anything like explain on this page. In the end the code looked like this:

                using System;
                using System.Collections.Generic;
                using System.Collections.ObjectModel;
                using System.ComponentModel;
                using System.IO;
                using System.Linq;
                using System.Runtime.CompilerServices;
                using System.Windows;
                using System.Windows.Media;
                using System.Windows.Media.Imaging;

                namespace Toy
                {

                internal class ToyItem : INotifyPropertyChanged, IDisposable
                {
                    #region Boilerplate INotifyPropertyChanged
                
                    protected void OnPropertyChanged(PropertyChangedEventArgs e)
                    {
                        PropertyChanged?.Invoke(this, e);
                    }
                
                    public void OnPropertyChanged(\[CallerMemberName\] string propertyName = "")
                    {
                        OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
                    }
                    public event PropertyChangedEventHandler? PropertyChanged;
                
                    #endregion
                
                    public void Dispose()
                    {
                        if (Data is BitmapImage image)
                        {
                            image.StreamSource.Dispose();
                        }
                
                        Data = null;
                    }
                
                    /// /// Build an image from Picture bytes
                	/// 
                	/// Picture as array of bytes
                	/// Pictures as BitmapImage
                	public static ImageSource? BitmapFromRaw(byte\[\]? imageData)
                	{
                		if (imageData == null) return null;
                		var image = new BitmapImage();
                        var mem = new MemoryStream(imageData, false);
                		//mem.Position = 0;
                		image.BeginInit();
                		//image.CreateOptions = BitmapCreateOptions.PreservePixelFormat;
                		//image.CacheOption = BitmapCacheOption.None;
                		//image.UriSource = null;
                		image.StreamSource = mem;
                		image.EndInit();
                        //mem.Close();
                        //mem.Dispose();
                        image.Freeze();
                
                        return image;
                	}
                
                	public ImageSource? Data
                    {
                        get { return \_Data; }
                        set
                        {
                
                L L 2 Replies Last reply
                0
                • S Starwer

                  Hi Richard, Thanks a lot for this very complete answer. I've removed the finalizer and implemented the IDisposable as you suggested. I like the DisposableObservableCollection proposal. It looks very neat this way. Unfortunatly this doesn't solve the problem. The leak is just as bad as before, with Image/BitmapImage and MemoryStream never being freed. It has probabably something to do with the fact that the MemoryStream.Dispose() doesn't do anything like explain on this page. In the end the code looked like this:

                  using System;
                  using System.Collections.Generic;
                  using System.Collections.ObjectModel;
                  using System.ComponentModel;
                  using System.IO;
                  using System.Linq;
                  using System.Runtime.CompilerServices;
                  using System.Windows;
                  using System.Windows.Media;
                  using System.Windows.Media.Imaging;

                  namespace Toy
                  {

                  internal class ToyItem : INotifyPropertyChanged, IDisposable
                  {
                      #region Boilerplate INotifyPropertyChanged
                  
                      protected void OnPropertyChanged(PropertyChangedEventArgs e)
                      {
                          PropertyChanged?.Invoke(this, e);
                      }
                  
                      public void OnPropertyChanged(\[CallerMemberName\] string propertyName = "")
                      {
                          OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
                      }
                      public event PropertyChangedEventHandler? PropertyChanged;
                  
                      #endregion
                  
                      public void Dispose()
                      {
                          if (Data is BitmapImage image)
                          {
                              image.StreamSource.Dispose();
                          }
                  
                          Data = null;
                      }
                  
                      /// /// Build an image from Picture bytes
                  	/// 
                  	/// Picture as array of bytes
                  	/// Pictures as BitmapImage
                  	public static ImageSource? BitmapFromRaw(byte\[\]? imageData)
                  	{
                  		if (imageData == null) return null;
                  		var image = new BitmapImage();
                          var mem = new MemoryStream(imageData, false);
                  		//mem.Position = 0;
                  		image.BeginInit();
                  		//image.CreateOptions = BitmapCreateOptions.PreservePixelFormat;
                  		//image.CacheOption = BitmapCacheOption.None;
                  		//image.UriSource = null;
                  		image.StreamSource = mem;
                  		image.EndInit();
                          //mem.Close();
                          //mem.Dispose();
                          image.Freeze();
                  
                          return image;
                  	}
                  
                  	public ImageSource? Data
                      {
                          get { return \_Data; }
                          set
                          {
                  
                  L Offline
                  L Offline
                  Lost User
                  wrote on last edited by
                  #8

                  I do a lot of image manipulation in UWP; but create an image from my "pixel buffer" instead of holding unto streams when I display it. I think you said you didn't want to use an image / jpg.

                  "Before entering on an understanding, I have meditated for a long time, and have foreseen what might happen. It is not genius which reveals to me suddenly, secretly, what I have to say or to do in a circumstance unexpected by other people; it is reflection, it is meditation." - Napoleon I

                  S 1 Reply Last reply
                  0
                  • L Lost User

                    I do a lot of image manipulation in UWP; but create an image from my "pixel buffer" instead of holding unto streams when I display it. I think you said you didn't want to use an image / jpg.

                    "Before entering on an understanding, I have meditated for a long time, and have foreseen what might happen. It is not genius which reveals to me suddenly, secretly, what I have to say or to do in a circumstance unexpected by other people; it is reflection, it is meditation." - Napoleon I

                    S Offline
                    S Offline
                    Starwer
                    wrote on last edited by
                    #9

                    Indeed, my application is a desktop application and a pixel buffer is not really what fits in here... I do get images from TagLib# or ZeroFormatter as a byte[] (or any other chunk of raw data type you may convert it to).

                    1 Reply Last reply
                    0
                    • S Starwer

                      Hi Geeks ! I bang my head on what looked to be a ridiculously trivial problem... In a sentence: I need to display a image which is in a form of byte[] (array of byte) in a WPF ItemsControl. 'Sounds simple enough right ? I could find tens of solutions on the web, all similarly making use of a MemoryStream assigned to an BitmapImage. See snippet bellow in C# 10:

                      	public static ImageSource? BitmapFromRaw(byte\[\]? imageData)
                      	{
                      		if (imageData == null) return null;
                      		var image = new BitmapImage();
                              var mem = new MemoryStream(imageData);
                      		//mem.Position = 0;
                      		image.BeginInit();
                      		//image.CreateOptions = BitmapCreateOptions.PreservePixelFormat;
                      		//image.CacheOption = BitmapCacheOption.None;
                      		//image.UriSource = null;
                      		image.StreamSource = mem;
                      		image.EndInit();
                              //mem.Close();
                              //mem.Dispose();
                              image.Freeze();
                      
                              return image;
                      	}
                      

                      At first it looks like it works fine. The images converted this way could be displayed in an Image WPF control. BUT: this leads to a big memory leak. Looking at the memory usage in Visual Studio it shows that neither the BitmapImage nor the MemoryStream get freed from memory. From the lines commented out, you could see I've already tried to tweak this with different options... no success. Here is the smallest code I could make to reproduce the problem: MainWindow.xaml

                      S Offline
                      S Offline
                      Starwer
                      wrote on last edited by
                      #10

                      I have eventually found a "solution" to this problem. Be prepared: that empirical solution makes no real sense, but this works... :omg: Solution was to go through the following convertions: - byte[] to Bitmap using GetConverter; - Bitmap redrawn to a new Bitmap using GDI+ Graphics - Bitmap to byte[] again using ImageConverter - byte[] to ImageSource using the previously mentioned BitmapFromRaw Method. Any attempt to simplify this code lead to the memory leak to come back. No clue how this could be the way to get the GC do its job. Like in relativity, the shortest path is not always the straight one... Here is the the extra code that works for me. It makes my eyes bleed and I'm not proud of it. So again, if anyone could get something cleaner working, please propose.

                          public static ImageSource? BitmapFromRawNoLeak(byte\[\]? imageData)
                          {
                              if (imageData == null) return null;
                              ImageSource? image = null;
                      
                              // byte\[\] to Bitmap
                              TypeConverter tc = TypeDescriptor.GetConverter(typeof(Bitmap));
                              using (Bitmap? bmp = (Bitmap?)tc.ConvertFrom(imageData))
                              {
                                  if (bmp == null) return null;
                      
                                  // Force DPI to normal (96 DPI = no rescale) to avoid ugly rescaling in WPF when image comes from format with DPI
                                  bmp.SetResolution(96, 96);
                      
                                  using (Bitmap source = new Bitmap(bmp.Width, bmp.Height))
                                  {
                                      if (source == null) return null;
                                      using (Graphics g = Graphics.FromImage(source))
                                      {
                                          g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
                                          g.DrawImage(bmp, 0, 0, bmp.Width, bmp.Height);
                      
                                      }
                      
                                      ImageConverter converter = new ImageConverter();
                                      var vect = (byte\[\])converter.ConvertTo(source, typeof(byte\[\]));
                                      image = BitmapFromRaw(vect);
                                      image?.Freeze();
                                  }
                              }
                      
                              return image;
                          }
                      
                      L Richard DeemingR 2 Replies Last reply
                      0
                      • S Starwer

                        I have eventually found a "solution" to this problem. Be prepared: that empirical solution makes no real sense, but this works... :omg: Solution was to go through the following convertions: - byte[] to Bitmap using GetConverter; - Bitmap redrawn to a new Bitmap using GDI+ Graphics - Bitmap to byte[] again using ImageConverter - byte[] to ImageSource using the previously mentioned BitmapFromRaw Method. Any attempt to simplify this code lead to the memory leak to come back. No clue how this could be the way to get the GC do its job. Like in relativity, the shortest path is not always the straight one... Here is the the extra code that works for me. It makes my eyes bleed and I'm not proud of it. So again, if anyone could get something cleaner working, please propose.

                            public static ImageSource? BitmapFromRawNoLeak(byte\[\]? imageData)
                            {
                                if (imageData == null) return null;
                                ImageSource? image = null;
                        
                                // byte\[\] to Bitmap
                                TypeConverter tc = TypeDescriptor.GetConverter(typeof(Bitmap));
                                using (Bitmap? bmp = (Bitmap?)tc.ConvertFrom(imageData))
                                {
                                    if (bmp == null) return null;
                        
                                    // Force DPI to normal (96 DPI = no rescale) to avoid ugly rescaling in WPF when image comes from format with DPI
                                    bmp.SetResolution(96, 96);
                        
                                    using (Bitmap source = new Bitmap(bmp.Width, bmp.Height))
                                    {
                                        if (source == null) return null;
                                        using (Graphics g = Graphics.FromImage(source))
                                        {
                                            g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
                                            g.DrawImage(bmp, 0, 0, bmp.Width, bmp.Height);
                        
                                        }
                        
                                        ImageConverter converter = new ImageConverter();
                                        var vect = (byte\[\])converter.ConvertTo(source, typeof(byte\[\]));
                                        image = BitmapFromRaw(vect);
                                        image?.Freeze();
                                    }
                                }
                        
                                return image;
                            }
                        
                        L Offline
                        L Offline
                        Lost User
                        wrote on last edited by
                        #11

                        I use WriteableBitmap (the UWP version) without any issues. Maybe it can help you (the other version). [WriteableBitmap Class (System.Windows.Media.Imaging) | Microsoft Docs](https://docs.microsoft.com/en-us/dotnet/api/system.windows.media.imaging.writeablebitmap?view=windowsdesktop-6.0)

                        "Before entering on an understanding, I have meditated for a long time, and have foreseen what might happen. It is not genius which reveals to me suddenly, secretly, what I have to say or to do in a circumstance unexpected by other people; it is reflection, it is meditation." - Napoleon I

                        S 1 Reply Last reply
                        0
                        • L Lost User

                          I use WriteableBitmap (the UWP version) without any issues. Maybe it can help you (the other version). [WriteableBitmap Class (System.Windows.Media.Imaging) | Microsoft Docs](https://docs.microsoft.com/en-us/dotnet/api/system.windows.media.imaging.writeablebitmap?view=windowsdesktop-6.0)

                          "Before entering on an understanding, I have meditated for a long time, and have foreseen what might happen. It is not genius which reveals to me suddenly, secretly, what I have to say or to do in a circumstance unexpected by other people; it is reflection, it is meditation." - Napoleon I

                          S Offline
                          S Offline
                          Starwer
                          wrote on last edited by
                          #12

                          Thanks Gerry. I've tried to apply your tip, but to be honest, I couldn't find the way to make this WriteableImage from a byte[] array. This byte[] doesn't contain raster bitmap, but a jpeg-formatted data.

                          1 Reply Last reply
                          0
                          • S Starwer

                            I have eventually found a "solution" to this problem. Be prepared: that empirical solution makes no real sense, but this works... :omg: Solution was to go through the following convertions: - byte[] to Bitmap using GetConverter; - Bitmap redrawn to a new Bitmap using GDI+ Graphics - Bitmap to byte[] again using ImageConverter - byte[] to ImageSource using the previously mentioned BitmapFromRaw Method. Any attempt to simplify this code lead to the memory leak to come back. No clue how this could be the way to get the GC do its job. Like in relativity, the shortest path is not always the straight one... Here is the the extra code that works for me. It makes my eyes bleed and I'm not proud of it. So again, if anyone could get something cleaner working, please propose.

                                public static ImageSource? BitmapFromRawNoLeak(byte\[\]? imageData)
                                {
                                    if (imageData == null) return null;
                                    ImageSource? image = null;
                            
                                    // byte\[\] to Bitmap
                                    TypeConverter tc = TypeDescriptor.GetConverter(typeof(Bitmap));
                                    using (Bitmap? bmp = (Bitmap?)tc.ConvertFrom(imageData))
                                    {
                                        if (bmp == null) return null;
                            
                                        // Force DPI to normal (96 DPI = no rescale) to avoid ugly rescaling in WPF when image comes from format with DPI
                                        bmp.SetResolution(96, 96);
                            
                                        using (Bitmap source = new Bitmap(bmp.Width, bmp.Height))
                                        {
                                            if (source == null) return null;
                                            using (Graphics g = Graphics.FromImage(source))
                                            {
                                                g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
                                                g.DrawImage(bmp, 0, 0, bmp.Width, bmp.Height);
                            
                                            }
                            
                                            ImageConverter converter = new ImageConverter();
                                            var vect = (byte\[\])converter.ConvertTo(source, typeof(byte\[\]));
                                            image = BitmapFromRaw(vect);
                                            image?.Freeze();
                                        }
                                    }
                            
                                    return image;
                                }
                            
                            Richard DeemingR Offline
                            Richard DeemingR Offline
                            Richard Deeming
                            wrote on last edited by
                            #13

                            Starwer wrote:

                            using (Bitmap source = new Bitmap(bmp.Width, bmp.Height))
                            {
                            if (source == null) return null;

                            The result of new Bitmap(...) can never be null. If the constructor fails for some reason, you will get an exception, not a null object.


                            "These people looked deep within my soul and assigned me a number based on the order in which I joined." - Homer

                            "These people looked deep within my soul and assigned me a number based on the order in which I joined" - Homer

                            1 Reply Last reply
                            0
                            • S Starwer

                              Hi Richard, Thanks a lot for this very complete answer. I've removed the finalizer and implemented the IDisposable as you suggested. I like the DisposableObservableCollection proposal. It looks very neat this way. Unfortunatly this doesn't solve the problem. The leak is just as bad as before, with Image/BitmapImage and MemoryStream never being freed. It has probabably something to do with the fact that the MemoryStream.Dispose() doesn't do anything like explain on this page. In the end the code looked like this:

                              using System;
                              using System.Collections.Generic;
                              using System.Collections.ObjectModel;
                              using System.ComponentModel;
                              using System.IO;
                              using System.Linq;
                              using System.Runtime.CompilerServices;
                              using System.Windows;
                              using System.Windows.Media;
                              using System.Windows.Media.Imaging;

                              namespace Toy
                              {

                              internal class ToyItem : INotifyPropertyChanged, IDisposable
                              {
                                  #region Boilerplate INotifyPropertyChanged
                              
                                  protected void OnPropertyChanged(PropertyChangedEventArgs e)
                                  {
                                      PropertyChanged?.Invoke(this, e);
                                  }
                              
                                  public void OnPropertyChanged(\[CallerMemberName\] string propertyName = "")
                                  {
                                      OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
                                  }
                                  public event PropertyChangedEventHandler? PropertyChanged;
                              
                                  #endregion
                              
                                  public void Dispose()
                                  {
                                      if (Data is BitmapImage image)
                                      {
                                          image.StreamSource.Dispose();
                                      }
                              
                                      Data = null;
                                  }
                              
                                  /// /// Build an image from Picture bytes
                              	/// 
                              	/// Picture as array of bytes
                              	/// Pictures as BitmapImage
                              	public static ImageSource? BitmapFromRaw(byte\[\]? imageData)
                              	{
                              		if (imageData == null) return null;
                              		var image = new BitmapImage();
                                      var mem = new MemoryStream(imageData, false);
                              		//mem.Position = 0;
                              		image.BeginInit();
                              		//image.CreateOptions = BitmapCreateOptions.PreservePixelFormat;
                              		//image.CacheOption = BitmapCacheOption.None;
                              		//image.UriSource = null;
                              		image.StreamSource = mem;
                              		image.EndInit();
                                      //mem.Close();
                                      //mem.Dispose();
                                      image.Freeze();
                              
                                      return image;
                              	}
                              
                              	public ImageSource? Data
                                  {
                                      get { return \_Data; }
                                      set
                                      {
                              
                              L Offline
                              L Offline
                              lmoelleb
                              wrote on last edited by
                              #14

                              While Dispose doesn't release the buffer of the MemoryStream, it is possible to do so as long as it is not closed - and you created it so it is expandable (even though you want to do the opposite). stream.Length = 0; stream.Capacity = 0; This will clear the internal buffer and allow it to be garbage collected. You can of course also make your own Stream implementation that wraps a MemoryStream - then you can clear the reference to the MemoryStream on dispose. A bit tedious with the many methods in a stream, but not exactly difficult.

                              S 1 Reply Last reply
                              0
                              • L lmoelleb

                                While Dispose doesn't release the buffer of the MemoryStream, it is possible to do so as long as it is not closed - and you created it so it is expandable (even though you want to do the opposite). stream.Length = 0; stream.Capacity = 0; This will clear the internal buffer and allow it to be garbage collected. You can of course also make your own Stream implementation that wraps a MemoryStream - then you can clear the reference to the MemoryStream on dispose. A bit tedious with the many methods in a stream, but not exactly difficult.

                                S Offline
                                S Offline
                                Starwer
                                wrote on last edited by
                                #15

                                Thanks for your answer. I've tried the fllowing:

                                stream.Length = 0;
                                stream.Capacity = 0;

                                But I coudn't access those properties from a MemoryStream. So I've actually followed your advice and developped my own Stream implementation. This allowed to track what was called at least. This unfortunately didn't solve the problem. I could only see that the Close() function was called, that I could clear the Buffer that was stored there. However the finalized never gets called, giving a hint that the parent ImageSource or Image never get freed either. Moreover, using my own Stream prevents me from freezing the ImageSource (I don't know why). Here follows the implementation of the Stream I used:

                                public class ManagedMemoryStream : Stream
                                {
                                private int Id;
                                private static int Index = 0;
                                private static int Counter = 0;

                                    private byte\[\]? Buffer;
                                
                                    public override bool CanRead => true;
                                
                                    public override bool CanSeek => false;
                                
                                    public override bool CanWrite => false;
                                
                                    public override long Length => \_Length;
                                    private long \_Length;
                                
                                    public override long Position { get; set; }
                                
                                    public override void Close()
                                    {
                                        System.Diagnostics.Debug.WriteLine($"ManagedMemoryStream: Close {Id} / {Counter}");
                                        base.Close();
                                        Dispose(true);
                                        Buffer = null;
                                    }
                                
                                    public ManagedMemoryStream(byte\[\] buffer)
                                    {
                                        Id = Index++;
                                        System.Diagnostics.Debug.WriteLine($"ManagedMemoryStream: Construct {Id} / {++Counter}");
                                
                                        Buffer = buffer;
                                        \_Length = Buffer.Length;
                                
                                    }
                                
                                
                                    ~ManagedMemoryStream()
                                    {
                                        System.Diagnostics.Debug.WriteLine($"ManagedMemoryStream: Finalize {Id} / {--Counter}");
                                        Dispose(false);
                                    }
                                
                                    public override void Flush()
                                    {
                                        System.Diagnostics.Debug.WriteLine($"ManagedMemoryStream: Flush {Id} / {Counter}");
                                    }
                                
                                    public override int Read(byte\[\] buffer, int offset, int count)
                                    {
                                        //System.Diagnostics.Debug.WriteLine($"ManagedMemoryStream: Read {Id}, {offset}, {count} / {Counter}");
                                        if (count == 0 || Buffer is null) return 0;
                                        int remaining = (int)(Buffer.Length - Position);
                                        if (count > remaining) count = remaining;
                                
                                        Array.Copy(Buffer, Position, buffer, offset, count);
                                        Position += count;
                                
                                L 1 Reply Last reply
                                0
                                • S Starwer

                                  Thanks for your answer. I've tried the fllowing:

                                  stream.Length = 0;
                                  stream.Capacity = 0;

                                  But I coudn't access those properties from a MemoryStream. So I've actually followed your advice and developped my own Stream implementation. This allowed to track what was called at least. This unfortunately didn't solve the problem. I could only see that the Close() function was called, that I could clear the Buffer that was stored there. However the finalized never gets called, giving a hint that the parent ImageSource or Image never get freed either. Moreover, using my own Stream prevents me from freezing the ImageSource (I don't know why). Here follows the implementation of the Stream I used:

                                  public class ManagedMemoryStream : Stream
                                  {
                                  private int Id;
                                  private static int Index = 0;
                                  private static int Counter = 0;

                                      private byte\[\]? Buffer;
                                  
                                      public override bool CanRead => true;
                                  
                                      public override bool CanSeek => false;
                                  
                                      public override bool CanWrite => false;
                                  
                                      public override long Length => \_Length;
                                      private long \_Length;
                                  
                                      public override long Position { get; set; }
                                  
                                      public override void Close()
                                      {
                                          System.Diagnostics.Debug.WriteLine($"ManagedMemoryStream: Close {Id} / {Counter}");
                                          base.Close();
                                          Dispose(true);
                                          Buffer = null;
                                      }
                                  
                                      public ManagedMemoryStream(byte\[\] buffer)
                                      {
                                          Id = Index++;
                                          System.Diagnostics.Debug.WriteLine($"ManagedMemoryStream: Construct {Id} / {++Counter}");
                                  
                                          Buffer = buffer;
                                          \_Length = Buffer.Length;
                                  
                                      }
                                  
                                  
                                      ~ManagedMemoryStream()
                                      {
                                          System.Diagnostics.Debug.WriteLine($"ManagedMemoryStream: Finalize {Id} / {--Counter}");
                                          Dispose(false);
                                      }
                                  
                                      public override void Flush()
                                      {
                                          System.Diagnostics.Debug.WriteLine($"ManagedMemoryStream: Flush {Id} / {Counter}");
                                      }
                                  
                                      public override int Read(byte\[\] buffer, int offset, int count)
                                      {
                                          //System.Diagnostics.Debug.WriteLine($"ManagedMemoryStream: Read {Id}, {offset}, {count} / {Counter}");
                                          if (count == 0 || Buffer is null) return 0;
                                          int remaining = (int)(Buffer.Length - Position);
                                          if (count > remaining) count = remaining;
                                  
                                          Array.Copy(Buffer, Position, buffer, offset, count);
                                          Position += count;
                                  
                                  L Offline
                                  L Offline
                                  lmoelleb
                                  wrote on last edited by
                                  #16

                                  Strange, I see Length and Capacity exposed here: Reference Source[^] and they are on the interface, so short of throwing NotSupportedException or similar they should not be able to stop you. :) The finalizer not being called is expected - not as in "it should not be called", but as in "if it had been called the original code you had would not leak" I have no idea why your stream stops the image freezing. There might have some "magic" detecting if it is an instance of MemoryStream - for example kicking it into synchronous initialization, but that is not something I have looked into for many years.

                                  1 Reply Last reply
                                  0
                                  Reply
                                  • Reply as topic
                                  Log in to reply
                                  • Oldest to Newest
                                  • Newest to Oldest
                                  • Most Votes


                                  • Login

                                  • Don't have an account? Register

                                  • Login or register to search.
                                  • First post
                                    Last post
                                  0
                                  • Categories
                                  • Recent
                                  • Tags
                                  • Popular
                                  • World
                                  • Users
                                  • Groups