如何在C#/WPF/WinForms中将WMF渲染为位图时启用抗锯齿?

5

为什么这样做时,线条等不会反锯齿呢?

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

委托函数长这样:
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;
}

我需要为特定类型重写PlayRecord以在此处获得抗锯齿效果吗?

这些WMF来自AutoCAD,如果有帮助的话。


这与WPF有什么关系?Graphics来自于System.DrawingMetafile来自于System.Drawing.Image,两个命名空间都属于WinForms。 - Clemens
我正在一个 WPF 应用程序中使用它,但是可能与此无关。我会更新标签。 - Macke
1个回答

4

使用WMF元文件在GDI+中无法实现此功能,但使用EMF Plus可以。您可以在源处将其转换为EMF Plus,也可以使用文档不全的GDI+方法进行即时转换(请参见下文)。

GDI(而不是GDI+)呈现WMF文件时,不使用底层的任何GDI+ Graphics对象的合成,它只是直接GDI调用的枚举。 有关更多信息,请参见此问题,但所有答案都说了同样的事情

如果您可以将文件转换为EMF Plus,则会使用GDI+方法来呈现内容,并使用GDI+合成,包括抗锯齿。如果您已经在使用WPF,则还可以考虑导出为WPF可以呈现抗锯齿的XPS。

如果无法在源处进行转换,则可以从C#调用GDI+方法,但这并不优雅。您需要访问System.Drawing类使用的本机句柄:

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

您可以使用类似以下代码的方式来使用它:
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");
    }
}

这里有一个LinqPad的工作示例。它将WMF文件(drawing.wmf)转换为EMF Plus元文件,并在结果面板中显示它。

Paint中的WMF文件: 没有抗锯齿的WMF文件

Paint中转换后的EMF+文件: 带有抗锯齿的EMF+文件


为了完整起见,上述GdipConvertToEmfPlus方法是GDI+的所谓 "flat API" 的一部分。它最初的目的只是为了服务于 GDI+ C++ 类。使用此方法的C++ API 被称为Metafile.ConvertToEmfPlus


谢谢!对于WMF(AutoCAD LT)的源代码,我无能为力,所以在这种情况下有点本地化是可以接受的。我会尝试一下,如果它有效的话,我会回来的(考虑到我的有限的WMF/EMF/GDI+经验,看起来应该是有效的)。 (我想GDI函数将支持一些全局“AntiAlias”标志,我只是错过了哪一个。) - Macke
更新了代码以提供一个可工作的示例。由于使用Metafile构造函数时出现了一些问题,所以我们需要以明显粗略但有效的方式直接调用SetNativeHandle - codekaizen
哎呀,我没有得到整个WMF文件,只有四分之一。请参见http://stackoverflow.com/questions/38071340/how-to-convert-wmf-to-emf-with-gdipconverttoemfplus-without-losing-three-quarte。 - Macke
@logicnet.dk 很遗憾,您不能以这种方式保存EMF文件:“当您使用Save方法将图形图像保存为Windows Metafile Format(WMF)或Enhanced Metafile Format(EMF)文件时,生成的文件会被保存为Portable Network Graphics(PNG)文件。这种行为发生是因为.NET Framework的GDI+组件没有编码器可用于将文件保存为.wmf或.emf文件。”- https://msdn.microsoft.com/en-us/library/vs/alm/system.drawing.imaging.metafile(v=vs.110).aspx - Sebastian Negraszus
输出文件 drawing.emfPNG - Andrei Krasutski
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接