Tuesday 1 March 2011

.net Graphics in Windows Forms – Part 2: Anti-Aliasing Your Primitives

I promised this second instalment on Windows Forms graphics would be on rendering settings. Like the previous part of this series (on ControlStyles), this topic will not show any tricks or undocumented features, but I will discuss a few of the framework settings you can alter that change its behaviour and which I don't see discussed very often. The third instalment will discuss text rendering.

If you've never used any of the vector drawing primitives, you might like to try one or two out – just create an empty Forms project, override OnPaint() and you can draw (outlines) or fill (solid colour) several different shapes using methods in the Graphics object. The instance of Graphics you need is passed to OnPaint() in the PaintEventArgs object.

The basic things you need to know are:

  • Coordinates start at the top left of the control's client area and increase to the right (x) and down (y)
  • Angles are measured from the positive X axis (i.e. horizontally, to the right); angles increase clockwise and are given in degrees (many graphics systems and System.Math use radians!)
  • Brushes and Pens (which you'll find you need to fill and draw shapes, respectively) should always be disposed of if you create them. I've never looked into the mechanisms at work here, but the essentials are that they wrap non-managed resources and we're led to believe that the garbage collector can't be relied upon to release these resources before the OS runs out of them.
  • Manual disposal applies to several other objects associated with drawing so make sure you check. The golden rule is that if you make it, you break it. If you were passed it from elsewhere (e.g. the Graphics object in the PaintEventArgs) you should NOT call Dispose() on it yourself
  • You can use ready-made brushes and pens, available in the Brushes and Pens static classes with a myriad of colours to choose from. As these are pre-existing objects don't call Dispose() on them yourself (you can't anyway – you'll get an exception)
  • If you don't need to draw everything, don't! You are given ClipRectangle as part of the PaintEventArgs object and if you can get away without drawing outside this rectangle, that can speed things up

There are a wealth of drawing tutorials available, so I don't want to say anything more about the basics. Instead I want to look at a few of the properties that are available in the Graphics object and discuss what they do. The ones we are interested in are as follows:

  • SmoothingMode
  • PixelOffsetMode
  • InterpolationMode

SmoothingMode tells the rendering engine to use anti-aliasing when drawing lines and shapes. As I'm sure many of you will know, (at least in this context) anti-aliasing reduces jagged edges between areas of different colours by inserting transitional pixels of an intermediate colour where the shape of the (idealised) edge would cover only a proportion of the entire pixel.

The enum which defines the possible values for SmoothingMode has six members. However, one (Invalid) can't be used and of the other five, two are synonyms for "use anti-aliasing" (AntiAlias, HighQuality) and three for "don't use ant-aliasing" (Default, None, HighSpeed). Note that within each of these sets the results are identical: there is no difference at all between rendering done with AntiAlias or with HighQuality. Notice also where "Default" is: by default, anti-aliasing is switched off.

Anti-aliasing is not always the answer. It's slower, can make shapes look blurred and it doesn't really do anything if all your lines are vertical and horizontal, but I would strongly suggest that you at least try switching it on and examining the results if you are drawing anything remotely complex in your controls.

PixelOffsetMode tells the rendering engine how to align pixels on the screen with the coordinate system used to define points on the drawing surface. Like SmoothingMode, several of the enum members are equivalent (Default, HighSpeed and None are the same and HighQuality and Half are also equivalent). "Invalid" is again present but unusable. This setting is also dependent on the use of SmoothingMode.AntiAlias (or one of its synonyms) – PixelOffsetMode makes no difference if anti-aliasing is not being used.

The difference between the two modes is that any integer coordinate is considered to be at the top left of the pixel (None) or the centre of the pixel (Half). PIxelOffsetMode.Half takes longer to process but theoretically offers higher quality rendering because it is easier to follow the idealised course of a line if it goes through the centre of pixels rather than butts up against their edges. However, you may find it makes lines look softer than you want, especially verticals and horizontals.

In my opinion, it makes only a marginal difference to your results and given the extra calculation work it generates, is definitely in the "try it out" rather than "switch it on regardless" category. If anyone knows of a class of problem in which it makes a noticeable (and useful) difference to the results, I'd love to hear about it.

The final setting I want to briefly discuss is InterpolationMode. This property allows you to tell the rendering engine how you want it to treat images if they are scaled or rotated and it's only of use if you are using a bitmap in this way. I'm not going to go into the different modes available: it's enough to know that you don't have to stick with the defaults and a proper treatment of each method can easily be found elsewhere. For an interesting comparison of quality and speed, Bertrand Le Roy benchmarked the different methods: timings and rendered results can be found here.

For completeness, I'm also going to nod towards CompositingMode and CompositingQuality. These properties are worth looking into if you are creating images by adding partially transparent layers to your drawing surface, but are fairly self-explanatory and I'm not going to go into any more detail in this post.

No comments:

Post a Comment