Loss of antialiasing when sharing a Graphics object between managed and unmanaged code

Passing Graphics object between native C ++ and C #

I am currently working on a Paint.NET application. I have several types of layers that are implemented in C #. These layers are inserted into the .NET Graphics object that is provided by the WinForms user control β€” it looks like a WPF canvas control. The base level class has a Draw method, which is implemented as follows:

public void Draw(IntPtr hdc) { using (var graphics = Graphics.FromInternalHDC(hdc) { // First: Setup rendering settings like SmoothingMode, TextRenderingHint, ... // Layer specific drawing code goes here... } } 

For performance and decompilation, I am composing the layers in a mixed-mode assembly, as I also apply effects like bevel or shadow. The wrapper, naturally written in C ++ / CLI, gets the direct name from the canvas control and transfers the metadata of each layer and the Graphics target (a graphic from my C # written by the user of the canvas user element) to its own C ++ class.

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

So far so good. The above code works almost as expected, but ...

Problem

The only thing in my current implementation is lack of antialiasing and translucency when rendering PNG with shadows, for example. Therefore, I have only 2 values ​​for the Alpha channel: transparent or full visible color at 255. This side effect makes PNG drawing with the alpha channel and fonts looking very ugly. I cannot get the same smooth and nice translucent anti-aliasing as before when I was working with pure C # code.

BUT: When drawing a line directly in your own graphic object

  layerGraphics->DrawString(...); 

smoothing and translucency are back forever. Therefore, the problem is only apparent when transferring Graphics HDC to .NET.

Questions

Is there any solution / workaround for this problem? I tried creating a Bitmap directly in the C # Layer class and returning IntPtr for HBITMAP to native code. This approach works, but in this case I have a different problem, since I cannot find the perfect solution for converting HBITMAP to GDI + Bitmap with alpha channel (white pixel noise surrounds the edges when drawing fonts).

Thanks for your input! :)

Demo solution

In the application you will find a demo solution here: Sources

In this demo solution, I am testing 3 different rendering methods (they are all implemented in NativeRenderer.cpp), and FIRST ONE shows the described problems:

Demo output

1) RenderViaBitmapFromCSharp () - a) Creates a new bitmap in C ++, creates a new Graphics object in C ++, calls the C # drawing code, passing the C ++ Graphics HDC object - Fails

But: b) Drawing directly from C ++ also works using the generated bitmap

2) RenderDirectlyFromCSharp () - Creates a new Graphics object from C # Graphic descriptor in C ++, calls the C # drawing code, passing the C ++ Graphics HDC object - Works

3) RenderDirectlyFromCPP () - Creates a new Graphics object from C # Graphic descriptor in C ++, draws text directly in C ++ - Works

+6
source share
2 answers
  Graphics graphics(targetGraphicsHDC); 

A new Graphics object is created. Thus, he will not have property settings, as was done in the original. Properties such as TextRenderingHint are not GDI device context properties, they are specific to Graphics.

A pretty ugly problem, you will need to reinitialize the Graphics object as it was in the calling code. These are two pieces of code that are far apart. Avoiding switching to HDC and vice versa is the only really decent way to fix the problem.

+1
source

I ended up creating a Bitmap in C # and passing an object in C ++ / CLI. As mentioned by Hans and Vincent, you need to avoid GetHDC. Therefore, my workaround is read in pseudocode as follows:

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

I hope this is useful to others - they did not find in the web fragment code the fragment that actually does the conversion controlled by unmanaged GDI + Bitmap.

Thank you all for your comments.

Cheers, P

+1
source

Source: https://habr.com/ru/post/916534/


All Articles