如何在不占用大量RAM的情况下显示图像

时间:2021-03-06 09:18:56

I'm working on a silverlight project where users get to create their own Collages.

我正在开发一个Silverlight项目,用户可以在其中创建自己的拼贴画。

The problem

When loading a bunch of images by using the BitmapImage class, Silverlight hogs up huge unreasonable amounts of RAM. 150 pictures where single ones fill up at most 4,5mb takes up about 1,6GB of RAM--thus ending up throwing memory exceptions.

使用BitmapImage类加载一堆图像时,Silverlight会占用大量不合理的RAM。 150张图片,其中单个填充最多4,5mb占用大约1,6GB的RAM - 因此最终导致内存异常。

I'm loading them through streams, since the user selects their own photos.

我正在通过流加载它们,因为用户选择了自己的照片。

What I'm looking for

我正在寻找什么

A class, method or some process to eliminate the huge amount of RAM being sucked up. Speed is an issue, so I don't want to be converting between images formats or anything like that. A fast resizing solution might work.

消除大量RAM被吸收的类,方法或一些过程。速度是一个问题,所以我不想在图像格式或类似的东西之间进行转换。快速调整大小的解决方案可能有效。

I've tried using a WriteableBitmap to render the images into, but I find this method forces me to reinvent the wheel when it comes to drag/drop and other things I want users to be able to do with the images.

我已经尝试使用WriteableBitmap渲染图像,但我发现这种方法迫使我重新发明*,当涉及到拖放和其他我希望用户能够对图像做的事情。

6 个解决方案

#1


6  

What I would try is to take load each stream and resize it to a thumbnail (say, 640x480) before loading the next one. Then let the user work with the smaller images. Once you're ready to generate the PDF, reload the JPEGs from the original streams one at a time, disposing of each bitmap before loading the next.

我要尝试的是在加载下一个流之前加载每个流并将其调整为缩略图(例如,640x480)。然后让用户使用较小的图像。准备好生成PDF后,一次一个地从原始流重新加载JPEG,在加载下一个位图之前处理每个位图。

#2


3  

I'm guessing you're doing something liek this:

我猜你正在做一些事情:

Bitmap bitmap = new Bitmap (filename of jpeg);

and then doing:

然后做:

OnPaint (...)
{
   Graphics g = ....;
   g.DrawImage (bitmap, ...);
}

This will be resizing the huge JPEG image to the size shown on screen every time you draw it. I'm guessing your JPEG is about 2500x2000 pixels in size. When you load a JPEG into a Bitmap, the bitmap loading code uncompresses the data and stores it either as RGB data in a format that will be easy to render (i.e. in the same pixel format as the display) or as a thing known as a Device Independant Bitmap (aka DIBitmap). These bitmaps require more RAM to store than a compressed JPEG.

这将是每次绘制时将巨大的JPEG图像调整为屏幕上显示的大小。我猜你的JPEG大小约为2500x2000像素。当您将JPEG加载到位图中时,位图加载代码会解压缩数据并将其作为RGB数据存储为易于渲染的格式(即与显示器采用相同的像素格式)或称为设备独立位图(又名DIBitmap)。这些位图需要比压缩JPEG更多的RAM存储。

Your current implementation is already doing format conversion and resizing, but doing it in an innefficent way, i.e. resizing a huge image down to screen size every time it's rendered.

您当前的实现已经在进行格式转换和调整大小,但是以一种无用的方式执行,即每次渲染时将大图像调整为屏幕大小。

Ideally, you want to load the image and scale it down. .Net has a system to do this:

理想情况下,您希望加载图像并将其缩小。 .Net有一个系统来做到这一点:

Bitmap bitmap = new Bitmap (filename of JPEG);
Bitmap thumbnail = bitmap.GetThumbnailImage (width, height, ....);
bitmap.Dispose (); // this releases all the unmanged resources and makes the bitmap unusable - you may have been missing this step
bitmap = null; // let the GC know the object is no longer needed

where width and height are the size of the required thumbnail. Now, this might produce images that don't look as good as you might want them to (but it will use any embedded thumbnail data if it's present so it'll be faster), in which case, do a bitmap->bitmap resize.

其中width和height是所需缩略图的大小。现在,这可能会产生看起来不像你想要的那样好的图像(但它会使用任何嵌入的缩略图数据,如果它存在,所以它会更快),在这种情况下,做一个位图 - >位图调整大小。

