从HBITMAP获取C++原始像素数据

8
我对使用p/invoke调用方法还比较新,并且想知道有没有人可以指导我如何从hbitmap中检索原始像素数据(unsigned char*)。
以下是我的情况:
我在C#端加载了一个.NET Bitmap对象,并将它的IntPtr发送到我的非托管c ++方法。一旦我在C++端收到hbitmap指针,我想访问Bitmap的像素数据。我已经创建了一个接受unsigned char *参数的方法,该方法代表来自C#的原始像素数据,但我发现从C#中提取byte[]相当慢。这就是为什么我希望发送Bitmap ptr而不是将Bitmap转换为byte[]并将其发送到我的C ++方法。
获取Bitmap IntPtr的C#代码
Bitmap srcBitmap = new Bitmap(m_testImage);
IntPtr hbitmap = srcBitmap.GetHbitmap();

用于导入C++方法的C#代码

[SuppressUnmanagedCodeSecurityAttribute()]
[DllImport("MyDll.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
public static extern int ResizeImage(IntPtr srcImg);

一个接收 Hbitmap 句柄的 C++ 方法

int Resize::ResizeImage(unsigned char* srcImg){
    //access srcImgs raw pixel data (preferably in unsigned char* format)
   //do work with that 
   return status;
}

问题:

1)由于我正在发送IntPrt,因此我的C++方法参数可以是unsigned char*吗?

2)如果不行,我该如何从C++中访问位图的原始数据?

3个回答

5
GetHbitmap方法不检索像素数据,而是返回一个GDI位图句柄,类型为HBITMAP。您的非托管代码将接收到该参数,并可以使用GDI调用从中获取像素数据。但它本身并不是原始像素。
实际上,我很确定您正在错误地解决此问题。您可能会这样做,因为GetPixelSetPixel很慢。这是真的。事实上,它们的GDI等效物也是如此。您需要做的是使用LockBits。这将允许您以有效的方式在C#中操作整个像素数据。关于该主题的良好描述可以在此处找到:https://web.archive.org/web/20141229164101/http://bobpowell.net/lockingbits.aspx。请注意,为了效率,这是一种使用不安全代码和指针通常是最佳解决方案的C#代码类型之一。
如果出于任何原因,您仍希望使用C++代码操作像素数据,则仍然可以使用LockBits作为获取指向像素数据的指针的最简单方法。这肯定比非托管GDI等效物要容易得多。

谢谢David,这正是我发现的!我把我的代码发布为答案,希望能帮助其他寻找相同解决方案的人。 - Matthew
你当然可以那样做。但是也应该有可能使用不安全代码和指针来生成高效的托管代码。 - David Heffernan

4
首先,一个HBITMAP不应该是unsigned char*。如果您正在将HBITMAP传递给C ++,那么参数应该是HBITMAPint Resize :: ResizeImage(HBITMAP hBmp) 接下来,将HBITMAP转换为像素:
std::vector<unsigned char> ToPixels(HBITMAP BitmapHandle, int &width, int &height)
{        
    BITMAP Bmp = {0};
    BITMAPINFO Info = {0};
    std::vector<unsigned char> Pixels = std::vector<unsigned char>();

    HDC DC = CreateCompatibleDC(NULL);
    std::memset(&Info, 0, sizeof(BITMAPINFO)); //not necessary really..
    HBITMAP OldBitmap = (HBITMAP)SelectObject(DC, BitmapHandle);
    GetObject(BitmapHandle, sizeof(Bmp), &Bmp);

    Info.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    Info.bmiHeader.biWidth = width = Bmp.bmWidth;
    Info.bmiHeader.biHeight = height = Bmp.bmHeight;
    Info.bmiHeader.biPlanes = 1;
    Info.bmiHeader.biBitCount = Bmp.bmBitsPixel;
    Info.bmiHeader.biCompression = BI_RGB;
    Info.bmiHeader.biSizeImage = ((width * Bmp.bmBitsPixel + 31) / 32) * 4 * height;

    Pixels.resize(Info.bmiHeader.biSizeImage);
    GetDIBits(DC, BitmapHandle, 0, height, &Pixels[0], &Info, DIB_RGB_COLORS);
    SelectObject(DC, OldBitmap);

    height = std::abs(height);
    DeleteDC(DC);
    return Pixels;
}

谢谢回复,我会尝试使用这种方法来比较性能差异,并与我发布的解决方案进行对比。 - Matthew

0

显然从Scan0发送指针相当于我正在寻找的内容。通过发送从bitmapData.Scan0方法检索到的IntPtr,我可以按预期操作数据。

Bitmap srcBitmap = new Bitmap(m_testImage);
Rectangle rect = new Rectangle(0, 0, srcBitmap.Width, srcBitmap.Height);
BitmapData bmpData = srcBitmap.LockBits(rect, ImageLockMode.ReadWrite, srcBitmap.PixelFormat);
//Get ptr to pixel data of image
IntPtr ptr = bmpData.Scan0;
//Call c++ method
int status = myDll.ResizeImage(ptr);
srcBitmap.UnlockBits(bmpData);

进一步解释,我从最初的帖子中只改变了第一个代码块。其余的都保持不变。(C++方法仍然接受unsigned char *作为参数)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接