Abstract

With the release of the GDI+ API, Microsoft significantly increased the power and flexibility of its graphics API, while at the same time increasing the complexity and surface-area of an already obfuscated GDI API. Fortunately, the .NET Framework provides the System.Drawing namespace hierarchy, wrapping much of the GDI+ API in a (somewhat) manageable facade. Unfortunately, much complexity remains, and consequently many simple image manipulation tasks, such as resizing a bitmap, cropping a region, or converting from one format to another, are either complicated or not immediately obvious, and sometimes both.

In this article, I present my BitmapManipulator class, written in C#, which implements some common image manipulation functions, as well as advanced capabilities like alpha-blending an overlay image atop a bitmap, while hiding messy GDI+ details. I originally developed this class for use in a web-based photo album application, and as a result the emphasis is on simplicity and ease of use, as well as performance. In spite of its origins, this class and the code therein should be suitable to a variety of applications, as evidenced by the Windows Forms test program included in the source archive.

Introduction

This article presents the BitmapManipulator class, reviews its functionality, and walks the reader through key elements of the source code, pointing out interesting techniques or caveats. For a reader interested only in using the class in an application, the code walk-through sections may be skipped, though all readers are encouraged to review the entire article.

The attached source archive contains Visual Studio.NET 2002 project files, however the source code should work with either VS.NET 2K2 or VS.NET 2K3.

Background

BitmapManipulator is built atop the .NET Framework GDI+ wrapper classes, which reside in the System.Drawing namespace. While a comprehensive survey of System.Drawing is well beyond the scope of this article, a cursory review will likely be useful.

All images are represented by the System.Drawing.Image class, or one of its derivatives. Image provides the base properties and methods applicable to all types of images. Its subclasses System.Drawing.Bitmap and System.Drawing.Imaging.Metafile provide additional functionality for bitmapped images (that is, raster images; images described by pixels), and metafile images, respectively. For the remainder of this article, we will focus our attention on the Bitmap class, which is used to represent not only BMP files, but JPEG, GIF, PNG, TIFF, and all other raster file formats supported by GDI+.

In addition to the Bitmap class, System.Drawing.Graphics features prominently in the code for BitmapManipulator. Graphics is the abstraction used to represent a "display device", though in this context "display device" means more than a monitor. Conceptually, in GDI+ almost all manipulations of a bitmap (such as rotating, cropping, etc) are not performed on a Bitmap at all, but rather, on a Graphics object. In turn, that Graphics object may represent the screen, a printer, or (in our case) the image content of a Bitmap object. Those of you familiar with the Win32 GDI API will recognize that Graphics is an object-oriented form of the GDI Device Context (DC).

In addition to Bitmap and Graphics, a few enumerations such as PixelFormat, and classes such as ImageFormat, will be encountered below. Fortunately, most of these instances are fairly self-explanatory, therefore the overview provided above should be sufficient to at least begin to review the implementation of BitmapManipulator.

BitmapManipulator Gestalt

BitmapManipulator is a static class, implemented in a single C# source file. From a design standpoint, it follows the pattern my colleague Richard Cooper calls "Function Bucket"; that is, it simply exposes static methods which are completely self-contained, and at best tangentially related to one another. While this pattern can be undesirable in some cases, in this case it makes for a coherent, concise, easy to use class that can be dropped in anywhere.

In general, each function performs some sort of manipulation on a bitmap. To that end, each function takes at least a Bitmap object, as well as any parameters necessary for the operation to be performed. Further, each function returns a Bitmap, which is always a separate Bitmap instance from that passed in. At the time I wrote BitmapManipulator, I required this functionality for obscure reasons, however it can be advantageous in other situations as well. One must remember, however, to Dispose() the input Bitmap when it is no longer needed, to make most efficient use of resources.

An example of a BitmapManipulator function is below:

publicstatic Bitmap ScaleBitmap(Bitmap inputBmp, double scaleFactor)

In this case, in addition to the input Bitmap parameter, a scale factor controls the scale operation to be performed. With few exceptions, most methods of BitmapManipulator take this form.

Though various overloaded forms of this method are provided, they all perform the same task: retrieve an image file from a URI, load that file into a Bitmap, and return the resulting object.

