Advice needed please - How to display an image from a file quickly?

I have a project that displays DPX files (an image file format common in digital film) in a PictureBox on a form. It works but takes several seconds to display a typical image that is 2048x 1556 pixels. Not so bad but .NET can display a similarly sized TIFF file in under a second.

My display routine is primitive it just reads each 32 bit word from the file, unpacks the three 10 bit RGB values to 8 bits and finally sets the pixel value in a bitmap with the unpacked 8bit RGB values and repeats for the 3 million plus pixels involved!

Doe anyone have any ideas how I might be able to approach the speed of .NET on a TIFF?

Thanks in advance
Steve

[661 byte] By [SteveJo] at [2007-12-16]
# 1
There is a way to create a bitmap from an array of integers, that lets you operate on the bitmap pixels directly with incredible speed. Post your code and we can see how to improve it.
FrankHileman at 2007-9-9 > top of Msdn Tech,Visual C#,Visual C# General...
# 2

Thanks for the reply.

Here's the code snippet that gets and processes the data into a bitmap.

Bitmap preview = new Bitmap(xpixels, ypixels, PixelFormat.Format24bppRgb);

//get to the image data
r.BaseStream.Position = image_data_offset;

//main loop: for every pixel in file set the bitmap pixel
for (int y = 0; y < ypixels; y++)
{
for (int x = 0; x < xpixels; x++)
{
dpxpixel = r.ReadInt32();

//winpixelblue = (dpxpixel >> 4) & Rmask;
//winpixelgreen = (dpxpixel >> 14 ) &Rmask;
//winpixelred = (dpxpixel >> 24) & Rmask;

preview.SetPixel(x, y, Color.FromArgb((dpxpixel >> 24) &Rmask,
(dpxpixel >> 14) & Rmask, (dpxpixel >> 4) & Rmask));
}
}
//tidy up
r.Close();

//set the image property and return true;
this.mpreviewimage = (Image) preview;
return true;

Rmask is 0x000000ff to mask off the RGB values to 8 bit.

I have wondered about the bitmap constructor that takes a pointer but haven't had the courage to experiment yet. Another idea was to read everything in one hit and then work through it.

Any pointers (!) you can offer will be most welcome.

Cheers
Steve

SteveJo at 2007-9-9 > top of Msdn Tech,Visual C#,Visual C# General...
# 3

The problem is SetPixel. The stream may be a bottleneck after you fix the SetPixel problem. Here is a class for extremely fast bitmap processing, without using unsafe code -- actually it seems to be faster than unsafe.

We are using the 32bppPARGB format as this is the format ultimately used internally by GDI+. It is the fastest format. For each integer in the Integers array, the most significant byte is the alpha, followed by the red byte, then green, then blue. So to get the alpha: value >> 24. To get the red: (value >> 16) & 0xFF.



using System;
using System.Collections;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Windows.Forms;
using System.Runtime.InteropServices;

internal sealed class EffectBuffer
{
private uint[] ints;
private GCHandle handle;
private Bitmap bitmap;

public EffectBuffer(int bufferWidth, int bufferHeight)
{
AllocateBitmap(bufferWidth, bufferHeight);
}

public void AllocateBitmap(int width, int height)
{
if (Width == width && Height == height)
return;
DisposeBitmap();
if (width == 0 || height == 0)
return;
// 4 bytes per pixel, format: ARGB
ints = new uint[width * height];
// if not pinned the GC can move around the array
handle = GCHandle.Alloc(ints, GCHandleType.Pinned);
IntPtr pointer = Marshal.UnsafeAddrOfPinnedArrayElement(ints, 0);
bitmap = new Bitmap(width , height, width * 4, PixelFormat.Format32bppPArgb, pointer);
}

public void Dispose()
{
DisposeBitmap();
}

private void DisposeBitmap()
{
if (bitmap != null)
{
handle.Free();
bitmap.Dispose();
bitmap = null;
ints = null;
}
}

/// <summary>
/// A bitmap that can be rendered upon, or rendered to another graphics.
/// </summary>
public Bitmap Bitmap
{
get { return bitmap; }
}

/// <summary>Width of <see cref="Bitmap"/>.</summary>
public int Width
{
get { return bitmap == null ? 0 : bitmap.Width; }
}

/// <summary>Height of <see cref="Bitmap"/>.</summary>
public int Height
{
get { return bitmap == null ? 0 : bitmap.Height; }
}

/// <summary>
/// The integers that make up the pixels in <see cref="Bitmap"/>. The
/// pixels can be modified directly by modifying this array.
/// The pixels must contain premuliplied ARGB: R, G, and B
/// components cannot be larger than the A component.
/// </summary>
public uint[] Integers
{
get { return ints; }
}
}


