3

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:

rotated example image

result: awesome 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);
        }
Community
  • 1
  • 1
CularBytes
  • 8,367
  • 6
  • 65
  • 94

2 Answers2

2

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.

dim
  • 543
  • 7
  • 16
  • Thank you! I was about to post an answer that does the same looping as they did, but building a bitmap like that seems a bit intensive. from 341ms to 11ms by first doing this and then following the rest of my solution – CularBytes Apr 29 '16 at 13:59
0

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.

CularBytes
  • 8,367
  • 6
  • 65
  • 94