转换BitmapImage后,内存不会被释放

时间:2011-02-04 11:12:57

标签: .net wpf

我遇到了以下c#(测试)代码的问题:

    public static void TestBitmap2ByteArray(BitmapImage bitmap)
    {
        JpegBitmapEncoder encoder = new JpegBitmapEncoder();
        MemoryStream memstream = new MemoryStream();
        encoder.Frames.Add(BitmapFrame.Create(bitmap));
        encoder.Save(memstream);

        memstream.Close();
    }

每次调用该函数时,都会分配内存而不会再次释放内存。在实际项目中,经常调用该方法,并且应用程序内存不足。

这是代码的精简版,没有返回任何内容。

我正在使用Visual Studio2010和.net 3.5 SP1。

帮助表示赞赏。 谢谢。

2 个答案:

答案 0 :(得分:4)

如我在其他答案中所述,解决此问题的更好方法是直接访问位图数据。 BitmapImage继承自BitmapSource。 BitmapSource非常适用于此,也适用于WPF绑定。

我自己使用BitmapSource来操作直接绑定到WPF(MVVM样式)的图像。基本上我在内存中创建一个区域并将BitmapSource指向它。这允许我直接读取/写入像素到内存并使BitmapSource无效,以便WPF重绘图像。我有一个标准的“Bitmap”对象,我用它。直接数据访问使其超快。 (说真的,完全没有问题修改4帧图像中的所有位30fps ..没有尝试过更高的速度,因为它没有被要求。)

可以在my blog上找到示例用法。但基本上你这样做:

unsafe {
   byte* imgBytePtr = (byte*)myBitmap.ImageData;
   Int32* imgInt32Ptr = (Int32*)myBitmap.ImageData;
   int height = (int)myBitmap.BitmapSource.Height;
   int width = (int)myBitmap.BitmapSource.Width;
   int bpp = myBitmap.BytesPerPixel;

   // Note: No need to iterate just for copy. A Marshal.Copy() at this point can copy all the bytes into a byte-array if you want.
   // But the best would be if your application could do its work directly in the imgBytePtr[]-array.

   for (int x = 0; x < height; x++)
   {
      for (int y = 0; y < width; y++)
      {
         // Get bytes into RGBA values
         int bytePos = x * (width * bpp) + (y * bpp);
         byte R = imgBytePtr[bytePos + 0];
         byte B = imgBytePtr[bytePos + 1];
         byte G = imgBytePtr[bytePos + 2];
         byte A = imgBytePtr[bytePos + 3];

         // Alternatively get Int32 value of color
         int intPos = x * width + y;
         int intColor = imgIntPtr[intPos];


         // Examples of manipulating data         

         // Remove blue
         imgBytePtr[bytePos + 1] = 0;
         // Alternative remove blue by bitmask
         imgIntPtr[intPos] = imgIntPtr[intPos] & 0xFF00FFFF; 

      }
   }
}
// Now execute Invalidate() and WPF will automagically update bound picture object :)

这会产生一个BitmapSource,如果您需要BitmapImage,您可以看看是否可以将其更改为有效。

    /// 
    /// This object holds a byte array of the picture as well as a BitmapSource for WPF objects to bind to. Simply call .Invalidate() to update GUI.
    /// 
    public class Bitmap : IDisposable
    {
        // some ideas/code borowed from CL NUI sample CLNUIImage.cs
        [DllImport("kernel32.dll", SetLastError = true)]
        static extern IntPtr CreateFileMapping(IntPtr hFile, IntPtr lpFileMappingAttributes, uint flProtect, uint dwMaximumSizeHigh, uint dwMaximumSizeLow, string lpName);
        [DllImport("kernel32.dll", SetLastError = true)]
        static extern IntPtr MapViewOfFile(IntPtr hFileMappingObject, uint dwDesiredAccess, uint dwFileOffsetHigh, uint dwFileOffsetLow, uint dwNumberOfBytesToMap);
        [DllImport("kernel32.dll", SetLastError = true)]
        static extern bool UnmapViewOfFile(IntPtr hMap);
        [DllImport("kernel32.dll", SetLastError = true)]
        static extern bool CloseHandle(IntPtr hHandle);

        private IntPtr _section = IntPtr.Zero;
        public IntPtr ImageData { get; private set; }
        public InteropBitmap BitmapSource { get; private set; }
        public int BytesPerPixel = 3;

        /// 
        /// Initializes an empty Bitmap
        /// 
        /// Image width
        /// Image height
        /// Image format
        public Bitmap(int width, int height, PixelFormat pixelFormat)
        {
            BytesPerPixel = pixelFormat.BitsPerPixel / 8;
            uint imageSize = (uint)width * (uint)height * (uint)BytesPerPixel;
            // create memory section and map
            _section = CreateFileMapping(new IntPtr(-1), IntPtr.Zero, 0x04, 0, imageSize, null);
            ImageData = MapViewOfFile(_section, 0xF001F, 0, 0, imageSize);
            BitmapSource = Imaging.CreateBitmapSourceFromMemorySection(_section, width, height, pixelFormat, width * BytesPerPixel, 0) as InteropBitmap;
        }

        /// 
        /// Invalidates the bitmap causing a redraw
        /// 
        public void Invalidate()
        {
            BitmapSource.Invalidate();
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected void Dispose(bool disposing)
        {
            if (disposing)
            {
                // free managed resources
            }
            // free native resources if there are any.
            if (ImageData != IntPtr.Zero)
            {
                UnmapViewOfFile(ImageData);
                ImageData = IntPtr.Zero;
            }
            if (_section != IntPtr.Zero)
            {
                CloseHandle(_section);
                _section = IntPtr.Zero;
            }
        }
    }

答案 1 :(得分:1)

您正在通过将位图数据保存到流来访问它。这会导致开销。 更好的方法是使用LockBits。这将使您可以直接访问图像中的字节,并且可以轻松地将其作为* byte []和* UInt32 []进行访问(注意:需要不安全的{})。

如果您仍然需要将字节复制到字节数组,则可以使用Marshal.Copy,但如果您的目的是直接对图像进行修改,则可以自由地进行修改。 LockBits链接有一个示例显示Marshal.Copy的使用。

如果您需要一些如何处理图片的示例,我已经发布了{KINct图像处理some sample code