Reputation: 1565
I am running an application which is working with bitmap images. Now I am looking for a fast way to swap the "Red" and "Blue" values of a "Format24bppRgb" bitmap image. In my C# code my first try was to use an unsafe code fragment:
var bmpData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height),
ImageLockMode.ReadWrite, bmp.PixelFormat);
unsafe
{
byte* array = (byte*)bmpData.Scan0.ToPointer();
byte temp;
for (int x = 0; x < bmp.Width * bmp.Height * 3; x = x + 3) {
temp = *(array + x + 2);
*(array + x + 2) = *(array + x);
*(array + x) = temp;
}
}
for my used bitmap sizes this takes around 50-70ms. Now I tried to do the work in an external library (based on C++) with a pinvoke call:
[DllImport("ByteSwitch.dll")]
public static extern IntPtr ChangeRB(IntPtr data, int width, int height);
data = ChangeRB(bmpData.Scan0, bmp.Width, bmp.Height);
Which is defined as follows:
extern "C" __declspec(dllexport) void* ChangeRB(void* xArray, int xHeight, int xWidth);
void* ChangeRB(void* array, int height, int width)
{
unsigned char* _array = (unsigned char*)array;
char temp;
for (int x = 0; x < height * width * 3; x = x + 3)
{
temp = _array[x + 2];
_array[x + 2] = _array[x];
_array[x] = temp;
}
return _array;
}
and this call takes around 1ms! So I cannot explain the huge performance difference here - or is it really the case that the unmanaged pinvoke is so much faster than the "unsafe" code fragment?
Upvotes: 1
Views: 1002
Reputation: 139316
The performance problem doesn't come from interop nor from C#, it comes from the fact you use bitmap's Width
and Height
in the loop. Both internally call a GDI Plus API:
public int Width {
get {
int width;
int status = SafeNativeMethods.Gdip.GdipGetImageWidth(new HandleRef(this, nativeImage), out width);
if (status != SafeNativeMethods.Gdip.Ok)
throw SafeNativeMethods.Gdip.StatusException(status);
return width;
}
}
And note you don't do this in the C/C++ case... you pass precomputed height and width. So, if you change the C# version for this:
unsafe
{
byte* array = (byte*)bmpData.Scan0.ToPointer();
byte temp;
var max = bmp.Width * bmp.Height * 3;
for (int x = 0; x < max; x = x + 3) {
temp = *(array + x + 2);
*(array + x + 2) = *(array + x);
*(array + x) = temp;
}
}
It may run globally even faster. You can also use a safe version like this:
private static void ChangeSafe(Bitmap bmp, BitmapData bmpData)
{
var array = bmpData.Scan0;
byte temp;
var max = bmp.Width * bmp.Height * 3;
for (var x = 0; x < max; x = x + 3)
{
temp = Marshal.ReadByte(array + x + 2);
Marshal.WriteByte(array + x + 2, Marshal.ReadByte(array + x));
Marshal.WriteByte(array + x, temp);
}
}
It's marginally slower but avoids the need for unsafe code.
Upvotes: 6