In this article, a universal Image (C# class) enumerator will be introduced - one that can make your life much more easier. It's not based on
GetPixel methods, so it's fast, yet still safe in the C# managed environment. This class can help you to read or write all the pixels, while you'll have a chance to modify the pixel color as you'll see fit. There's plenty of room for you to extend on this enumerator, and make yourself a - for example - quick convertor to a grayscale method. The possibilities are only limited by your imagination.
I'm working on the image editor, and I needed some way to paste the images from clipboard to my application. But unfortunately my editor is about to handle several pixel formats (many actually), and the images came from clipboard in all different formats. So I needed a way to convert from many different formats to my many formats. So I decided to write universal pixel format converter. The easiest way to achieve that, I wondered, was to somehow enumerate all the pixels no matter the format. And then simply read one pixel in a source image and paste it into a target image. So I did exactly that. I saw many people on the internet (when I was searching for such a thing) were trying to simply parse an image, or parse it and do a slight modification per pixel. So I prepared few extensions methods, that will help you to quickly achieve your goal, without studying Microsoft pixel formats, and other distractions along the way.
Using the Code
The goodness comes in a form of the extensions methods, for
System.Drawing.Image class, so once referenced, they will appear as the standard methods for any Image instance. Also, there's one class
Pixel that does all the pixel reading/writing work. It contains a color value of any pixel format there is. I will describe all those methods down below.
Here goes a list of the extension methods, some of them are just a bridge from
Bitmap class to be used in
BitmapData LockBits(this Image image, ImageLockMode lockMode)
void UnlockBits(this Image image, BitmapData data)
IEnumerable>Pixel< EnumerateImagePixels(this Image image, ImageLockMode accessMode)
Image ChangePixelFormat(this Image image,
PixelFormat targetFormat, IColorQuantizer quantizer)
void AddColorsToQuantizer(this Image image, IColorQuantizer quantizer)
List<Color> GetPalette(this Image image)
void SetPalette(this Image image, List<Color> palette)
The LockBits() and UnlockBits() Methods
These methods do the same thing as the
Bitmap.UnlockBits() methods. Only they're provided on the image, and also they lock the data in a
Image.PixelFormat format. The
LockBits() method returns a locked image data, those must be unlocked by the
UnlockBits() method to be of any use. Even when you're just reading from an image, you've to unlock it. Those two methods are primarily for internal use, but are provided anyway. So hopefully, you won't need those methods at all.
The EnumerateImagePixels() Method
This method is the core method of this article. It allows you - depending on the access mode provided (
ImageLockMode) - read, write or both at the same time. Just supply an image, and process all its pixels via a structure
Pixel in a simple loop:
foreach (Pixel pixel in myImage.EnumerateImagePixels(ImageLockMode.ReadWrite))
It doesn't matter if the source image is a 4-bit indexed, or a 32-bit truecolor format. The pixel structure can handle them all. Once I accomplished that, it seemed like a good idea to go one step further, and add a method which will convert between two pixel formats, which should now be easy. Checkout the The Pixel structure section for more information. This brings us to our next method, the first fruit of pixel enumeration.
The ChangePixelFormats() Method
This method lets you to change the format of a given image, as long as the image is in a supported format (you can check that with
IsSupported method), and the target format is also supported. It basically enumerates the source image pixels for read, while the target image is enumerated for write. Then each pixel is read from the source and set on the target. In between, it's transformed as needed, or in case of deep-color formats, copied as is. You need to provide a
IColorQuantizer for the conversion from non-indexed format to an indexed one. It will automatically gather the information from the image, and performs the quantization. A palette will be created and set on the target image. The usage is easy as:
Image targetImage = sourceImage.ChangePixelFormat
The Rest of the Methods
SetPalette(). The first one lets you process the multiple images to have a common palette. It also uses the enumeration. It adds all the pixel colors to the quantizer to be latter used by all those images. The
GetPalette() method will afterwards retrieve the unified palette from the quantizer. The last method
SetPalette() sets this palette to any image you like. See the previous article (here) about palette quantization to get more detailed information about how that works.
The PixelFormat Extension Methods
While I was at it, I also added some extensions to the
PixelFormat enumeration. The
GetBithDepth method determines a bit count needed per the format's pixel. The
GetColorCount lets you know how much color is available for that pixel format. This method is only allowed for indexed formats. Whether a format is indexed or not can be determined by another method
IsIndexed, which returns
True for all the indexed formats, and
False for all the highcolor and truecolor formats. There's also the
IsSupported method which is used by my demo. It's basically a list check whether the format is supported by Microsoft because not all of the listed formats are actually supported (for example
16bppGrayscale). If the
HasAlpha returns a
True then the pixel format contains also a transparency color component in it. The last method
IsDeepColor determines whether a format is non-standard. That is, if it uses more than 8-bit per color component. These are usually special modes, and should be handled with care.
The Pixel Structure
This class handles internally all those format differences, while on the outside is behaving almost like an old
GetPixel combo. It contains three pairs of methods and three properties.
GetColor, SetColor and Color
The first two methods are used to read and write color value for the non-indexed formats. The
Color just basically wraps both methods.
GetIndex, SetIndex and Index
The same goes for these two methods and properties. Only these are used for indexed pixel formats. To get a color, you need to have to first retrieve it from your image's palette like this:
Color color = myImage.Palette.Entries[pixel.Index]
I can imagine a method, a wrapper for some of those extension methods, which will automatize the process further, but I always leave some space to figure it out for yourself. Keeping things at their basic state.
GetValue, SetValue and Value
Finally, these are used to handle 48-bit and 64-bit formats, as they unfortunately don't fit into a standard
A Simple Sample
Just to show you the basic idea of how this stuff is used, I put together a sample program, which takes an image and converts it to a grayscale. That's all from me for now. I surely will be posting another article soon as many unusual things are popping up during this editor programming.
static class Program
static void Main()
Image image = Image.FromFile("Source.png");
foreach (Pixel pixel in image.EnumerateImagePixels(ImageLockMode.ReadWrite))
Color color = pixel.GetColor();
Int32 gray = (color.R + color.G + color.B)/3;
Color outputColor = Color.FromArgb(color.A, gray, gray, gray);
Points of Interest
- Microsoft actually (as they describe it "kind of") supports only first 13-bits per color component in the 48-bit and 64-bit pixel formats.
- You really should dispose the
- 2010-03-18: The initial article was posted