FrankHileman at 2007-9-9 > top of Msdn Tech,Visual C#,Visual C# General...
# 4
Thanks a million!
Your direct access to the integers in the bitmap worked a treat.
Using setpixel took around 7 seconds to process the file.
Using EffectBuffer.Integers to allow direct access the bitmap brings the time down to about 0.9 secs - an improvement beyond my wildest dreams.
Thanks again
Steve
SteveJo at 2007-9-9 > top of Msdn Tech,Visual C#,Visual C# General...
# 5
Maybe you can reassure me. I've taken care to call the Dispose() method when I'm done with the image but looking with task manager the memory doesn't seem to be released.
In fact every time I create an EffectBuffer with a different size that amount of memory gets added to the memory usage. Looking at your code I can see that before a new size is allocated space the old one is released so am not really sure what is going on.
It may be that the Garbage collector will sort things out when it needs to but i've had the memory usage up to 200MB from a no image norm of 25MB.
Any ideas what might be happening?
Cheers
Steve
SteveJo at 2007-9-9 > top of Msdn Tech,Visual C#,Visual C# General...
# 6
There is a tool called the CLRProfiler that can give you more information. If possible, I recommend keeping the same EffectBuffer around, and not allocating a new one.

When DisposeBitmap is called, both the handle and the Bitmap are disposed. That should free the block of memory used for pixels and make it eligible for garbage collection. However, if the block is larger than 85K (I think) it will go into the large object heap (LOH) and not the regular heap. In this heap, memory is not compacted, but even if not compacted, it is free, so eventually it should be reused. So it could be the GC is just not being agressive enough. Does it eventually stop getting larger? Does GC.Collect make any difference, after DisposeBitmap?

I don't think there is a bug in GCHandle.

The CLR profiler will show you exactly what is happening in both heaps.

FrankHileman at 2007-9-9 > top of Msdn Tech,Visual C#,Visual C# General...
# 7
An idea a friend had, for lots of small bitmaps (<85K), is to allocate one huge chunk of pixels, and dole out pieces of that. I don't think that is necessarily a good idea. His reasoning was that the GCHandle pin might fragment memory -- the GC cannot compact pinned memory. So by allocating one big chunk you can manage the fragmentation yourself.

Firstly this is only of interest if you have lots of small bitmaps -- 85K is small. Secondly you would have to prevent fragmentation within your own array. So I don't see a big advantage, but I thow the idea out there anyway.

FrankHileman at 2007-9-9 > top of Msdn Tech,Visual C#,Visual C# General...
# 8
My effectbuffer falls into the large category - a typical 2K digital film inage is about 12 MB, a 4K image in 50MB. I don't think I can just use the one as I'm dealing with lots of different sized images.
I've had another play around and the memory does not keep on growing it does get reclaimed - seems to be when a new effectbuffer is created - from time to time. Adding a GC.Collect gets it back straightaway. I guess it's something to do with the large object heap you mentioned.
I've downloaded CLR Profiler but am not sure it is compatible with the Beta 2 .Net I'm using.
Thanks for the help - I think I'm just going to move on and keep an eye out for memory usage issues but all seems to be OK.
Cheers
Steve
SteveJo at 2007-9-9 > top of Msdn Tech,Visual C#,Visual C# General...
# 9

Hi,

I got a similar problem: I have to show a sequence of bitmaps from the hard disk on the display for simulation purpose.

This job must be done in a minimum of time, because the main issue is the analysis of the shown content. So I found your thread..

My question is: how can I make my program using one and the same part of memory each time a new bitmap is loaded? This is necessary due to avoiding out-of-memory exceptions and due to increasing speed. All the pictures have the same size and pixelformat.

So maybe you know a way to realize it using managed code (programming in C++/CLI ...but understanding C# too)

Thanks

Staehff

Staehff at 2007-9-9 > top of Msdn Tech,Visual C#,Visual C# General...
# 10

Hi Staehff,

If you control the bitmap format, you can read in the bytes and push them into the effect buffer as pixels (4 bytes each). If you do not control the format or you wish to use the Bitmap loading methods, you have to accept that they will allocate memory, and you cannot access that memory as you can with a pinned array. The reason is, there is a conversion process during the loading of a from the disk (unless you control that). Even if you load the bitmap yourself, you need a binary stream, so unless you construct that binary stream to use the same pinned array as your effect buffer, it will also allocate memory.

In short it is possible but difficult without controlling the bitmap format entirely.

Regards,

Frank Hileman

www.vgdotnet.com

FrankHileman at 2007-9-9 > top of Msdn Tech,Visual C#,Visual C# General...