Introduction

Microsoft's System.Drawing.Bitmap class is deceptively simple - just create a Bitmap from a file, then use the GetPixel() and SetPixel() methods to manipulate the image, right? Unfortunately, these two methods are terribly slow, so a lower-level traversal via pointers is necessary for decent performance (a 10x - 75x improvement in my tests!).

However, there is a reason that the .NET language designers are steadily moving away from pointers - code that utilizes them is usually brittle and error-prone, even when run under the CLR. Therefore, to minimize and isolate the use of unsafe code in my projects, I have encapsulated the necessary unsafe pointer code in this class, plus I have added several handy methods for dealing with image pixels.

The end result is a robust class for traversing images (ie, retrieving and setting individual pixels) - hence the moniker ImageTraverser.

Background

Unsafe Code

"Unsafe" code blocks in C# are blocks of code that use pointers. While C# lets you leverage the power of pointers, it does retain a few safety features, such as:

You may not create a pointer to a managed type (compiler error)

You may not access some of the memory you're not supposed to (runtime error - see MSDN for more details)

While you may not create pointers to managed types, several of the basic types such as byte, int, bool, etc. are "special" types that exist in both the managed and unmanaged worlds, so the system allows you to point to those types without issue.

Please see this article or search MSDN for more information on using unsafe code.

Image Processing

If you are new to image processing or just need a review, this page has some good diagrams and explanations. CodeProject also has severalarticlesonthistopic.

Using the Code

The zip file contains documentation in the MSDN format, so that is the best resource for method and property information. Here are some of the more important members however:

Properties

Image - the Image (or Bitmap) object to traverse. On Set, ImageTraverser actually creates a copy of the passed Image and works exclusively with that, leaving the original untouched. In fact, it does not even retain a reference to the original Image object. On Get, ImageTraverser returns a COPY of the underlying Image object, so use this accessor sparingly!

Image____ (Height, PixelFormat, Scan0AsBytePointer, Scan0AsIntPointer, Size, Stride, Width) - Retrieves the corresponding
property from the underlying Image object. You should use these accessors rather than using ImageTraverser.Image.______ because each call to ImageTraverser.Image actually returns a COPY of the underlying Image object.

Methods

this[int x, int y], this[Point location] - Returns the int value of the pixel at the indicated position

GetPixel(int x, int y), SetPixel(int x, int y, Color value) - Gets or Sets the Color of the specified pixel. These methods are just wrappers around the indexers, but they are included for convenience because their signatures match those of Bitmap.GetPixel()/SetPixel(). These are the only methods that return Colors rather than ints.

GetRow(int row), SetRow(int row, int[] values) - Gets or Sets the int values of the pixels in the specified row

ToArray() - Returns a two-dimensional int array filled with the values of all the pixels in the Image

Interfaces

ImageTraverser implements both the IDisposable and IEnumerable interfaces, making it easy to use with
"using" and "foreach" statements. The enumeration is pixel-by-pixel left-to-right across each row, top-to-bottom, and returns a simple Pixel class that contains a Point indicating the position in the Image and an int representing the color value of that pixel.

Points of Interest

Ints vs Colors

ImageTraverser returns ints rather than Colors because the Color.FromArgb(int) call is actually relatively time-consuming. It is much quicker just to return the raw ints because those are what is read
directly from memory. You can easily make the Color.FromArgb(int) call yourself if you need an actual Color struct.

PixelFormats

ImageTraverser works exclusively in the 32bppArgb PixelFormat. However, you can pass in Images with different bit-depths and the class will convert its internal copy to 32bpp. Be aware then that even if you pass in, say, a 1-bpp Image, you will get back 32bpp values when traversing it and when calling ImageTraverser.Image.

Pixels, Rows, Arrays, oh my!

ImageTraverser supports several different views of the underlying Image - by individual pixel, by row, or as an entire two-dimensional array. This is useful because some algorithms operate pixel-by-pixel, others row-by-row, others over the entire image - just use whichever method makes the most sense in your project. There are some performance differences between these methods (see below), but all are MUCH faster than Bitmap.GetPixel()/SetPixel().

Performance

In the introduction, I claimed a 75x speed increase over the BCL's Image.GetPixel()/SetPixel() methods. Here are the numbers to back that up.

I ran several tests to compare the time it takes to read every pixel in the Image (repeated in a loop 100x). The Demo zip file above is the tester program - its purpose is to create a negative of the image, and it lets you specify the number of trials and iteration method, then reports the time back. Note that the time reported is the "iteration" time only and will not match the wall time because in-between iterations, time is spent updating the GUI (which can be quite psychedelic).

Here are the times I got on my machine:

Access Method

Time per 100 Traversals (seconds)

IT Enumerator

3.5152650

IT Indexer

1.0625000

IT Array

.7500000

IT Scan0 Ptr

.4843750

IT Rows

.5781250

IT GetPixel

2.1562500

MS GetPixel

35.6250000

ImageTraverser's fastest traversal method (pointer) is ~75x as fast as the Bitmap.GetPixel() method, and ImageTraverser's slowest traversal method (enumerator) is still ~10x as fast as Bitmap.GetPixel()!

History

4/17/2007 - initial release

License

This article, along with any associated source code and files, is licensed under The MIT License

I have some new code (for me) that uses the getPixel method in vb.net - I have a large library of aerial photos (see AerialGISTechnology.com) and need to scan for targets. I don't expect to be able to detect the targets through code, so I manually locate them, then place a yellow[255,255,0] square on a target location (using a photoshop script) and then later look at the image to recover the upper left pixel address [x,y] of the square which then through some real magical code [ ] results in a lat/lon location of that pixel. This works sorta ok until the jpg compressor gets involved, and during the compression process, the RGB values are slightly altered. Yellow becomes [255,254,1] or [254,253,3]. So - the test for a fixed yellow fails randomly due to data. Is there a way to test for a color range - I could stand a few false positives, but could not accept misses, as there is an evaluation step after the data extraction that will catch the false positives.