The only interesting point to note about these methods is the wrapper around the WebRequest/WebResponse classes, and their associated exceptions: when I was writing BitmapManipulator, I needed to present a meaningful error message to users when image downloading failed, therefore in these methods I trap the exceptions associated with the usual HTTP errors, and package them into a catch-all BitmapManipException, along with a (somewhat) user-friendly error message. The original exception is always available in the InnerException property, of course, but this feature makes it easy to integrate image download functionality into an application without exposing users to any sharp edges.

ConvertBitmap

As the name implies, each of the overloads of this method convert from one bitmap format to another (e.g., JPEG to GIF or TIFF to PNG). The code required to do this is an example of the simple yet non-obvious contortions one often goes through with GDI+:

//Create an in-memory stream which will be used to save//the converted image
System.IO.Stream imgStream = new System.IO.MemoryStream();
//Save the bitmap out to the memory stream, //using the format indicated by the caller
inputBmp.Save(imgStream, destFormat);
//At this point, imgStream contains the binary form of the//bitmap in the target format. All that remains is to load it//into a new bitmap object
Bitmap destBitmap = new Bitmap(imgStream);

That's right; the image is saved to a memory stream in the destination file format, then loaded from the stream into a new Bitmap object. Absurd, but functional.

ConvertBitmapToJpeg

A special case of ConvertBitmap, this function converts an bitmap to JPEG, with the additional option to specify the quality parameter of the JPEG encoder. This parameter varies between 0 (horrific distortion, excellent compression) to 100 (lossless, minimal compression). I originally required this functionality because, in my photo album application, I wanted to force user photos to a quality level of 50 or less, to minimize disk space usage. Passing -1 for the quality is equivalent to using ConvertBitmap with a target format of JPEG.

Unfortunately, specifying the quality parameter to the JPEG encoder is non-trivial, and non-obvious. As this seems to be a frequently asked question, it is likely useful to present the code required:

//Create an in-memory stream which will be used to save//the converted image
System.IO.Stream imgStream = new System.IO.MemoryStream();
//Get the ImageCodecInfo for the desired target format
ImageCodecInfo destCodec = FindCodecForType
(MimeTypeFromImageFormat(ImageFormat.Jpeg));
if (destCodec == null) {
//No codec available for that formatthrownew ArgumentException("The requested format " +
MimeTypeFromImageFormat(ImageFormat.Jpeg) +
" does not have an available codec installed",
"destFormat");
}
//Create an EncoderParameters collection to contain the//parameters that control the dest format's encoder
EncoderParameters destEncParams = new EncoderParameters(1);
//Use quality parameter
EncoderParameter qualityParam = new
EncoderParameter(Encoder.Quality, quality);
destEncParams.Param[0] = qualityParam;
//Save w/ the selected codec and encoder parameters
inputBmp.Save(imgStream, destCodec, destEncParams);
//At this point, imgStream contains the binary form of the//bitmap in the target format. All that remains is to load it//into a new bitmap object
Bitmap destBitmap = new Bitmap(imgStream);

To summarize, one must find the ImageCodecInfo object for the target format (JPEG in this case), know that the undocumented JPEG encoder accepts a parameter Encoder.Quality with a value between 0 and 100, create an EncoderParameter object to represent this parameter, and an EncoderParameters collection to contain the EncoderParameterObject. Finally, one passes the ImageCodecInfo and EncoderParameters objects to the Save method of the Bitmap class, and voila.

ConvertBitmapToTiff

Conceptually, this method is identical to ConvertBitmapToJpeg, except this method converts a bitmap to the TIFF format. While TIFF does not support the quality parameter exposed by the JPEG encoder, it does support a number of compression algorithms based on the color depth of the image. Therefore, instead of the quality parameter, ConvertBitmapToTiff takes a parameter of type BitmapManipulator.TiffCompressionEnum, which can have any one of the following values:

CCITT3

CCITT4

LZW

RLE

None

Unspecified

Be warned that CCITT3, CCITT4, and RLE do not appear to work correctly with 24 or 32-bit TIFF files; an exception is raised from deep within GDI+. Given that this area of GDI+ is virtually undocumented, a few idiosyncrasies are to be expected. This problem can be explored somewhat readily with the sample application included in the source archive.

Passing TiffCompressionEnum.Unspecified is equivalent to calling ConvertBitmap and specifying TIFF as the target format.

The same code used in ConvertBitmapToJpeg applies to ConvertBitmapToTiff, except the magic encoder parameter is compression instead of quality.

ScaleBitmap

