I have received an .DLL for testing purposes, this .dll includes the functionality that will later be used to process live images from hardware.
For this simple .dll, I can open an image (load into memory), get the width and height, and get the pixels which need to be converted to a image. Loading, getting width and getting height is fine, but getting the pixels and converting that to a Bitmap or Image is a problem.
C++ example source that I received:
ApiFunc->OpenImageFile(this->OpenPictureDialog1->FileName.c_str());
ApiFunc->AllocMemory();
w = ApiFunc->GetImageWidth();
h = ApiFunc->GetImageHeight();
Image1->Width = w;
Image1->Height = h;
unsigned char* ptr = ApiFunc->GetImagePixels();
COLORREF pixel;
int r,g,b;
for(int y=0; y<w; y++)
{
for(int x=0; x<h; x++)
{
r = (int)*(ptr+3*x);
g = (int)*(ptr+3*x+1);
b = (int)*(ptr+3*x+2);
Image1->Canvas->Pixels[y][x] = RGB(r,g,b);
}
ptr += 3*h;
}
Where in ApiFunc, this can be found:
void __fastcall TAPIFunc::LoadDll(HINSTANCE m_hMain)
{
//some others above
GET_IMAGE_PIXELS = (func_GET_IMAGE_PIXELS )GetProcAddress( m_hMain, "GET_IMAGE_PIXELS");
//some others below
}
unsigned char* __fastcall TAPIFunc::GetImagePixels(void)
{
return GET_IMAGE_PIXELS();
}
So now what I have tried so far, I’ve tried using byte[] as return parameter, but that throw an MarshalDirectiveException.
[DllImport("ImageTest.dll")]
public static extern IntPtr GET_IMAGE_PIXELS();
private void OpenImage(string filename)
{
OPEN_IMAGE_FILE(filename);
ALLOC_MEMORY();
int width = GET_IMAGE_WIDTH(); //=800
int height = GET_IMAGE_HEIGHT(); //=600
IntPtr buffer = GET_IMAGE_PIXELS();
int size = width * height * 3;//not sure what the size must be, I think this is one of the issues, just following logic of one answer below.
//but source: https://stackoverflow.com/a/16300450/2901207
byte[] bitmapImageArray = new byte[size];
Marshal.Copy(buffer, bitmapImageArray, 0, size);
Bitmap bitmap = new Bitmap(width, height, PixelFormat.Format24bppRgb);
BitmapData bmData = bitmap.LockBits(new System.Drawing.Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadWrite, bitmap.PixelFormat);
IntPtr pNative = bmData.Scan0;
Marshal.Copy(imageData, 0, pNative,size);
bitmap.UnlockBits(bmData);
bitmap.Save(Environment.CurrentDirectory + @"result.bmp");
using (var ms = new MemoryStream(bitmapImageArray))
{
//both throw exception: Parameter is not valid
Bitmap bmp = new Bitmap(ms);
Image bitmapImage = Image.FromStream(ms);
}
}
answer sources:
answer 1, using bitmap.LockBits method
answers 2 and 3, using memoryStream
Just to make sure I have a valid image to test with, I saved an image in photoshop, with this option:
ugly test image:
Beautiful isn’t it? 🙂
Also tried using a for-loop, and run until it crashes. ran until count = 1441777 and on another image count = 1527793 (same dimensions).
int count = 0;
for (int i = 0; i < width * height * 4; i++)
{
count++;
bitmapImageArray[i] = Marshal.ReadByte(buffer, i);
}
2
Answers
Ok, while this does solve the problem, I am still not satisfied, of course I am happy to get some result, but it takes to long, in my opinion. It takes, from instantiating the BitMap to before saving: 241ms. While this might look small, retrieving images like this that needs to produce a fluent video is a bit wrong.
@Dim's answer helped, by doing that conversion first and then process as before increased the speed dramatically.
So the result:
Now I have to somehow improve the speed of getting the image from the .DLL, but that is of course out of the scope for this question.
It appears from the wrong result that the horizontal/vertical is actually switched. Your image is obtained with pixels organized columns by columns, instead of rows by rows (as usually done).
This is confirmed by the example source you received: the outer loop goes to w (width) and the inner loop goes to h (height), although the outer variable is y and the inner variable is x, which is confusing.
It also appears that the R and B components are switched (I don’t have the explanation, here, but trust me).
Therefore, after having obtained the array using
byte[] bitmapImageArray = new byte[size];
Marshal.Copy(buffer, bitmapImageArray, 0, size);
You must reorganize it. allocate another buffer
bitmapImageArray2
of the same size, loop over all pixels (row by row, or column by column, as you prefer, but with correct naming of the variables unlike the example : x goes to w and y goes to h), and write it to the destination array like that:Note: your value for
size
seems to be correct.