【问题标题】:How to enable anti-aliasing when rendering WMF to BitMap in C#/WPF/WinForms?在 C#/WPF/WinForms 中将 WMF 渲染为 BitMap 时如何启用抗锯齿?
【发布时间】:2016-10-24 09:22:38
【问题描述】:

为什么在执行此操作时不对线条等进行抗锯齿处理?

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 的命名空间。
  • 我在 WPF 应用程序中使用它,但是是的,可能不相关。我会更新标签。

标签: c# system.drawing metafile wmf .emf


【解决方案1】:

这在使用 WMF 图元文件的 GDI+ 中是不可能的,但在 EMF Plus 中却可以。您可以在源代码中转换为 EMF Plus,也可以使用记录不充分的 GDI+ 方法即时转换(见下文)。

GDI(不是 GDI+)渲染 WMF 文件时不使用任何底层 GDI+ 图形对象的合成,它只是直接 GDI 调用的枚举。 See this question for more, but all answers say about the same thing.

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

如果不能从源代码转换,可以从 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");
    }
}

Here's a working example for LinqPad。它将 WMF 文件 (drawing.wmf) 转换为 EMF Plus 图元文件,并将其显示在结果面板中。

Paint 中的 WMF 文件:

在 Paint 中转换的 EMF+ 文件:


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

【讨论】:

  • 谢谢!对于 WMF (AutoCAD LT) 的来源,我真的无能为力,所以在这种情况下,有点原生性就可以了。如果它有效,我会试一试并回来,它看起来应该(鉴于我有限的 WMF/EMF/GDI+ 经验)。 (我认为 GDI 函数会支持一些全局“AntiAlias”标志,但我只是错过了哪一个。)
  • 我没有到达那里。 (this, g) 应该是 (graphics, g) 对吧?然后我得到 isSuccess=true,但 Metafile 构造函数失败并出现“一般错误 System.Runtime.InteropServices.ExternalException (0x80004005):GDI+ 中发生一般错误。”还有,imageAttr 好像根本没用?
  • 你是对的,代码还没有准备好使用,并假设你可能有一个 ImageAttributes,你可能没有。您是否有可以共享的 WMF 来重现该问题?
  • 更新了代码以提供一个工作示例。使用 Metafile 构造函数时出现了一些问题,因此我们需要直接调用SetNativeHandle,这种方式非常粗糙,但很有效。
  • 谢谢!非常感谢! SetNativeHandle 成功了。我真的必须在某个时候深入研究它是如何工作的。 (顺便说一句,您的代码中没有使用 imageAttribute / ia,它可以通过删除所有相关调用来正常工作。)(另外,感谢您让我进入 LinqPad 的世界;-)
猜你喜欢
  • 1970-01-01
  • 2011-01-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-12-23
  • 2021-01-07
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多