Title: Clip an image to a polygon in C#
This example lets you select a polygon and then uses it to clip an image to it. It uses the technique described in my previous post Build a polygon selector class in C# to let you select the polygon.
The following section describes how the program lets you select a polygon. The section after that explains how the program clips images.
Selecting Polygons
When the program starts, the following code prepares a PolygonSelector object to let you select polygons.
// The polygon selector.
private PolygonSelector Selector;
private PointF[] Polygon = null;
// Prepare the selector.
private void Form1_Load(object sender, EventArgs e)
{
Selector = new PolygonSelector(picCanvas, new Pen(Color.Red, 3));
Selector.PolygonSelected += Selector_PolygonSelected;
}
The field Selector holds a references to a PolygonSelector. The Polygon array will hold the points that define the polygon.
The form's Load event handler creates the PolygonSelector and registers the Selector_PolygonSelected method to catch the selector's PolygonSelected event.
The selector is attached to the picCanvas control. When you press the mouse down on that control, the selector automatically takes over and lets you select a polygon. See the previous post for an explanation of how the selector works.
When you finish selecting a polygon, the selector raises its PolygonSelected event and the following event handler catches it.
// The user has selected a polygon. Save it.
void Selector_PolygonSelected(object sender, PolygonEventArgs args)
{
Polygon = PointsToPointFs(args.Points);
picCanvas.Refresh();
}
This code gets the polygon's points from the event handler's e.Points list. It calls the PointsToPointFs method to convert the Point values into an array of PointF values, saves the result in the Polygon field, and refreshes the picCanvas PictureBox to show the new image.
The following code shows the PointsToPointFs method.
// Convert Point data into a PointF array.
private PointF[] PointsToPointFs(IEnumerable<Point> points)
{
var query = from Point point in points select (PointF)point;
return query.ToArray();
}
This method uses LINQ to convert an IEnumerable<Point> into a PointF array. The method creates a query that iterates over the values in the points enumerable and casts the values into PointF values. The method then calls the query's ToArray method and returns the result.
The code saves the PointF array in the Polygon field and refreshes the picCanvas PictureBox to show the image clipped in the polygon.
Drawing the Image
The following code shows the Paint event handler that draws the clipped image.
private void picCanvas_Paint(object sender, PaintEventArgs e)
{
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
e.Graphics.Clear(picCanvas.BackColor);
if (Polygon != null)
{
DrawImageInPolygon(e.Graphics,
Polygon, Properties.Resources.Smiley_with_bg);
using (Pen pen = new Pen(Color.Green, 3))
{
e.Graphics.DrawPolygon(pen, Polygon.ToArray());
}
}
}
This code sets the Graphics object's SmoothingMode and clears the picture. If the Polygon field is not null, the code calls the DrawImageInPolygon method described shortly to clip an image to the polygon and draw it. It finishes by creating a thick, green pen and using the pen to draw the polygon.
The following code shows the DrawImageInPolygon method.
// Draw an image so it fills the polygon.
public static void DrawImageInPolygon(Graphics gr,
PointF[] points, Image image)
{
// Get the polygon's bounds and center.
float xmin, xmax, ymin, ymax;
GetPolygonBounds(points, out xmin, out xmax, out ymin, out ymax);
float wid = xmax - xmin;
float hgt = ymax - ymin;
float cx = (xmin + xmax) / 2f;
float cy = (ymin + ymax) / 2f;
// Calculate the scale needed to make
// the image fill the polygon's bounds.
float xscale = wid / image.Width;
float yscale = hgt / image.Height;
float scale = Math.Max(xscale, yscale);
// Calculate the image's scaled size.
float width = image.Width * scale;
float height = image.Height * scale;
float rx = width / 2f;
float ry = height / 2f;
// Find the source rectangle and destination points.
RectangleF src_rect = new RectangleF(0, 0,
image.Width, image.Height);
PointF[] dest_points =
{
new PointF(cx - rx, cy - ry),
new PointF(cx + rx, cy - ry),
new PointF(cx - rx, cy + ry),
};
// Clip the drawing area to the polygon and draw the image.
GraphicsPath path = new GraphicsPath();
path.AddPolygon(points);
GraphicsState state = gr.Save();
gr.SetClip(path); // Comment out to not clip.
gr.DrawImage(image, dest_points, src_rect, GraphicsUnit.Pixel);
gr.Restore(state);
}
This code first calls the GetPolygonBounds method to find the largest and smallest X and Y coordinates used by the polygon's points. That method simply loops through the array's points and keeps track of the largest and smallest values. It's relatively straightforward so it isn't shown here.
The DrawImageInPolygon method then calculates the width and height of the points' bounds, and finds the center of those bounds.
The method then calculates a scale factor that it should use to make the image fill the points' bounds. First it calculates the amounts by which it should scale the image to make it fill the bounds horizontally and vertically. It then uses the larger of the two scales to ensure that the image completely fills the bounds. The code uses the scale factor to calculate the image's scaled size.
Next the code creates a Rectangle that defines the image's entire area. It also creates an array of PointF values that indicate where the image should be drawn. That area is the same as the polygon's bounds.
The code then creates a GraphicsPath object and uses its AddPolygon method to add the polygon's points to it. The code saves the Graphics object's state and calls its SetClip method to clip future drawing to the path. The method draws the image, now clipped to the polygon, and then restores the Graphics object's state to remove the clipping. (That isn't necessary in this example but would be if the program performed additional drawing after drawing the clipped polygon.)
Summary
The key to this example is the Graphics object's SetClip method, which restricts the object's drawing to a GraphicsPath. This example sets the path equal to the polygon that you selected. For other programs, you could make the path much more complicated so it contained rectangles, ellipses, and other shapes.
After you set the Graphics object's clipping region, you simply draw as usual.
See the previous post to see how the PolygonSelector works.
Download the example to experiment with it and to see additional details.
|