Rendering Partial Bitmaps

In Chapter 7 we saw that the Bitmap class provides the LockBits and UnlockBits methods, but we didn't get to use them. LockBits and UnlockBits lock and unlock bitmap pixels in system memory. Each call to LockBits should be followed by a call to UnlockBits.

Why might you want to lock bitmap pixels? Rendering (painting) bitmaps and images is a resource-consuming operation, and it is one of the most frequently performed graphics operations. Suppose you want to change the color or intensity level of a bitmap. You could always loop though the bitmap pixel by pixel and use SetPixel to modify its properties, but that is a huge time- and resource-consuming operation.

Note

The code used in this chapter uses classes defined in the System.Drawing.Imaging namespace, so be sure to add a reference to this namespace in your applications.

A better option would be to use LockBits and UnlockBits. These methods allow you to control any part of the bitmap by specifying a range of pixels, eliminating the need to loop through each pixel of the bitmap.

To use this option, first call LockBits, which returns the BitmapData object. BitmapData specifies the attributes of a bitmap. Before we examine the members of the BitmapData class, let's take a look at the LockBits and UnlockBits methods. The LockBits method is defined as follows:

 

public BitmapData LockBits( Rectangle rect, ImageLockMode flags, PixelFormat format);

 

LockBits takes three parameters of type Rectangle, ImageLockMode enumeration, and PixelFormat enumeration, and it returns an object of type BitmapData. The rectangle defines the portion of the bitmap to be locked in system memory.

UnlockBits takes a single parameter of type BitmapData, which was returned by LockBits. This method is defined as follows:

 

public void UnlockBits(BitmapData bitmapdata);

 

The ImageLockMode enumeration used in LockBits provides the access level to the data. Table 8.1 describes the members of ImageLockMode.

The pixel format defines the number of bits of memory associated with one pixel of data, as well as the order of the color components within a single pixel. Generally the number of bits per pixel is directly proportional to the quality of the image because the pixel can store more colors.

Table 8.1. ImageLockMode members

Member

Description

ReadOnly

The locked portion of the bitmap is for reading only.

ReadWrite

The locked portion of the bitmap is for reading or writing.

UserInputBuffer

The buffer used for reading or writing pixel data is allocated by the user.

WriteOnly

The locked portion of the bitmap is for writing only.

The PixelFormat enumeration represents the pixel, which is useful when you need to change the format of a bitmap or a portion of it. The members of the PixelFormat enumeration are described in Table 8.2.

8.1.1 Drawing Grayscale or Other Color Images

To demonstrate the use of LockBits and UnlockBits, we will change the pixels of a bitmap using the GetPixel and SetPixel methods. As we discussed in Chapter 7, an application can use GetPixel and SetPixel to get and set the colors of each pixel of a bitmap. To set a bitmap color to grayscale or other colors, an application reads the current color using GetPixel, calculates the grayscale value, and calls SetPixel to apply the new color.

In the following code snippet we read the color of a pixel; calculate the grayscale value by applying a formula to the red, green, and blue components; and call SetPixel to set the pixel's new grayscale color.

 

Color curColor = curBitmap.GetPixel(i, j); int ret = (curColor.R + curColor.G + curColor.B) / 3; curBitmap.SetPixel(i, j, Color.FromArgb(ret, ret, ret));

 

Listing 8.1 draws an image with its original color settings and later redraws it in grayscale. The Width and Height properties of the Bitmap class are used to loop through each pixel of the bitmap, and SetPixel is used to set the pixel's color to grayscale.

Table 8.2. PixelFormat members

Member

Description

Alpha

The pixel data contains alpha values that are not premultiplied.

DontCare

No pixel format is specified.

Format1bppIndexed

1 bit per pixel, using indexed color. The color table therefore has two colors in it.

Format4bppIndexed

4 bits per pixel, using indexed color.

Format8bppIndexed

8 bits per pixel, using indexed color.

Format16bppArgb1555

16 bits per pixel, giving 32,768 colors; 5 bits each are used for red, green, and blue, and 1 bit is used for alpha.

Format16bppGrayScale

16 bits per pixel, giving 65,536 shades of gray.

Format16bppRgb555

16 bits per pixel; 5 bits each are used for red, green, and blue. The last bit is not used.

Format16bppRgb565

16 bits per pixel; 5 bits are used for red, 6 bits for green, and 5 bits for blue.

Format24bppRgb

24 bits per pixel; 8 bits each are used for red, green, and blue.

Format32bppArgb

32 bits per pixel; 8 bits each are used for alpha, red, green, and blue. This is the default GDI+ color combination.

Format32bppPArgb

32 bits per pixel; 8 bits each are used for alpha, red, green, and blue. The red, green, and blue components are premultiplied according to the alpha component.

Format32bppRgb

32 bits per pixel; 8 bits each are used for red, green, and blue. The last 8 bits are not used.

Format48bppRgb

48 bits per pixel; 16 bits each are used for red, green, and blue.

Format64bppArgb

64 bits per pixel; 16 bits each are used for alpha, red, green, and blue.

Format64bppPArgb

64 bits per pixel; 16 bits each are used for alpha, red, green, and blue. The red, green, and blue components are premultiplied according to the alpha component.

Gdi

GDI colors.

