views:

446

answers:

6

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

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.

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.

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.

+4  A: 

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.

Gabe
This would turn your 4.5 megabyte images into ~.9 megabyte images. You could get that down to .6 megabytes by going to 16-bit color. Now your 150 images would take about 60 megabytes. Still a lot, but much more plausible. Again, as long as you used the original images for the final rendering, you'd be fine.
Aric TenEyck
I've already tried doing this, and it really bogs up processing power.What I figure is that silverlight somehow is already doing this, when it renders the images. Would it just not be possible to force the bitmapimages to throw out their resources, while keeping the final rendered image in memory?
Sheeo
WPF, at least, does this as part of the compositing process. The scaling is thus done on the fly by the graphics hardware. I suspect silverlight does something similar. In which case the answer is no, it isn't already doing this, at least not in a way that's accessible to you.
Russell Mull
+1  A: 

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.

Alex
Yes, I found out about thise while snooping around for answers.There isn't a workaround for now, or what?
Sheeo
System.GC.Collect() and System.GC.WaitForPendingFinalizers() was how I seemed to help it out, but there is probably a better way.
Alex
+1  A: 

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.

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.

Ken Smith
Thanks for the answer, I've stumbled upon FJCore aswell--had an amount of problems with it, for example that the jpegs actually got bigger when decoding when compared to loading them through BitmapImage.The bottom part of your answer is discussed above.
Sheeo
+3  A: 

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.

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:

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.

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.

Skizz
You're correct, but in silverlight I use classes that derive from these. I'm required to use BitmapImage because there's no other equivalent.That means I don't have direct control over the format conversion, and I don't have any methods to get thumbnails from jpegs.So my only option is to make a bitmapimage from the original, and use that to create a thumbnail from--however, then I run into the issue pointed out by Alex above.Thank you for your reply.
Sheeo
@Sheeo: I think the Image.Dispose will solve the issue raised by Alex as you're telling the system you don't want the resources anymore - the docs state the Dispose method releases all the unmanged resources (and most of it will be unmanaged - the DIBitmap)
Skizz
A: 

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

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.

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;
    }
Sheeo
A: 

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.

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.

Stremlenye