rjray
rjray

Reputation: 6663

Extracting Width/Height Dimensions from EMF Image Files

I maintain a small Perl library that extracts width/height from images, for people who want that functionality without using a larger, more generalized library. I've been asked if I can support the Windows EMF format. However, I haven't had much luck with my Google-fu in trying to find a good specification of the format, or better yet example code (in any language). I'm looking for either a decent spec on the format, or examples of reading/parsing the files. As usually, and and all help is greatly appreciated.

Upvotes: 2

Views: 3207

Answers (3)

Xtrophic
Xtrophic

Reputation: 1

All answers I previously found were either incomplete or giving the incorrect result. After needing to extract EMF dimensions myself I looked into the file format and came up with the following (in C#) to extract it from:

// C# Example
public static RectangleF? ExtractMetafileData(Stream stream)
{
    if (stream == null)
        return null;
    if (stream.Length == 0)
        return null;

    var bytes = new byte[88];
    stream.Seek(0, SeekOrigin.Begin);
    stream.Read(bytes, 0, bytes.Length);

    var rectOffset = 24;
    var x = BitConverter.ToInt32(bytes, rectOffset);
    var y = BitConverter.ToInt32(bytes, rectOffset+4);
    var width = BitConverter.ToInt32(bytes, rectOffset+8) - x;
    var height = BitConverter.ToInt32(bytes, rectOffset+12) - y;

    rectOffset = 72;
    var x1 = BitConverter.ToInt32(bytes, rectOffset);
    var y1 = BitConverter.ToInt32(bytes, rectOffset + 4);
    var x2 = BitConverter.ToInt32(bytes, rectOffset + 8);
    var y2 = BitConverter.ToInt32(bytes, rectOffset + 12);

    float factor1 = ((float)x1 / (float)x2) / 96;   // 96dpi
    float factor2 = ((float)y1 / (float)y2) / 96;   // 96dpi

    var rect = new RectangleF((float) x*factor1, (float) y*factor2, (float) width*factor1, (float) height*factor2);

    stream.Seek(0, SeekOrigin.Begin);

    return rect;
}

Or if Python is your poison:

# Python example
with open("mymetafile.emf" , "rb") as f:
    f.read(24)
    left, top = int.from_bytes(f.read(4), 'little'), int.from_bytes(f.read(4), 'little')
    width, height = int.from_bytes(f.read(4), 'little') - left, int.from_bytes(f.read(4), 'little') - top
    f.read(32)
    xf1, yf1 = int.from_bytes(f.read(4), 'little'), int.from_bytes(f.read(4), 'little')
    xf2, yf2 = int.from_bytes(f.read(4), 'little'), int.from_bytes(f.read(4), 'little')

    xfact = (xf1 / xf2) / 96
    yfact = (yf1 / yf2) / 96
    
    pixWidth  = (width * xfact)
    pixHeight = (height * yfact)

print("Width:  " + str(pixWidth))
print("Height: " + str(pixHeight))
print()
print("Width:  " + str(round(pixWidth ,0)))
print("Height: " + str(round(pixHeight,0)))

Upvotes: 0

Yuri Khristich
Yuri Khristich

Reputation: 14502

Based on Andreas Rejbrand's researches I've made the code in Python that gets the sizes of EMF file:

with open("img.emf", "rb") as f:
    f.read(16)
    w1, w2 = f.read(1).hex(), f.read(1).hex()
    f.read(2)
    h1, h2 = f.read(1).hex(), f.read(1).hex()

width  = int(str(w2) + str(w1), 16) * 762 # I've no idea why is '762' but it works this way
height = int(str(h2) + str(h1), 16) * 762

I belive it can be implemented in Perl as well if it's able to read bytes.

Here is how it works in a real task: Unable to insert EMF into Word using Python

Upvotes: 0

Andreas Rejbrand
Andreas Rejbrand

Reputation: 108963

The official specification can be downloaded directly from MSDN at http://msdn.microsoft.com/en-us/library/cc230514(PROT.10).aspx.

It will take some time to read and understand, but it should definitely be doable if you have worked with binary file formats before.

But please notice that EMF is a (pseudo-) vector image format, and so images can be scaled to any size. But there might be a default width and height. In particular, there really should be a well-defined aspect ratio.

Update

I think that the width (in pixels) of the metafile is the 5th cardinal of the file, and the height (in pixels) the 6th cardinal. In a typical case. At least this might be a decent starting point for you.

I just created a sample EMF file, which starts

01 00 00 00 88 00 00 00 00 00 00 00 00 00 00 00
ae 01 00 00 75 01 00 00 00 00 00 00 00 00 00 00

The fifth cardinal is AE010000 which, due to the byte little-endianness, is 000001AE in hex, i.e. 430 in decimal. The sixth cardinal is 75010000, i.e. 00000175 in hex or 373 in decimal. Hence I get the dimension to be 430×373 sq. pixels. Paint reports 432×374 sq. pixels.

If I get more time left, I will study the file format more extensively. But at least I hope this might be a starting point for you.

Update 2

The third and forth 32-bit integers are apparently the left and top coordinates of the image, in logical units, respectively, while the fifth and sixth 32-bit integers are the right and bottom coordinates. In most cases (top, left) = (0, 0), and then my text above is correct (i.e. then width = right, height = top coordinate).

If (top, left) <> (0, 0), then, naturally, width = right - left and height = bottom - top.

Now this is probably not the whole story; if you compare the obtained numbers with the ones reported by Paint you will get small deviations. So To be continued....

Upvotes: 12

Related Questions