Critical Development

Language design, framework development, UI design, robotics and more.

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.

Clipping Illustration

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.

first version

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:

transparent background

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.]

Advertisements

12 Responses to “Compact Framework Controls (Part 2): Polygon Clipping”

  1. 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.

  2. 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!

  3. 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

    • Hans said

      Same here! Any solution to the OutOfMemoryException anyone?

      • Jacco said

        The issue is that the garbage collector cannot clean up the discarded objects fast enough. Use a “Using” clause for the Texture brush and you will be fine. Some mobile devices are faster, and the error will not occur. Remember that in the onPaint event is called many times, especially if the control updates regularly. The garbage collector can’t keep up…

  4. Maik Wiege said

    There is a bug in the CF 2.0 and you need to use P/Invoke to fix the OutOfMemoryException. More Information in this thread:
    http://social.msdn.microsoft.com/forums/en-US/netfxcompact/thread/e831ea2f-039a-4b92-adb6-941954bee060/

    • Michael said

      I dont see Dispose() being called on this brush object at any point – maybe that’s the problem? Perhaps surrounding it in a using{} would suffice?

  5. AALL said

    Hi,

    In CF 2.0 I have the same problem. I also have had a look at: http://social.msdn.microsoft.com/forums/en-US/netfxcompact/thread/e831ea2f-039a-4b92-adb6-941954bee060/?prof=required

    but whenever I open a new form, I have noticed that the memory recquired by gwes.exe increases.

    The PDA has a 128 MB RAM memory, but when gwes.exe reaches about 25, my button gets an OutOfMemoryException so I cannot go on working.

    Any known solution to the TextBrushes problem???

  6. Andikki said

    As people above stated, you dispose of the Graphics object, but don’t do that for Solid Brush or Pen, which can lead to problems. Quite often an application will use the limited set of styling options, so instead of creating brushes and such as you use them, have you thought of making some classes that help with styling and make use of IDisposable interface? SolidBrushes, Pen, Fonts, StringFormats are often used when drawing controls and they all are disposable.

    I’m asking because I’ve been thinking of the best solution for this problem since I started drawing controls recently. I’d like to come up with some highly reusable template…

    • Andikki said

      Just found this great article on the topic:
      Diposer for GDI+ resources (Pen, Brush, …)
      http://www.codeproject.com/KB/GDI-plus/DisposeGDI.aspx

      The article shows a nice way to keep track of disposable resources used in a method (like OnPaint()) and to call Dispose() without fail in the end.

      So my question is this:
      During the lifetime of a form I might redraw it time and time again (every scrolling action, for example). Should I create and dispose of resources every time I paint something, or is it better to keep an instance of each resource as long as the form lives and reuse it? If I have a consistent style, I won’t need many of those – probably five to twenty objects.

  7. […] Compact Framework Controls (Part 2): Polygon Clipping  Creating Custom Controls for Microsoft .NET Compact Framework in Visual Studio 2005 Creating Custom Controls with the .NET Compact Framework […]

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

 
%d bloggers like this: