SoFunction
Updated on 2025-03-07

Detailed explanation of the common methods of image processing in C# (Bitmap, BitmapData, IntPtr)

introduce

In C#, several key classes in the namespace are mainly used when image processing, among which Bitmap, BitmapData and IntPtr are important tools for efficient pixel operations. Here is an overview of how to use these classes for image processing:

kind:

It is a class that encapsulates bitmap data, which allows you to load, save, display and manipulate image files or bitmap resources in memory.

Use new Bitmap(width, height, pixelFormat) to create a new blank bitmap.

Load image files via (path) or (stream).

The GetPixel(x, y) and SetPixel(x, y, color) methods are provided for getting and setting the colors of a single pixel, but please note that this method is less efficient for large-scale image processing.

kind:

BitmapData represents a pixel data buffer of a locked Bitmap object, which can directly read and write pixels to improve performance.

Use the (Rectangle area, ImageLockMode mode, PixelFormat format, out BitmapData data) method to lock some or all areas of Bitmap to obtain a pointer to the pixel data of that area.

The data.Scan0 property is an IntPtr type that points to the data address of the first pixel.

Unlocking bitmap data requires a call (BitmapData) to free up resources and ensure that the graphics device is updated correctly.

Types and Marshal class:

IntPtr is a data type that represents an unmanaged pointer (i.e., a memory address).

When processing BitmapData, the memory block pointed to by the Scan0 property is usually mapped directly into the managed code for quick pixel operations.

Use (IntPtr source, byte[] destination, int startIndex, int length) to copy unmanaged memory block contents into a managed array, which allows for more convenient pixel traversal and modification in C#.

After the modification is completed, the modified array content can be copied back to the memory area pointed to by BitmapData through a similar method.

Based on the above, an efficient image processing process may be like this:

  • Create or load a Bitmap object.
  • Lock the pixel data of Bitmap and get the BitmapData.
  • Use copy the pixel data of BitmapData into the local array.
  • Perform the required image processing operations on the array (such as adjusting brightness, contrast, filters, etc.).
  • Use again to copy the processed array data back to BitmapData's memory.
  • Unlock BitmapData so that GDI+ can automatically update the corresponding Bitmap image.

This method avoids the performance bottlenecks caused by frequent calls to GetPixel and SetPixel functions, and achieves faster image processing speed.

Bitmap class

Namespace:

Encapsulates a GDI+ bitmap, which consists of pixel data of the graphic image and its properties.

Bitmap is an object used to process images defined by pixel data.

The most convenient way to use the C# class for image processing is to use the Bitmap class, using the GetPixel() and SetPixel() of this class to access each pixel point of the image. Here is the sample code in MSDN:

public void GetPixel_Example(PaintEventArgs e)   
{   
    // Create a Bitmap object from an image file.   
    Bitmap myBitmap = new Bitmap("");   
    // Get the color of a pixel within myBitmap.   
    Color pixelColor = (50, 50);   
    // Fill a rectangle with pixelColor.   
    SolidBrush pixelBrush = new SolidBrush(pixelColor);   
    (pixelBrush, 0, 0, 100, 100);   
}  

It can be seen that the Bitmap class uses an elegant way to operate images, but the performance reduction brought about cannot be ignored. For example, for a 800*600 color image grayscale, the time it takes to be calculated in seconds. This speed is absolutely unbearable when performing image processing in actual projects.

BitmapData class

Namespace:

Specifies the properties of the bitmap image. The BitmapData class is used by the LockBits and UnlockBits methods of the Bitmap class. Not inheritable.

Fortunately, we also have the BitmapData class, which can lock Bitmap into system memory through BitmapData BitmapData LockBits ( ). The public properties of this class are:

Width Gets or sets the pixel width of the Bitmap object. This can also be regarded as the number of pixels in a scan line.

Height Gets or sets the pixel height of the Bitmap object. Sometimes called the number of scan lines.

PixelFormat Gets or sets the format for the pixel information in the Bitmap object that returns this BitmapData object.

Scan0 Gets or sets the address of the first pixel data in the bitmap. It can also be regarded as the first scan line in the bitmap.

Stride Gets or sets the span width (also known as the scan width) of the Bitmap object.

The following sample code in MSDN demonstrates how to use PixelFormat, Height, Width, and Scan0 properties; LockBits and UnlockBits methods; and ImageLockMode enumeration.

private void LockUnlockBitsExample(PaintEventArgs e)   
{  
    // Create a new bitmap.   
    Bitmap bmp = new Bitmap("c:\\");  
    // Lock the bitmap‘s bits.    
    Rectangle rect = new Rectangle(0, 0, , );   
     bmpData =   
        (rect, ,   
        );   
    // Get the address of the first line.   
   IntPtr ptr = bmpData.Scan0;  
    // Declare an array to hold the bytes of the bitmap.   
    int bytes  =  * ;   
    byte[] rgbValues = new byte[bytes];  
    // Copy the RGB values into the array.   
    (ptr, rgbValues, 0, bytes);  
    // Set every red value to 255.    
    for (int counter = 0; counter < ; counter+=3)   
        rgbValues[counter] = 255;   
    // Copy the RGB values back to the bitmap   
    (rgbValues, 0, ptr, bytes);  
    // Unlock the bits.   
    (bmpData);  
    // Draw the modified image.   
    (bmp, 0, 150);  
}  
 
//or 
Bitmap bitmap = new Bitmap("");
BitmapData bitmapData = (new Rectangle(0, 0, , ), , PixelFormat.Format32bppArgb);
// Perform image processing operations on BitmapData(bitmapData);

The above code demonstrates how to access an image in an array without using the inefficient GetPixel() and SetPixel().

IntPtr

Use IntPtr to directly manipulate the memory data of the image.

By obtaining the handle of the image (IntPtr), you can use pointer operations to access and modify the pixel values ​​of the image.

This approach requires more advanced programming skills and understanding of memory management.

Bitmap bitmap = new Bitmap("");
IntPtr ptr = (new Rectangle(0, 0, , ), , PixelFormat.Format32bppArgb);
// Use IntPtr for image processing(ptr);

unsafe code

In reality, the above practice still cannot meet our requirements. Image processing is an operation with relatively large computing volume, which is different from the general application we write. What we need is an image processing program that has performance comparable to C++ programs. How does C++ improve efficiency? The answer is: pointer. Fortunately .Net also allows us to use pointers, only in non-secure code blocks. What is non-secure code?

To keep type-safe, pointer arithmetics are not supported by default in C#. However, by using the unsafe keyword, you can define an unsafe context in which pointers can be used. In a common language runtime (CLR), unsafe code refers to code that cannot be verified. Unsafe code in C# is not necessarily dangerous, it is just code whose security cannot be verified by CLR. Therefore, CLR only performs operations on unsafe code in a fully trusted assembly. If you use unsafe code, it is your responsibility to ensure that your code does not cause security risks or pointer errors. The unsafe code has the following properties:

  • Methods, types, and blocks of code that can be defined as unsafe.
  • In some cases, unsafe code can improve the performance of your application by removing array bounds checks.
  • When calling native functions that require pointers, unsafe code is required.
  • Using unsafe codes will cause security risks and stability risks.
  • In C#, in order to compile unsafe code, the application must be compiled with /unsafe.

As stated in the C# Language Specification, unsafe code is actually a "safe" feature, both from the developer and from the user's perspective. Unsafe code must be explicitly marked with the modifier unsafe so that developers do not misuse unsafe features, and the execution engine will ensure that unsafe code is not executed in an untrusted environment.

The following code demonstrates how to use the BitmapData class to traverse an image using a pointer. The code in the unsafe code block here is non-safe code.

//Create an imageBitmap image =  new Bitmap( "c:\\images\\" );   
//Get the BitmapData object of the imageBitmapData data = ( new Rectangle( 0 , 0 ,  ,  ) ,   , PixelFormat.Format24bppRgb  );    
//Loop processingunsafe   
{    
       byte* ptr = ( byte* )( data.Scan0 );    
       for( int i = 0 ; i <  ; i ++ )   
       {   
          for( int j = 0 ;  j <  ;  j ++ )   
           {   
             // write the logic implementation here   
             ptr += 3;     
           }   
         ptr +=  -  * 3;   
       }   
}  

There is no doubt that this method is the fastest, so in actual engineering, pointers are used to access image pixels.

Byte alignment problem

In the above example, ptr += - * 3 means to cross a useless area. The reason is that the image data is aligned by 4 bytes when stored in memory. The specific explanation is as follows:

Suppose that an image has a width of 6, and assume that it is in Format24bppRgb format (3 bytes per pixel, in the following discussion, unless otherwise specified, Bitmap is considered 24-bit RGB). Obviously, each line needs 6*3=18 bytes of storage. That's true for Bitmap. But for BitmapData, although it is still equal, probably for display performance considerations, the actual number of bytes on each line will become an integer greater than or equal to the closest 4 to it, and the actual number of bytes at this time is Stride. In this case, 18 is not an integer multiple of 4, while the 4 closest to 18 than 18 is 20, so this = 20. Obviously, when the width itself is a multiple of 4, = * 3.

Drawing a picture may be better understood. R, G, and B represent 3 primary color component bytes respectively, and BGR represents one pixel. To make it easier, I inserted a space between each pixel, but it actually didn't. X represents the bytes that are automatically inserted by filling multiples of 4. In order to conform to human reading habits, I have branched out, but in computer memory, it should be regarded as a continuous segment.

|-------Stride-----------| 
|-------Width---------| 
Scan0: 
BGR BGR BGR BGR BGR BGR XX 
BGR BGR BGR BGR BGR BGR XX 
BGR BGR BGR BGR BGR BGR XX 

First, use data.Scan0 to find the address of the 0th component of the 0th pixel. This address points to a byte type, so it was defined as byte*ptr at that time. During row scanning, three values ​​(3 primary color components are taken successively at the current pointer position (see "as if it is the 0th color component of the current pixel). Note that the order represented by 0 1 2 is B G R. When taking the value pointed to by the pointer, it seems that p[n] and p += n and then p[0] are equivalent), and then move down by 3 positions (ptr += 3, which is regarded as referring to the 0th color component of the next pixel). After doing the operation, you have reached the * 3 position. You should skip the bytes marked X in the figure (there are a total of Stride - Width * 3 bytes), and the code is ptr += - * 3.

Summarize

By reading this article, I believe you have already gained an understanding of several possible methods that may be used for image processing using C#. As for which method is adopted, it depends on your performance requirements. The first method is the most elegant; the third method is the fastest, but not the safe code; the second method takes a compromise, ensuring that it is the safe code while improving efficiency. People who are familiar with C/C++ programming may prefer the third method, and I personally prefer the third method.

The above is the detailed explanation of the common methods of image processing in C# (Bitmap, BitmapData, IntPtr). For more information about C# image processing, please pay attention to my other related articles!