Compact Framework Controls (Part 2): Polygon Clipping
Posted by Dan Vanderboom on May 4, 2008
[This article is part of a series that starts in this article.]
My intention is to cover a full spectrum of activities around custom control development, with an emphasis on the compromises, workarounds, and special care that must be taken when developing controls for the Compact Framework. In my first article, I talked about how most design-time attributes must be applied to control classes and their members, and what some of those attributes mean. I have a number of articles planned that explore those attributes more, and will go into extending the design-time experience in more depth, but I’m going to take a detour into custom painting of the control surface first, so we have a control to reference and work with in the examples.
Polygon Clipping
If you’re new to creating graphical effects and unfamiliar with the techniques invovlved, clipping refers to the chopping off of an image based on some kind of border or boundary. In Windows Forms interfaces, controls are inherently rectangular because clipping occurs automatically at the window’s boundary (which is a shame considering how this presumption of need slows rendering, and WPF takes good advantage of not doing so). Everything outside the control’s outer shape doesn’t get drawn at all. You can draw anywhere you want, including negative coordinates, but only the points that fall within the clipping region will be displayed.
But what if you want to make your control a different shape, other than the standard rectangle, like an ellipse or a rounded rectangle? How do you make sure that whatever you draw inside never leaks outside of your defined shape? In the full .NET Framework, there is a Region property in the Control class that defines these boundaries, and there are several good articles that explain this. The clipping mask is applied based on that Region’s definition. In Compact Framework, the Region property doesn’t exist, and you’re forced to find your own way of defining different shapes.
The key to this is to understand the Graphics class’s Fill methods. While FillEllipse and FillRectangle definitely have their uses, I’d like to focus on situations that are a little bit more demanding, such as when you want to represent a more complex shape like a rounded rectangle (with many points) or some kind of UML diagram element. FillPolygon takes a list of Points, and with them can define the most eccentric and specific of shapes. By filling a polygon with an image using a TextureBrush, clipping happens automatically as part of the operation.
Let’s take a look at some code to see how we perform each of these steps: preparing and drawing on a bitmap image in memory, defining our shape’s boundaries, and then clipping that image within the specified shape.
using System; using System.Collections.Generic; using System.Windows.Forms; using System.Drawing; using System.Reflection; namespace CustomControlsDemo { public class ClippingControl : Control { protected override void OnPaint(PaintEventArgs e) { // define a canvas for the visual content of the control Bitmap MyBitmap = new Bitmap(Width, Height); // create a painting tools object Graphics g = Graphics.FromImage(MyBitmap); // draw graphics on our bitmap g.FillRectangle(new SolidBrush(Color.PaleGoldenrod), ClientRectangle); g.DrawLine(new Pen(Color.Black), 0, 0, Width - 1, Height - 1); g.DrawEllipse(new Pen(Color.Black), 0, 0, Width - 1, Height - 1); // dispose of the painting tools g.Dispose(); // define the custom shape of the control: a trapezoid in this example List<Point> Points = new List<Point>(); Points.AddRange(new Point[] { new Point(20, 0), new Point(Width - 21, 0), new Point(Width - 1, Height - 1), new Point(0, Height - 1) }); // draw that content inside our defined shape, clipping everything that falls outside of the region; // only if the image is much smaller than the control does it really get "tiled" and act like a textured painting brush // but our bitmap image is the same size as the control, so we're just taking advantage of clipping Brush ImageBrush = new TextureBrush(MyBitmap); e.Graphics.FillPolygon(ImageBrush, Points.ToArray()); } } }
Although this control has a silly shape and doesn’t do much yet, it does illustrate the basics of painting within the bounds of an irregular shape. As long as we draw on MyBitmap, everything will be properly clipped by the call to FillPolygon. However, as you can see in the screenshots below, the white background around our custom shape could be a problem. You can change the BackColor property to match the color of the container its on (a Panel control in this case, which is Color.BurlyWood), but really it makes more sense for BackColor to describe the color within our shape. We’d like the surrounding background to blend in with whatever container the control is sitting in.
We can accomplish this with two simple changes. First, at some point before the FillPolygon call, we need to fill the entire control’s area with the BackColor property of the parent control. We will draw using the e.Graphics object, which paints on the whole rectangular control, not our g Graphics object, whose contents get clipped. Then, instead of hard coding Color.PaleGodenrod, we can use the BackColor property to specify our fill color. Here is the changed section of code:
// draw graphics on our bitmap g.FillRectangle(new SolidBrush(BackColor), ClientRectangle); g.DrawLine(new Pen(Color.Black), 0, 0, Width - 1, Height - 1); g.DrawEllipse(new Pen(Color.Black), 0, 0, Width - 1, Height - 1); // dispose of the painting tools g.Dispose(); e.Graphics.FillRectangle(new SolidBrush(Parent.BackColor), ClientRectangle);
Now if we set the BackColor to PaleGodenrod, we’ll get this rendering:
Dragging the control off the panel and into the white area will cause the area around the control to paint white, so now you can see how it blends in with whatever background we have as long as it’s a solid color.
In a future article, after I’ve covered how to draw arcs and curves, I will revisit this technique and demonstrate how to draw rectangles with rounded corners.
[This article is part of a series that continues in this article.]

nick said
Hello, thanks for the great series – i’ve picked up lots of tips.
I’m trying to use your method to skew an image to produce a ‘perspective’ look. I have the following code in the OnPaint method where thumbnail is a bitmap and backBuffer is a Graphics object:
…
Point[] skew = new Point[4];
skew[0] = new Point(8, 18);
skew[1] = new Point(158, 8);
skew[2] = new Point(158, 92);
skew[3] = new Point(8, 82);
if (thumbnail != null)
{
Brush imageBrush = new TextureBrush(thumbnail);
backBuffer.FillPolygon(imageBrush, skew);
}
…
however this code crashes when creating the TextureBrush – throwing an OutOfMemory exception. The thumbnail image is a 150 * 84 px (loaded from a jpeg and held in memory as a System.Drawing.Image).
Do you have any ideas on how to get around this or other techniques for skewing an image?
many thanks.
Dan Vanderboom said
I tried your code in a custom control, and had no out of memory exceptions. However, it doesn’t skew the image. Instead, it applies polygon clipping in the shape of a parallelogram. So I see what direction you want to skew in.
I haven’t attempted skewing yet. From this article (http://www.aurigma.com/Support/DocViewer/30/Transformations.htm.aspx), written by the developers of a full framework graphics library called Graphics Mill, I can tell you that skewing an image is a mathematically intensive linear transformation that uses matrix math. The System.Drawing.Drawing2D.Matrix class, which is available for full framework but naturally not compact framework, could be reverse engineered (via Reflector or a similar tool) and reproduced in Compact Framework without too much hassle.
This won’t get you a skew method (.NET’s Matrix operation only supports affine transformations), but I found some links on the math and algorithms that could get you going in the right direction.
First, the formal term for “skew” is “shear mapping” or “transvection”.
http://en.wikipedia.org/wiki/Shear_mapping
There is much more information about this under Eigenvectors, Eigenspaces, etc.
http://en.wikipedia.org/wiki/Eigenvector
Good luck!
MS said
TextureBrush with FillPolygon works only for few times, if you repeat these calls resulting ‘Out of memory’ error.
Any suggestion?
Thanks in advance!
MS