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:
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
Post a Comment