When you create the PDF file, you'll need to reload the JPEG data, but from a user's point of view, that's OK. I'm sure the user won't mind waiting a short while to export the data to a PDF so long as you have some feedback to let the user know it's being worked on. You can also do this in a background thread and let the user work on another collage.

创建PDF文件时,您需要重新加载JPEG数据,但从用户的角度来看,这没关系。我确信用户不会介意等待一段时间将数据导出为PDF,只要您有一些反馈让用户知道它正在被处理。您也可以在后台线程中执行此操作,并让用户处理另一个拼贴。

#3


1  

What might be happening to you is a little known fact about the garbage collection that got me as well. If an object is big enough ( I don't remember where the line is exactly ) Garbage Collection will decide that even though nothing currently in scope is linked to the object (in both yours and mine the objects are the images) it keeps the image in memory because it has decided that in case you ever want that image again it is cheaper to keep it around rather than delete it and reload it later.

可能发生在你身上的事情是关于垃圾收集的一个鲜为人知的事实。如果一个对象足够大(我不记得该行的确切位置)垃圾收集将决定即使当前在范围内没有任何东西链接到该对象(在你的和我的对象都是图像)它保留图像在内存中,因为它已经决定,如果你再次想要那个图像,保持它而不是删除它并在以后重新加载它会更便宜。

#4


1  

This isn't a complete solution, but if you're going to be converting between bitmaps and JPEG's (and vice versa), you'll need to look into the FJCore image library. It's reasonably simple to use, and allows you to do things like resize JPEG images or move them to a different quality. If you're using Silverlight for client-side image processing, this library probably won't be sufficient, but it's certainly necessary.

这不是一个完整的解决方案,但如果您要在位图和JPEG之间进行转换(反之亦然),则需要查看FJCore图像库。它使用起来相当简单,并允许您执行调整JPEG图像大小或将其移动到不同质量的操作。如果您使用Silverlight进行客户端图像处理,那么这个库可能还不够,但肯定是必要的。

You should also look into how you're presenting the images to the user. If you're doing collages with Silverlight, presumably you won't be able to use virtualizing controls, since the users will be manipulating all 150 images at once. But as other folks have said, you should also make sure you're not presenting bitmaps based on full-sized JPEG files either. A 1MB compressed JPEG is probably going to expand to a 10MB Bitmap, which is likely where a lot of your trouble is coming from. Make sure that you're basing the images you present to the user on much smaller (lower quality and resized) JPEG files.

您还应该了解如何向用户呈现图像。如果您正在使用Silverlight进行拼贴,可能您将无法使用虚拟化控件,因为用户将同时处理所有150个图像。但正如其他人所说,你也应该确保你没有根据全尺寸的JPEG文件呈现位图。一个1MB的压缩JPEG可能会扩展到一个10MB的位图,这很可能是你遇到很多麻烦的地方。确保您将所呈现的图像基于更小(质量较低且已调整大小)的JPEG文件。

#5


0  

The solution that finally worked for me was using WriteableBitmapEX to do the following:

最终为我工作的解决方案是使用WriteableBitmapEX来执行以下操作:

Of course I only use thumbnails if the image isn't already small enough to store in memory.

当然,如果图像的尺寸不足以存储在内存中,我只会使用缩略图。

The gotch'a I had was the fact that WriteableBitmap doesn't have a parameterless constructor, but initializing it with 0,0 as size and then loading the source sets these automatically. That didn't come naturally to me.

我得到的是,WriteableBitmap没有无参数构造函数,但是以0,0作为大小初始化它,然后加载源自动设置它们。这对我来说并不自然。

Thanks for the help everybody!

感谢大家的帮助!

private WriteableBitmap getThumbnailFromBitmapStream(Stream bitmapStream, PhotoFrame photoFrame)
    {
        WriteableBitmap inputBitmap = new WriteableBitmap(0,0);
        inputBitmap.SetSource(bitmapStream);

        Size thumbnailSize = getThumbnailSizeFromWriteableBitmap(inputBitmap, photoFrame.size);

        WriteableBitmap thumbnail = new WriteableBitmap(0,0);
        thumbnail = inputBitmap.Resize((int)thumbnailSize.Width, (int)thumbnailSize.Height, WriteableBitmapExtensions.Interpolation.NearestNeighbor);

        return thumbnail;
    }

#6


0  

One additional variant to reduce ram using: Dont load images, which ar invisible at this moment, and load them while user scrolling the page. This method uses by web developers to improve page load speed. For you its the way not to store hole amount of images in ram.

另一种减少使用ram的变体:不加载此时不可见的图像,并在用户滚动页面时加载它们。 Web开发人员使用此方法来提高页面加载速度。对于你来说,它不是在ram中存储孔数量的方式。

And I think the better way not to make thumbnails on run, but store them near the fullsize pictures and get only links for them. When it needed, you alway can get the link to fullsize picture and load it.

而且我认为更好的方法是不要在运行时制作缩略图,而是将它们存储在全尺寸图片附近并获取它们的链接。当它需要时,你总是可以获得全尺寸图片的链接并加载它。

#1


6  

What I would try is to take load each stream and resize it to a thumbnail (say, 640x480) before loading the next one. Then let the user work with the smaller images. Once you're ready to generate the PDF, reload the JPEGs from the original streams one at a time, disposing of each bitmap before loading the next.

我要尝试的是在加载下一个流之前加载每个流并将其调整为缩略图(例如,640x480)。然后让用户使用较小的图像。准备好生成PDF后,一次一个地从原始流重新加载JPEG,在加载下一个位图之前处理每个位图。

#2


3  

I'm guessing you're doing something liek this:

我猜你正在做一些事情:

Bitmap bitmap = new Bitmap (filename of jpeg);

and then doing:

然后做:

OnPaint (...)
{
   Graphics g = ....;
   g.DrawImage (bitmap, ...);
}

This will be resizing the huge JPEG image to the size shown on screen every time you draw it. I'm guessing your JPEG is about 2500x2000 pixels in size. When you load a JPEG into a Bitmap, the bitmap loading code uncompresses the data and stores it either as RGB data in a format that will be easy to render (i.e. in the same pixel format as the display) or as a thing known as a Device Independant Bitmap (aka DIBitmap). These bitmaps require more RAM to store than a compressed JPEG.

这将是每次绘制时将巨大的JPEG图像调整为屏幕上显示的大小。我猜你的JPEG大小约为2500x2000像素。当您将JPEG加载到位图中时,位图加载代码会解压缩数据并将其作为RGB数据存储为易于渲染的格式(即与显示器采用相同的像素格式)或称为设备独立位图(又名DIBitmap)。这些位图需要比压缩JPEG更多的RAM存储。

Your current implementation is already doing format conversion and resizing, but doing it in an innefficent way, i.e. resizing a huge image down to screen size every time it's rendered.

您当前的实现已经在进行格式转换和调整大小,但是以一种无用的方式执行,即每次渲染时将大图像调整为屏幕大小。

Ideally, you want to load the image and scale it down. .Net has a system to do this:

理想情况下,您希望加载图像并将其缩小。 .Net有一个系统来做到这一点:

Bitmap bitmap = new Bitmap (filename of JPEG);
Bitmap thumbnail = bitmap.GetThumbnailImage (width, height, ....);
bitmap.Dispose (); // this releases all the unmanged resources and makes the bitmap unusable - you may have been missing this step
bitmap = null; // let the GC know the object is no longer needed

where width and height are the size of the required thumbnail. Now, this might produce images that don't look as good as you might want them to (but it will use any embedded thumbnail data if it's present so it'll be faster), in which case, do a bitmap->bitmap resize.

其中width和height是所需缩略图的大小。现在,这可能会产生看起来不像你想要的那样好的图像(但它会使用任何嵌入的缩略图数据,如果它存在,所以它会更快),在这种情况下,做一个位图 - >位图调整大小。

When you create the PDF file, you'll need to reload the JPEG data, but from a user's point of view, that's OK. I'm sure the user won't mind waiting a short while to export the data to a PDF so long as you have some feedback to let the user know it's being worked on. You can also do this in a background thread and let the user work on another collage.

创建PDF文件时,您需要重新加载JPEG数据,但从用户的角度来看,这没关系。我确信用户不会介意等待一段时间将数据导出为PDF,只要您有一些反馈让用户知道它正在被处理。您也可以在后台线程中执行此操作,并让用户处理另一个拼贴。

#3


1  

What might be happening to you is a little known fact about the garbage collection that got me as well. If an object is big enough ( I don't remember where the line is exactly ) Garbage Collection will decide that even though nothing currently in scope is linked to the object (in both yours and mine the objects are the images) it keeps the image in memory because it has decided that in case you ever want that image again it is cheaper to keep it around rather than delete it and reload it later.

可能发生在你身上的事情是关于垃圾收集的一个鲜为人知的事实。如果一个对象足够大(我不记得该行的确切位置)垃圾收集将决定即使当前在范围内没有任何东西链接到该对象(在你的和我的对象都是图像)它保留图像在内存中,因为它已经决定,如果你再次想要那个图像,保持它而不是删除它并在以后重新加载它会更便宜。

#4


1  

This isn't a complete solution, but if you're going to be converting between bitmaps and JPEG's (and vice versa), you'll need to look into the FJCore image library. It's reasonably simple to use, and allows you to do things like resize JPEG images or move them to a different quality. If you're using Silverlight for client-side image processing, this library probably won't be sufficient, but it's certainly necessary.

这不是一个完整的解决方案,但如果您要在位图和JPEG之间进行转换(反之亦然),则需要查看FJCore图像库。它使用起来相当简单,并允许您执行调整JPEG图像大小或将其移动到不同质量的操作。如果您使用Silverlight进行客户端图像处理,那么这个库可能还不够,但肯定是必要的。

You should also look into how you're presenting the images to the user. If you're doing collages with Silverlight, presumably you won't be able to use virtualizing controls, since the users will be manipulating all 150 images at once. But as other folks have said, you should also make sure you're not presenting bitmaps based on full-sized JPEG files either. A 1MB compressed JPEG is probably going to expand to a 10MB Bitmap, which is likely where a lot of your trouble is coming from. Make sure that you're basing the images you present to the user on much smaller (lower quality and resized) JPEG files.

您还应该了解如何向用户呈现图像。如果您正在使用Silverlight进行拼贴,可能您将无法使用虚拟化控件,因为用户将同时处理所有150个图像。但正如其他人所说,你也应该确保你没有根据全尺寸的JPEG文件呈现位图。一个1MB的压缩JPEG可能会扩展到一个10MB的位图,这很可能是你遇到很多麻烦的地方。确保您将所呈现的图像基于更小(质量较低且已调整大小)的JPEG文件。

#5


0  

The solution that finally worked for me was using WriteableBitmapEX to do the following:

最终为我工作的解决方案是使用WriteableBitmapEX来执行以下操作:

Of course I only use thumbnails if the image isn't already small enough to store in memory.

当然,如果图像的尺寸不足以存储在内存中,我只会使用缩略图。

The gotch'a I had was the fact that WriteableBitmap doesn't have a parameterless constructor, but initializing it with 0,0 as size and then loading the source sets these automatically. That didn't come naturally to me.

我得到的是,WriteableBitmap没有无参数构造函数,但是以0,0作为大小初始化它,然后加载源自动设置它们。这对我来说并不自然。

Thanks for the help everybody!

感谢大家的帮助!

private WriteableBitmap getThumbnailFromBitmapStream(Stream bitmapStream, PhotoFrame photoFrame)
    {
        WriteableBitmap inputBitmap = new WriteableBitmap(0,0);
        inputBitmap.SetSource(bitmapStream);

        Size thumbnailSize = getThumbnailSizeFromWriteableBitmap(inputBitmap, photoFrame.size);

        WriteableBitmap thumbnail = new WriteableBitmap(0,0);
        thumbnail = inputBitmap.Resize((int)thumbnailSize.Width, (int)thumbnailSize.Height, WriteableBitmapExtensions.Interpolation.NearestNeighbor);

        return thumbnail;
    }

#6


0  

One additional variant to reduce ram using: Dont load images, which ar invisible at this moment, and load them while user scrolling the page. This method uses by web developers to improve page load speed. For you its the way not to store hole amount of images in ram.

另一种减少使用ram的变体:不加载此时不可见的图像,并在用户滚动页面时加载它们。 Web开发人员使用此方法来提高页面加载速度。对于你来说,它不是在ram中存储孔数量的方式。

And I think the better way not to make thumbnails on run, but store them near the fullsize pictures and get only links for them. When it needed, you alway can get the link to fullsize picture and load it.

而且我认为更好的方法是不要在运行时制作缩略图,而是将它们存储在全尺寸图片附近并获取它们的链接。当它需要时,你总是可以获得全尺寸图片的链接并加载它。