在托管代码和非托管代码之间共享Graphics对象时丢失抗锯齿功能

时间:2012-05-24 08:04:21

标签: c# graphics c++-cli gdi+ mixed-mode

在本机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则显示了所描述的问题:

Demo output

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

2 个答案:

答案 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