Click here to Skip to main content
15,880,905 members
Articles / Multimedia / GDI+
Article

Painless yet unsafe grayscale conversion in C#

Rate me:
Please Sign up or sign in to vote.
4.60/5 (22 votes)
17 Apr 20063 min read 167.4K   42   43
This is an article using pointer arithmetic for a quick conversion of an image to grayscale.

original image

modified image

Introduction

I was busy trying to write a motion detection algorithm in C#. To determine motion, I compared change values from a previous image by calculating the absolute value of the pixel difference. For doing this, grayscale is easier and probably faster.

Background

If MIT thinks using grayscale for motion detection is a good idea, then I am definitely on the right track with my motion detection algorithm. For a reference, see the Cog project.

I got the code for the image loop from here because I originally attempted to use SetPixel which is painfully slow.

Using the code

If you use this code in a multithreaded environment, be aware that the parameter image will be locked until this method unlocks it. This will raise an exception when another code attempts to lock the bitmap, such as a customer OnPaint. To avoid this, you can always make a copy of the bitmap.

Here is the entire method:

C#
public Bitmap processImage(Bitmap image){
    Bitmap returnMap = new Bitmap(image.Width, image.Height, 
                           PixelFormat.Format32bppArgb);
    BitmapData bitmapData1 = image.LockBits(new Rectangle(0, 0, 
                             image.Width, image.Height), 
                             ImageLockMode.ReadOnly, 
                             PixelFormat.Format32bppArgb);
    BitmapData bitmapData2 = returnMap.LockBits(new Rectangle(0, 0, 
                             returnMap.Width, returnMap.Height), 
                             ImageLockMode.ReadOnly, 
                             PixelFormat.Format32bppArgb);
    int a = 0;
    unsafe {
        byte* imagePointer1 = (byte*)bitmapData1.Scan0;
        byte* imagePointer2 = (byte*)bitmapData2.Scan0;
        for(int i = 0; i < bitmapData1.Height; i++) {
            for(int j = 0; j < bitmapData1.Width; j++) {
                // write the logic implementation here
                a = (imagePointer1[0] + imagePointer1[1] + 
                     imagePointer1[2])/3;
                imagePointer2[0] = (byte)a;
                imagePointer2[1] = (byte)a;
                imagePointer2[2] = (byte)a;
                imagePointer2[3] = imagePointer1[3];
                //4 bytes per pixel
                imagePointer1 += 4;
                imagePointer2 += 4;
            }//end for j
            //4 bytes per pixel
            imagePointer1 += bitmapData1.Stride - 
                            (bitmapData1.Width * 4);
            imagePointer2 += bitmapData1.Stride - 
                            (bitmapData1.Width * 4);
        }//end for i
    }//end unsafe
    returnMap.UnlockBits(bitmapData2);
    image.UnlockBits(bitmapData1);
    return returnMap;
}//end processImage

The PixelFormat is crucial. This format determines the layout of the bitmap data. This is why the literal 4 is hard-coded (actually, it is hard-coded because I am lazy). If you use a different PixelFormat, you will need to determine the layout and offset for those formats.

C#
BitmapData bitmapData1 = image.LockBits(new Rectangle(0, 0, 
                         image.Width, image.Height), 
                         ImageLockMode.ReadOnly, 
                         PixelFormat.Format32bppArgb);
BitmapData bitmapData2 = returnMap.LockBits(new Rectangle(0, 0, 
                         returnMap.Width, returnMap.Height), 
                         ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);

This code locks the bitmap and returns its image data. Since I am converting the entire image to grayscale, I lock the entire bitmap. It is possible to lock a smaller area.

C#
byte* imagePointer1 = (byte*)bitmapData1.Scan0;
byte* imagePointer2 = (byte*)bitmapData2.Scan0;

Pointer arithmetic is unsafe in C# :(. Too bad because it is fast! Although, it is strange getting Access Violation errors in C#. If you change the code, test thoroughly using Mathematics to make sure you don't overwrite any protected memory. Scan0 is the pointer to the first byte of the bitmap's data.

