Ask Your Question
0

Convert DLL IntPtr to byte[] to bitmap

asked 2015-02-23 03:45:24 -0600

Tumelo M. Motloutsi gravatar image

Initially I thought to send an image from C++ opencv to C# required converting the opencv mat to an equivalent emgucv object. I have come to learn that it just requires unpacking the mat object to a type that C# can understand, which is uchar . . .apparently same as byte in C#

I have a C++ program that reads and an image and converts it to uchar (which is what a Mat object is anyway).

void DisplayImage::ReadImage()
{
    cv::Mat image = cv::imread("2015-02-05.jpg");
    image_rows = image.rows;
    image_cols = image.cols;
    image_type  = image.type();
    //image_uchar is data member of uchar*
    image_uchar = MatToBytes(image);
}

uchar *DisplayImage::MatToBytes(cv::Mat image)
{
    int image_size = image.total() * image.elemSize();
    uchar * image_uchar = new uchar[image_size];
    //image_uchar is a class data member of uchar*
    std::memcpy(image_uchar,image.data,image_size * sizeof(uchar));
    return image_uchar;
}

cv::Mat DisplayImage::BytesToMat()
{
    cv::Mat img(image_rows,image_cols,image_type,image_uchar,cv::Mat::AUTO_STEP);
    return img;
}

In the main program I assign a Mat object to the returned value from BytesToMat() and image looks fine.

My DLL looks like below

DisplayImage displayImage = DisplayImage();

extern "C" __declspec(dllexport) void SetImage()
{
    displayImage.ReadImage();   
}

extern "C" __declspec(dllexport) uchar *GetImage()
{
    return displayImage.image_uchar;
}

Just by the way; I firstly started by assigning the pointer to uchar (in C++) with a simple text and displayed it C# by Marshaling returned IntPtr (from dll) to PtrToStringAnsi and it worked.