Obviously, this function scales the dimensions of a bitmap by a scale factor. Passing 1.0 returns an exact copy of the input bitmap, 2.0 yields a bitmap twice the size of the original, 0.5 a bitmap at half the size, etc.

The implementation of ScaleBitmap is notable for its counter-intuitiveness:

//Create a new bitmap object based on the input
Bitmap newBmp = new Bitmap(
(int)(inputBmp.Size.Width*xScaleFactor),
(int)(inputBmp.Size.Height*yScaleFactor),
PixelFormat.Format24bppRgb);
//Graphics.FromImage doesn't like Indexed pixel format//Create a graphics object attached to the new bitmap
Graphics newBmpGraphics = Graphics.FromImage(newBmp);
//Set the interpolation mode to high quality bicubic//interpolation, to maximize the quality of the scaled image
newBmpGraphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
newBmpGraphics.ScaleTransform((float)xScaleFactor, (float)yScaleFactor);
//Draw the bitmap in the graphics object, which will apply//the scale transform//Note that pixel units must be specified to //ensure the framework doesn't attempt//to compensate for varying horizontal resolutions //in images by resizing; in this case,//that's the opposite of what we want.
Rectangle drawRect = new Rectangle(0, 0,
inputBmp.Size.Width, inputBmp.Size.Height);
newBmpGraphics.DrawImage(inputBmp, drawRect,
drawRect, GraphicsUnit.Pixel);
//Return the bitmap, as the operations on the graphics object//are applied to the bitmap
newBmpGraphics.Dispose();
//newBmp will have a RawFormat of MemoryBmp because it was created//from scratch instead of being based on inputBmp. //Since it it inconvenient//for the returned version of a bitmap to be of a //different format, now convert//the scaled bitmap to the format of the source bitmapreturn ConvertBitmap(newBmp, inputBmp.RawFormat);

Here, a Graphics object is created, bound to a new blank bitmap with the same dimensions as the input bitmap scaled by the scaling factors. The interpolation mode is set appropriately, in this case the slowest but highest quality mode. Next, ScaleTransform is called on the Graphics object, with the x and y scale factors passed as parameters. Note that, at this point, the input image has not been copied to the Graphics object; instead, ScaleTransform is adding a transformation to the "pipeline" for the Graphics object, so that any future operations on the Graphics object will undergo a scaling. This is a powerful concept, but counter-intuitive initially.

Finally, the input image is drawn onto the Graphics object, and since the scale transform is installed, the image will be scaled before being rendered into the previously created blank bitmap.

This concept of a transformation pipeline is central to many of the transformations supported by GDI+.

Since GDI+ does not provide the notion of a resize transform, resizing must be implemented in terms of scaling factors. Due to the precision of floating point math, it is possible the resulting image could be off by a pixel after round-off error, but for most applications this is not critical.

ThumbnailBitmap

Until now, all of the methods we have examined have been somewhat pedestrian. This method, on the other hand, presents a very useful, and somewhat obscure ability: given a bitmap, and a bounding rectangle, returns a copy of the input bitmap, scaled to the largest size that will fit within the bounding rectangle, without changing the image aspect ratio.

Once again, I created this feature for my photo album application, so I could present a table of thumbnails, which each table cell a uniform size.

The implementation of this method is unremarkable; as one might imagine, it relies upon ScaleBitmap to do the heavy lifting.

Amazingly, the Bitmap class provides a RotateFlip method, which performs the rotation. No Graphics object or burdensome additional lines of code needed. Fortunately, some confusion is retained in that this intuitiveness is itself anomalous.

CropBitmap

Crops the input image, returning a bitmap containing the portion of the input bitmap enclosed by the crop rectangle. The implementation of this method is another simple yet non-obvious solution:

//Create a new bitmap object based on the input
Bitmap newBmp = new Bitmap(cropRectangle.Width,
cropRectangle.Height,
PixelFormat.Format24bppRgb);
//Graphics.FromImage doesn't like Indexed pixel format//Create a graphics object and attach it to the bitmap
Graphics newBmpGraphics = Graphics.FromImage(newBmp);
//Draw the portion of the input image in the crop rectangle//in the graphics object
newBmpGraphics.DrawImage(inputBmp,
new Rectangle(0, 0, cropRectangle.Width, cropRectangle.Height),
cropRectangle,
GraphicsUnit.Pixel);
//Return the bitmap
newBmpGraphics.Dispose();
//newBmp will have a RawFormat of MemoryBmp because it was created//from scratch instead of being based on inputBmp. //Since it it inconvenient//for the returned version of a bitmap to be //of a different format, now convert//the scaled bitmap to the format of the source bitmapreturn ConvertBitmap(newBmp, inputBmp.RawFormat);