Indexed

Color-indexed values, which are an index to colors in the system color table, as opposed to individual color values.

Max

The maximum value for this enumeration.

PAlpha

The format contains premultiplied alpha values.

Undefined

The format is undefined.

Listing 8.1 Using SetPixel to change the color scale of a bitmap

// Create a Graphics object from a button // or menu click event handler Graphics g = this.CreateGraphics(); g.Clear(this.BackColor); // Create a Bitmap object Bitmap curBitmap = new Bitmap("roses.jpg"); // Draw bitmap in its original color g.DrawImage(curBitmap, 0, 0, curBitmap.Width, curBitmap.Height); // Set each pixel to grayscale using GetPixel // and SetPixel for (int i = 0; i < curBitmap.Width; i++) { for (int j = 0; j < curBitmap.Height; j++) { Color curColor = curBitmap.GetPixel(i, j); int ret = (curColor.R + curColor.G + curColor.B) / 3; curBitmap.SetPixel(i, j, Color.FromArgb(ret, ret, ret)); } } // Draw bitmap again with gray settings g.DrawImage(curBitmap, 0, 0, curBitmap.Width, curBitmap.Height); // Dispose of object g.Dispose();

8.1.2 Using BitmapData to Change Pixel Format

In the previous section we discussed how to set the pixel format of a bitmap by reading pixels one by one. You can also set the pixel format by using the BitmapData class and its members.

The BitmapData object specifies the attributes of a bitmap, including size, pixel format, starting address of the pixel data in memory, and length of each scan line (stride). These properties are described in Table 8.3. All of the properties have both get and set types.

Now let's set the color of pixels in a bitmap by using LockBits and UnlockBits. This approach is faster than using the SetPixel method. Listing 8.2 uses LockBits and UnlockBits to set a bitmap pixel format. First we create an Image object from a file, followed by a Bitmap object from the Image object. Then we call LockBits, which returns a BitmapData object. Next we call PixelFormat to set the pixel format. You can use any of the PixelFormat enumeration values. Finally, we call UnlockBits to unlock the locked bits. Notice that the lockedRect rectangle in the LockBits method is the size of the bitmap.

Listing 8.2 Using LockBits and UnlockBits to set the grayscale of a bitmap

private void Form1_Paint(object sender, System.Windows.Forms.PaintEventArgs e) { Image img = Image.FromFile("roses.jpg"); Bitmap curImage = new Rectangle(0,0,curImage.Width,curImage.Height); Rectangle lockedRect = new Rectangle(0, 0, curImage.Width, curImage.Height); BitmapData bmpData = curImage.LockBits(lockedRect, ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb); // Set the format of BitmapData pixels bmpData.PixelFormat = PixelFormat.Max; // Unlock the locked bits curImage.UnlockBits(bmpData); // Draw image with new pixel format e.Graphics.DrawImage(curImage, 0, 0, curImage.Width, curImage.Height); }

Table 8.3. BitmapData properties

Property

Description

Height

Represents the pixel height.

PixelFormat

Represents the format of the pixels using the PixelFormat enumeration.

Scan0

Represents the address of the first pixel data in the bitmap.

Stride

Represents stride (also called scan width).

Width

Represents the pixel width.

Figure 8.1 shows the output from Listing 8.2. The entire bitmap is grayscale.

Figure 8.1. Using BitmapData to set grayscale

If a bitmap is huge and we want to change the format of only a few pixels, LockBits and UnlockBits really help. Using these methods, we can lock and render only the part of a bitmap we want to work on instead of rendering the entire bitmap. Suppose we want to change the pixel format of only the section of the bitmap starting at point (50, 50) and ending at point (200, 200). We simply change the rectangle passed to LockBits.

Listing 8.3 locks only that portion of the image specified by a rectangle.

Listing 8.3 Changing the pixel format of a partial bitmap

private void Form1_Paint(object sender, System.Windows.Forms.PaintEventArgs e) { Image img = Image.FromFile("roses.jpg"); Bitmap curImage = new Bitmap(img, new Size(img.Width, img.Height)); // Call LockBits, which returns a BitmapData object Rectangle lockedRect = new Rectangle(50,50,200,200); /* Rectangle lockedRect = new Rectangle(0,0,curImage.Width,curImage.Height); */ BitmapData bmpData = curImage.LockBits(lockedRect, ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb); // Set the format of BitmapData pixels bmpData.PixelFormat = PixelFormat.Max; // Unlock the locked bits curImage.UnlockBits(bmpData); // Draw image with new pixel format e.Graphics.DrawImage(curImage, 0, 0, curImage.Width, curImage.Height); }

Figure 8.2 shows the output from Listing 8.3. You may not see any difference between this illustration and Figure 8.1, but if you run the sample code yourself, you will notice that the color of only a small rectangle in the image is changed.

Figure 8.2. Changing the pixel format of a partial bitmap

GetPixel SetPixel versus LockBits UnlockBits

Comparing the two samples used in Listings 8.1 and 8.2 shows that the LockBits/UnlockBits method is significantly faster than the GetPixel/SetPixel method. To draw the same image, the GetPixel/SetPixel method takes about 150 milliseconds, and the LockBits/UnlockBits method takes about 50 milliseconds.

Категории