Rasterizing EMF files with .Net / C#

I can almost hear you think: "Why on earth would anyone use EMF?" Well... I don't... but MS Office does! Thumbnails that are saved in MS Word documents are in the EMF format. As I struggled to do something with one, I realized that I had to convert it first to a more "easy to handle" format: PNG. Easier said than done.

What is EMF?

EMF is the abbreviation of Enhanched Metafile. It's a vector format based on the Windows Metafile (WMF) specification. Wiki says:

Windows Metafile (WMF) is an image file format originally designed for Microsoft Windows in the 1990s. Windows Metafiles are intended to be portable between applications and may contain both vector graphics and bitmap components. It acts in a similar manner to SVG files.

Essentially, a WMF file stores a list of function calls that have to be issued to the Windows Graphics Device Interface (GDI) layer to display an image on screen. Since some GDI functions accept pointers to callback functions for error handling, a WMF file may erroneously include executable code.

In 1993, the 32-bit version of Win32/GDI introduced the Enhanced Metafile (EMF), a newer version with additional commands. EMF is also used as a graphics language for printer drivers. Microsoft recommends that "Windows-format" (WMF) functions only "rarely" be used and "enhanced-format" (EMF) functions be used instead.

Use GDI Luke!

Because EMF is basically GDI it should be pretty easy to use .Net to render WMF or EMF files. I've written a static method that aides in the conversion:

/// <summary>
/// Saves the meta file.
/// </summary>
/// <param name="source">The source.</param>
/// <param name="destination">The destination.</param>
/// <param name="scale">The scale. Default value is 4.</param>
/// <param name="backgroundColor">Color of the background.</param>
/// <param name="format">The format. Default is PNG.</param>
/// <param name="parameters">The parameters.</param>
public static void SaveMetaFile(
    Stream source,
    Stream destination,
    float scale = 4f,
    Color? backgroundColor = null,
    ImageFormat format = null,
    EncoderParameters parameters = null)
{
    if (source == null)
    {
        throw new ArgumentNullException(nameof(source));
    }
    if (destination == null)
    {
        throw new ArgumentNullException(nameof(destination));
    }

    using (var img = new Metafile(source))
    {
        var f = format ?? ImageFormat.Png;

        //Determine default background color. 
        //Not all formats support transparency. 
        if (backgroundColor == null)
        {
            var transparentFormats = new ImageFormat[] { ImageFormat.Gif, ImageFormat.Png, ImageFormat.Wmf, ImageFormat.Emf };
            var isTransparentFormat = transparentFormats.Contains(f);

            backgroundColor = isTransparentFormat ? Color.Transparent : Color.White;
        }

        //header contains DPI information
        var header = img.GetMetafileHeader();

        //calculate the width and height based on the scale
        //and the respective DPI
        var width = (int)Math.Round((scale * img.Width / header.DpiX * 100), 0, MidpointRounding.ToEven);
        var height = (int)Math.Round((scale * img.Height / header.DpiY * 100), 0, MidpointRounding.ToEven);

        using (var bitmap = new Bitmap(width, height))
        {
            using (var g = System.Drawing.Graphics.FromImage(bitmap))
            {
                //fills the background
                g.Clear(backgroundColor.Value);

                //reuse the width and height to draw the image
                //in 100% of the square of the bitmap
                g.DrawImage(img, 0, 0, bitmap.Width, bitmap.Height);
            }

            //get codec based on GUID
            var codec = ImageCodecInfo.GetImageEncoders().FirstOrDefault(c => c.FormatID == f.Guid);

            bitmap.Save(destination, codec, parameters);
        }
    }
}

Convert to PNG

PNG conversion is pretty straight forward. If you want to generate smaller images I recommend to generate a large image and use a different library that smooths the image down. The following example rasterizes the EMF to a PNG 4 times its original size:

string sourceFilePath = "MyFile.emf";
string destinationFilePath = "MyFile.png";

using (var source = File.OpenRead(sourceFilePath))
{
    using (var destination = File.OpenWrite(destinationFilePath))
    {
        MetafileUtility.SaveMetaFile(source, destination, 4, Color.White, ImageFormat.Png);
    }
}

Convert to JGP

JPG conversion is a little bit harder, because you'll might want to specify a quality (because JPEG is a lossy format):

string sourceFilePath = "MyFile.emf";
string destinationFilePath = "MyFile.png";

var parameters = new EncoderParameters(1);
parameters.Param[0] = new EncoderParameter(Encoder.Quality, 80L);

using (var source = File.OpenRead(sourceFilePath))
{
    using (var destination = File.OpenWrite(destinationFilePath))
    {
        MetafileUtility.SaveMetaFile(source, destination, format: ImageFormat.Jpeg, parameters: parameters);
    }
}

GDI Rendering

Rendering EMF to small images will make them very pixelated. I've taken the thumbnail of a MS Word file and I've tested some strategies with the following results:

EMF --- 300x300 JPG
EMF --- 1200x1200 PNG --- 300x300 JPG
EMF --- x4 PNG --- 300x300 JPG
EMF ->
300x300 JPG
EMF ->
1200x1200 PNG ->
300x300 JPG
EMF ->
x4 PNG ->
300x300 JPG

Final thoughts

I've posted a class on GitHub that helps with EMF conversion. It is a slightly more advanced version that uses a bounding box to determine the size. It also has the 2 stage rendering feature. You should check it out.

  1. Josh says:

    Hi I can’t get this to work in Azure Webapp as MetaFile is restricted. Is there another way to convert them to png?

    1. Kees C. Bakker says:

      I’m not that familiar with Azure. It might be possible to execute GDI functions in a Web Role. Check this StackOverflow answer: http://stackoverflow.com/questions/24921518/running-azure-webjob-with-gdi-requirements

  2. Abel Campos says:

    Hi, good moorning… sorry but I can’t compile because there is an error
    with BoundingBox and MetafileMeta classes, what are the assemblies
    where they are defined? thank you.

  3. Paul Brewer says:

    Kees – In your MetafileUtility class on GitHub, you reference a class MetafileMeta. Where is that defined?

  4. Sergio says:

    Hello Kees, thanks for sharing, but seems to me this code is not actually rendering an EMF file.
    If you check ImageCodecInfo.GetImageEncoders() there’s no encoder for emf, this code is encoding using different encoder.
    I mean, your code works to treat images and probably it works for Word, but it’s not a real emf, I say this because I’m struggling same thing but Visio scenario, and Visio doesn’t support an image converted using any of the current encoders from .net framework.

    Check these links
    https://docs.microsoft.com/en-us/dotnet/framework/winforms/advanced/using-image-encoders-and-decoders-in-managed-gdi
    https://docs.microsoft.com/en-us/dotnet/api/system.drawing.imaging.metafile?view=netframework-4.7.1#Remarks

    Quote from msdn: “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”

  5. Lycanphoenix says:

    Is there any chance that we could see a Codec pack for the Windows Imaging Component that would allow encoding and decoding WMF/WMZ, EMF/EMZ, and EMF+? (Obviously, decoding in a way that avoids the security holes in the format, such as through API translation and the like)

    1. Kees C. Bakker says:

      Hard to say. The formats are really proprietary and I’m not sure if it is high on the list at Microsoft. I thought Office would switch in the future to a more acceptable format like SVG. But I looks like Windows Meta File just got an update last year to version 14: https://msdn.microsoft.com/en-us/library/cc250370.aspx

expand_less