The Metafile and the Metafile Picture

Using the clipboard to transfer metafiles from one program to another involves complexities not present when dealing with text and bitmaps. You can determine the length of a NULL-terminated string by simply searching for the NULL terminator. You can determine the dimensions of a bitmap using GetObject. But if you have a handle to a metafile, how can you determine how large the image will be when you play the metafile? Unless you start digging into the internals of the metafile itself, you can't.

Moreover, when a program obtains a metafile from the clipboard, it has the most flexibility in working with it if the metafile has been designed to be played in an MM_ISOTROPIC or MM_ANISOTROPIC mapping mode. The program that receives the metafile can then scale the image by simply setting viewport extents before playing the metafile. But if the mapping mode is set to MM_ISOTROPIC or MM_ANISOTROPIC within the metafile, then the program that receives the metafile is stuck. The program can make GDI calls only before or after the metafile is played. It can't make a GDI call in the middle of a metafile.

To solve these problems, metafile handles are not directly put into the clipboard and retrieved by other programs. Instead, the metafile handle is part of a ”metafile picture,“ which is a structure of type METAFILEPICT. This structure allows the program that obtains the metafile picture from the clipboard to set the mapping mode and viewport extents itself before playing the metafile.

The METAFILEPICT structure is 8 bytes long and has four fields: mm (int), the mapping mode; xExt (int) and yExt (int), the width and height of the metafile image; and hMF (WORD), the handle to the metafile. For all the mapping modes except MM_ISOTROPIC and MM_ANISOTROPIC, the xExt and yExt values are the size of the image in units of the mapping mode given by mm. With this information, the program that copies the metafile picture structure from the clipboard can determine how much display space the metafile will encompass when it is played. The program that creates the metafile can set these values to the largest x- and y-coordinates it uses in the GDI drawing functions that enter the metafile.

For the MM_ISOTROPIC and MM_ANISOTROPIC mapping modes, the xExt and yExt fields function differently. You will recall from Chapter 11 that a program uses the MM_ISOTROPIC or MM_ANISOTROPIC mapping mode when it wants to use arbitrary logical units in GDI functions independent of the measurable size of the image. A program uses MM_ISOTROPIC when it wants to maintain an aspect ratio regardless of the size of the viewing surface and MM_ANISOTROPIC when it doesn't care about the aspect ratio. You will also recall from Chapter 11 that after a program sets the mapping mode to MM_ISOTROPIC or MM_ANISOTROPIC, it generally makes calls to SetWindowExt and SetViewportExt. The SetWindowExt call uses logical units to specify the units the program wants to use when drawing. The SetViewportExt call uses device units based on the size of the viewing surface (for instance, the size of the window's client area).

If a program creates an MM_ISOTROPIC or MM_ANISOTROPIC metafile for the clipboard, then the metafile should not itself contain a call to SetViewportExt, because the device units in that call would be based on the display surface of the program creating the metafile and not on the display surface of the program that reads the metafile from the clipboard and plays it. Instead, the xExt and yExt values should assist the program that obtains the metafile from the clipboard in setting appropriate viewport extents for play- ing the metafile. But the metafile itself contains a call to set the window extent when the mapping mode is MM_ISOTROPIC or MM_ANISOTROPIC. The coordinates of the GDI drawing functions within the metafile are based on these window extents.

The program that creates the metafile and metafile picture follows these rules:

The mm field of the METAFILEPICT structure is set to specify the mapping mode.

For mapping modes other than MM_ISOTROPIC and MM_ANISOTROPIC, the xExt and yExt fields are set to the width and height of the image in units corresponding to the mm field. For metafiles to be played in an MM_ISOTROPIC or MM_ANISOTROPIC environment, mat- ters get a little more complex. For MM_ANISOTROPIC, 0 values of xExt and yExt are used when the program is suggesting neither a size nor an aspect ratio for the image. For MM_ISOTROPIC or MM_ANISO- TROPIC, positive values of xExt and yExt indicate a suggested width and height of the image in units of 0.01 mm (MM_HIMETRIC units). For MM_ISOTROPIC, negative values of xExt and yExt indicate a suggested aspect ratio of the image but not a suggested size.

For the MM_ISOTROPIC and MM_ANISOTROPIC mapping modes, the metafile itself contains calls to SetWindowExt and (possibly) SetWindowOrg. That is, the program that creates the metafile calls these functions in the metafile device context. Generally, the metafile will not contain calls to SetMapMode, SetViewportExt, or SetViewportOrg.

The metafile should be a memory-based metafile, not a disk-based metafile.

Here's some sample code for a program creating a metafile and copying it to the clipboard. If the metafile uses the MM_ISOTROPIC or MM_ANISOTROPIC mapping mode, the first calls in the metafile should be to set the window extent. (The window extent is fixed in the other mapping modes.) Regardless of the mapping mode, the window origin can also be set:

hdcMeta = CreateMetaFile (NULL) ;

SetWindowExt (hdcMeta, ...) ;

SetWindowOrg (hdcMeta, ...) ;

The coordinates in the drawing functions of the metafile are based on these window extents and the window origin. After the program uses GDI calls to draw on the metafile device context, the metafile is closed to get a handle to the metafile:

hmf = CloseMetaFile (hdcMeta) ;

