Click here to Skip to main content
13,348,874 members (78,484 online)
Click here to Skip to main content
Add your own
alternative version


41 bookmarked
Posted 30 Sep 2003

C# Wrapper to the FreeImage DLL for graphical image format conversion

, 30 Sep 2003
Rate this:
Please Sign up or sign in to vote.
This article provides a simplified C# wrapper to the FreeImage project for graphical file format conversion.


FreeImage is an extremely useful Open Source project for reading, manipulating and converting a large amount of graphical formats. However at present the library exists as Win32 Dynamic Link Library. As my latest fascination is C#, I decided to create a simple Interop wrapper for it.

Using the code

The code is very straightforward to use, which is a reflection on the FreeImage implementation itself. For my needs I wanted to be able to take a picture file in Portable Bitmap (PBM) format and convert it to a standard Bitmap (BMP). For this I needed 3 methods exposed via the FreeImage DLL, FreeImage_Load, FreeImage_Save and FreeImage_Unload, the signature for which are:

       const char *filename, int flags FI_DEFAULT(0));
   FIBITMAP *dib, const char *filename, int flags FI_DEFAULT(0)); 
DLL_API void DLL_CALLCONV FreeImage_Unload(FIBITMAP* dib);

Now the C# Interop signatures for these are:

public class FreeImage
     public static extern int FreeImage_Load(FIF format,
                    string filename, int flags);

     public static extern void FreeImage_Unload(int handle);

     public static extern bool FreeImage_Save(FIF format,
        int handle, string filename, int flags);

As an example, to convert a graphical image from PBM to BMP, all you need to do is the following (note the enum FIF is not documented here, but can be found in the source code).

