System.Drawing.Bitmap class is deceptively simple - just create a Bitmap from a file, then use the
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
"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
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.
Running unsafe code requires that your Assembly be fully-trusted (which it probably is, unless you're running on a hosted machine somewhere).
Please see this article or search MSDN for more information on using unsafe code.
If you are new to image processing or just need a review, this page has some good diagrams and explanations. CodeProject also has several articles on this topic.
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:
Image - the
Bitmap) object to traverse. On
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
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
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) -
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
GetRow(int row), SetRow(int row, int values) -
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
implements both the
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
class that contains a
indicating the position in the
representing the color value of that pixel.
Points of Interest
Ints vs Colors
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
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
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
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:
||Time per 100 Traversals (seconds)|
|IT Scan0 Ptr||.4843750|
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
4/17/2007 - initial release