Notice that GDI+ is not providing any explicit crop support. Instead, a new bitmap is created, equal in dimensions to the crop rectangle. Then, a Graphics object is bound to this new Bitmap, and Graphics.DrawImage is called to draw the input image data within the crop rectangle onto the Graphics object and thus the new bitmap. Simple, and non-obvious.

This method is the main element that sets this class apart from myriad other C# imaging classes. With this method, one (presumably smaller) bitmap can be overlaid atop another (presumably larger) bitmap, at a corner, the center, or an arbitrary point, and with an arbitrary alpha (transparency). This was developed so that my photo album application could place a small, translucent watermark on each photo.

Use of this method is straightforward: pass the image upon which the overlay is placed, the image to overlay, some specification of where the overlay image goes, and an optional alpha value (0 to 100; 0 is transparent, 100 is opaque).

Implementation of this method is anything but straightforward. While overlaying one image atop another is relatively straightforward (just bind a Graphics object to the input bitmap, and call Graphics.DrawImage to copy the overlay image), including transparency in the overlay is rather complex:

First, a new Bitmap object is created with the same dimensions as the input bitmap. A Graphics object is bound to the Bitmap object, and the input Bitmap is copied to the new Bitmap object with DrawImage(Unscaled). Similarly, a new Bitmap object is created for the overlay bitmap, the first pixel in the overlay bitmap is set to be the transparent pixel (this is optional; remove it if the overlay bitmaps shouldn't have a transparent background), and a Graphics object bound to the copy of the overlay Bitmap.

Next, a 5x5 matrix is created, which consists of the identity matrix with cell (4,4) set to the alpha value, scaled from 0 to 1. Those readers with any linear algebra experience will recognize that a linear transformation is being built here, though to what end is perhaps not yet clear.

This matrix is used to create an ImageAttributes object, passed to ImageAttributes.SetColorMatrix. The matrix effectively encodes a transformation on the color of each pixel in the image, where color is a quintuplet with the fourth element consisting of alpha. Again, a bit of linear algebra experience will lead the reader to conclude that all of this matrix work is a verbose way of saying "scale the alpha channel by the scaling factor (0-1)".

Finally, the familiar Graphics.DrawImage is used to draw the overlay Bitmap into the overlay copy Bitmap, passing the overlay Bitmap through the color transformation matrix, so the result is the original overlay bitmap, but with transparency information set according to the alpha value.

//overlayBmp now contains bmpToOverlay w/ the alpha applied.//Draw it onto the target graphics object//Note that pixel units must be specified //to ensure the framework doesn't attempt//to compensate for varying horizontal resolutions //in images by resizing; in this case,//that's the opposite of what we want.
newBmpGraphics.DrawImage(overlayBmp,
new Rectangle(overlayPoint.X, overlayPoint.Y,
bmpToOverlay.Width, bmpToOverlay.Height),
drawRect,
GraphicsUnit.Pixel);
newBmpGraphics.Dispose();
//Recall that newBmp was created as a memory bitmap; //convert it to the format//of the input bitmapreturn ConvertBitmap(newBmp, destBmp.RawFormat);

Now, given a version of the overlay Bitmap with transparency, that version is placed atop the input bitmap with another Graphics.DrawImage call. Apart from cleanup and formalities, that is the extent of the translucent overlay odyssey.

Clearly, this is the most painful example of GDI+ obfuscation and contortions. However, the end result is a professional, compelling blending of the input and overlay bitmaps, well worth the effort.

Misc

A few other public methods exist for MIME type conversion, etc, however they do not perform any substantive bitmap manipulation roles.

The sample application

Included in the source archive for this article is a simple Windows Forms application in C#, which exercises all of the functionality in BitmapManipulator. Note that this application is written to demonstrate use of the BitmapManipulator class and to grant the reader instant gratification, not to demonstrate the author's mastery of Windows Forms development. Error handling and input validation have been elided, and UI cleanup left as an exercise to the reader.

Conclusion

This article introduced the BitmapManipulator class, and delved somewhat into its inner workings with GDI+. Hopefully the reader has gained some understanding of basic GDI+ programming concepts in the process, or at the very least a healthy aversion to any future encounters with GDI+. The ambitious reader is encouraged to extend BitmapManipulator to perform other useful functions. In particular, a forthcoming article will explore the issue of dithering between color depths, a capability which is conspicuously absent from the .NET wrapper atop GDI+.

History

9-1-03 Initial publication

License

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.

Share

About the Author

My name is Adam Nelson. I've been a professional programmer since 1996, working on everything from database development, early first-generation web applications, modern n-tier distributed apps, high-performance wireless security tools, to my last job as a Senior Consultant at BearingPoint posted in Baghdad, Iraq training Iraqi developers in the wonders of C# and ASP.NET. I am currently an Engineering Director at Dell.

I have a wide range of skills and interests, including cryptography, image processing, computational linguistics, military history, 3D graphics, database optimization, and mathematics, to name a few.

Comments and Discussions

Adam,
I have done some further head scrathing and have concluded that by using the original (higher quality output) code and passing the output (with borderbug) through you "Crop" procedure I can remove the Border.

However I am now faced with the issue of padding an imaged to a fixed size. For example if a run an image through tumbnail and I want it to be 300x225, if the propotions of the image only allow it to be 300x212. How would I add a white pad to the image to evenly build it up to 300x225.

Simon:
To do that, you need only create a new bitmap, attach a graphics object, fill it with white using Graphics.Clear(Color.White), then use DrawImage to draw your scaled image in the middle of the graphics object. If you look at the code that does the scaling, it has most of these components already.

If it's acceptable to lose half a pixel off your image on all sides, you can make your destination rectangle one pixel bigger and then offset it by half a pixel up and to the left. This way you can keep your HighQualityBicubic interpolation and lose the border. This worked for me (VB):

The reason the memory stream can't be closed is that it must remain open until the pertinent bitmap has been disposed. The only way I've found around this is (within the function) to create a copy of the bitmap, dispose of the stream bitmap, dispose of the stream, and return the copy.

I thought someone here would be able to help me, I am trying to write a program in C# in which the user will be able to use an eraser to erase parts of a bitmap, like the one in Ms Paint to be clear.

The bitmap will only be white and red so if the user tries to erase something, it becomes white.

Also i was thinking..the matrix I will be using is made up of a matrix which does not always have the same size and what i do is to resize it so that it fits the picture box. However after the user edits the Bitmap I must be able to reflect the changes back to the original matrix..if I resize the bitmap back to its original size, will it maintain its original state, appart of course the pixels which have been modified by the user or will there be some serious distortions??

You seem to have quite a bit of experince with this and I haven't found any help at the MS message boards, so I'll turn to The Code Project contributors.

My company currently stores images in tiff format in an SQL Server db. Inside an asp.net web service I extract the tiff data, push it into a System.Drawing.Image object, convert that object to .gif format, and write the data from the Image to a memory stream. I then use that memory stream in an http response in order to display the gif in a web page. (since there is no support for tiffs in IE).

We have had very sporadic problems with this, in that the image constructor very randomly and very rarely will throw errors. After some digging I found that MS has added a new line to the .net 1.1 documentation in System.Drawing. It reads "Caution: Classes within the System.Drawing namespace are not supported for use within a Windows or ASP.NET service. Attempting to use these classes from within one of these application types may produce unexpected problems, such as diminished service performance and run-time exceptions." .... http://msdn2.microsoft.com/en-us/library/system.drawing ...

So, unfortunately (and with much pain and anguish), it appears I need to take the System.Drawing classes out of my middle tier. I guess my question to you is, do you know of a somewhat lightweight class that I can use for the conversion from tiff to gif (or jpg) which I can add to my middle tier in order to pull out the System.Drawing classes I'm currently using for my conversions? Anyone have any suggestions?

By the way, Adam, yours is a nice class. The System.Drawing namespace is a bear to navigate and anything which simplifies it is highly valuable. Unfotunately, I don't need to simplify it, I need to eliminate it.

Brandon:
Thanks for trying out my library; I'm sorry it didn't work out.

bparks1972 wrote:

We have had very sporadic problems with this, in that the image constructor very randomly and very rarely will throw errors. After some digging I found that MS has added a new line to the .net 1.1 documentation in System.Drawing. It reads "Caution: Classes within the System.Drawing namespace are not supported for use within a Windows or ASP.NET service. Attempting to use these classes from within one of these application types may produce unexpected problems, such as diminished service performance and run-time exceptions." .... http://msdn2.microsoft.com/en-us/library/system.drawing ...

This is the first I've heard of this issue. I wrote this library originally for an ASP.NET photo album tool, and I don't recall encountering any problems. It's astonishing to me that MS would simply deprecate such a fundamental area of the CLR, without so much as a scrap of justification or workaround.

I have a couple of thoughts:

* ASP.NET 2.0 introduces the Image Generation Service, which I covered in an MSDN Magazine article a while back. This is basically a way to add dynamic images to ASP.NET apps. The existence of such a service makes me think MS have at least partially solved the problem in .NET 2.0. Given how new .NET 2.0 is, this probably isn't an option for you, but it may be worth investigating at least.

* ImageMagick (http://www.imagemagick.org/) , the open-source unmanaged imaging toolkit, has a COM wrapper which should be callable from .NET using wrappers generated by tlbimp. I've not done this myself, but I have worked with ImageMagick a bit, and it seems fairly comprehensive. Of course, this assumes you have the ability to install, register, and call unmanaged code in your solution. Most shared hosting providers look down on that sort of thing, to say the least.

* There are almost certainly commercial imaging toolkits which fill the gaping chasm created by the shittiness of System.Drawing

Scott Duensing wrote:While working with this code (which has been REALLY helpful - thanks!), I think I've found a recurring memory leak.

I'm glad it was useful; thanks for the feedback.

Scott Duensing wrote:Has reduced my applications memory leaks by 50%. As I dig more into the functions my code uses, I find more occurances of directly returning the value from ConvertBitmap.

Am I on to something here, or am I just on crack?

Heh, you're def on to something. At the time I wrote that code, I wasn't clear on the concept that the managed graphics classes are wrappers around unmanaged resources, and thus should be Dispose()'d with prejudice.

Memory leaks in .NET apps are hard to spot, as the GC is not particularly deterministic, so what looks like a leak could be the GC being lazy about freeing resources, however the semantics of an object which implements IDispose dictate that Dispose() be called as soon as the object is no longer used, which my code fails to do. I suspect that your app's memory consumption would go down eventually, as the bitmap objects are GC'd, but caling Dispose() is the better way to approach the problem.

Adam Nelson wrote:That's quite surprising. I would expect the finalizer to dispose the object at GC time.

Me too, but what do I know?

Adam Nelson wrote:You're the first to request a change to the library, but it definitely seems worthwhile. If you have any changes you want to submit, I'd be happy to incorporate them and credit you appropriately.

So far, I've only made the change I mentioned to the functions I'm actually using. Basically, any function that directly returns the results of ConvertBitmap() needs to be fixed so the bitmap passed into the call can be Dispose()ed of. (Of course, if you didn't create the bitmap in that function, no need to Dispose() it.)

I've attempted to find a fix for the MemoryStream.Close() exception when converting bitmap formats but so far haven't had any luck. Maybe it doesn't need closed, I don't know.

Scott Duensing wrote:So far, I've only made the change I mentioned to the functions I'm actually using. Basically, any function that directly returns the results of ConvertBitmap() needs to be fixed so the bitmap passed into the call can be Dispose()ed of. (Of course, if you didn't create the bitmap in that function, no need to Dispose() it.)

Ok, should be easy enough to generalize.

Scott Duensing wrote:I've attempted to find a fix for the MemoryStream.Close() exception when converting bitmap formats but so far haven't had any luck. Maybe it doesn't need closed, I don't know.

The MemoryStream is a fully managed resource; it's backed by a block of memory, but it's not wrapping, say, a GDI handle, so failure to Dispose() it shouldn't cause a persistent leak. Then again, I said the same thing about the Bitmap class

If you come up w/ anything along that line, do let me know. I felt dirty after implementing the hack to deliberately leave it un-Dispose()'d...

Scott Duensing wrote:
Thanks again for the great collection of image routines!

In my ASP.NET application I use a web form to upload an image file to the server.

I get the file,
create a Bitmap object from it,
rescale the Bitmap if I need to,
create another Bitmap file which is the thumbnail of the first one,
then I use your code to convert both into JPEG,
then I convert both into byte[],
then I save them into an Image field in a DB (SQL Server 2000).

I'm not sure why you're having this problem; I'd have to see your code to do a more thorough diagnosis. My first inclination is to examine the JPEG quality setting you're using; if you convert to JPEG with a quality value on or around 100, the resulting file size can be quite large.