Click here to Skip to main content
6,597,576 members and growing! (18,613 online)
Email Password   helpLost your password?
Multimedia » General Graphics » Graphics     Intermediate

Painless yet unsafe grayscale conversion in C#

By Ennis Ray Lynch, Jr.

This is an article using pointer arithmetic for a quick conversion of an image to grayscale.
C#, Windows, .NET, Visual Studio, GDI+, Dev
Posted:17 Apr 2006
Views:58,329
Bookmarked:37 times
Announcements
Loading...
 
Search    
Advanced Search
Add to IE Search
printPrint   add Share
      Discuss Discuss   Broken Article?Report  
16 votes for this article.
Popularity: 4.47 Rating: 3.71 out of 5
5 votes, 31.3%
1

2
2 votes, 12.5%
3
2 votes, 12.5%
4
7 votes, 43.8%
5

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:

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.

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.

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.

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.

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.

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.

imagePointer1 += 4;
imagePointer2 += 4;

Move forward 4 bytes:

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.

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

About the Author

Ennis Ray Lynch, Jr.


Member
My company is ERL GLOBAL, INC. I develop Custom Programming solutions for business of all sizes.
Occupation: Architect
Company: ERL GLOBAL, INC
Location: United States United States

Other popular General Graphics articles:

  • A flexible charting library for .NET
    Looking for a way to draw 2D line graphs with C#? Here's yet another charting class library with a high degree of configurability, that is also easy to use.
  • CxImage
    CxImage is a C++ class to load, save, display, transform BMP, JPEG, GIF, PNG, TIFF, MNG, ICO, PCX, TGA, WMF, WBMP, JBG, J2K images.
  • 3D Pie Chart
    A class library for drawing 3D pie charts.
  • Barcode Image Generation Library
    This library was designed to give an easy class for developers to use when they need to generate barcode images from a string of data.
  • ImageStone
    An article on a library for image manipulation.
Article Top
You must Sign In to use this message board.
FAQ FAQ 
 
Noise Tolerance  Layout  Per page   
 Msgs 1 to 25 of 41 (Total in Forum: 41) (Refresh)FirstPrevNext
QuestionLocking a smaller portion of bitmap PinmemberAl_S3:38 23 Feb '09  
AnswerRe: Locking a smaller portion of bitmap PinmemberEnnis Ray Lynch, Jr.3:56 23 Feb '09  
GeneralRe: Locking a smaller portion of bitmap PinmemberAl_S4:20 23 Feb '09  
GeneralRe: Locking a smaller portion of bitmap PinmemberEnnis Ray Lynch, Jr.4:29 23 Feb '09  
GeneralRe: Locking a smaller portion of bitmap PinmemberAl_S4:36 23 Feb '09  
GeneralRe: Locking a smaller portion of bitmap PinmemberEnnis Ray Lynch, Jr.4:41 23 Feb '09  
GeneralRe: Locking a smaller portion of bitmap PinmemberAl_S5:00 23 Feb '09  
GeneralRe: Locking a smaller portion of bitmap PinmemberAl_S5:25 23 Feb '09  
QuestionLocking a smaller portion of bitmap PinmemberAl_S3:36 23 Feb '09  
Questionaccessing a sub-portion of image PinmemberAl_S4:12 20 Feb '09  
Generalpointer indexing Pinmemberkanza azhar12:42 22 Apr '08  
GeneralRe: pointer indexing PinmemberEnnis Ray Lynch, Jr.12:53 22 Apr '08  
QuestionTypo in ImageLockMode? Pinmemberdwieringa12:46 5 Sep '07  
AnswerYou would think PinmemberEnnis Ray Lynch, Jr.13:33 5 Sep '07  
GeneralSimple improvement to make it 3 times faster Pinmembermattrs2:01 18 May '07  
GeneralI can believe I missed that PinmemberEnnis Ray Lynch, Jr.3:42 18 May '07  
GeneralRe: Simple improvement to make it 3 times faster PinmemberTerespl23:15 23 Jul '09  
QuestionStride Question and Issue [modified] Pinmemberjouwpaard2:03 21 Sep '06  
AnswerRe: Stride Question and Issue PinmemberEnnis Ray Lynch, Jr.14:54 21 Sep '06  
GeneralCheesy alternative PinmemberRavi Bhavnani13:58 18 Apr '06  
GeneralRe: Cheesy alternative PinmembermrBussy5:12 25 Apr '06  
JokeUse ColorMatrix PinmemberRay Hayes12:00 18 Apr '06  
General:( Too Slow Pinmemberelynch12:55 18 Apr '06  
GeneralRe: :( Too Slow PinmemberRay Hayes13:06 18 Apr '06  
GeneralRe: :( Too Slow Pinmemberelynch13:17 18 Apr '06  

General General    News News    Question Question    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

PermaLink | Privacy | Terms of Use
Last Updated: 17 Apr 2006
Editor: Smitha Vijayan
Copyright 2006 by Ennis Ray Lynch, Jr.
Everything else Copyright © CodeProject, 1999-2009
Web17 | Advertise on the Code Project