The program also needs to define a far pointer to a structure of type METAFILEPICT and allocate a block of global memory for this structure:

GLOBALHANDLE hGMem ;

LPMETAFILEPICT lpMFP ;

[other program lines]

hGMem = GlobalAlloc (GHND, (DWORD) sizeof (METAFILEPICT)) ;

lpMFP = (LPMETAFILEPICT) GlobalLock (hGMem) ;

Next, the program sets the four fields of this structure.

lpMFP->mm = MM_... ;

lpMFP->xExt = ... ;

lpMFP->yExt = ... ;

lpMFP->hMF = hmf ;

GlobalUnlock (hGMem) ;

The program then transfers the global memory block containing the metafile picture structure to the clipboard:

OpenClipboard (hwnd) ;

EmptyClipboard () ;

SetClipboardData (CF_METAFILEPICT, hGMem) ;

CloseClipboard () ;

Following these calls, the hGMem handle (the memory block containing the metafile picture structure) and the hmf handle (the metafile itself) become invalid for the program that created them.

Now for the hard part. When a program obtains a metafile from the clipboard and plays this metafile, it must do the following:

1.The program uses the mm field of the metafile picture structure to set the mapping mode.

2.For mapping modes other than MM_ISOTROPIC or MM_ANISOTROPIC, the program uses the xExt and yExt values to set a clipping rectangle or simply to determine the size of the image. For the MM_ISOTROPIC and MM_ANISOTROPIC mapping modes, the program uses xExt and yExt to set the viewport extents.

3.The program then plays the metafile.

Here's the code. You first open the clipboard, get the handle to the metafile picture structure, and lock it:

OpenClipboard (hwnd) ;

hGMem = GetClipboardData (CF_METAFILEPICT) ;

lpMFP = (LPMETAFILEPICT) GlobalLock (hGMem) ;

You can then save the attributes of your current device context and set the mapping mode to the mm value of the structure:

SaveDC (hdc) ;

SetMappingMode (lpMFP->mm) ;

If the mapping mode isn't MM_ISOTROPIC or MM_ANISOTROPIC, you can set a clipping rectangle to the values of xExt and yExt. Because these values are in logical units, you have to use LPtoDP to convert the coordinates to device units for the clipping rectangle. Or you can simply save the values so you know how large the image is.

For the MM_ISOTROPIC or MM_ANISOTROPIC mapping mode, you use xExt and yExt to set the viewport extent. One possible function to perform this task is shown below. This function assumes that cxClient and cyClient represent the pixel height and width of the area in which you want the metafile to appear if no suggested size is implied by xExt and yExt.

void PrepareMetaFile (HDC hdc, LPMETAFILEPICT lpmfp,

SHORT cxClient, SHORT cyClient)

{

long xlScale, ylScale, lScale ;

SetMapMode (hdc, lpmfp->mm) ;

if (lpmfp->mm == MM_ISOTROPIC || lpmfp->mm == MM_ANISOTROPIC)

{

if (lpmfp->xExt == 0)

SetViewportExt (hdc, cxClient, cyClient) ;

else if (lpmfp->xExt > 0)

SetViewportExt (hdc,

(short) ((long) lpmfp->xExt *

GetDeviceCaps (hdc, HORZRES) /

GetDeviceCaps (hdc, HORZSIZE) / 100),

(short) ((long) lpmfp->yExt *

GetDeviceCaps (hdc, VERTRES) /

GetDeviceCaps (hdc, VERTSIZE) / 100)) ;

else if (lpmfp->xExt < 0)

{

xlScale = 100L * (long) cxClient *

GetDeviceCaps (hdc, HORZSIZE) /

GetDeviceCaps (hdc, HORZRES) /

-lpmfp->xExt ;

ylScale = 100L * (long) cyClient *

GetDeviceCaps (hdc, VERTSIZE) /

GetDeviceCaps (hdc, VERTRES) /

-lpmfp->yExt ;

lScale = min (xlScale, ylScale) ;

SetViewportExt (hdc,

(short) ((long) -lpmfp->xExt * lScale *

GetDeviceCaps (hdc, HORZRES) /

GetDeviceCaps (hdc, HORZSIZE) / 100),

(short) ((long) -lpmfp->yExt * lScale *

GetDeviceCaps (hdc, VERTRES) /

GetDeviceCaps (hdc, VERTSIZE) / 100)) ;

}

}

}

This code assumes that both xExt and yExt are 0, greater than 0, or less than 0, (which should be the case). If the extents are 0, no size or aspect ratio is suggested. The viewport extents are set to the area in which you want to display the metafile. Positive values of xExt and yExt are a suggested image size in units of 0.01 mm. The GetDeviceCaps function assists in determining the number of pixels per 0.01 mm, and this value is multiplied by the extent values in the metafile picture structure. Negative values of xExt and yExt indicate a suggested aspect ratio but not a suggested size. The value lScale is first calculated based on the aspect ratio of the size in millimeters corresponding to cxClient and cyClient. This scaling factor is then used to set a viewport extent in pixels.

With this job out of the way, you can set a viewport origin if you want, play the metafile, and return the device context to normal:

PlayMetaFile (lpMFP->hMF) ;

RestoreDC (hdc, -1) ;

Then you unlock the memory block and close the clipboard:

GlobalUnlock (hGMem) ;

CloseClipboard () ;