C# WPF Snippet - Single Pixel Lines [Beginner]


WPF is great in so many ways - but it can be terribly frustrating in others. One of its greatest assets - the fact that as much as possible all drawing is vector based and done in device independent units - can be a huge pain when the time comes that you actually want to draw on a per pixel basis. This is because by default WPF anti-aliases everything, and it anti-aliases with a vengeance. What we are going to take a look at today is how to turn that anti-aliasing off in certain cases.

Most likely you have already heard of the boolean property on UIElement called SnapsToDevicePixels. This in many cases does exactly what we want - it forces WPF to snap drawing to the nearest pixel - i.e., if you tell something to be at position 10.7, it will snap to pixel 11 (on a 96 dpi display) when drawing, instead of sort of fading across pixels 10 and 11. But this does not turn off anti-aliasing, and so it is still not possible to draw single pixel width lines. Take a look at the image below to see an example of what I'm talking about:

Rectangle Comparison

The picture above is a screenshot of a small sample app blown up to 400%. Both rectangles are drawn using the exact same command, and as you can see, the one on the left is anti-aliased and the one on the right is not. The only difference is a special rendering flag called EdgeMode.Aliased. Let's take a look at the code behind this example:
public class SillyUserControl : FrameworkElement
{
  protected override void OnRender(DrawingContext dc)
  {
    dc.DrawRectangle(null, new Pen(Brushes.Black, 1), 
        new Rect(5, 5, 10, 10));
  }
}
 
This is the code for the control that is generating those rectangles. Yes, I know that a much better way to make a rectangle is with the Rectangle visual, but I wanted to do my own drawing for the example. So all we have here is a single call to DrawRectangle in the OnRender method, which should draw a 1 DIU thick black border. Since this is happening on a 96dpi display, that should translate to a 1 pixel thick border, but as you can see in the above screenshot, that is not always the case.

Here is the rather simple xaml for the window these SillyUserControls are sitting in:
<Window x:Class="RenderingExample.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:me="clr-namespace:RenderingExample"
    Title="Window1" Height="50" Width="40">
  <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
    <me:SillyUserControl x:Name="Fuzzy" Height="20" Width="20"/>
    <me:SillyUserControl x:Name="Crisp" Height="20" Width="20"/>
  </StackPanel>
</Window>
 
Again, no difference except for the name. So on to that one very special call:
public partial class Window1 : Window
{
  public Window1()
  {
    InitializeComponent();
    Crisp.SetValue(RenderOptions.EdgeModeProperty, EdgeMode.Aliased);
  }
}
 
On the rectangle named Crisp, we set the attached property EdgeMode to Aliased. By default, this is always set to Unspecified, which essentially means that the element will be anti-aliased. By setting it to Aliased, we are essentially turning off anti-aliasing for that element. And that is all you have to do!

There are a few caveats for this property - you can't use it to turn off anti-aliasing for an entire WPF application. This is because only non-text drawing primitives listen to this flag - there is no way to turn off anti-aliasing for text.

Well, I hope this answers some questions about WPF and anti-aliasing - I know it took me a while (and a good amount a frustration) to find this flag when I first needed it.

Comments