Title: Make and use a metafile in C#
This example shows how to draw and use a metafile. A metafile is a drawing that doesn't just contain the pixels in an image. Instead it contains a record of the commands used to produce a drawing so you can rerun those commands later.
When the program starts, it executes the following code.
private void Form1_Load(object sender, EventArgs e)
{
// Make a metafile.
Metafile mf = MakeMetafile(100, 100, "test.emf");
// Draw on the metafile.
DrawOnMetafile(mf);
// Convert the metafile into a bitmap.
Bitmap bm = MetafileToBitmap(mf);
// Display in various ways.
picCanvas1.Image = bm; // Original size.
picCanvas2.Image = bm; // Stretches pixelated.
picCanvas3.Image = mf; // Stretches smoothly.
}
This code calls the MakeMetafile method (I'll describe all of these methods shortly) to create a 100x100 pixel metafile, and then calls DrawOnMetafile to draw some shapes on the metafile. It then calls MetafileToBitmap to convert the metafile into a bitmap. It displays the bitmap on two PictureBox controls and displays the metafile itself on a third.
If you look at the picture above, you'll see that the top PictureBox shows a nice crisp image. That's the first PictureBox displaying the bitmap at its correct size.
The second and third PictureBox controls stretch when you resize the form. In the second the bitmap is stretched so it looks a bit fuzzy and pixelated.
The third PictureBox displays the metafile itself. When it is stretched, it uses the metafile to repeat the original drawing commands. That gives it a smooth, crisp result even when it's resized. (In case you didn't make the obvious connection, this is the best way to use a metafile. Simply display it in a PictureBox and let it redraw as needed.)
The following code shows the MakeMetafile method.
// Return a metafile with the indicated size.
private Metafile MakeMetafile(float width, float height,
string filename)
{
// Make a reference bitmap.
using (Bitmap bm = new Bitmap(16, 16))
{
using (Graphics gr = Graphics.FromImage(bm))
{
RectangleF bounds =
new RectangleF(0, 0, width, height);
Metafile mf;
if ((filename != null) && (filename.Length > 0))
mf = new Metafile(filename, gr.GetHdc(),
bounds, MetafileFrameUnit.Pixel);
else
mf = new Metafile(gr.GetHdc(), bounds,
MetafileFrameUnit.Pixel);
gr.ReleaseHdc();
return mf;
}
}
}
This method creates a metafile of the specified size, optionally saving it into a file.
To create a new metafile, you need a graphics device handle. To get one, the code create a 16x16 pixel bitmap. The bitmap's size isn't important; the code really just needs to use it to get a graphics device handle.
Next the code creates a Graphics object associated with the bitmap. This is the object that will provide the device handle.
The code then creates a RectangleF representing the metafile's desired size.
The code then creates the metafile. If the filename is not null or blank, it includes the filename in the metafile constructor to make it create the file. (You don't need to save the file in a separate step.)
The code also passes the constructor the Graphics object's HDC (handle to device context), which the constructor uses to get information about the device where the metafile will be drawn. Finally the code passes the constructor the rectangle defining the metafile's bounds and a flag indicating that we're measuring in pixels.
The call to the Graphics object's GetHdc method locks the object's device context so the code then calls its ReleaseHdc method to it can clean up the Graphics object correctly.
The method finishes by returning the metafile.
The MakeMetafile method is probably the most confusing part of the program. The following code shows the DrawOnMetafile method.
// Draw on the metafile.
private void DrawOnMetafile(Metafile mf)
{
using (Graphics gr = Graphics.FromImage(mf))
{
gr.SmoothingMode = SmoothingMode.AntiAlias;
using (Pen pen = new Pen(Color.Red, 5))
{
gr.DrawEllipse(pen, 5, 5, 90, 90);
}
using (Brush brush = new SolidBrush(
Color.FromArgb(255, 128, 255, 128)))
{
gr.FillEllipse(brush, 5, 25, 90, 50);
}
using (Brush brush = new SolidBrush(
Color.FromArgb(128, 128, 128, 255)))
{
gr.FillEllipse(brush, 25, 5, 50, 90);
}
Point[] points =
{
new Point(50, 5),
new Point(94, 50),
new Point(50, 94),
new Point(5, 50),
};
gr.DrawPolygon(Pens.Blue, points);
}
}
This method is straightforward. It simply creates a Graphics object associated with the metafile and uses it to draw some shapes just as you would if you were drawing on a bitmap or in a Paint event handler.
Finally, the following code shows the MetafileToBitmap method that converts the metafile into a bitmap.
// Draw the metafile onto a bitmap.
private Bitmap MetafileToBitmap(Metafile mf)
{
Bitmap bm = new Bitmap(mf.Width, mf.Height);
using (Graphics gr = Graphics.FromImage(bm))
{
GraphicsUnit unit = GraphicsUnit.Pixel;
RectangleF source = mf.GetBounds(ref unit);
PointF[] dest =
{
new PointF(0, 0),
new PointF(source.Width, 0),
new PointF(0, source.Height),
};
gr.DrawImage(mf, dest, source, GraphicsUnit.Pixel);
}
return bm;
}
This method creates a bitmap the same size as the metafile and creates an associated Graphics object. It then uses the somewhat awkward GetBounds method to get the metafile's bounds in pixels, makes an array of points to define the destination area on the bitmap, and then draws the metafile onto the bitmap. It finishes by returning the bitmap.
The MetafileToBitmap method is mostly here to show you how to draw the metafile to a specific place on an image. For example, you could draw several metafiles and other graphics onto a single image. If you only want to display the metafile, you should just load it and set a PictureBox control's Image property equal to the metafile, as I mentioned earlier.
Download the example to experiment with it and to see additional details.
|