0

I have a camera which has a library developed in C# and a client application written in C++ 6. I have created a C# COM wrapper to link the two systems together and it is working quite well.

In the C# COM wrapper I have a System.Drawing.Bitmap object that contains the current frame from the camera.

In the C# COM wrapper I have a function public void GetFrameHandle(ref IntPtr hBitmap, [MarshalAs(UnmanagedType.SysInt)] ref int width, [MarshalAs(UnmanagedType.SysInt)] ref int height) which basically gets the HBITMAP from the frame bitmap into C++

This seems to work somewhat, and I can see the image in C++ but it is all garbled due to the pixelformat not being correct.

How can I determine the pixelformat from the HBITMAP in C++ and then display the correct image in the picture control?

I have performed a lot of investigation and it seems as if I need to create a new BITMAP from the transferred HBITMAP and then create a new HBITMAP from that which will be the correct pixel format and then use that to populate the picture control.

I'm not very sure how to begin this process

C# COM server:

    /// <summary>
    /// Called when an image has been grabbed and is ready to process
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    public void StreamGrabber_ImageGrabbed(object sender, ImageGrabbedEventArgs e)
    {

        // process the image
        try
        {
            // Acquire the image from the camera. Only show the latest image. The camera may acquire images faster than the images can be displayed.

            // Get the grab result.
            IGrabResult grabResult = e.GrabResult;

            // Check if the image can be displayed.
            if (grabResult.GrabSucceeded)
            {
                if (grabResult.IsValid)
                {
                    // create a new bitmap object for holding the image data from the camera
                    // the bits and things need to be able to be set according to the c++ program's requirements.
                    System.Drawing.Bitmap bitmap = new System.Drawing.Bitmap(grabResult.Width, grabResult.Height, System.Drawing.Imaging.PixelFormat.Format32bppRgb);
                    // Lock the bits of the bitmap.
                    System.Drawing.Imaging.BitmapData bmpData = bitmap.LockBits(new System.Drawing.Rectangle(0, 0, bitmap.Width, bitmap.Height), System.Drawing.Imaging.ImageLockMode.ReadWrite, bitmap.PixelFormat);
                    // Place the pointer to the buffer of the bitmap.
                    converter.OutputPixelFormat = PixelType.BGR8packed;
                    IntPtr ptrBmp = bmpData.Scan0;
                    converter.Convert(ptrBmp, bmpData.Stride * bitmap.Height, grabResult); //Exception handling TODO
                    bitmap.UnlockBits(bmpData);

                    // Assign a temporary variable to dispose the bitmap after assigning the new bitmap to the display control.
                    System.Drawing.Bitmap bitmapOld = this.currentFrame;
                    // Provide the display control with the new bitmap. This action automatically updates the display.
                    // this may require some mutex to ensure that only entire frames are retrieved
                    this.currentFrame = bitmap;

                    // if the client window is set, output bitmap to it.
                    if (m_clientWindow != null)
                        m_clientWindow.DrawImage(currentFrame, new System.Drawing.Point(0, 0));


                    if (bitmapOld != null)
                    {
                        // Dispose the bitmap.
                        bitmapOld.Dispose();
                    }

                    // notify that a frame is ready to obtain
                    BroadcastFrameEvent();
                }
            }
            else
            {
                BroadcastErrorEvent("StreamGrabber_ImageGrabbed", String.Format("Error: {0} {1}", grabResult.ErrorCode, grabResult.ErrorDescription));
            }
        }
        catch (Exception exception)
        {
            BroadcastErrorEvent("StreamGrabber_ImageGrabbed", exception.ToString());
        }
        finally
        {
            // Dispose the grab result if needed for returning it to the grab loop.
            e.DisposeGrabResultIfClone();
        }


    }


   public void GetFrameHandle(ref IntPtr hBitmap, [MarshalAs(UnmanagedType.SysInt)] ref int width, [MarshalAs(UnmanagedType.SysInt)] ref int height)
    {
        if (this.currentFrame != null)
        {
            // System.Drawing.Imaging.BitmapData bmpData = null;
            try
            {

                hBitmap = this.currentFrame.GetHbitmap();

                width = currentFrame.Width;
                height = currentFrame.Height;

            }
            catch (Exception ex)
            {
                BroadcastErrorEvent("GetFrame", ex.ToString());
            }
        }
    }


    public void SaveFrame(string filename)
    {
        try
        {
            if (m_debugMode == 1)
                MessageBox.Show("Filename = " + filename, "GigE COM Wrapper Debug");

            if (this.currentFrame != null)
                this.currentFrame.Save(filename, System.Drawing.Imaging.ImageFormat.Bmp);
        }
        catch (Exception ex)
        {
            BroadcastErrorEvent("SaveFrame", ex.ToString());
        }
    }

