How to display images without taking up huge amounts of RAM - c#

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.

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.

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.

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.

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.

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

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.

Related

C# Fast load Bitmap into PictureBox

I receve Bitmap image from a camera at 30 fps, and I need to display all images in a pictureBox.
The problem is that the PictureBox is very slow!
I have try to implement a custom PictureBox with DoubleBuffer enabled but the problem is not resolved.
Do you have a custom PictureBox or an user control or a solution that can display the image faster?
Additional information:
The image resolution is 2048x1088 with 256 graylevel (8bit image).
I use AForge.NET for elaborate the images.
Thank you
That image gets expensive to draw when it has to be resized to fit the PB's client area. Which is very likely in your case because your images are pretty large. It uses a high-quality bi-cubic filter to make the resized image look good. That's pretty expensive, albeit that the result is good.
To avoid that expense, resize the image yourself before assigning it to the Image property. Make it just as large as the PB's ClientSize.
That's going to make a big difference in itself. The next thing you can do is to create the scaled bitmap with the 32bppPArgb pixel format. It's the format that's about 10 times faster then any other because it matches the video adapter on most machines so no pixel format conversions are necessary.

Factors Affecting Time to Render Image using GDI+

This is a follow on to my previous question Speeding Up Image Handling
Apologies if I should have amended that question in some way rather than starting a new one.
I have tried all sorts of different things to speed up the drawing of an image on screen.
I thought that compressing the image until it is smaller would have an effect. However while this might save memory for the object I do not think it has any effect on how long it takes to draw. I tried converting the image to jpeg and using 100% compression. However while this creates a blocky image it does not affect the drawing time. I now think this is because the number of pixels that get rendered is not changed by this
I tried reducing the color palette to 256 colors. This makes the size smaller since it uses less bytes per pixel but does not seem to affect drawing on screen. I had thought that reducing the bytes per pixel GDI+ has to handle might save some time but it is not enough for me to see so far.
So am I wasting my time looking at compression and palette?
I assume that time taken will be affected by the number of pixels to be drawn (width x height) and I have resized the image to match the pixel size as displayed on screen. I think this is the one thing that does have an effect....
I have looked at how to stop autoscaling of the image - does my resize stop that or can the image still be autoscaled when it is rendered?
I am wondering if I can replace the DrawImage call with something using p/Invoke or other API call (which I admit I don't really understand).
You could use the Bitblt P/Invoke, which copies image data directly. It does require some initialisation to convert the image data to something the display device understands, but this copy operation is lightning fast. If you would like to know what exactly is happening in DrawImage btw, use a tool like ILSpy or Reflector.NET to inspect the method.
See BitBlt code not working for an example. For some information about Bitblt see http://www.codeproject.com/KB/GDI-plus/flicker_free.aspx.

Resizing an image after loading it from the database using asp.net, C#

I have a user-uploaded image pulled from the database that I am resizing smaller to display on a web page that I intend to print. I thought about saving a smaller version when the user uploads it, but since the design of this document hasn't been finalized yet, I was looking for something more dynamic. Also, this document only needs to be printed up once, while the image uploaded is displayed at various places in the app numerous times.
Using javascript to resize it while keeping its proportions, it was printing fine for a while. After adding a margin for styling, the printer started printing the image at its full size. I'm assuming it's the margin. It looks fine on screen but pushes everything off the page on paper.
This led me to look into resizing it on the server, in the C# code, but we use user images uploaded to the database, and I can't seem to find the right time or place in the page life cycle to access and change the width and height. I've tried the various methods on the web using Bitmaps, but they all want a file, when I am using a FileDownloader page as the image url.
Perhaps I'm looking in the wrong place entirely and need to go back to the client. Advice and help is appreciated.
As long as your FileDownloader page returns the proper resized image, it shouldn't matter that you're not point to an actual image.
I'd something like this in your FileDownloader page (pseudo code):
using (Image img = LoadImageFromDatabase())
{
using (Image resized = ResizeImage(img))
{
Response.Clear();
// Set proper headers
using (MemoryStream ms = new MemoryStream())
{
resized.Save(ms); // maybe the function isn't called Save, can't remember a 100%
ms.Seek(0); // Can't remember if this is necessary
Response.BinaryWrite(ms);
}
}
}
Like I mentioned it's highly pseudo code, but it should be straight forward with a Visual Studio open, I just haven't access to it right now, and it's been quite a while since I last used this (since I'm stored the resized images like most other in this question recommends - I do so too, however I realize this is not an option for you)
When you are going to have to resize an image to known constraints, and there's the possibility of having to do that multiple times, I'd always advocate doing the resize once (on upload) and storing the result. Of course, you don't say that you need to retain the original image size, but if you do, then you just have to store the image twice - once original size and once at the resized dimensions.
Once you've done that, you can worry about defining your print layout based on the known dimensions of the resized image, and not have to faff about resizing for each use.
I would suggest converting on upload and possibly saving both images in case you want to let the user click through to the full image. Using this model you only do the conversion once and can render either size image. The GetThumbnailImage() method on the Image class will do what you desire, something like this:
String imageFile = uploadedFileName;
Image baseImage = Image.FromFile(imageFile);
Image thumbImage = baseImage.GetThumbnailImage(300,300,..., ...);
SaveMyImage(baseImage);
SaveMyImage(thumbImage);
Be sure to check the documentation for the parameters to GetThumbnailImage() to verify scaling issues and callback handling.
Could you implement such a process:
User sends an image
Image is opened by a function/routine/script, while the user waits
Image is resized on the fly and saved in the correct location which returns a code for success
User receives a message depending of the return value of the script.
EDIT:
I agree with most of the replies you got here.
If the pictures are stored in a database you need to first make thumbmails for all pictures and put them in the same database, then you need to implement a process to create the thumbmails on the fly when adding new pictures.
Disclaimer: I'm suggesting the open-source library I designed for this purpose. I'm definitely biased, but 4 years and thousands of happy users justify my bias :)
You shouldn't be resizing images inside an .ASPX page, or serving them either. Images should be handled in separate requests by a HttpModule so responsiveness doesn't suffer.
If you have some kind of ID in SQL for each image, you can use the SqlReader VirtualPathProvider to make it accessible via a URL, like /sqlimages/id
Combine that with this free, open-source dynamic image resizing module, and you're set.
You'll simply reference images like this: http://localhost/sqlimages/id?width=300&height=200 from your HTML, and you may not even have to write a line of C#.
If you write your own solution, read these 28 pitfalls you should avoid, so you don't end up crashing the server.
Hope this helps!