namespace Test
    class MainClass
        public static void Main(string[] args)
            int handle = FreeImageAPI.FreeImage.FreeImage_Load(


This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


About the Author

David Boland
Web Developer
United States United States
No Biography provided

You may also be interested in...


Comments and Discussions

QuestionConversion of JPG to TGA gives zero KB TGA file Pin
dineshlepakshi16-Dec-13 15:24
memberdineshlepakshi16-Dec-13 15:24 
QuestionHow can I convert to JPG 2000 Pin
AmerSawan28-Dec-12 5:21
memberAmerSawan28-Dec-12 5:21 
GeneralStill no luck on Windows Mobile 6 Pin
Paul Kamath29-Oct-10 7:13
memberPaul Kamath29-Oct-10 7:13 
Generalfreeimage.dll must be in your windows\system32 folder Pin
Paul Kamath29-Oct-10 3:47
memberPaul Kamath29-Oct-10 3:47 
Generalc# compact frmework Pin
vertex_x3-Jun-08 11:22
membervertex_x3-Jun-08 11:22 
GeneralRe: c# compact frmework Pin
Riyas Aboobaker24-Mar-09 7:28
memberRiyas Aboobaker24-Mar-09 7:28 
GeneralRe: c# compact frmework Pin
Riyas Aboobaker24-Mar-09 19:39
memberRiyas Aboobaker24-Mar-09 19:39 
QuestionHelp, I am getting zero file size as result Pin
Romashka_NN9-Apr-07 8:54
memberRomashka_NN9-Apr-07 8:54 
AnswerRe: Help, I am getting zero file size as result Pin
NoEscom15-Nov-07 22:37
memberNoEscom15-Nov-07 22:37 
GeneralResquest.OutputStream Pin
kernellius4-Apr-07 13:03
memberkernellius4-Apr-07 13:03 
QuestionWhat about large images? Pin
Gerrit Horeis6-Oct-06 4:17
memberGerrit Horeis6-Oct-06 4:17 
Questionhelp Pin
xenia gr3-Sep-06 23:48
memberxenia gr3-Sep-06 23:48 
QuestionHow to convert double pointers (**)? Pin
vor0nwe26-Apr-06 6:37
membervor0nwe26-Apr-06 6:37 
QuestionI can't make C# works with FreeImage Pin
mesh20055-Mar-06 22:41
membermesh20055-Mar-06 22:41 
Questioninserting images in word document? Pin
Hiral Patel27-Nov-05 0:51
memberHiral Patel27-Nov-05 0:51 
GeneralGrayscale Pin
EmailSolidale21-Sep-05 21:55
memberEmailSolidale21-Sep-05 21:55 
GeneralC:\projects\SVGTest\SVG.cs(145): Argument '2': cannot convert from 'int' to 'uint' Pin
eric paul2-Aug-05 13:12
membereric paul2-Aug-05 13:12 
GeneralRe: C:\projects\SVGTest\SVG.cs(145): Argument '2': cannot convert from 'int' to 'uint' Pin
David Boland2-Aug-05 13:32
memberDavid Boland2-Aug-05 13:32 
Here is the interrop code I have for the Win32 FreeImage.dll library. As you can see this definition using unsigned int to represent the FIBITMAP object within the C# application. What I suggest is that you remove your reference to FreeImageNet.dll, paste this code into your own class and change you project to reference your interrop class. If your still getting problems then I can only suggest you change the definition or you make sure your using a compatible version of the FreeImage.dll file itself:

using System;
using System.IO;
using System.Runtime.InteropServices;

namespace FreeImageAPI
using PVOID = IntPtr;
using FIBITMAP = UInt32;

public class RGBQUAD
public byte rgbBlue;
public byte rgbGreen;
public byte rgbRed;
public byte rgbReserved;

public uint size;
public int width;
public int height;
public ushort biPlanes;
public ushort biBitCount;
public uint biCompression;
public uint biSizeImage;
public int biXPelsPerMeter;
public int biYPelsPerMeter;
public uint biClrUsed;
public uint biClrImportant;

public class BITMAPINFO
public BITMAPINFOHEADER bmiHeader;
public RGBQUAD bmiColors;

public enum FIF
FIF_BMP = 0,
FIF_ICO = 1,
FIF_JNG = 3,
FIF_LBM = 5,
FIF_MNG = 6,
FIF_PBM = 7,
FIF_PCD = 9,
FIF_PCX = 10,
FIF_PGM = 11,
FIF_PNG = 13,
FIF_PPM = 14,
FIF_RAS = 16,
FIF_TIFF = 18,
FIF_WBMP = 19,
FIF_PSD = 20,
FIF_CUT = 21,
FIF_XBM = 22,
FIF_XPM = 23,
FIF_DDS = 24,
FIF_GIF = 25

public enum FI_QUANTIZE

public enum FI_DITHER
FID_FS = 0,
FID_BAYER4x4 = 1,
FID_BAYER8x8 = 2,

public enum FI_FILTER

public enum FI_COLOR_CHANNEL

public enum FIT // FREE_IMAGE_TYPE
FIT_UINT16 = 2,
FIT_INT16 = 3,
FIT_UINT32 = 4,
FIT_INT32 = 5,

public delegate void FreeImage_OutputMessageFunction(FIF format, string msg);

public class FreeImage
// Init/Error routines ----------------------------------------
[DllImport("FreeImage.dll", EntryPoint="FreeImage_Initialise")]
public static extern void Initialise(bool loadLocalPluginsOnly);

// alias for Americans Smile | :)
[DllImport("FreeImage.dll", EntryPoint="FreeImage_Initialise")]
public static extern void Initialize(bool loadLocalPluginsOnly);

[DllImport("FreeImage.dll", EntryPoint="FreeImage_DeInitialise")]
public static extern void DeInitialise();

// alias for Americians Smile | :)
[DllImport("FreeImage.dll", EntryPoint="FreeImage_DeInitialise")]
public static extern void DeInitialize();

// Version routines -------------------------------------------
[DllImport("FreeImage.dll", EntryPoint="FreeImage_GetVersion")]
public static extern string GetVersion();

[DllImport("FreeImage.dll", EntryPoint="FreeImage_GetCopyrightMessage")]
public static extern string GetCopyrightMessage();

// Message Output routines ------------------------------------
// missing void FreeImage_OutputMessageProc(int fif,
// const char *fmt, ...);

[DllImport("FreeImage.dll", EntryPoint="FreeImage_SetOutputMessage")]
public static extern void SetOutputMessage(FreeImage_OutputMessageFunction omf);

// Allocate/Clone/Unload routines -----------------------------
[DllImport("FreeImage.dll", EntryPoint="FreeImage_Allocate")]
public static extern FIBITMAP Allocate(int width, int height,
int bpp, uint red_mask, uint green_mask, uint blue_mask);

[DllImport("FreeImage.dll", EntryPoint="FreeImage_AllocateT")]
public static extern FIBITMAP AllocateT(FIT ftype, int width,
int height, int bpp, uint red_mask, uint green_mask,
uint blue_mask);

[DllImport("FreeImage.dll", EntryPoint="FreeImage_Clone")]
public static extern FIBITMAP Clone(FIBITMAP dib);

[DllImport("FreeImage.dll", EntryPoint="FreeImage_Unload")]
public static extern void Unload(FIBITMAP dib);

// Load/Save routines -----------------------------------------
[DllImport("FreeImage.dll", EntryPoint="FreeImage_Load")]
public static extern FIBITMAP Load(FIF format, string filename, int flags);

// missing FIBITMAP FreeImage_LoadFromHandle(FIF fif,
// FreeImageIO *io, fi_handle handle, int flags);

[DllImport("FreeImage.dll", EntryPoint="FreeImage_Save")]
public static extern bool Save(FIF format, FIBITMAP dib, string filename, int flags);

// missing BOOL FreeImage_SaveToHandle(FIF fif, FIBITMAP *dib,
// FreeImageIO *io, fi_handle handle, int flags);

// Plugin interface -------------------------------------------
// missing FIF FreeImage_RegisterLocalPlugin(FI_InitProc proc_address,
// const char *format, const char *description,
// const char *extension, const char *regexpr);
// missing FIF FreeImage_RegisterExternalPlugin(const char *path,
// const char *format, const char *description,
// const char *extension, const char *regexpr);

[DllImport("FreeImage.dll", EntryPoint="FreeImage_GetFIFCount")]
public static extern int GetFIFCount();

[DllImport("FreeImage.dll", EntryPoint="FreeImage_SetPluginEnabled")]
public static extern int SetPluginEnabled(FIF format, bool enabled);

[DllImport("FreeImage.dll", EntryPoint="FreeImage_IsPluginEnabled")]
public static extern int IsPluginEnabled(FIF format);

[DllImport("FreeImage.dll", EntryPoint="FreeImage_GetFIFFromFormat")]
public static extern FIF GetFIFFromFormat(string format);

[DllImport("FreeImage.dll", EntryPoint="FreeImage_GetFIFFromMime")]
public static extern FIF GetFIFFromMime(string mime);

[DllImport("FreeImage.dll", EntryPoint="FreeImage_GetFormatFromFIF")]
public static extern string GetFormatFromFIF(FIF format);

[DllImport("FreeImage.dll", EntryPoint="FreeImage_GetFIFExtensionList")]
public static extern string GetFIFExtensionList(FIF format);

[DllImport("FreeImage.dll", EntryPoint="FreeImage_GetFIFDescription")]
public static extern string GetFIFDescription(FIF format);

[DllImport("FreeImage.dll", EntryPoint="FreeImage_GetFIFRegExpr")]
public static extern string GetFIFRegExpr(FIF format);

[DllImport("FreeImage.dll", EntryPoint="FreeImage_GetFIFFromFilename")]
public static extern FIF GetFIFFromFilename(string filename);

[DllImport("FreeImage.dll", EntryPoint="FreeImage_FIFSupportsReading")]
public static extern bool FIFSupportsReading(FIF format);

[DllImport("FreeImage.dll", EntryPoint="FreeImage_FIFSupportsWriting")]
public static extern bool FIFSupportsWriting(FIF format);

[DllImport("FreeImage.dll", EntryPoint="FreeImage_FIFSupportsExportBPP")]
public static extern bool FIFSupportsExportBPP(FIF format, int bpp);

[DllImport("FreeImage.dll", EntryPoint="FreeImage_FIFSupportsExportType")]
public static extern bool FIFSupportsExportType(FIF format, FIT ftype);

[DllImport("FreeImage.dll", EntryPoint="FreeImage_FIFSupportsICCProfiles")]
public static extern bool FIFSupportsICCProfiles(FIF format, FIT ftype);

// Multipage interface ----------------------------------------
[DllImport("FreeImage.dll", EntryPoint="FreeImage_OpenMultiBitmap")]
public static extern FIMULTIBITMAP OpenMultiBitmap(
FIF format, string filename, bool createNew, bool readOnly, bool keepCacheInMemory);

[DllImport("FreeImage.dll", EntryPoint="FreeImage_CloseMultiBitmap")]
public static extern long CloseMultiBitmap(FIMULTIBITMAP bitmap, int flags);

[DllImport("FreeImage.dll", EntryPoint="FreeImage_GetPageCount")]
public static extern int GetPageCount(FIMULTIBITMAP bitmap);

[DllImport("FreeImage.dll", EntryPoint="FreeImage_AppendPage")]
public static extern void AppendPage(FIMULTIBITMAP bitmap, FIBITMAP data);

[DllImport("FreeImage.dll", EntryPoint="FreeImage_InsertPage")]
public static extern void InsertPage(FIMULTIBITMAP bitmap, int page, FIBITMAP data);

[DllImport("FreeImage.dll", EntryPoint="FreeImage_DeletePage")]
public static extern void DeletePage(FIMULTIBITMAP bitmap, int page);

[DllImport("FreeImage.dll", EntryPoint="FreeImage_LockPage")]
public static extern FIBITMAP LockPage(FIMULTIBITMAP bitmap, int page);

[DllImport("FreeImage.dll", EntryPoint="FreeImage_UnlockPage")]
public static extern void UnlockPage(FIMULTIBITMAP bitmap, int page, bool changed);

[DllImport("FreeImage.dll", EntryPoint="FreeImage_MovePage")]
public static extern bool MovePage(FIMULTIBITMAP bitmap, int target, int source);

[DllImport("FreeImage.dll", EntryPoint="FreeImage_GetLockedPageNumbers")]
public static extern bool GetLockedPageNumbers(FIMULTIBITMAP bitmap, IntPtr pages, IntPtr count);

// File type request routines ---------------------------------
[DllImport("FreeImage.dll", EntryPoint="FreeImage_GetFileType")]
public static extern FIF GetFileType(string filename, int size);

// missing FIF FreeImage_GetFileTypeFromHandle(FreeImageIO *io,
// fi_handle handle, int size);

// Image type request routines --------------------------------
[DllImport("FreeImage.dll", EntryPoint="FreeImage_GetImageType")]
public static extern FIT GetImageType(FIBITMAP dib);

// Info functions ---------------------------------------------
[DllImport("FreeImage.dll", EntryPoint="FreeImage_IsLittleEndian")]
public static extern bool IsLittleEndian();

// Pixel access functions -------------------------------------
[DllImport("FreeImage.dll", EntryPoint="FreeImage_GetBits")]
public static extern IntPtr GetBits(FIBITMAP dib);

[DllImport("FreeImage.dll", EntryPoint="FreeImage_GetScanLine")]
public static extern IntPtr GetScanLine(FIBITMAP dib, int scanline);

[DllImport("FreeImage.dll", EntryPoint="FreeImage_GetPixelIndex")]
public static extern bool GetPixelIndex(FIBITMAP dib, uint x, uint y, byte value);

[DllImport("FreeImage.dll", EntryPoint="FreeImage_GetColorsUsed")]
public static extern uint GetColorsUsed(FIBITMAP dib);

[DllImport("FreeImage.dll", EntryPoint="FreeImage_GetBPP")]
public static extern uint GetBPP(FIBITMAP dib);

[DllImport("FreeImage.dll", EntryPoint="FreeImage_GetWidth")]
public static extern uint GetWidth(FIBITMAP dib);

[DllImport("FreeImage.dll", EntryPoint="FreeImage_GetHeight")]
public static extern uint GetHeight(FIBITMAP dib);

[DllImport("FreeImage.dll", EntryPoint="FreeImage_GetLine")]
public static extern uint GetLine(FIBITMAP dib);

[DllImport("FreeImage.dll", EntryPoint="FreeImage_GetPitch")]
public static extern uint GetPitch(FIBITMAP dib);

[DllImport("FreeImage.dll", EntryPoint="FreeImage_GetDIBSize")]
public static extern uint GetDIBSize(FIBITMAP dib);

[DllImport("FreeImage.dll", EntryPoint="FreeImage_GetPalette")]
[return: MarshalAs(UnmanagedType.LPStruct)]
public static extern RGBQUAD GetPalette(FIBITMAP dib);

[DllImport("FreeImage.dll", EntryPoint="FreeImage_GetDotsPerMeter")]
public static extern uint GetDotsPerMeterX(FIBITMAP dib);

[DllImport("FreeImage.dll", EntryPoint="FreeImage_GetDotsPerMeterY")]
public static extern uint GetDotsPerMeterY(FIBITMAP dib);

[DllImport("FreeImage.dll", EntryPoint="FreeImage_GetInfoHeader")]
[return: MarshalAs(UnmanagedType.LPStruct)]
public static extern BITMAPINFOHEADER GetInfoHeader(FIBITMAP dib);

[DllImport("FreeImage.dll", EntryPoint="FreeImage_GetInfo")]
[return: MarshalAs(UnmanagedType.LPStruct)]
public static extern BITMAPINFO GetInfo(FIBITMAP dib);

[DllImport("FreeImage.dll", EntryPoint="FreeImage_GetColorType")]
public static extern int GetColorType(FIBITMAP dib);

[DllImport("FreeImage.dll", EntryPoint="FreeImage_GetRedMask")]
public static extern uint GetRedMask(FIBITMAP dib);

[DllImport("FreeImage.dll", EntryPoint="FreeImage_GetGreenMask")]
public static extern uint GetGreenMask(FIBITMAP dib);

[DllImport("FreeImage.dll", EntryPoint="FreeImage_GetBlueMask")]
public static extern uint GetBlueMask(FIBITMAP dib);

[DllImport("FreeImage.dll", EntryPoint="FreeImage_GetTransparencyCount")]
public static extern uint GetTransparencyCount(FIBITMAP dib);

[DllImport("FreeImage.dll", EntryPoint="FreeImage_GetTransparencyTable")]
public static extern IntPtr GetTransparencyTable(FIBITMAP dib);

[DllImport("FreeImage.dll", EntryPoint="FreeImage_SetTransparent")]
public static extern void SetTransparent(FIBITMAP dib, bool enabled);

[DllImport("FreeImage.dll", EntryPoint="FreeImage_IsTransparent")]
public static extern bool IsTransparent(FIBITMAP dib);

[DllImport("FreeImage.dll", EntryPoint="FreeImage_ConvertTo8Bits")]
public static extern FIBITMAP ConvertTo8Bits(FIBITMAP dib);

[DllImport("FreeImage.dll", EntryPoint="FreeImage_ConvertTo16Bits555")]
public static extern FIBITMAP ConvertTo16Bits555(FIBITMAP dib);

[DllImport("FreeImage.dll", EntryPoint="FreeImage_ConvertTo16Bits565")]
public static extern FIBITMAP ConvertTo16Bits565(FIBITMAP dib);

[DllImport("FreeImage.dll", EntryPoint="FreeImage_ConvertTo24Bits")]
public static extern FIBITMAP ConvertTo24Bits(FIBITMAP dib);

[DllImport("FreeImage.dll", EntryPoint="FreeImage_ConvertTo32Bits")]
public static extern FIBITMAP ConvertTo32Bits(FIBITMAP dib);

[DllImport("FreeImage.dll", EntryPoint="ColorQuantize")]
public static extern FIBITMAP ColorQuantize(FIBITMAP dib, FI_QUANTIZE quantize);

[DllImport("FreeImage.dll", EntryPoint="FreeImage_Threshold")]
public static extern FIBITMAP Threshold(FIBITMAP dib, uint t);

[DllImport("FreeImage.dll", EntryPoint="FreeImage_Dither")]
public static extern FIBITMAP Dither(FIBITMAP dib, FI_DITHER algorithm);

[DllImport("FreeImage.dll", EntryPoint="FreeImage_ConvertFromRawBits")]
public static extern FIBITMAP ConvertFromRawBits(byte[] bits, int width, int height,
int pitch, uint bpp, uint redMask, uint greenMask, uint blueMask, bool topDown);

[DllImport("FreeImage.dll", EntryPoint="FreeImage_ConvertToRawBits")]
public static extern void ConvertToRawBits(IntPtr bits, FIBITMAP dib, int pitch,
uint bpp, uint redMask, uint greenMask, uint blueMask, bool topDown);

[DllImport("FreeImage.dll", EntryPoint="FreeImage_RotateClassic")]
public static extern FIBITMAP RotateClassic(FIBITMAP dib, Double angle);

[DllImport("FreeImage.dll", EntryPoint="FreeImage_RotateEx")]
public static extern FIBITMAP RotateEx(
FIBITMAP dib, Double angle, Double xShift, Double yShift, Double xOrigin, Double yOrigin, bool useMask);

[DllImport("FreeImage.dll", EntryPoint="FreeImage_FlipHorizontal")]
public static extern bool FlipHorizontal(FIBITMAP dib);

[DllImport("FreeImage.dll", EntryPoint="FreeImage_FlipVertical")]
public static extern bool FlipVertical(FIBITMAP dib);

[DllImport("FreeImage.dll", EntryPoint="FreeImage_Rescale")]
public static extern FIBITMAP Rescale(FIBITMAP dib, int dst_width, int dst_height, FI_FILTER filter);

[DllImport("FreeImage.dll", EntryPoint="FreeImage_AdjustCurve")]
public static extern bool AdjustCurve(FIBITMAP dib, byte[] lut, FI_COLOR_CHANNEL channel);

[DllImport("FreeImage.dll", EntryPoint="FreeImage_AdjustGamma")]
public static extern bool AdjustGamma(FIBITMAP dib, Double gamma);

[DllImport("FreeImage.dll", EntryPoint="FreeImage_AdjustBrightness")]
public static extern bool AdjustBrightness(FIBITMAP dib, Double percentage);

[DllImport("FreeImage.dll", EntryPoint="FreeImage_AdjustContrast")]
public static extern bool AdjustContrast(FIBITMAP dib, Double percentage);

[DllImport("FreeImage.dll", EntryPoint="FreeImage_Invert")]
public static extern bool Invert(FIBITMAP dib);

[DllImport("FreeImage.dll", EntryPoint="FreeImage_GetHistogram")]
public static extern bool GetHistogram(FIBITMAP dib, int histo, FI_COLOR_CHANNEL channel);

[DllImport("FreeImage.dll", EntryPoint="FreeImage_GetChannel")]
public static extern bool GetChannel(FIBITMAP dib, FI_COLOR_CHANNEL channel);

[DllImport("FreeImage.dll", EntryPoint="FreeImage_SetChannel")]
public static extern bool SetChannel(FIBITMAP dib, FIBITMAP dib8, FI_COLOR_CHANNEL channel);

[DllImport("FreeImage.dll", EntryPoint="FreeImage_Copy")]
public static extern FIBITMAP Copy(FIBITMAP dib, int left, int top, int right, int bottom);

[DllImport("FreeImage.dll", EntryPoint="FreeImage_Paste")]
public static extern bool Paste(FIBITMAP dst, FIBITMAP src, int left, int top, int alpha);

GeneralRe: C:\projects\SVGTest\SVG.cs(145): Argument '2': cannot convert from 'int' to 'uint' Pin
minorello5-Aug-05 4:46
memberminorello5-Aug-05 4:46 
GeneralRe: C:\projects\SVGTest\SVG.cs(145): Argument '2': cannot convert from 'int' to 'uint' Pin
David Boland5-Aug-05 6:53
memberDavid Boland5-Aug-05 6:53 
Generaland now FreeImaged.dll :( Pin
leoni5101-Jul-05 8:23
memberleoni5101-Jul-05 8:23 
GeneralRe: and now FreeImaged.dll :( Pin
David Boland1-Jul-05 8:35
memberDavid Boland1-Jul-05 8:35 
GeneralRe: and now FreeImaged.dll :( Pin
leoni5101-Jul-05 22:24
memberleoni5101-Jul-05 22:24 
GeneralRe: and now FreeImaged.dll :( Pin
alex_mp3-Dec-05 14:49
memberalex_mp3-Dec-05 14:49 
GeneralI cant create FreeImage.dll Pin
leoni51026-Jun-05 9:19
memberleoni51026-Jun-05 9:19 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.180111.1 | Last Updated 1 Oct 2003
Article Copyright 2003 by David Boland
Everything else Copyright © CodeProject, 1999-2018
Layout: fixed | fluid