C++ client:

    // When a frame is ready to process
    HRESULT CameraEventHandler::OnFrameReady()
    {
        HRESULT hr = S_OK;

        //  if (m_debug == 1)
        //      MessageBox(NULL, "Frame is ready", "COM event received", MB_OK);

        // grab the frame from the COM wrapper and display it


        long pOutHb;

        if (pCamera != NULL)
        {
            BSTR bsFilename = SysAllocString(L"frame.bmp");

            hr = pCamera->SaveFrame(bsFilename);
            if (!SUCCEEDED(hr))
            {
                MessageBox(NULL, "COM Error", "Fail", MB_OK);
                return hr;
            }

            hr = pCamera->GetFrameHandle(&pOutHb, &width, &height);

            if (SUCCEEDED(hr))
            {

                BITMAP bm;
                HBITMAP hbFrameFromCOM;

                hbFrameFromCOM = (HBITMAP)pOutHb;

                // get the bitmap from the HBITMAP
                GetObject(hbFrameFromCOM, sizeof(BITMAP), &bm);

                LONG size = bm.bmWidthBytes * bm.bmHeight;

                        //create the bitmap?
                        // copy data across?
                        // create a new HBITMAP (hb)

                if (pDialog != NULL)
                {
                    // post the message
                    // this signals the dialog to update the picture control with the HBITMAP

                    pDialog->SendMessage(WM_REFRESH_BITMAP);
                }
            }
            else
                if (m_debug == 1)
                    MessageBox(NULL, "Get bitmap handle failed", "Fail", MB_OK);

        }

        return hr;
    }

Unsure how to proceed with this.

Interestingly, the frame.bmp that I'm saving from the C# code is also garbled. I think the two issues are related but I can't join the dots here.

Thanks for any help with it.

Karl M
  • 3
  • 4
  • What do you mean by "C++ 6"? Visual Studio 6? – Some programmer dude Jan 08 '19 at 07:02
  • If the bitmap is incorrect in C# when you save it, then the problem exists somewhere before even C++ code calling, no? – Simon Mourier Jan 08 '19 at 08:19
  • @Someprogrammerdude yes, VC++ 6. This is an old application and I can't port it due to size and complexity. It is far easier to upgrade just the image acquisition section of it and inject the image into the program at the point where it would acquire the image. – Karl M Jan 08 '19 at 20:32
  • @SimonMourier Thanks I did think the same thing but I assumed it was because when I saved the image I was simply dumping the bitmap to disk. There is some buffer copying happening between the camera and the final bitmap and perhaps this is causing the issues. I'll post that code as well. – Karl M Jan 08 '19 at 20:34
  • @SimonMourier, well that was interesting. I modified the line `converter.OutputPixelFormat = PixelType.BGR8packed;` in the C# frame event handler to `converter.OutputPixelFormat = PixelType.BGRA8packed;` and now I have a consistent grayscale image in both the C# COM wrapper and the C++ client. It should be colour. But this may indeed be the key. – Karl M Jan 08 '19 at 21:13
  • This is all working now. Thanks for all the help. The fun part will be to determine which pixelFormat the legacy software expects, but for now I can see the video stream in the C++ client control. – Karl M Jan 08 '19 at 22:40

1 Answers1

0

Thanks for the help I was able to get it working.

The code above was pretty much correct, except the pixelFormats needed to match in the C# COM wrapper between the camera and the bitmap that is used as the internal frame buffer.

The grayscale image output after the frame became stable was caused because I was setting the camera mode to grayscale in the c++ client. I changed it to YUV and it was all good.

There are a lot of places where modes can be set!

I didn't need to worry about changing anything in the C++ client. All that is required is to throw the HBITMAP sent by the COM wrapper at the dialog's picture control to display it.

Cheers.

Karl M
  • 3
  • 4