You want to implement some image-processing algorithms, and you want the operations to be reasonably fast.
Sample code folder: Chapter 10\LockImage
Use the InteropServices.Marshal.LockBits() method to prevent the operating system from moving the bitmap data around in memory. This greatly speeds up the program's access to the pixel data. This recipe presents a LockImage class that wraps the LockBits() functionality for easy use.
The LockImage class presented in this recipe and the remaining recipes in this chapter contains several image-processing methods. (The full LockImage class is listed in Recipe 10.21.) The goal is to provide enough examples to enable you to design your own image-processing functionality.
The processing function demonstrated in this recipe is Mirror(), a method of the LockImage class that flips an image left and right. To see how it works, create a form with a PictureBox on it that has its Dock property set to Fill and its SizeMode property set to StretchImage. Load a picture into its Image property, and add the following code to its Click event:
Private Sub PictureBox1_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles PictureBox1.Click ' ----- Mirror-image the bitmap. Dim mirrorIt As New LockImage mirrorIt.Image = PictureBox1.Image mirrorIt.Mirror( ) PictureBox1.Image = mirrorIt.Image End Sub
When you click on the picture, this procedure creates an instance of the LockImage class, copies the PictureBox's image to it, calls the Mirror() method to process the image, and then copies the image back into the PictureBox. This is the pattern for using any of the processing methods of the LockImage class.
Now let's look at the portions of the LockImage class that relate to the mirroring processs.
First, you must import the requisite namespaces. InteropServices.Marshal is required for its LockBits() method. The class defines a few class-level variables:
Imports System.Drawing.Imaging Imports System.Runtime.InteropServices.Marshal Public Class LockImage Private BaseImage As Bitmap Private BaseImageWidth As Integer Private BaseImageHeight As Integer Private TotalPixels As Integer Private ImageAddress As IntPtr Private ImageContent As BitmapData Private ImageBuffer( ) As Integer
The Image property stores or retrieves the bitmap image to be locked and processed:
Public Property Image( ) As Bitmap ' ----- User access to the relevant image. Get Return BaseImage End Get Set(ByVal Value As Bitmap) Dim canvas As Graphics BaseImage = New Bitmap(Value.Width, _ Value.Height, Value.PixelFormat) canvas = Graphics.FromImage(BaseImage) canvas.DrawImage(Value, 0, 0, _ Value.Width, Value.Height) canvas.Dispose( ) End Set End Property
The LockTheImage() method provides the important core functionality of this class; with it, you can lock down the bits of the bitmap and present the pixel data in an integer array for efficient processing. All pixel processing in the methods you create, such as the Mirror() method presented later, will process in place the integer pixel data stored in ImageBuffer().
Each 32-bit integer in ImageBuffer() represents a single pixel. The most significant byte is alpha, the opacity value. The next most significant byte is for red, then green, and the least significant byte is for blue. Each of these four values ranges from 0 to 255. Two other variables of importance for your image-processing methods are BaseImageWidth and BaseImageHeight. The ImageBuffer() array is one-dimensional, so these two values are required to determine the rectangular layout of the pixels:
Private Sub LockTheImage( ) ' ----- Lock the image in memory. How much room ' do we need? BaseImageWidth = BaseImage.Width BaseImageHeight = BaseImage.Height TotalPixels = BaseImageWidth * BaseImageHeight ' ----- Create a stable (locked) area in memory. It ' will store 32-bit color images. ReDim ImageBuffer(TotalPixels - 1) ImageContent = BaseImage.LockBits( _ New Rectangle(0, 0, BaseImageWidth, _ BaseImageHeight), ImageLockMode.ReadWrite, _ PixelFormat.Format32bppRgb) ImageAddress = ImageContent.Scan0 ' ----- Associate the buffer and the locked memory. Copy(ImageAddress, ImageBuffer, 0, TotalPixels) End Sub
The Mirror() method works by locating the first and last pixels of each row of the image, then swapping the pixels at those locations. The next and previous pixels in the row are swapped next, and this continues until all pixels in the row have been swapped. Here is the code for the Mirror() method:
Public Sub Mirror( ) ' ----- Make a left-to-right mirror image. Dim pixelIndex1 As Integer Dim pixelIndex2 As Integer Dim holdPixel As Integer Dim down As Integer ' ----- Lock the image for speed. LockTheImage( ) ' ----- Process each row of the image. For down = 0 To BaseImageHeight - 1 ' ----- Process each column, up to halfway across. pixelIndex1 = down * BaseImageWidth pixelIndex2 = pixelIndex1 + BaseImageWidth - 1 Do While pixelIndex1 < pixelIndex2 ' ----- Swap two pixels. holdPixel = ImageBuffer(pixelIndex1) ImageBuffer(pixelIndex1) = _ ImageBuffer(pixelIndex2) ImageBuffer(pixelIndex2) = holdPixel pixelIndex1 += 1 pixelIndex2 -= 1 Loop Next down ' ----- Finished. Unlock the image. UnlockTheImage( ) End Sub
The UnlockTheImage( ) method restores the processed pixel data in ImageBuffer( ) to the bitmap, ready to be retrieved by the code that uses the class:
Private Sub UnlockTheImage( ) ' ----- Unlock the memory area. Copy(ImageBuffer, 0, ImageAddress, TotalPixels) Image.UnlockBits(ImageContent) ImageContent = Nothing ReDim ImageBuffer(0) End Sub
Figure 10-26 shows a sample picture just before being flipped; Figure 10-27 shows the picture immediately afterwards.
Figure 10-26. An image about to be flipped horizontally
Recipe 10.21 includes the full source code for the LockImage class.
Figure 10-27. The same image after the Mirror( ) method has worked its magic