C#
for(int i = 0; i < bitmapData1.Height; i++) {
    for(int j = 0; j < bitmapData1.Width; j++) {

The astute will immediately recognize this will only work if the bitmaps are the same size. And fortunately, this always is since this method will create the returnMap bitmap based on the input bitmap. An incorrect loop will also cause an Access Violation.

C#
imagePointer2[0] = (byte)a; //Array index 0 is blue
imagePointer2[1] = (byte)a; //Array index 1 is green
imagePointer2[2] = (byte)a; //Array index 2 is red
imagePointer2[3] = imagePointer1[3]; //Array index 3 is alpha

See the comments in the code snippet above, to understand the layout of the bitmap data.

C#
a = (imagePointer1[0] + imagePointer1[1] + imagePointer1[2])/3;

Average the three color components in the original bitmap. Keep the alpha the same, otherwise your grayscale will also cause undesired blending.

C#
imagePointer1 += 4;
imagePointer2 += 4;

Move forward 4 bytes:

C#
imagePointer1 += bitmapData1.Stride - (bitmapData1.Width * 4);
imagePointer2 += bitmapData1.Stride - (bitmapData1.Width * 4);

This is definitely the most confusing part. See this for a picture of what is happening. The width of a bitmap is actually the composed width and an unused buffer to pad the bitmap to be a multiple of 4. Width + buffer = stride. It works, so good enough for me.

C#
returnMap.UnlockBits(bitmapData2);
image.UnlockBits(bitmapData1);
return returnMap;

UnlockBits unlocks the bitmap data, probably a good thing to do.

Points of Interest

A good place to find answers to hard problems: Google.

People much smarter than me: MIT Cog Project.

Possible Errors

I use the bitmap data from bitmapData1 for both bitmapData1 and bitmapData2. This could lead to unexpected errors if the data were some how different. However, in testing, everything works fine. A lot more prudent coder would verify a lot of things before putting this sort of code into anything critical.

If there are any more errors, I am quite sure that the overly critical among you will gladly point them out as glaring criticisms.

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


Written By
Architect ERL GLOBAL, INC
United States United States
My company is ERL GLOBAL, INC. I develop Custom Programming solutions for business of all sizes. I also do Android Programming as I find it a refreshing break from the MS.

Comments and Discussions

 
GeneralCheesy alternative Pin
Ravi Bhavnani18-Apr-06 12:58
professionalRavi Bhavnani18-Apr-06 12:58 
GeneralRe: Cheesy alternative Pin
mrBussy25-Apr-06 4:12
mrBussy25-Apr-06 4:12 
JokeUse ColorMatrix Pin
Ray Hayes18-Apr-06 11:00
Ray Hayes18-Apr-06 11:00 
General:( Too Slow Pin
Ennis Ray Lynch, Jr.18-Apr-06 11:55
Ennis Ray Lynch, Jr.18-Apr-06 11:55 
GeneralRe: :( Too Slow Pin
Ray Hayes18-Apr-06 12:06
Ray Hayes18-Apr-06 12:06 
GeneralRe: :( Too Slow Pin
Ennis Ray Lynch, Jr.18-Apr-06 12:17
Ennis Ray Lynch, Jr.18-Apr-06 12:17 
GeneralRe: :( Too Slow Pin
Ray Hayes18-Apr-06 12:34
Ray Hayes18-Apr-06 12:34 
GeneralApologies Pin
dfgdiewocxn18-Apr-06 6:54
dfgdiewocxn18-Apr-06 6:54 
Hello All,

Code Project has always been an inspiration for me, and I have learned a great deal from it. I do apologize for use of harsh words but that was the result of my disappointment which I experienced when I came across this posting.

Dirk Veldhuis, we sure are poor mortal beings and we all have our limits when it comes to thinking, knowledge and wisdom that is why this very portal exists that makes our jobs a little easier to work with, we should always be very careful to what we pass on so that we could save hundred of hours of man’s work all around the world. I am sure that all of you haven’t tested this code. Do me a favor, create a BMP with partly colored with RGB (255,0,0) and partly with RGB (85,85,85) which is (255+0+0)/3 and then turn it to grayscale using some image viewer such as Irfanview and see the difference.

Aparra, I m not sure what algorithm you have been using and how you came across to know the mean of RGB colors as the ‘defecto’ method but what Adobe has been following (http://www.fho-emden.de/~hoffmann/gray10012001.pdf) is the variations of:

0.30*R + 59*G + 0.11*B


Like, The_Myth said, life is short so we must make the best use of it, there are two type of people, ones who look at “what is said” and there are others who look at “why its said”, now one has to make a decision between the two to make best use of our life,

Dear Abu Abdillah, I fully understand that the “blasting is not the spirit of this site” is not this site but lots of people “like me” use this site as a source of knowledge and I just felt like making the comment but I guess I should stick to “rate it” from now on because unfortunately I do not have much time to at my hand to answer such comments.

elynch, if you have searched the net and found this article most trivial then you haven’t searched hard enough or you need to polish your goggling skills. I would recommend you to visit the following link:

http://www.bobpowell.net/grayscale.htm

One other thing I will point out would be, not to use ‘unsafe code’ (pointers) as they ruin the spirit of .NET Framework (I hope there is nothing offensive in saying that).

Ayyaz-
GeneralRe: Apologies Pin
Ennis Ray Lynch, Jr.18-Apr-06 7:07
Ennis Ray Lynch, Jr.18-Apr-06 7:07 
GeneralRe: Apologies Pin
dfgdiewocxn18-Apr-06 7:22
dfgdiewocxn18-Apr-06 7:22 
GeneralRe: Apologies Pin
Ennis Ray Lynch, Jr.18-Apr-06 7:44
Ennis Ray Lynch, Jr.18-Apr-06 7:44 
GeneralRe: Apologies Pin
Nish Nishant18-Apr-06 8:18
sitebuilderNish Nishant18-Apr-06 8:18 
GeneralRe: Apologies Pin
Lukas Fellechner26-Apr-07 4:01
Lukas Fellechner26-Apr-07 4:01 
GeneralRe: Apologies Pin
colithium26-Apr-06 12:32
colithium26-Apr-06 12:32 
General[Message Deleted] Pin
dfgdiewocxn17-Apr-06 22:53
dfgdiewocxn17-Apr-06 22:53 
GeneralRe: Very Poor Conversion Algo. Pin
Dirk Veldhuis17-Apr-06 23:14
Dirk Veldhuis17-Apr-06 23:14 
GeneralRe: Very Poor Conversion Algo. Pin
aparra18-Apr-06 0:43
aparra18-Apr-06 0:43 
GeneralRe: Very Poor Conversion Algo. Pin
Mohamed Y. Elamrani18-Apr-06 2:03
Mohamed Y. Elamrani18-Apr-06 2:03 
GeneralRe: Very Poor Conversion Algo. Pin
AnasHashki18-Apr-06 4:15
AnasHashki18-Apr-06 4:15 
GeneralSorry you don't like Java style Pin
Ennis Ray Lynch, Jr.18-Apr-06 4:05
Ennis Ray Lynch, Jr.18-Apr-06 4:05 
GeneralRe: Very Poor Conversion Algo. Pin
colithium26-Apr-06 12:35
colithium26-Apr-06 12:35 
GeneralExcellent! Pin
Marc Clifton17-Apr-06 13:20
mvaMarc Clifton17-Apr-06 13:20 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.