保存图像文件和节省内存的最佳方法

发布于 2024-11-30 20:01:40 字数 454 浏览 0 评论 0原文

在我的程序中,我正在创建一些大图片(Image 对象),并将它们保存到磁盘。然后我将它们添加到列表 List 中,但在保存 50 张图片并将它们作为 Image 对象添加到我的 imageList 后,它占用了失去记忆了。我尝试在 50 个图像上执行此操作,仅保存纯图像对象,我的程序在进程管理器中达到 160 MB。所以我必须找到一种方法来保存图片并将它们添加到列表中,而不会让程序耗尽所有内存。

所以我有几个解决方案,我很想听听您对它们的看法或者您是否有更好的解决方案。

  1. 压缩图像对象的byte[]数组。
  2. 压缩内存流对象的流。
  3. 将图像对象的byte[]数组转换为字符串,然后压缩该字符串。

我正在用 c# 做这个。

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 in 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#.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

家住魔仙堡 2024-12-07 20:01:40

不要使用 .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);
    }
}

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);
    }
}
饮惑 2024-12-07 20:01:40

为什么要压缩?当然,您不必同时显示所有图像(不是全分辨率) - 因此,要么创建较小的拇指,要么仅显示一小部分。

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.

从来不烧饼 2024-12-07 20:01:40

由于图像是通过滚动条更改的,为什么不只显示当前索引周围的图像子集,就像如果您有 10 个图像并且位于 #5,则仅加载 4/5/6 并卸载其余图像,并且如下滚动移动到6加载7,如果你有很多图像并且你担心滚动移动会比加载快,你可以加载3/4/5/6/7,当它移动到6时加载8,依此类推在。

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.

忘羡 2024-12-07 20:01:40

使用 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;
}

执行此操作时,每个图像仅存储一些 kB,并且 UI 加载的图像将按比例缩小,因此使用的内存是有限的。

通过这种方法,我可以在扫描仪应用程序中加载 100 多个图像,并且它们全部显示在 ListBox 中,并带有 VirtualizingStackPanel 并排,垂直分辨率为 800 像素。原始图像的分辨率超过 2200 x 3800 像素和 24 bpp。

加载具有数百万像素的 PNG 通常需要一秒钟,但使用此解决方案,您无需从磁盘加载它们。

并且不要忘记处置和删除临时对象等。您还可以运行 GC.Collect() 来确保删除未使用的数据。

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.

一个人练习一个人 2024-12-07 20:01:40

我喜欢第二个选择。使用 PNG 格式将图像保存到内存应该比使用 zlib 或 gzipstream 等常见压缩库更有效。

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

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?
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文