How can you draw 32bppargb images with the Win32 AlphaBlend function?

I've been fussing with this for the better part of the night, so maybe one of you can give me a hand.
I have found GDI+ DrawImage in C# to be far too slow for what I'm trying to render, and from the looks of forums, it's the same for other people as well.
I decided I would try using AlphaBlend or BitBlt from the Win32 API to get better performance. Anyway, I've gotten my images to display just fine except for one small detail—no matter what image format I use, I can't get the white background to disappear from my (transparent) graphics.
I've tried BMP and PNG formats so far, and verified that they get loaded as 32bppargb images in C#.
Here's the call I'm making:
// Draw the tile to the screen.
Win32GraphicsInterop.AlphaBlend(graphicsCanvas, destination.X, destination.Y, this.TileSize, this.TileSize,
this.imageGraphicsPointer, tile.UpperLeftCorner.X, tile.UpperLeftCorner.Y,
this.TileSize, this.TileSize,
new Win32GraphicsInterop.BLENDFUNCTION(Win32GraphicsInterop.AC_SRC_OVER, 0,
Convert.ToByte(opacity * 255),
Win32GraphicsInterop.AC_SRC_ALPHA));
For the record, AC_SRC_OVER is 0x00 and AC_SRC_ALPHA is 0x01 which is consistent with what MSDN says they ought to be.
Do any of you guys have a good solution to this problem or know a better (but still fast) way I can do this?
Graphics.DrawImage() speed is critically dependent on the pixel format. Format32bppPArgb is 10 times faster than any other one on any recent machine I've tried.
Also make sure you the image doesn't get resized, be sure to use a DrawImage() overload that sets the destination size equal to the bitmap size. Very important if the video adapter's DPI setting doesn't match the resolution of the bitmap.
Have you tried an opacity of just 255 rather than a calculated one?
This blog post describes what you're trying to do:-
http://blogs.msdn.com/andreww/archive/2007/10/10/preserving-the-alpha-channel-when-converting-images.aspx
Critical thing is that he carries out a conversion of the image to make the alpha channel compatible..
Ok. From a pure Win32 perspective:
In order for AlphaBlend to actually alpha blend... it needs the source device context to contain a selected HBITMAP representing an image with 32bpp bitmap with a pre-multiplied alpha channel.
To get a device bitmap with 32bpp you can either call one of the many functions that will create a screen compatible device bitmap and hope like hell the user has selected 32bpp as the desktop bitdepth. OR, ensure that the source bitmap is a DIBSection. Well, the library or framework that is creating it from the loaded image for you.
So, C# is loading your images with 32bpp argb, BUT, how are you converting that C# representation of the bitmap into a HBITMAP? You need to ensure that a DIB Section is being created, not a DDB (or device dependent bitmap), and that the DIB Section is 32bpp.

