6

ネイティブ C++ と C# の間で Graphics オブジェクトを渡す

私は現在、ペイント .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 で記述されたラッパーは、キャンバス コントロールから直接呼び出され、各レイヤーのメタデータとターゲット グラフィックス オブジェクト (C# で記述されたキャンバス ユーザー コントロールのグラフィックス オブジェクト) をネイティブ C++ クラスに転送します。

C++/CLI ラッパー:

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
         };
}

ネイティブ C++ レンダラー:

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 をレンダリングするときに、現在の実装にアンチエイリアシングと半透明性が欠けていることです。したがって、アルファ チャネルには 2 つの値しかありません。255 の透過色または完全に表示される色です。この副作用により、アルファ チャネルとフォントを使用した PNG の描画が非常に見苦しくなります。純粋な C# コードで作業したときのように、以前と同じように滑らかで素敵な半透明のアンチ エイリアシングを取得することはできません。

BUT: ネイティブ Graphics オブジェクトで文字列を直接描画する場合、

 layerGraphics->DrawString(...);

アンチエイリアシングと半透明性が完全に復活しました。そのため、Graphics HDC を .NET に渡す場合にのみ問題が明らかになります。

質問

この問題の解決策/回避策はありますか? C# Layer クラスで Bitmap を直接作成し、HBITMAP の IntPtr をネイティブ コードに返そうとしました。このアプローチは機能していますが、この場合、HBITMAP を GDI+ ビットマップにアルファ チャネルで変換するための完璧なソリューションが見つからないため、別の問題があります (フォントを描画するときに白いピクセル ノイズがエッジを囲んでいます)。

ご意見ありがとうございます。:)

デモ ソリューション

添付のデモ ソリューションは次の場所にあります:ソース

このデモ ソリューションでは、3 つの異なるレンダリング方法 (すべて NativeRenderer.cpp に実装されています) をテストしていますが、最初の 1 つは説明されている問題を示しています。

デモ出力

1) RenderViaBitmapFromCSharp() - a) C++ で新しいビットマップを作成し、C++ で新しい Graphics オブジェクトを作成し、C++ Graphics オブジェクト HDC を渡して C# 描画コードを呼び出します -失敗

ただし: b) C++ からの直接描画は、作成されたビットマップ経由でも機能します。

2) RenderDirectlyFromCSharp() - C++ で C# グラフィック ハンドルから新しいグラフィック オブジェクトを作成し、C++ グラフィック オブジェクト HDC を渡すことによって C# 描画コードを呼び出します -動作します

3) RenderDirectlyFromCPP() - C++ で C# グラフィックス ハンドルから新しいグラフィックス オブジェクトを作成し、C++ でテキストを直接描画します -動作します

4

2 に答える 2

1
 Graphics graphics(targetGraphicsHDC);

新しいGraphicsオブジェクトを作成しています。そのため、元のプロパティのようにプロパティが設定されません。TextRenderingHintのようなプロパティは、GDIデバイスコンテキストのプロパティではなく、グラフィックスに固有です。

かなり醜い問題です。呼び出し元のコードで行われた方法で、Graphicsオブジェクトを再初期化する必要があります。これは、互いに遠く離れた2つのコードチャンクです。HDCへの変換とその逆変換を回避することが、問題を回避するための唯一の適切な方法です。

于 2012-05-24T12:20:43.373 に答える
1

C# でビットマップを作成し、オブジェクトを C++/CLI に渡すことになりました。Hans と Vincent が既に述べたように、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 +ビットマップに実際に変換するコードスニペットがWeb上で見つかりませんでした。

コメントありがとうございます。

乾杯、P

于 2012-05-25T06:35:42.510 に答える