Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Image Per Pixel Enumeration, Pixel Format Conversion and More

0.00/5 (No votes)
20 Jun 2013 5  
A set of handy extension methods to help you with quick Image modifications

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. 

The version 2.0, is (kinda) synchronized with version 5.0 in an article: A Simple - Yet Quite Powerful Palette Quantizer. It contains many more options, speed optimizations, parallel processing, and much more. Check it out!

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.

ImagePixelEnumeratorVS2008

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.

  • 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.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:

foreach (Pixel pixel in myImage.EnumerateImagePixels(ImageLockMode.ReadWrite))
{
   // modify a pixel here
}

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
	(PixelFormat.Format8bppIndexed, myQuantizer);

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.

using System;
using System.Drawing;
using System.Drawing.Imaging;
using Extensions;
using Extensions.Helpers;

namespace Sample
{
    // this sample only works for non-indexed format images, 
    // but can be easily extended to support them
    static class Program
    {
        static void Main()
        {
            // loads the source image
            Image image = Image.FromFile("Source.png");

            // converts it to a grayscale
            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);
                pixel.SetColor(outputColor);
            }

            // saves the converted image
            image.Save("Output.png", ImageFormat.Png);
        }
    }
}

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.
using System;
using System.Drawing;
using System.Drawing.Imaging;
using ImagePixelEnumerator.Helpers;

namespace Sample
{
    static class Program
    {
        static void Main()
        {
            // loads the source image
            Image image = Image.FromFile("Source.png");

            // prepares our target image
            Image targetImage;

            // this sample only works for non-indexed format images, 
            // actually the code inside the archive contains such 2-pass version
            ImageBuffer.TransformImagePerPixel(image, PixelFormat.Format32bppArgb, null, out targetImage, null, 4, (passIndex, sourcePixel, targetPixel) =>
            {
                Color color = sourcePixel.GetColor();
                Int32 gray = (color.R + color.G + color.B)/3;
                Color grayColor = Color.FromArgb(color.A, gray, gray, gray);
                targetPixel.SetColor(grayColor, null);
                return true;
            });

            // saves the converted image
            targetImage.Save("Output.png", ImageFormat.Png);
        }
    }
}

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 IEnumerator<> classes.

Related Articles

History

  • 2013-06-20: Version 2.0 published
  • 2013-06-20: The section A Not So Simple Sample added
  • 2010-03-18: The initial article was posted

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.

A list of licenses authors might use can be found here