How to serve high-resolution imagery in a low-resolution form using C#

Trying to use 300dpi tif images for display on the web. At the moment, when the user uploads an image, I am dynamically creating a thumbnail. If a page is created referencing the high-res image with a width of 500x500px, can I use the same functionality to convert to a gif/jpg on the fly. What is the impending resolution of the jpg that would be created?
EDIT:
To further explain the usage, the user uploads 300dpi images which are approx 3000x3000 pixels. The user is using these images to create a catalog page which will be used for pdf printing. When they are creating the page, we only need 72dpi images to display to the screen, but for print, need the 300dpi images. Obviously they do not want to add a 3000x3000px image to the page, so it needs to be resized to the correct viewing area, e.g. 500x500px etc.
This boils down to a simple image resize. The discussion of DPIs is just ancillary data to calculate the scale factor.
As #Guffa said, you should do this at the time of the upload so that you can just serve static images in your viewer.
This will be a load on the server:
Load the full image. This will be about 27 MB of memory for your 3000x3000 images.
Resize. Lot's of math done lazily (still CPU intensive).
Compress. More CPU + Cost of writing to your drive.
Since you are already taking the time to generate a thumbnail, you can amortize that cost and this cost by not having to repeat Step 1 above (see the code).
After an image is uplaoded, I would recommend spinning off a thread to do this work. It's a load on the web server for sure, but you're only other option is to devote a second machine to performing the work. It will have to be done eventually.
Here is some code to do the job. The important lines are these:
OutputAsJpeg(Resize(big, 300.0, 72.0), new FileStream("ScreenView.jpg"));
OutputAsJpeg(Resize(big, bigSize, 64.0), new FileStream("Thumbnail.jpg"));
We can resize the big image however we need. In the first line we just scale it down by a fixed scale (72.0 / 300.0). On the second line, we force the image to have a final max dimension of 64 (scale factor = 64.0 / 3000.0).
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.IO;
BitmapSource Resize(BitmapSource original,
double originalScale,
double newScale) {
double s = newScale / originalScale;
return new TransformedBitmap(original, new ScaleTransform(s, s));
}
void OutputAsJpeg(BitmapSource src, Stream out) {
var encoder = new JpegBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(src));
encoder.Save(out);
}
// Load up your bitmap from the file system or whatever,
// then dump it out to a smaller version and a thumbnail.
// Assumes thumbnails have a max dimension of 64
BitmapSource big = new BitmapImage(new Uri("BigPage0.png",
UriKind. RelativeOrAbsolute));
double bigSize = Math.Max(big.PixelWidth, big.PixelHeight);
OutputAsJpeg(Resize(big, 300.0, 72.0), new FileStream("ScreenView.jpg"));
OutputAsJpeg(Resize(big, bigSize, 64.0), new FileStream("Thumbnail.jpg"));
If I understand what you want - you're trying to make a gif or jpg thumbnail of a very high resolution tif, for web display - if not, I apologize in advance....
If you want the thumbnail to be 500x500px, that is the resolution of the jpg/gif you'll want to create - 500x500, or at least 500x<500 or <500x500 (to fit in the box, unless you want distorted images).
For display on the web, the DPI does not matter. Just use the pixel resolution you wish directly.
Technically the JPG/GIF is created at 72-DPI by the Image classes in .NET. But the DPI really doesn't have any meaning to the browser - it just uses the dimensions 500x500.
When displaying an image on a web, the dpi (or more correctly ppi) setting is irrelevant. It's only the size in pixels that is relevant.
You can convert an image on the fly, but it is very work intensive for the server to do that every time the image is displayed. You should create the sizes that you need when the user uploads the image.

Categories

Resources