When working with Halcon's HImage objects in C#, converting them into standard .NET Bitmaps can present performance challenges, especial when dealing with color images that have separate red, green, and blue channels.
The key issue revolves around correctly interpreting pointer data returned by Halcon methods like GetImagePointer3. When this method returns an HTuple of type Long, accessing the actual memory address requires using .L, not .I. This distinction becomes critical on 64-bit platforms where incorrect casting may lead to invalid addresses.
Rather than directly manipulating pointers through unsafe code, an alternative approach involves copying pixel data into managed byte arrays using Marshal.Copy:
// Load source image
HImage sourceImg = new HImage("sample.png");
// Extract individual channel pointers
sourceImg.GetImagePointer3(out IntPtr redPtr, out IntPtr greenPtr, out IntPtr bluePtr,
out string imgType, out int width, out int height);
// Allocate buffers for each channel
byte[] redChannel = new byte[width * height];
byte[] greenChannel = new byte[width * height];
byte[] blueChannel = new byte[width * height];
// Copy raw pixel values from unmanaged memory
Marshal.Copy(redPtr, redChannel, 0, redChannel.Length);
Marshal.Copy(greenPtr, greenChannel, 0, greenChannel.Length);
Marshal.Copy(bluePtr, blueChannel, 0, blueChannel.Length);
Two distinct approaches exist for constructing the final Bitmap object:
Managed Memory Approach
This method avoids unsafe contexts entirely but incurs higher overhead due to repeated marshaling operations during pixel assignment:
Bitmap resultBmp = new Bitmap(width, height, PixelFormat.Format32bppRgb);
Rectangle imageRect = new Rectangle(0, 0, width, height);
BitmapData bmpData = resultBmp.LockBits(imageRect, ImageLockMode.WriteOnly,
PixelFormat.Format32bppRgb);
try
{
IntPtr scanStart = bmpData.Scan0;
for (int idx = 0; idx < redChannel.Length; idx++)
{
// Write BGRA components sequentially
Marshal.WriteByte(scanStart, idx * 4, blueChannel[idx]);
Marshal.WriteByte(scanStart, idx * 4 + 1, greenChannel[idx]);
Marshal.WriteByte(scanStart, idx * 4 + 2, redChannel[idx]);
Marshal.WriteByte(scanStart, idx * 4 + 3, 255); // Alpha channel
}
}
finally
{
resultBmp.UnlockBits(bmpData);
}
Direct Pointer Manipulation
Using fixed/unsafe blocks provides better performance at the cost of increased complexity and potential memory safety issues:
Bitmap fastBmp = new Bitmap(width, height, PixelFormat.Format32bppRgb);
Rectangle area = new Rectangle(0, 0, width, height);
BitmapData rawData = fastBmp.LockBits(area, ImageLockMode.WriteOnly,
PixelFormat.Format32bppRgb);
unsafe
{
byte* pixelBase = (byte*)rawData.Scan0.ToPointer();
int totalPixels = width * height;
for (int pos = 0; pos < totalPixels; pos++)
{
int targetOffset = pos * 4;
pixelBase[targetOffset] = blueChannel[pos]; // B
pixelBase[targetOffset + 1] = greenChannel[pos]; // G
pixelBase[targetOffset + 2] = redChannel[pos]; // R
pixelBase[targetOffset + 3] = 255; // A
}
}
fastBmp.UnlockBits(rawData);
Benchmark results show significant differences between these implementations. For a test image sized 3072×2048 pixels:
- Managed approach: ~250 milliseconds
- Unsafe pointer method: ~10 milliseconds
Reducing pixel format from 32bpp to 24bpp (by omitting alpha) improves performance further—approximately 20% faster for the managed version and even more so for direct memory access patterns.