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 ->
300×300 JPG
EMF ->
1200×1200 PNG ->
300×300 JPG
EMF ->
x4 PNG ->
300×300 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.