ネイティブ 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++ でテキストを直接描画します -動作します