Friday, April 1, 2011

Avoiding Anti-Aliasing when drawing Rects, Lines in DrawingVisual's DrawingContext

When building my own high-speed grid control recently, I came across the problem that, when drawing the grid lines, they were anti-aliased and looked very ugly. I searched the web and came up with nothing for phrases like "anti aliasing drawingvisual" or "blurry lines drawingvisual" or even "crisp lines drawingvisual". None of them returned any result, until I came across an answer on StackOverflow (which I currently can't find again).

The solution is very easy, while not even remotely logical to anyone not knowing the deep internals of DrawingVisual. First, here is what you usually would try to draw lines from position (10, 10) to position (200, 10):

drawingContext.DrawLine(new Pen (Brushes.Black, 1), new Point(10, 10), new Point(200, 10));

The result would like something like this:

Non-antialiased line

As you can see, the line is not really black. And it is not one pixel wide. That's because it is rendered "blurry".

The obvious, totally intuitive way (oh, did you notice the irony?) to avoid this, you have to do this:

drawingContext.DrawLine(new Pen (Brushes.Black, 1), new Point(10.5, 10.5), new Point(200.5, 10.5));

And here is our desired result:

aliased line

Yes, you have to add 0.5 to your fraction-less position. And you will get pixel-perfect lines. This works for DrawRect, too.

Here's a full example with some grid lines:

class DrawElement : FrameworkElement
    {
        protected override void OnRender(DrawingContext drawingContext)
        {
            double x = 10.1;
            Pen pen = new Pen(Brushes.Black, 1);
            while (x < ActualWidth)
            {
                double actualX = Math.Ceiling(x) + 0.5;
                drawingContext.DrawLine(
                    pen,
                    new Point(actualX, 0),
                    new Point(actualX, ActualHeight));
                x += 10.1;
            }

            double y = 10.1;
            while (y < ActualHeight)
            {
                double actualY = Math.Ceiling(y) + 0.5;
                drawingContext.DrawLine(
                    pen,
                    new Point(0, actualY),
                    new Point(ActualWidth, actualY));
                y += 10.1;
            }
            base.OnRender(drawingContext);
        }
    }

And here's the result: