Introduction

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 SetPixel/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.

Disclaimer: Version 2.0 makes most of the interfaces, and API described in this article obsolete. If you're unable to handle version 2.0, you should use version 1.0. Or ask in the forum below. I've included version 2.0 sample only.

Background

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.

The List

Here goes a list of the extension methods, some of them are just a bridge from Bitmap class to be used in Image class.

The LockBits() and UnlockBits() Methods

These methods do the same thing as the Bitmap.LockBits() and 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:

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:

The Rest of the Methods

Those are AddColorsToQuantizer(), GetPalette() and 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 SetPixel/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 Color structure.

A Simple Sample

(for version 1.0 only)

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.

A Not So Simple Sample

(for version 2.0 only)

I've included an example for version 2.0 as well, it tries to mimic the example in version 1.0, but code inside the archive actually contains the extended version with "real" gray-scale calculation as pointed out by nb2.

Comments and Discussions

One thing that I noted when I was running the sample application is that it seems to convert my 24-bit color image (JPEG and PNG) to a grayscale image, regardless of which pixel format I choose. I suppose that this is not the intention?

Am I doing something wrong when using the application, or is there a bug in the current version (2.0) of the code? I have build the application with both VS 2010 and VS 2013, if that should be relevant to the issue.

I think 'Pixel' is heavy. Consider you have 1000x1000 image. You must allocate 1 000 000 pixel object. If pixel object is for example 50 bytes (I didn't counted), then one image takes ~50mb in memory. I think it's a bad idea.

Luckily the pixel structure is not used in that way. It is only an encapsulation of pixel format. And this is used as a kind of lens you're looking through on the raw image data. So to read 8-bit indexed format for example, you need pixel with 8-bit indexed format structure "lens". You point at coordinates, and then you "look", and it performs whatever the underlying format does, it maps raw data directly to color without any transformation, thus speed . In this case it retrieves an index as a byte.

So for one processing (resp. transforming) operation you need only one (resp. two, source/target pixels). These pixels are then moved around as a lens, across the given path of coordinates (usually whole screen), and performs either only read, or also write operation. These can have different formats, so pixel format conversion is no big deal for them. This way you use only 1 or 2 at max pixels per any operation (or image, if operation is full screen operation). The path is also not stored, but is provided in a form of enumeration, thus only what you need is allocated at each time. Also this path allows parallel processing, by splitting the path to N parts for each Task. Ensuring maximal performance.

Hi, I'm going to update this article soon (not really sure how soon though). There is an option to use sources from my other article. This article's code is a base part of it. But that one evolved much further, giving you the option for parallel processing, many speed and functional optimizations, and amongst others also the coordinates. Check it out:

Hi, the 2bpp is not supported by C#. You should use 4bpp (16 colors). The code in itself does not have significant problem with 2bpp, it's just that there is no PixelFormat enumeration value for 2bpp. I guess you could hijack the 4bpp format (for example), and change it's implementation to 2bpp internally. Like changing the Extend.PixelFormat.cs.

GetBitDepth method:

case PixelFormat.Format4bppIndexed: return2;

GetColorCount metho:

case PixelFormat.Format4bppIndexed: return4;

But this is untested (and unsupported) scenario right now. I'm pretty sure it would generate correct 2bpp image, but I'm not sure how would be such image saved.

Before I found this I wrote a similar enumerable accessor for pixels. But found your site as I was trying to figure out if C# has any structs to store a 64bit pixel.

My idea was to make an interface that would easily output the Pixel Colors in whatever format I want. At the time i was not thinking about ReadWrite abilities, not sure if that is very possible with multiple types of return objects really.

So i thought i would throw my idea out here, it would save the need the call pixel.GetColor().ToArpg() in certain situations for example.

So pixel.GetColor().ToArgb() is the same as pixel.Value or (Int32)pixel.Value (if you need exactly Int32).
This would hold for 24-bit (after the fix), and 32-bit formats. For indexed formats you would use pixel.Index instead.

regards,
Kate

P.S.: If I have some spare time, I'll post updated sources with in-memory processing (quite faster), and also with this bugfix included).

First thank you for this wonderful article. While enumerating the pixels, I need to get the position of the current pixel, which, can be done by adding X and Y to your Pixel struct, and extending the EnumerateImagePixels method (adding assignment statements for X and Y within the two for-loops).

However, according to MS documents, there are two types of bitmap images, "bottom-up" and "top-down". The "bottom-up" is a bitmap with the origin in the lower-left corner. The "top-down" is bitmap with the origin in the upper-left corner.

I wonder, in order to always get the correct position, is it necessary and is there any way to determine if an image is "bottom-up" or "top-down"?