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

1

5

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.

Wheat answered 22/6, 2016 at 12:24 Comment(2)
How is this related to WPF? Graphics is from System.Drawing, Metafile is from System.Drawing.Image, both namespaces of WinForms.Uproarious
I'm using it in a WPF-application, but yeah, probably not related. I'll update the tags.Wheat
J
4

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.

Jemmie answered 26/6, 2016 at 11:21 Comment(10)
Thanks! I can't really do much about the source of the WMF (AutoCAD LT), so a bit of nativeness is fine in this case. I'll give it a try and get back if it works, which it looks like it should (given my limited WMF/EMF/GDI+ experience). (I figured the GDI functions would support some global "AntiAlias" flag and I've just missed which one.)Wheat
I'nm not getting there. (this, g) should be (graphics, g), right? Then I get isSuccess=true, but Metafile constructor fails with "generic error System.Runtime.InteropServices.ExternalException (0x80004005): A generic error occurred in GDI+." Also, imageAttr doesn't seem to be used at all?Wheat
You're right, the code isn't ready to use, and assumes you may have an ImageAttributes, which you may not. Do you have a WMF you can share to reproduce the issue?Jemmie
Updated code to provide a working example. There were some problems using the Metafile constructor, so we need to call the SetNativeHandle directly in a notably crude, but effective, manner.Jemmie
Thanks! Very much appreciated! SetNativeHandle did the trick. I really have to dig into how that works sometime. (Btw, imageAttribute / ia is not used in your code, and it works fine by removing all related calls.) (Also, thanks for opening me to the world of LinqPad ;-)Wheat
Ouch. I'm not getting the entire WMF, just 1/4 of it. See #38071840Wheat
When I run this, the result of emfPlus.Save(@"drawing.emf") is actually a PNG file, not an EMF+, so it doesn't scale well.Sonar
@Sonar Unfortunately, you cannot save EMF files that way: "When you use the Save method to save a graphic image as a Windows Metafile Format (WMF) or Enhanced Metafile Format (EMF) file, the resulting file is saved as a Portable Network Graphics (PNG) file instead. This behavior occurs because the GDI+ component of the .NET Framework does not have an encoder that you can use to save files as .wmf or .emf files." - msdn.microsoft.com/en-us/library/vs/alm/…Chequerboard
Note that if your goal is to save the resulting EMF+ to file, simply use GdipConvertToEmfPlusToFile instead. You won't need SetNativeImage at all.Electrodialysis
Output file drawing.emf it is PNGStillmann

© 2022 - 2024 — McMap. All rights reserved.