Windows Metafile Format

The Windows Metafile Format stores information about the graphic in a vector format. Viewing a metafile is also referred to as "playing" the file, since the file stores information on what to draw, but it's up to the viewer to actually draw them. Since I am converting the WMF files to Java classes, I don't have the luxury of using Windows API functions that know how to play metafiles, such as PlayMetaFile(). Instead, I need to store within the file the code that actually draws the graphic. A conversion program, then, needs to parse the WMF file, determine what needs to be drawn, and generate Java code to draw the graphic.

As Table 1 shows, three structures comprise a WMF: MetaFileHeader, MetaHeader, and MetaRecord. Normally, when Windows loads a metafile, it loads the entire file into memory and then plays it. This may not be desirable if your metafile is very large. For this reason, metafiles store certain file attributes in the MetaFileHeader and the rest of the information in the MetaHeader, as opposed to using one unified header. This way, the viewer can load just the MetaFileHeader and read the rest of the file information directly from the disk.

The MetaFileHeader and MetaHeader appear once in the metafile. MetaRecords store the function and data for each drawing operation in the metafile. The MetaRecord consists of the size of the current record in 16-bit words as a long, an integer index value to the appropriate GDI function, and the parameters to the function as a string. Conversion of a metafile consists of reading the MetaFileHeader and MetaHeader data, then reading and interpreting each MetaRecord.

Windows GDI Functions

When dealing with Windows metafiles and GDI operations, it is not necessary to understand the intricacies of internal operations, such as the algorithms for drawing a rectangle. Windows GDI operations work by specifying handlers to a device context (DC). The DC is an object that contains information about the current drawing environment, such as the color or brush shape.

It is easy to understand how an operation to draw a rectangle works. The GDI function is Rectangle, and the parameters are the DC and four integers that describe the upper-left corner and lower-right corner of the rectangle.

When Rectangle is called, a rectangle is drawn into the specified DC using the currently selected pen and the currently selected brush. The pen contains information about the outline of the rectangle like color and thickness. The brush contains information about the inside of the rectangle. Brushes can be solid, hollow, or have patterns.

The corresponding MetaRecord contains a length, the integer 0x41b representing the GDI rectangle function, and four integers. The DC is not given. This makes sense, since the metafile viewer will have its own device context. The four integers that describe the corners are stored in reverse order. When called by the GDI function Rectangle, they are x1, y1, x2, y2. When stored in the metafile, they are y2, x2, y1, x1.

Converting Metafiles into Java

The converter, written in Java, reads the metafile structures and converts each metarecord into the Java code necessary to declare, initialize, and draw it. The main methods are parsewmf, parseit, WMFlist, and WMFlistrecord.

parsewmf validates the input and output files using code based on the file copy code in David Flanagan's Java in a Nutshell (O'Reilly & Associates, 1996). If the destination file exists, the user is asked if he wants to overwrite it.

parsewmf calls parseit, which stores each metarecord into the vector MetaRecordVector as a MetaRecord object. The MetaRecord class is very simple and corresponds to the Windows metarecord structure. MetaRecordVector is a growable array that contains a set of MetaRecord class objects. The WMFlist method calls WMFlistrecord for each MetaRecord in MetaRecordVector. When all records have been processed, wmflist calls writeCode to write the code.

WMFlistrecord does the work of conversion. A switch is done on the function portion of the MetaRecord. Inside the switch, the metarecord data is read, massaged, and converted into the necessary Java code. WMFlist builds three strings: javaDeclare, javaInit, and javaGraphic. These three strings are used by writeCode to create the Java applet.

The supporting cast of conversion functions is fairly large. First, some type manipulation is required. WMF files are stored in Little-endian format. Byte flipping is necessary to process these files in Java. The methods readInt and readLong perform this byte flipping. A long in Windows corresponds to a Java int, and an int in Windows corresponds to a Java short.

Metafiles are stored in twips (one twentieth of a point), while Java source requires pixels. I created a utility class called convertToolkit to handle twip to pixel conversion and to handle color conversion. convertToolkit uses the Java Toolkit class, which provides information about the current environment. The Toolkit class contains methods for getting the screen size and screen resolution. I use the screen resolution information to convert twips to pixels.

Converting the metafile drawing functions is fairly straightforward. Listing One hows a case statement from the WMFlistrecord method that converts a rectangle. The generated code declares a rectangle, initializes it, and draws it. The rectangle could have been drawn directly. I chose to create, declare, and initialize the rectangle because it allows the generated code to be more easily modified. By creating a class, I have access to all of the rectangle's values. To resize the rectangle, I would multiply the width and height by a constant.

Sometimes, my choice to make the generated code easy to modify comes at the expense of performance. This is seen most dramatically when rendering polylines. I considered two methods for polylines. The first was to declare, initialize, and draw the polygon. The second was to draw a line from point to point in the polyline. My intuition told me that the first method would create more efficient code, since the data for each point is referenced only one time.

My intuition was wrong. Listing Two draws a smile on a cartoon dog. (I've been drawing this dog since I was seven; see Figure 1.) When compiled with optimization, the first method creates a class of 758 bytes. The class for the second method is 597 bytes. That's a savings of over 20 percent.

In the original version of the converter, I ignored the GDI functions SelectObject and DeleteObject, which select and delete previously created objects into the DC. For example, when a brush is created, a handle is returned. SelectObject changes the DC's value of a particular object (like the brush) using that handle. When I drew the dog picture, I selected a brush that was previously defined instead of creating a new brush for the eyes. After conversion, this presented an eerie picture of the dog with hollow eyes (Figure 1).

The handleTable class processes the handles used by SelectObject and DeleteObject using methods selectObject, deleteObject, and addObject. The addObject method determines the next available handle, which is an index of the vector handleTableVector. When addObject is called the first time, the value passed becomes the first element in the vector. The position in the vector is the handle to the object. So selecting handle 1 is the same as getting the element from the vector at index 1. The deleteObject method of handleTable sets the element of the handle to -1.

Limitations and Future Development

This converter works fairly well, but is relatively limited. There are over 70 GDI functions, and this program only supports a handful of them. For example, neither bitmap functions nor pen functionality are supported. Many of these GDI functions would be nontrivial to implement. For example, in Java, there is no direct support for a pen width or brush patterns. It would require some work to provide this functionality.

Implementing such additional functionality would be easier with a more robust set of Java classes for graphic support. These classes could provide easy support for things like pen and brush colors, widths, and background and foreground colors.

Consider an abstract class called drawnObject, which would have a name, type, and information about placement and size. It would contain methods to draw itself and to scale, rotate, translate, and revolve itself. It would also contain a method to generate Java code to describe itself.

Each primitive drawing object would extend drawnObject, such as drawnRectangle, drawnOval, and drawnPolygon. To convert the metafile to Java code based on these classes, each metarecord would create a new drawnObject. Now consider a drawnObjectCollection class that would contain a collection of drawnObjects. The drawnObjectCollection class would have the same methods as the drawnObject class, so it would know how to draw itself.

Other possibilities include creating a drawing program to generate the appropriate Java bytecodes directly.