简体   繁体   中英

Get bitmap from .DLL, convert to byte[] and to image

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: photoshoppie

ugly test image:

旋转示例图像

result: 很棒的结果

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);
        }

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:

bitmapImageArray2[(y * w + x) * 3] = bitmapImageArray[(x * h + y) * 3 + 2];
bitmapImageArray2[(y * w + x) * 3 + 1] = bitmapImageArray[(x * h + y) * 3 + 1];
bitmapImageArray2[(y * w + x) * 3 + 2] = bitmapImageArray[(x * h + y) * 3];

Note: your value for size seems to be correct.

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.

        for (int y = 0; y < width; y++)
        {
            for (int x = 0; x < height; x++)
            {
                int red = imageData[offset + 3 * x] ;
                int green = imageData[offset + 3 * x + 1];
                int blue = imageData[offset + 3 * x + 2];
                Color color = Color.FromArgb(red, green, blue);
                bitmap.SetPixel(y, x, color);
            }
            offset += 3 * height;
        }

@Dim's answer helped, by doing that conversion first and then process as before increased the speed dramatically.

So the result:

    private void OpenImage(string filename)
    {
        OPEN_IMAGE_FILE(filename);
        ALLOC_MEMORY();
        int width = GET_IMAGE_WIDTH();
        int height = GET_IMAGE_HEIGHT();
        IntPtr buffer = GET_IMAGE_PIXELS();
        int size = width * height * 3;
        byte[] bitmapImageArray = new byte[size];
        Marshal.Copy(buffer, bitmapImageArray, 0, size);
        Bitmap bitmap3 = ConvertImage3(bitmapImageArray, height, width);
    }

    public Bitmap ConvertImage3(byte[] imageData, int height, int width)
    {
        int size = width * height * 3;
        byte[] bitmapImageArray2 = new byte[size];
        for (int y = 0; y < height; y++)
        {
            for (int x = 0; x < width; x++)
            {
                bitmapImageArray2[(y * width + x) * 3] = imageData[(x * height + y) * 3 + 2];
                bitmapImageArray2[(y * width + x) * 3 + 1] = imageData[(x * height + y) * 3 + 1];
                bitmapImageArray2[(y * width + x) * 3 + 2] = imageData[(x * height + y) * 3];
            }
        }

        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(bitmapImageArray2, 0, pNative, width * height * 3);
        bitmap.UnlockBits(bmData);
        return bitmap;
    }

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM