Ron Gery
Microsoft Developer Network Technology Group
Created: March 20, 1992
ABSTRACT
This article provides an overview of metafiles, their creation, and their use. All metafile functions are discussed:
CloseMetaFile
CopyMetaFile
CreateMetaFile
DeleteMetaFile
EnumMetaFile
GetMetaFile
GetMetaFileBits
PlayMetaFile
PlayMetaFileRecord
SetMetaFileBits
A metafile is a mechanism for storing a graphics device interface (GDI) “picture”—a series of GDI functions that are used to draw an image. A metafile consists of a series of records, each representing a GDI function. When the metafile is played back, each stored function is executed using its recorded parameters.
In effect, a metafile is a journal of GDI operations, and because all GDI primitives can be recorded, any image that can be drawn can be stored in a metafile. Because a metafile is in a standard format, applications can exchange metafiles and use them for image storage.
The mapping mode of a metafile can be altered during playback. Thus, the image can be scaled arbitrarily, with every component scaling separately, which minimizes the loss of information for the image as a whole and which is not characteristic of bitmaps. In addition, if the image is sparse, a metafile uses less memory than does a bitmap of the same image.
Because of their device independence and scaling abilities, metafiles are useful for transferring images between applications, and most applications support the Clipboard format associated with metafiles (CF_METAFILEPICT). When treated as a single graphics primitive, a metafile is easy to paste into an application without that application needing to know about the specific content of the picture.
Creating a metafile is as simple as calling the CreateMetaFile function. An application can store a metafile in global memory or to disk; using a memory metafile is faster, but it does use up memory. The CreateMetaFile function returns a handle to a metafile device context (DC). To record any function that performs an output operation or sets a drawing attribute into a metafile, use this handle in place of a normal DC handle when calling that function.
When the desired picture is stored in the metafile DC, the application calls the CloseMetaFile function. As its name implies, the CloseMetaFile function closes the metafile DC so that it can no longer be used for recording. The function returns a handle to a metafile.
Now the metafile is ready for playback. The PlayMetaFile function is the simplest way to play back a metafile. It accepts a destination DC, which is where the image is to be drawn, and the metafile itself. In this function, GDI recalls every stored instruction in the metafile and executes it to the destination DC.
The application can place the image anywhere in the destination DC and scale it to the desired size by altering the logical coordinate mapping (see below).
When the application is through with the metafile and before terminating, the application must free GDI memory used by the metafile by means of the DeleteMetaFile function. If the metafile is stored on disk, the file remains untouched; only GDI memory associated with the metafile is freed. GDI deletes all objects created during a metafile playback as soon as the playing is complete.
In MicrosoftÒ WindowsÔ version 3.0, some major improvements greatly increased the practicality of metafiles. The size restriction on metafiles was essentially removed, and now their size is limited to 232 bytes of information (a DWORD value for the size). The size of each record is no longer limited to 64K, so large bitmap operations can now be handled successfully. This size increase would be useless, however, if the number of objects were still limited by the size of GDI’s heap, so the META_DELETEOBJECT record was added to allow object cleanup during playback.
For the object deletion to work correctly when recording to a metafile, first deselect the object being deleted. Deleting an object that is currently selected into a metafiling DC will work, but no META_DELETEOBJECT record is created for that object. The “stranded” object is deleted when the metafile playback is complete, but it remains in the system throughout the playback.
Using device-independent bitmaps (DIBs) to store all bitmap information significantly improves device independence. Using the new DIB-based records, an application can place color bitmaps in metafiles without losing information for functions such as BitBlt and StretchBlt. The conversion to a DIB while recording and from a DIB during playback is automatic.
The ability to use a metafile DC as the destination DC of a playback is also new; a metafile can now be played into another metafile. Thus, you can easily embed metafile information inside another metafile or copy pieces of one metafile into another. A word of caution: Windows version 3.0 crashes if you use PlayMetaFile and the destination DC is a memory-based metafile. This situation is corrected in Windows version 3.1, and you can use PlayMetaFileRecord for both memory-based and disk-based metafiles in Windows versions 3.0 and 3.1.
Some limitations in GDI APIs do not permit metafiles to be fully functional. Because Windows version 3.0 lacked a scaling font technology, fonts used in metafiles did not scale nicely as the metafile was sized. The addition of TrueTypeÒ in Windows version 3.1 eliminates this problem. Because no curve primitive is defined beyond the basic Ellipse functionality, including a complex curve in a metafile is not possible. Although you can use the Polygon function to draw a curve, it will not scale smoothly. Regions do not scale at all, rendering them virtually useless for any sort of complex clipping within a metafile.
Under Windows version 3.0, an application cannot determine if a DC is a real DC or a metafile DC. Under Windows version 3.1, GetDeviceCaps(hDC, TECHNOLOGY) correctly identifies a DC as a metafile DC (return value is DT_METAFILE) when appropriate. Unfortunately, Windows version 3.0 crashes or returns an incorrect value when GetDeviceCaps is used with a metafile DC.
A metafile that is created by an application and then passed to another application is likely to be scaled. Scaling may alter the desired image in a way not anticipated by the creating application that does not scale the image. Every logical measure defined in a logical object is scaled before the object is realized into physical form.
For a logical object such as pens, the width is transformed from logical to physical as an x-scalar value. If the metafile is scaled in y but not in x, the pen width is unchanged. If the metafile is scaled in x but not in y, the pen width does scale. Thus, using a pen of width 1 in a metafile results in a pen that is wider (thick and slow) when the metafile is scaled. If a nominal width pen (width of 1 at all times) is desired, use 0 as the width because it is not affected by mapping modes. A 0-width pen is drawn as having a width of 1.
Font sizing is more complicated. The two values that scale in a logical font are the height and the width. Most applications use a width of 0 to define a font, which results in a physical font with a width that was designed for the given height. As the metafile is stretched in x, the font remains the same size. As the metafile is stretched in y, however, the physical font grows bigger and probably wider. In and of itself, this is not a bad thing, but problems arise when the metafile makes assumptions about the width of the font by placing the characters of a text string individually by using ExtTextOut with a width array or using a TextOut for each character. In either case, the x-placement of each character scales with the metafile, but the font’s width does not necessarily scale accordingly, which causes characters to overlap or be widely spaced.
The simplest way to overcome this situation is not to place the characters individually but to use TextOut (or ExtTextOut with no width array) to output the whole string. The text string remains intact, but its size may change in relation to the rest of the image when x and y are not scaled identically. Another possibility is to define the font with a nonzero width so that it scales in x as well as in y. Doing so in Windows version 3.0 is not wonderful because its bitmapped fonts do not scale independently in x and y. Scaling a font’s width is possible with TrueType in Windows version 3.1. Unfortunately, anytime a font’s width is scaled, the look of the typeface changes in ways not necessarily intended by the designers, and a typographically “incorrect” typeface results.
GDI functions that return data either do not work properly or crash the system if the DC passed in is a metafile DC. This category of functions includes all Get functions as well as RectVisible, PtVisible, EnumFonts, EnumObjects, DPtoLP, and LPtoDP. Any Escape function that involves a data return is recorded in the metafile but returns no meaningful data. A list of all functions that are supported for metafiling is in the Microsoft Software Development Kit (SDK) Reference—Volume 1 manual.
A number of functions in the Windows API appear to the naked eye to be GDI functions with a DC parameter that should be able to be recorded in a metafile; in reality these are functions of the window manager interface and are not recorded in a metafile. They are DrawFocusRect, DrawIcon, DrawText, ExcludeUpdateRgn, FillRect, FrameRect, GrayString, InvertRect, ScrollDC, and TabbedTextOut.
Because a metafile DC is not actually associated with a device, you cannot use SetDIBits, GetDIBits, and CreateDIBitmap with a metafile DC. You can use SetDIBitsToDevice and StretchDIBits with a metafile DC as the destination. CreateCompatibleDC, CreateCompatibleBitmap, and CreateDiscardableBitmap are not meaningful with a metafile DC.
Calling SelectObject with a metafile DC does not return the previously selected object in the metafile; it returns either 1 for a successful recording or 0 for a failed recording. Attempting to use SelectObject with a return of 1 to restore the previous object does not work and causes a UAE in Windows version 3.0.
Support is limited for regions in metafiles. Regions do not scale properly and should be avoided.
Metafiles that are created by an application and then passed to another application should avoid altering the viewport extent in order to be easily scalable (see below for a discussion of using mapping modes with metafiles).
Metafiles have several layers of headers. GDI deals with the METAHEADER structure, which sits directly before the bits. It is described in the SDK Reference—Volume 2 manual. The METAFILEPICT structure is associated with a metafile when it is placed on the Clipboard. Its basic function is to identify the mapping mode and drawing size of the image, which are helpful for proper pasting into another application.
Each metafile record consists of two parts, a descriptor and the contents. Both are defined in the METARECORD structure:
rdSize | The size of the record in WORDs. For many records, this value is a constant because the number of parameters is not variable. The size is a DWORD value that allows records of more than 64K, a common occurrence with bitmap manipulations. |
rdFunction | Identifies the function being recorded. It can be one of the META_* values defined in WINDOWS.H. That list of values also shows which GDI API functions can actually be recorded in a metafile. |
rdParm[ ] | The space holder for the function’s parameters. The size of this array varies to fit as much memory as is needed. |
The SDK Reference—Volume 2 manual for Windows version 3.0 describes the ordering of parameters for all possible records, detailing those with nonstandard parameters. This description is on pages 9-7 to 9-28. Those GDI functions with a fixed number of parameters (that is, those that have no arrays or strings) are recorded as they are called, with the parameters stored in reverse order from the function definition. The functions with complex parameters vary in the way they are stored.
One set of records does more than simply store parameters: the one dealing with the creation, selection, and deletion of objects. Instead of recording actual handles, which is not useful for playback, a SelectObject function generates two records. The first is a creation record (for example, META_CREATEPENINDIRECT). The second is META_SELECTOBJECT, which has a parameter that is an index into the object table. This object table is associated with the metafile and grows as objects are added. Each new object gets a new entry in the table and, hence, an index into the table. If an object is reselected into a metafile, the corresponding META_SELECTOBJECT record references the initial object table entry. When an application calls DeleteObject for an object that was in the metafile, a META_DELETEOBJECT record is added. It references the entry, and that entry is marked as open. The next object that is created for the metafile reuses that entry and its index. Object creation, selection, and deletion depend on proper ordering during playback to achieve the proper results. For PlayMetaFileRecord and the EnumMetaFile callback, this handle table becomes a third component of the metafile. It is used invisibly in the PlayMetaFile function.
The METAFILEPICT structure contains information about the desired size of the metafile. When an application pastes a metafile, it can use this information to determine the size of the metafile output. For this to work cleanly between applications, be aware that:
The metafile is responsible for the window part of the mapping mode.
The player of the metafile is responsible for the viewport part of the mapping mode.
To perform a simple playback of the metafile, the application sets the mapping mode to the mode specified in the METAFILEPICT structure, sets the viewport origin to the desired placing of the metafile, and calls PlayMetaFile. How big is this output image? Because the x-extent and y-extent are given in logical units based on the specified mapping mode, you can use LPtoDP to calculate the size of the image in pixels.
If the mapping mode is MM_ANISOTROPIC or MM_ISOTROPIC, sizing is not quite so simple. Because the x-extent and y-extent of the image are given in MM_HIMETRIC coordinates, first convert them to pixel values. You can use LPtoDP after setting the mapping mode to MM_HIMETRIC or use the HORZSIZE/HORZRES and VERTSIZE/VERTRES ratios (values obtained using GetDeviceCaps) to convert manually. Before playback, the application needs to set the viewport origin to the desired location, set the mapping mode to the specified mode, and set the viewport extents to the values calculated above. A properly created metafile that uses the MM_ANISOTROPIC or the MM_ISOTROPIC mapping mode sets the window extent at the start of the metafile to complete the mapping mode equation. (The viewport itself is not sufficient; using any of the other mapping modes automatically sets appropriate values for the window extents.) If no desired extents are provided in the METAFILEPICT structure, the application doing the playback can arbitrarily choose a size.
Scaling a metafile that uses MM_ANISOTROPIC or MM_ISOTROPIC is easy—simply change the viewport extents to the desired size before playback. The viewport defines the size of the metafile image. To scale metafiles that use any other mapping mode, first transform the metafile to use MM_ANISOTROPIC. You need not change the metafile itself, but you do need to change the mapping mode setup before beginning the playback. Here is some simple code to do this:
SetMapMode(hDC, lpMetaFilePict->mm);
SetMapMode(hDC, MM_ANISOTROPIC);
The first call sets up the viewport and window for the desired mapping mode. The second changes the mapping mode to be scalable but doesn’t change the viewport and the window information. Thus, the window setting is in line with the mapping mode of the metafile and the logical coordinates within while leaving the viewport ready for scaling as desired.
You need not blindly play back metafiles. Windows has a mechanism that allows an application to inspect every record before it is played, change that record, or even invent a record of its own. EnumMetaFile calls a callback routine with every record found in the metafile. The application then calls PlayMetaFileRecord to play an individual record. In the simplest case, the information passed to the callback can be sent directly to PlayMetaFileRecord to simulate PlayMetaFile. In more complicated scenarios, the application can change the colors of objects or text of a TextOut, omit certain records, or simply add new records to the playback.
Note:
Altering the order of object creation, selection, or deletion calls can adversely affect object management during playback. It is important to keep in mind how the object table works when manipulating object-based records.
Applications that want to store private information (for retrieval during playback) in the metafile can do so by calling the Escape function with MFCOMMENT. The function to be performed does nothing on a regular DC but records that information in the metafile for a metafile DC. Of course, EnumMetaFile must be used during playback for the application to see and use the information.
Note:
If a privately commented metafile is placed on the Clipboard for transferring to another application, the record is ignored during normal playback. To ensure that two commenting applications do not become confused and attempt to interpret the other’s private data, place some kind of signature (in the form of a few identifier bytes at the start) in the comment.
The following functions concern metafile management:
CopyMetaFile | Creates a metafile that is a copy of another metafile. |
GetMetaFile | Retrieves a disk metafile, returning a handle to the metafile. |
GetMetaFileBits | Returns a handle to a global memory block that contains the actual metafile internals. The application can then change, inspect, or record the data. |
SetMetaFileBits | Creates a memory metafile using the given metafile data. |
The following list identifies problems with metafiles in Windows version 3.0 that have been fixed in Windows version 3.1:
Attempting to record the ScaleWindowExt or ScaleViewportExt functions occasionally caused crashes.
Recording StretchDIBits when using BITMAPCOREHEADER caused crashes. To avoid this problem, convert all DIBs to the BITMAPINFO format.
Using PlayMetaFile to play into a memory-based metafile caused crashes. Using disk-based metafiles or using PlayMetaFileRecord caused no problem.
Using EnumMetaFile and causing GDI’s heap to get shuffled (by doing extensive object manipulation) sometimes resulted in a crash when the enumeration was complete.
Passing a NULL hDC to EnumMetaFile caused invalid handle selections.
If an EnumMetaFile was aborted, a global selector remained allocated.
An aborted EnumMetaFile would not give the return value of the last callback.
CopyMetaFile returned a nonzero value even if it failed a copy from a disk-based metafile to a memory-based metafile or vice versa. Memory-to-memory and disk-to-disk properly returned an error on failure.
Calling CopyMetaFile to copy a metafile from a floppy disk after calling GetMetaFile caused a crash. To avoid this problem, copy the metafile from the floppy disk into a memory buffer before using CopyMetaFile.