开发者

Best way of saving image file and to save memory

In my program i am creating some big pictures (Image objects), and saving them to disk. Then I add them to a list List<Image> but after saving 50 pictures and adding them as Image objects to my imageList it eats up a loooooot of memory. I tried doing this on 50 images and just saving pure image object, my program went up to 160 MB i开发者_运维百科n process manager. So I must find out a way of saving the pictures and adding them to the list without the program eating up all memory.

So I have a couple of solutions and I would love to hear what you think about them or if you have any better ones.

  1. Compress the byte[] array of the image object.
  2. Compress the stream of the memorysteam object.
  3. Convert the byte[] array of the image object to string and then compress the string.

I am doing this in c#.


Do not compress your images using the .Net DeflateStream or such (as there is a known issue where it actually increases the size of the data, in all cases) - save it directly to something like a .png.

Bitmap bmp;
// ...
bmp.Save("foo.png", System.Drawing.Imaging.ImageFormat.Png);

Make sure you dispose the images that you created (after you have saved them).

You can't compress the images in memory - because Windows GDI (which .Net uses) requires the images in, essentially, uncompressed bitmap form (so when you load a compressed image it will get decompressed).

You should look at loading them on-demand. Here is a class similar to ImageList which you may find useful:

public class DelayedImagedList : Component
{
    // Item1 = Dispose for the image.
    // Item2 = At creation: the method to load the image. After loading: the method to return the image.
    // Item3 = The original filename.
    private List<Tuple<Action, Func<Image>, string>> _images = new List<Tuple<Action,Func<Image>,string>>();
    private Dictionary<string, int> _imageKeyMap = new Dictionary<string, int>();

    // Access images.
    public Image this[string key] { get { return _images[_imageKeyMap[key]].Item2(); } }
    public Image this[int index] { get { return _images[index].Item2(); } }
    public int Count { get { return _images.Count; } }

    // Use this to add an image according to its filename.
    public void AddImage(string key, string filename) { _imageKeyMap.Add(key, AddImage(filename)); }
    public int AddImage(string filename)
    {
        var index = _images.Count;
        _images.Add(Tuple.Create<Action, Func<Image>, string>(
            () => {}, // Dispose
            () => // Load image.
            {
                var result = Image.FromFile(filename);
                // Replace the method to load the image with one to simply return it.
                _images[index] = Tuple.Create<Action, Func<Image>, string>(
                    result.Dispose, // We need to dispose it now.
                    () => result, // Just return the image.
                    filename);
                return result;
            }, 
            filename));
        return index;
    }

    // This will unload an image from memory.
    public void Reset(string key) { Reset(_imageKeyMap[key]); }
    public void Reset(int index)
    {
        _images[index].Item1(); // Dispose the old value.
        var filename = _images[index].Item3;

        _images[index] = Tuple.Create<Action, Func<Image>, string>(
            () => { }, 
            () =>
            {
                var result = Image.FromFile(filename);
                _images[index] = Tuple.Create<Action, Func<Image>, string>(
                    result.Dispose, 
                    () => result, 
                    filename);
                return result;
            }, 
            filename);
    }

    // These methods are available on ImageList.
    public void Draw(Graphics g, Point pt, int index) { g.DrawImage(this[index], pt); }
    public void Draw(Graphics g, int x, int y, int index) { g.DrawImage(this[index], x, y); }
    public void Draw(Graphics g, int x, int y, int width, int height, int index) { g.DrawImage(this[index], x, y, width, height); }

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            foreach (var val in _images) { val.Item1(); }
            _images.Clear();
            _imageKeyMap.Clear();
        }
        base.Dispose(disposing);
    }
}


why compress? Surely you don't have to show all images at the same time (not in full resolution) - so either create smaller thumbs or show only a small subset.


since the images are changed by a scroll bar, why not show only the subset of images around the current index, like if you have 10 images and you are at #5, only load 4/5/6 and unload the rest, and as the scroll moves to 6 load 7, if you have lots of images and you are afraid the scroll movement will be faster than loading, you can load 3/4/5/6/7 and when it moves to 6 load 8, and so on.


When using WPF you can simply save the images in a list of MemoryStreams which contain the images as PNG or JPEG. Then you can bind to the images using some converter or a wrapper class that creates an ImageSource object with a reduced resolution. Unfortunately you didn't tell which technique you are using and I currently don't know a solution for WinForms.

public List<MemoryStream> ImageStreams {get; private set;}

public static ImageSource StreamToImageSource(Stream stream)
{
    BitmapImage bitmapImage = new BitmapImage();

    bitmapImage.BeginInit();
    bitmapImage.StreamSource = stream;
    bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
    bitmapImage.DecodePixelHeight = 200;
    bitmapImage.EndInit();

    bitmapImage.Freeze();

    return bitmapImage;
}

When doing this you'll only store some kB per image and the loaded image for the UI will be scaled down, so that the used memory is limited.

With this approach I could load over 100 images in a scanner application and they were all displayed in a ListBox with a VirtualizingStackPanel side by side with a vertical resolution of 800 pixels. The original images had a resolution of over 2200 x 3800 pixels and 24 bpp.

Loading a PNG with several million pixels normaly takes a second, but with this solution you won't need to load them from disk.

And don't forget to dispose and remove temporary objects etc. You can also run GC.Collect() to make sure that unused data will be removed.


I like the 2nd choice. Saving your Image to memory using PNG format should be more efficient than using a common compress library like zlib or gzipstream.

MemoryStream mStream= new MemoryStream ();
myBitmap.Save( mStream, ImageFormat.Png );
// and then do myBitmap.Dispose() to retrieve the memory?
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