Лучший способ сохранить файл изображения и сохранить память - PullRequest
5 голосов
/ 19 августа 2011

В моей программе я создаю несколько больших картинок (Image объектов) и сохраняю их на диск. Затем я добавляю их в список List<Image>, но после сохранения 50 изображений и добавления их в качестве Image объектов в мой imageList он поглощает много памяти. Я попытался сделать это на 50 изображениях и просто сохранив чистый объект изображения, моя программа поднялась до 160 МБ в диспетчере процессов. Поэтому я должен найти способ сохранить фотографии и добавить их в список, чтобы программа не занимала всю память.

Так что у меня есть пара решений, и я хотел бы услышать, что вы о них думаете, или если у вас есть какие-нибудь лучшие решения.

  1. Сжать массив byte[] объекта изображения.
  2. Сжатие потока объекта memorysteam.
  3. Преобразовать массив byte[] объекта изображения в строку, а затем сжать строку.

Я делаю это в c #.

Ответы [ 5 ]

10 голосов
/ 24 августа 2011

Не сжимайте ваши изображения с помощью .Net DeflateStream или чего-то подобного (поскольку существует известная проблема, когда во всех случаях фактически увеличивается размер данных) - сохраняйте их напрямую в формате .png.

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

Убедитесь, что вы удаляете созданные вами изображения (после того, как сохранили их).

Вы не можете сжимать изображения в памяти - потому что Windows GDI (который использует .Net) требует изображения в основном в несжатом растровом виде (поэтому при загрузке сжатого изображения оно будет распаковано).

Вы должны посмотреть на их загрузку по требованию. Вот класс, похожий на ImageList, который может оказаться полезным:

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);
    }
}
3 голосов
/ 19 августа 2011

зачем сжимать?Конечно, вам не нужно показывать все изображения одновременно (не в полном разрешении) - поэтому создайте большие пальцы меньшего размера или покажите только небольшое подмножество.

2 голосов
/ 24 августа 2011

, так как изображения меняются с помощью полосы прокрутки, почему бы не показать только подмножество изображений вокруг текущего индекса, например, если у вас есть 10 изображений, и вы находитесь на # 5, только загрузить 4/5/6 и выгрузить остальныеи когда прокрутка перемещается к 6, загружается 7, если у вас много изображений и вы боитесь, что движение прокрутки будет быстрее, чем загрузка, вы можете загрузить 3/4/5/6/7, а когда оно перейдет к 6, загрузить 8и т. д.

1 голос
/ 25 августа 2011

При использовании WPF вы можете просто сохранить изображения в списке MemoryStreams, которые содержат изображения в формате PNG или JPEG. Затем вы можете привязать изображения к какому-либо конвертеру или классу-оболочке, который создает объект ImageSource с уменьшенным разрешением. К сожалению, вы не сказали, какую технику вы используете, и в настоящее время я не знаю решения для 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;
}

При этом вы будете хранить только несколько килобайт на изображение, а загруженное изображение для пользовательского интерфейса будет уменьшено, так что используемая память будет ограничена.

При таком подходе я мог загрузить более 100 изображений в приложении сканера, и все они отображались в формате ListBox с VirtualizingStackPanel рядом с вертикальным разрешением 800 пикселей. Исходные изображения имели разрешение более 2200 x 3800 пикселей и 24 бит / с.

Загрузка PNG с несколькими миллионами пикселей обычно занимает секунду, но с этим решением вам не нужно загружать их с диска.

И не забудьте удалить и удалить временные объекты и т. Д. Вы также можете запустить GC.Collect(), чтобы убедиться, что неиспользуемые данные будут удалены.

0 голосов
/ 26 августа 2011

Мне нравится второй выбор.Сохранение изображения в память в формате PNG должно быть более эффективным, чем использование обычной библиотеки сжатия, такой как zlib или gzipstream.

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