在本机C ++和C#之间传递Graphics对象
我目前正在开发类似Paint .NET的应用程序。我有多种类型的层,用C#实现。这些图层被绘制到由WinForms用户控件提供的.NET Graphics对象中 - 它类似于WPF画布控件。图层基类有一个Draw方法,实现如下:
public void Draw(IntPtr hdc)
{
using (var graphics = Graphics.FromInternalHDC(hdc)
{
// First: Setup rendering settings like SmoothingMode, TextRenderingHint, ...
// Layer specific drawing code goes here...
}
}
对于性能和反编译问题,我正在混合模式程序集中进行图层组合,因为我还应用了斜角或投影等效果。包装器,当然是用C ++ / CLI编写的,直接从canvas控件调用,并将每个层的元数据和目标Graphics对象(来自我的C#编写的canvas用户控件的Graphics对象)转发到本机C ++类。
C ++ / CLI Wrapper:
public ref class RendererWrapper
{
public:
void Render(IEnumerable<Layer^>^ layersToDraw, Graphics^ targetGraphics)
{
// 1) For each layer get metadata (position, size AND Draw delegate)
// 2) Send layer metadata to native renderer
// 3) Call native renderer Render(targetGraphics.GetHDC()) method
// 4) Release targetGraphics HDC
};
}
Native C ++ Renderer:
class NativeRenderer
{
void NativeRenderer::Render(vector<LayerMetaData> metaDataVector, HDC targetGraphicsHDC)
{
Graphics graphics(targetGraphicsHDC);
// Setup rendering settings (SmoothingMode, TextRenderingHint, ...)
for each metaData in metaDataVector
{
// Create bitmap and graphics for current layer
Bitmap* layerBitmap = new Bitmap(metaData.Width, metaData.Height, Format32bppArgb);
Graphics* layerGraphics = new Graphics(layerBitmap);
// Now the interesting interop part
// Get HDC from layerGraphics
HDC lgHDC = layerGraphics->GetHDC();
// Call metaData.Delegate and pass the layerGraphics HDC to C#
// By this call we are ending up in the Draw method of the C# Layer object
metaData.layerDrawDelegate(lgHDC);
// Releasing HDC - leaving interop...
layerGraphics->ReleaseHDC(lgHDC);
// Apply bevel/shadow effects
// Do some other fancy stuff
graphics.DrawImage(layerBitmap, metaData.X, metaData.Y, metaData.Width, metaData.Height);
}
}
}
到目前为止一切顺利。上面的代码几乎按预期工作,但是......
问题
唯一的问题是,当渲染带有阴影的PNG时,我当前的实现缺少抗锯齿和半透明度。所以我只有Alpha通道的2个值:255处的透明或完全可见颜色。这个副作用使得绘制带有alpha通道和字体的PNG非常难看。当我使用纯C#代码时,我不能像之前那样得到相同的平滑和漂亮的半透明抗锯齿。
但是:直接在本地Graphics对象中绘制字符串时,
layerGraphics->DrawString(...);
抗锯齿和半透明度恢复良好。所以问题只有在将Graphics HDC传递给.NET时才会明显。
问题
这个问题有解决方案/解决方法吗?我试图直接在C#Layer类中创建Bitmap,并将HBITMAP的IntPtr返回到本机代码。这种方法很有效,但在这种情况下我还有另外一个问题,因为我找不到一个完美的解决方案,可以将HBITMAP转换为带有alpha通道的GDI + Bitmap(绘制字体时白色像素噪声围绕着边缘)。
感谢您的投入! :)
演示解决方案
附上你会在这里找到一个演示解决方案:Sources
在这个演示解决方案中,我正在测试3种不同的渲染方法(全部在NativeRenderer.cpp中实现),而FIRST ONE则显示了所描述的问题:
1) RenderViaBitmapFromCSharp() - a)在C ++中创建一个新的位图,用C ++创建一个新的Graphics对象,通过传递C ++ Graphics调用C#绘图代码对象HDC - 失败
但是: b)直接从C ++绘图也可以通过创建的位图工作
2) RenderDirectlyFromCSharp() - 从C ++中的C#Graphics句柄创建一个新的Graphics对象 ,通过传递C ++ Graphics对象HDC - Works
来调用C#绘图代码3) RenderDirectlyFromCPP() - 用C ++中的C#Graphics句柄创建一个新的Graphics对象,直接用C ++绘制文本 - Works
答案 0 :(得分:1)
Graphics graphics(targetGraphicsHDC);
您正在创建新图形对象。所以它不会像原来那样设置它的属性。像TextRenderingHint这样的属性不是GDI设备上下文的属性,它们特定于Graphics。
相当丑陋的问题,你需要像在调用代码中那样重新初始化Graphics对象。这是两个相互远离的代码块。避免转换到HDC并返回是解决问题的唯一正确方法。
答案 1 :(得分:1)
我最终在C#中创建了Bitmap并将对象传递给了C ++ / CLI。正如汉斯和文森特已经提到的,你必须避免使用GetHDC。所以我的解决方法读取伪代码如下:
Layer.cs C#:
public Bitmap Draw()
{
var bitmap = new Bitmap(Width, Height, PixelFormat.Format32bppArgb);
using (var graphics = Graphics.FromBitmap(bitmap)
{
// First: Setup rendering settings like SmoothingMode, TextRenderingHint, ...
// Layer specific drawing code goes here...
}
return bitmap;
}
NativeRenderer.cs C ++:
void NativeRenderer::RenderFromBitmapCSharp(System::Drawing::Bitmap^ bitmap)
{
// Create and lock empty native bitmap
Bitmap *gdiBitmap = new Bitmap(bitmap->Width, bitmap->Height, PixelFormat32bppARGB);
Rect rect(0, 0, bitmap->Width, bitmap->Height);
BitmapData bitmapData;
gdiBitmap->LockBits(&rect, Gdiplus::ImageLockModeRead | Gdiplus::ImageLockModeWrite, PixelFormat32bppARGB, &bitmapData);
// Lock managed bitmap
System::Drawing::Rectangle rectangle(0, 0, bitmap->Width, bitmap->Height);
System::Drawing::Imaging::BitmapData^ pBitmapData = bitmap->LockBits(rectangle, System::Drawing::Imaging::ImageLockMode::ReadOnly, System::Drawing::Imaging::PixelFormat::Format32bppArgb);
// Copy from managed to unmanaged bitmap
::memcpy(bitmapData.Scan0, pBitmapData->Scan0.ToPointer(), bitmap->Width * bitmap->Height * 4);
bitmap->UnlockBits(pBitmapData);
gdiBitmap->UnlockBits(&bitmapData);
// Draw it
_graphics->DrawImage(gdiBitmap, 0, 0, bitmap->Width, bitmap->Height);
}
希望对其他人有帮助 - 没有在网上找到任何实际上将托管转换为非托管GDI +位图的代码片段。
谢谢大家的意见。
干杯, P