Reading/Writing Image Data
Fast reading/writing of bitmap data in VB.NET using pointers
Background
I was looking for a solution in VB.NET to speed up image read/write operations by using pointers to a bitmap instead of accessing the pixel object.
Using the Code
a) Reading in Bitmap Data in VB.NET Works as Follows
- Read in the bitmap data into a bitmap object.
- Get the bitmap data and lock its position in memory, so the garbage collection cannot move the data around anymore.
- Get a pointer to the first byte of the image data, which is
bmpData.Scan0
. - Calculate the offset of the pixel from the first byte as
(bmpData.Stride * row) + (4 * column)
. - Read out the bytes corresponding to the individual R, G, B channels of the pixel. Here, the pointer position is calculated for the
32bppArgb
format in which 1 bytes is reserved for each channel in the order B, G, R, transparency (alpha channel). In this example, the channel intensities are converted to a grayscale value. - Unlock the bitmap data.
- Do some calculation with the data in the array.
'read in bitmap
fs = New FileStream(m_imgpath, FileMode.Open, FileAccess.Read)
bm = New Bitmap(fs)
fs.Close()
Dim grayval as double
Dim arr(bm.height, bm.width) as Double
Dim pixptr As Integer
'weights for grayscal conversion
Dim wb, wg, wr as double
'chose suitable weights for your application here (display, visual effect, computer vision etc.)
wr = 0.2126 : wg = 0.7125 : wb = 1 - (wr + wg)
'get bitmap data and lock it in memory
Dim rect As New Rectangle(0, 0, bm.Width, bm.Height)
Dim bmpData As System.Drawing.Imaging.BitmapData = bm.LockBits
(rect, Drawing.Imaging.ImageLockMode.ReadWrite, Imaging.PixelFormat.Format32bppArgb)
'iterate over the rows and columns and read the data into an array
For i = 0 To bmpData.Height - 1
For j = 0 To bmpData.Width - 1
pixptr = (bmpData.Stride * i) + (4 * j)
grayval = wb*CType(Marshal.ReadByte(bmpData.Scan0, pixptr), Integer) 'B
grayval += wg*CType(Marshal.ReadByte(bmpData.Scan0, pixptr + 1), Integer) 'G
grayval += wr*CType(Marshal.ReadByte(bmpData.Scan0, pixptr + 2), Integer) 'R
arr(i, j) = grayval
Next 'j
Next 'i
'Unlock the bitmap data
bm.UnlockBits(bmpData)
'do some calculation with the data...
b) Writing Image Data to a Bitmap Using VB.NET Works in a Similar Way
- Create the bitmap to write to.
- Get the
bitmapdata
and lock it in memory. - Get the pointer to the first byte.
- Get the offset of the actual pixel to write to.
- Get the offset of the pixel's channel.
- Normalize the value to the interval [0, 255].
- Write the normalized value to the channel byte.
- Unlock the bitmap data.
'create a bitmap with required width and height
bm = New Bitmap(Width, Height)
Dim rect As New Rectangle(0, 0, bm.Width, bm.Height)
Dim bmpData As System.Drawing.Imaging.BitmapData = bmPhase.LockBits
(rect, Drawing.Imaging.ImageLockMode.ReadWrite, Imaging.PixelFormat.Format32bppArgb)
maxval = max(arr)
For i = 0 To bm.Height - 1
For j = 0 To bm.Width - 1
'convert into value btw 0 and 255, then convert into byte
byteval = CByte(Math.Abs(arr(i, j)) / maxval * 255)
pixptr = (bmpData.Stride * i) + (4 * j)
Marshal.WriteByte(bmpData.Scan0, pixptr, byteval) 'B
Marshal.WriteByte(bmpData.Scan0, pixptr + 1, byteval) 'G
Marshal.WriteByte(bmpData.Scan0, pixptr + 2, byteval) 'R
Marshal.WriteByte(bmpData.Scan0, pixptr + 3, 255) 'alpha value,
'set transparency to zero
Next
Next
bm.UnlockBits(bmpData)
c) Writing bitmap in C++/CLI
For comparison, I include the same steps for writing image data in C++/CLI. C++ allows the use of pointers. As a result, the code is shorter and faster. Herein, "col
" is a struct
that contains the RGB values and "RainbowNumberToColor
" computes a color coding. I took this function from C# helper (Map numeric values to and from colors in a color gradientC# Helper (csharphelper.com)).
Bitmap^ output = gcnew Bitmap(width, height);
Imaging::BitmapData^ bitmapData1 = output->LockBits(Rectangle(0, 0, width, height),
Imaging::ImageLockMode::ReadOnly, Imaging::PixelFormat::Format32bppArgb);
IntPtr^ pbm = bitmapData1->Scan0;
Byte* imagePointer1 = (Byte*)pbm->ToPointer();
//compute scaling factor for display
//assume there is a function "max" for computing the maximum value
double max = max(array);
double value;
for (i = 0; i < height; i++)
{
for (j = 0; j < width; j++)
{
//map value into range [0,1]
value = array[i,j]/max;
//apply color coding
col = Utilities::RainbowNumberToColor(value);
imagePointer1[0] = (Byte)col.B;
imagePointer1[1] = (Byte)col.G;
imagePointer1[2] = (Byte)col.R;
imagePointer1[3] = 255;
//4 bytes per pixel
imagePointer1 += 4;
}//loop over j
//4 bytes per pixel
imagePointer1 += (bitmapData1->Stride - (bitmapData1->Width * 4));
}//loop over i
output->UnlockBits(bitmapData1);
Points of Interest
VB.NET does not have pointer arithmetic or unsafe code. As a result, I had to use the read/write functions in the namespace "Marshal
" instead. This is somewhat slower than direct pointer arithmetics but still much faster than accessing the pixel object. The functions in the namespace Marshal
also help to translate code from C# or C++ which uses pointer arithmetics to VB.NET.
History
- 25th February, 2021: Initial version
- 27th February, 2021: C++/CLI example added