Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to enable anti-aliasing when rendering WMF to BitMap in C#/WPF/WinForms?

Tags:

Why won't lines etc be anti-aliased when doing this?

using (var myGraphics = Graphics.FromImage(bitmap))
{
myGraphics.CompositingQuality = CompositingQuality.HighQuality;
myGraphics.SmoothingMode = SmoothingMode.HighQuality;
myGraphics.TextRenderingHint = TextRenderingHint.AntiAliasGridFit;

myGraphics.Clear(backgroundColor);

myGraphics.EnumerateMetafile(m_metafile, new Point(0, 0), m_metafileDelegate);
}

The delegate function looks like this:

private bool MetafileCallback(EmfPlusRecordType recordType, int flags, int dataSize, IntPtr data, PlayRecordCallback callbackData)
{
        byte[] dataArray = null;
        if (data != IntPtr.Zero)
        {
            // Copy the unmanaged record to a managed byte buffer 
            // that can be used by PlayRecord.
            dataArray = new byte[dataSize];
            Marshal.Copy(data, dataArray, 0, dataSize);
        }

        m_metafile.PlayRecord(recordType, flags, dataSize, dataArray);

        return true;
}

Do I need to override PlayRecord for a specific type to get anti-aliasing here?

The WMFs come from AutoCAD, if that's any help.

like image 428
Macke Avatar asked Jun 22 '16 12:06

Macke


1 Answers

This is not possible in GDI+ using a WMF metafile, but it is with EMF Plus. You can convert to EMF Plus at the source, or on-the-fly with a poorly documented GDI+ method (see below).

GDI (not GDI+) renders the WMF file without using any of the compositing of the GDI+ Graphics object underlying it, it just is an enumeration of the direct GDI calls. See this question for more, but all answers say about the same thing.

If you can convert the file to EMF Plus, this will use the GDI+ methods to render the content, and use the GDI+ compositing including anti-aliasing. If you're already using WPF, you might also consider exporting to XPS which WPF can render antialiased.

If you cannot convert at the source, you can call a GDI+ method from C#, but it is not elegant. You need to have access to the native handles used by the System.Drawing classes:

[DllImport("gdiplus.dll", SetLastError = true, ExactSpelling = true, CharSet = System.Runtime.InteropServices.CharSet.Unicode)]
internal static extern int GdipConvertToEmfPlus(HandleRef graphics,
                                                HandleRef metafile,
                                                out Boolean conversionSuccess,
                                                EmfType emfType,
                                                [MarshalAsAttribute(UnmanagedType.LPWStr)]
                                                String description,
                                                out IntPtr convertedMetafile);

You would use this with code similar to the following:

using (var graphics = Graphics.FromImage(bmp))
using (var metafile = Metafile.FromFile(@"drawing.wmf"))
using (var imageAttr = new ImageAttributes())
{
    graphics.SmoothingMode = SmoothingMode.AntiAlias;
    graphics.CompositingQuality = CompositingQuality.HighQuality;
    graphics.TextRenderingHint = TextRenderingHint.AntiAliasGridFit;

    var metafileHandleField = typeof(Metafile).GetField("nativeImage", BindingFlags.Instance | BindingFlags.NonPublic);
    var imageAttributesHandleField = typeof(ImageAttributes).GetField("nativeImageAttributes", BindingFlags.Instance | BindingFlags.NonPublic);
    var graphicsHandleProperty = typeof(Graphics).GetProperty("NativeGraphics", BindingFlags.Instance | BindingFlags.NonPublic);
    var setNativeImage = typeof(Image).GetMethod("SetNativeImage", BindingFlags.Instance | BindingFlags.NonPublic);
    IntPtr mf = (IntPtr)metafileHandleField.GetValue(metafile);
    IntPtr ia = (IntPtr)imageAttributesHandleField.GetValue(imageAttr);
    IntPtr g = (IntPtr)graphicsHandleProperty.GetValue(graphics);

    Boolean isSuccess;
    IntPtr emfPlusHandle;
    var status = GdipConvertToEmfPlus(new HandleRef(graphics, g),
                                      new HandleRef(metafile, mf),
                                      out isSuccess,
                                      EmfType.EmfPlusOnly,
                                      "",
                                      out emfPlusHandle);
    if (status != 0)
    {
        throw new Exception("Can't convert");
    }

    using (var emfPlus = (Metafile)System.Runtime.Serialization.FormatterServices.GetSafeUninitializedObject(typeof(Metafile)))
    {
        setNativeImage.Invoke(emfPlus, new object[] { emfPlusHandle });

        // use EnumerateMetafile on emfPlus as per your example code or save it:
        emfPlus.Save(@"drawing.emf");
    }
}

Here's a working example for LinqPad. It converts a WMF file (drawing.wmf) to an EMF Plus metafile, and displays it in the results panel.

WMF file in Paint: WMF file with no anti-aliasing

Converted EMF+ file in Paint: EMF+ file with anti-aliasing


For the sake of completeness, the above GdipConvertToEmfPlus method is part of what is known as the "flat API" of GDI+. Its original purpose was to serve only the GDI+ C++ classes. The C++ API which uses this method is called Metafile.ConvertToEmfPlus.

like image 52
codekaizen Avatar answered Nov 15 '22 05:11

codekaizen