1.6 图形类 Graphics_Gdip 编程基础

使用 C# 生成 EMF 矢量图形文件

前言

公众号上有网友询问我如何生成 EMF 文件的问题:

本以为非常简单,我快速给出了解决方案:

var?bitmap?=?new?Bitmap,?);
var?g?=?Graphics.FromImage(bitmap);
g.DrawString("My?IO",?new?Font(FontFamily.GenericSerif,?),?Brushes.Blue,?,?);
bitmap.Save("MyIO.emf",ImageFormat.Emf);

结果,网友告诉我这是错误的:

用编辑器查看文件内容,发现实际生成的是PNG格式文件:

这是怎么回事呢?

原因

在官方文档上找到这样一段话:

当使用Save此方法将图形图像另存为Windows元文件格式 (WMF) 或增强的图元文件格式 (EMF) 文件时,生成的文件将改为保存为可移植网络图形 (PNG) 文件。发生此行为是因为.NET Framework的GDI+组件没有可用于将文件另存为 .wmf 或 .emf 文件的编码器。

不理解这样设计的原因,不支持应该抛出异常吧?!

实现

不过还好,从文档上我们也找到了解决方案,那就是使用Metafile类。

可是在实现时,又踩了不少坑。

创建实例失败

按照示例代码,使用文件名创建实例:

var?metafile?=?new?Metafile("MyIO.emf");

结果报了个通用异常,完全没有指导意义:

只好反编译代码查错。

发现,底层实现使用的
GdipCreateMetafileFromFile
API:

public?Metafile(string?filename)
{
?Path.GetFullPath(filename);
?SafeNativeMethods.Gdip.CheckStatus(SafeNativeMethods.Gdip.GdipCreateMetafileFromFile(filename,?out?IntPtr?metafile));
?SetNativeImage(metafile);
}

也就是说,参数必须是一个已存在的 EMF 文件名

查看其他构造函数的实现,发现传递referenceHdc的构造函数使用的是
GdipRecordMetafileFileName
API:

public?Metafile(string?fileName,?IntPtr?referenceHdc,?EmfType?type,?string??description)
{
?Path.GetFullPath(fileName);
?SafeNativeMethods.Gdip.CheckStatus(SafeNativeMethods.Gdip.GdipRecordMetafileFileName(fileName,?referenceHdc,?type,?IntPtr.Zero,?MetafileFrameUnit.GdiCompatible,?description,?out?IntPtr?metafile));
?SetNativeImage(metafile);
}

也就是说,这个 API 可以创建 EMF 文件。看来可以用。

referenceHdc可以使用Graphics.GetHdc()得到。

于是,实现代码如下:

using?(Graphics?g1?=?Graphics.FromHwnd(IntPtr.Zero))
{
????using?(var?metafile?=?new?Metafile("MyIO.emf",?g1.GetHdc()))
????{
????????using?(Graphics?g2?=?Graphics.FromImage(metafile))
????????{?
????????????g2.DrawString("My?IO",?new?Font(FontFamily.GenericSerif,?),?Brushes.Blue,?,?);
????????????g2.DrawString("My?IO",?new?Font(FontFamily.GenericSerif,?),?Brushes.Blue,?,?);
????????}
????}
}

生成的确实是矢量图形文件:

绘制位置错误

可以明显看到,第一个My IO绘制的位置是错误的,绘制到了左上角,而不是(, )

再次查找构造函数,发现可以传递Rectangle参数:

修改实现代码如下:

using?(Graphics?g1?=?Graphics.FromHwnd(IntPtr.Zero))
{
????using?(var?metafile?=?new?Metafile("MyIO.emf",?g1.GetHdc(),?new?Rectangle(0,?0,?,?),?MetafileFrameUnit.Pixel))
????{
????????using?(Graphics?g2?=?Graphics.FromImage(metafile))
????????{
????????????g2.DrawString("My?IO",?new?Font(FontFamily.GenericSerif,?),?Brushes.Blue,?,?);
????????????g2.DrawString("My?IO",?new?Font(FontFamily.GenericSerif,?),?Brushes.Blue,?,?);
????????}
????}
}

这次总算成功了:

结论

后来发现,生成的图片实际是 x 像素,这应该是因为我的显示属性设置了缩放的原因( / = ):

原文链接:,转发请注明来源!