[DllImport(@".\WrapperForPlayer.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
unsafe public static extern IntPtr GetImage();

[DllImport(@".\WrapperForPlayer.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
    public static extern void SetImage();

static void Main(string[] args)
{
    //byte[] image_byte  = new byte[500];
    SetImage();
    IntPtr intPtr = GetImage();
    unsafe
    {
        byte* image_byte = (byte*)intPtr;

    }
    Console.Read();
}

The only difference now is that the returned IntPtr contains address to image data which I want to turn to byte[] and then it will be easy to convert to Bitmap. How can I do that?

edit retag flag offensive close merge delete

Comments

1

Hi,

Maybe a stupid question, is there a particular reason to not do the reading directly with EmguCV ?

Eduardo gravatar imageEduardo ( 2015-02-23 04:21:26 -0600 )edit
2

imho, you can pass the IntPtr 'as is' to the Bitmap constructor.

but you got a memleak here: uchar * image_uchar = new uchar[image_size];

berak gravatar imageberak ( 2015-02-23 04:24:08 -0600 )edit

@berak thanks for your response, the Bitmap constructor does not take IntPtr, it takes a System.Drawing.Image type. I tried to cast it but no luck @Eduardo I need to do this exercise because later I am going to read the image using emgucv and send it C++ for processing using opencv. Your next question might be to just use Emgucv since its equivalent to opencv and has most methods wrapped. Well I might just do that . . .port everything to C# Emgucv and compromise performance.

Tumelo M. Motloutsi gravatar imageTumelo M. Motloutsi ( 2015-02-23 08:38:46 -0600 )edit

just try:

IntPtr ptr= get_img();
pictureBox1.Image = new Bitmap(w, h, 3 * w, System.Drawing.Imaging.PixelFormat.Format24bppRgb, ptr);`
berak gravatar imageberak ( 2015-02-23 08:59:14 -0600 )edit

@berak I tried that, thanks! but nothing loaded in the PictureBox. I debugged the code and the value ptr changes to indicate that it received something. I've been doing some more reading. . .is it not because char * in C++ is unmanaged and cannot be interpreted by C#?

Tumelo M. Motloutsi gravatar imageTumelo M. Motloutsi ( 2015-02-24 00:58:26 -0600 )edit

1 answer

Sort by ยป oldest newest most voted
2

answered 2015-04-09 09:34:13 -0600

Kamil gravatar image

I have tested two ways that solve C# (Bitmap) and native C++ (cv::Mat) communication problem. All communication is based by C++ Dll which is used in C# by DllImport-ing (PInvoke). For image information storing I use two similar structures. C++ structure consists char* member for image data and C# structure consists InPtr for same data. C# structure is defined as [StructLayout(LayoutKind.Sequential)]. This "ImgBridge" is main condition for successful communication.

There are two functions for C# that convert C# (Bitmap) to C++ (cv::Mat) and vice versa via mentioned "ImgBridge".:

public static unsafe Bitmap ImgBridge2Bitmap(ImgBridge imgBridge)
{
    if (    (0 < imgBridge.mImgWidth)
        &&  (0 < imgBridge.mImgHeight))
    {
        PixelFormat pixelFormat = GetPixelFormat(imgBridge.mBitsPerPixel);

        if (pixelFormat != PixelFormat.Undefined)
        {
            Bitmap bitmap = new Bitmap(imgBridge.mImgWidth, imgBridge.mImgHeight, pixelFormat);

            if (pixelFormat == PixelFormat.Format8bppIndexed)
            {
                SetGrayscalePalette(bitmap);
            }                                            

            BitmapData bitmapData = bitmap.LockBits(
                 new Rectangle(0, 0, bitmap.Width, bitmap.Height),
                 ImageLockMode.WriteOnly,
                 pixelFormat);

            CopyImgData(
                imgBridge.mBytesPerPixel,
                imgBridge.mImgWidth,
                imgBridge.mImgHeight,
                imgBridge.mBytesPerRow,
                imgBridge.mImgData,
                bitmapData.Stride,
                bitmapData.Scan0);

            bitmap.UnlockBits(bitmapData);

            return bitmap;
        }
    }

    return null;
}

_

public static unsafe ImgBridge Bitmap2ImgBridge(Bitmap bitmap)
{
    ImgBridge imgBridge = new ImgBridge();

    if (bitmap != null)
    {
        int BitsPerPixel = GetBitsPerPixel(bitmap.PixelFormat);

        if (BitsPerPixel != -1)
        {
            BitmapData bitmapData = bitmap.LockBits(
               new Rectangle(0, 0, bitmap.Width, bitmap.Height),
               ImageLockMode.ReadOnly,
               bitmap.PixelFormat);

            imgBridge.mImgWidth = bitmap.Width;
            imgBridge.mImgHeight = bitmap.Height;

            imgBridge.mChannelsCount = (BitsPerPixel / 8);
            imgBridge.mPixelsCount = (imgBridge.mImgWidth * imgBridge.mImgHeight);
            imgBridge.mBytesPerPixel = (BitsPerPixel / 8);
            imgBridge.mBitsPerPixel = BitsPerPixel;
            imgBridge.mBytesPerRow = (imgBridge.mBytesPerPixel * imgBridge.mImgWidth);

            imgBridge.mImgSize = (imgBridge.mPixelsCount * imgBridge.mBytesPerPixel);
            imgBridge.mImgData = Marshal.AllocHGlobal(imgBridge.mImgSize);

            CopyImgData(
                imgBridge.mBytesPerPixel,    
                bitmap.Width,
                bitmap.Height,    
                bitmapData.Stride,
                bitmapData.Scan0,    
                imgBridge.mBytesPerRow,
                imgBridge.mImgData);

            bitmap.UnlockBits(bitmapData);                              
        }
    }

    return imgBridge;
}

Both these function use CopyImgData function.:

public static unsafe void CopyImgData(int PixelSize, int ImgWidth, int ImgHeight,
    int SrcImgBytesPerRow, IntPtr SrcImgData, 
    int DstImgBytesPerRow, IntPtr DstImgData)
{
    if ((0 < PixelSize) && (0 < ImgWidth) && (0 < ImgHeight))
    {
        for (int h = 0; h < ImgHeight; h++)
        {
            byte* pSrcRow = ((byte*) SrcImgData + (h * SrcImgBytesPerRow));
            byte* pDstRow = ((byte*) DstImgData + (h * DstImgBytesPerRow));

            for (int w = 0; w < ImgWidth; w++)
            {
                for (int p = 0; p < PixelSize; p++)
                {
                    pDstRow[w * PixelSize + p] = pSrcRow[w * PixelSize + p];
                }
            }
        }
    }
}

At the beginning I had written that I have tested two ways. The second one inspired by GitHub/shimat/opencvsharp project https://github.com/shimat/opencvsharp... If you would like to try this one you will need to extract CopyMemory function https://github.com/shimat/opencvsharp... and implement it in your C# code. After this process you have to swap original CopyImgData function by

byte* pSrcImgData = (byte*) imgBridge.mImgData.ToPointer();
byte* pDstImgData = (byte*) bitmapData.Scan0.ToPointer();

int SrcStep = imgBridge.mBytesPerRow;
int DstStep = (((imgBridge.mImgWidth * imgBridge.mChannelsCount) + 3) / 4 * 4);

switch (pixelFormat)
{
    case PixelFormat.Format1bppIndexed:
    {
        // TODO

        break;
    }
    case PixelFormat.Format8bppIndexed:
    case PixelFormat.Format24bppRgb:
    {                                           
        for (int h = 0; h < imgBridge.mImgHeight; h++)
        {
            long SrcOffset = (h * SrcStep);
            long DstOffset = (h * DstStep);

            CopyMemory((pDstImgData + DstOffset), (pSrcImgData + SrcOffset), (uint) (imgBridge.mImgWidth * imgBridge.mChannelsCount));
        }                                     

        break;
    }
    default:
    {
        break;
    }
}

for ImgBridge2Bitmap and by this

int SrcStep = (((imgBridge.mImgWidth * imgBridge.mBytesPerPixel) + 3) / 4 * 4);
int DstStep = imgBridge.mBytesPerRow;                                

switch (bitmap.PixelFormat)
{
    case PixelFormat.Format1bppIndexed:
    {
        // TODO

        break;
    }
    case ...
(more)
edit flag offensive delete link more

Comments

Excellent, this works. Basically I needed the CopyImgData function. :)

Subba Rao gravatar imageSubba Rao ( 2015-04-09 22:47:33 -0600 )edit

This is what I exactly need ! Can you please mention the references you gave?

h.selcen gravatar imageh.selcen ( 2017-02-26 11:52:52 -0600 )edit

Question Tools

3 followers

Stats

Asked: 2015-02-23 03:45:24 -0600

Seen: 8,779 times

Last updated: Apr 09 '15