 Latest Article:

My Tags:  Silverlight Rectangles, Paths, and Line Comparison

Beta 1 Conversion

This was simply a drop-in of the Beta 1 control, but I was interested in seeing how this one would come out in Beta 1, for obvious reasons.

As you can easily see, the 'problem' if you want to call it that, is still there in Beta 1. I think more importantly, it's just something to keep in mind.

Original Text

This is not so much a tutorial article as it is a question. [4/25/2007 Note: Second part of article is the solution to this puzzle]

In the course of trying to draw a rectangle with a smaller rectangle appended onto it, I found I was having trouble matching up line thicknesses. I posed the question in Michael Schwarz' Google group wpf-everywhere, and also on the MSDN Silverlight forum. The forum ended up being a 3-way conversation about this between Michael, Luis Abreu, and me. Everyone had good ideas, and Michael had probably the best answer for what we're seeing.

So What is It?

On the Silverlight canvas above, there are 4 objects in this order from back to front:
• 1 pixel-width Black stroke Rectangle
• 1 pixel-width Black stroke rectangle drawn using a Path ... this is the rectangle offset to the left
• 1 pixel-width Black stroke rectangle drawn using Lines ... this is the rectangle offset to the right
• 2 pixel-width Red stroke rectangle drawn using a Path
As you can see, the Black rectangles drawn using a Path and Line objects not only do not appear to be black, but they are not 1 pixel. In the image below, I've captured a zoom in of 7:1 to show the effects closer.

Remember the z-order of the objects! As Michael pointed out on the forum, the Path and Line objects appear to be a black line antialiased and therefore are transparent, because you can clearly see the black rectangle which is beneath the other two objects on the canvas!

As an example of demonstrating the z-order and showing a 2-pixel Path for comparison, the red rectangle drawn with a Path statement appears to have a stroke the same as the 1-pixel Path and Line statements, however, it is not transparent and is very obviously above the other objects on the canvas.

What Does This Mean?

I don't know if this is an algorithmic effect when the pixel width is less than 2, but I've tried 1.5, 1.1, .5, and all seem to have the same thickness (2 pixels), but varying degrees of opacity.

I think this will be something to try in the next CTP, and defnitely something to be aware of when drawing Path or Line objects. For right now, it appears to me that the only way to get a single-pixel line is to clip a Rectangle object. `<Canvas    xmlns="http://schemas.microsoft.com/client/2007"   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">    <Rectangle Canvas.Left="50" Canvas.Top="5" Width="50" Height="50" Stroke="Black" StrokeThickness="1" />    <Path Stroke="Black" StrokeThickness="1"          Data="M20,20 L70,20 L70,70 L20,70z"/>    <Line Stroke="Black" StrokeThickness="1" X1="80" Y1="20" X2="130" Y2="20" />    <Line Stroke="Black" StrokeThickness="1" X1="130" Y1="20" X2="130" Y2="70" />    <Line Stroke="Black" StrokeThickness="1" X1="130" Y1="70" X2="80" Y2="70" />    <Line Stroke="Black" StrokeThickness="1" X1="80" Y1="70" X2="80" Y2="20" />    <Path Stroke="Red" StrokeThickness="2"          Data="M35,40 L115,40 L115,85 L35,85z"/></Canvas>`

Resolution of the puzzle!

Today, April 25, 2007, 'small_mountain_0705' posted on the MSDN Silverlight Forum and I believe answered the question I'd been asking!

In his(her?) last post, the point was made that drawing a line from (10,10) to (20,10) is essentially a zero-height line. It took me a few minutes to agree with that statement, but started thinking over problems I've had in the past getting widths or heights of graphic objects... such as is (0,0) the pixel AT (0,0) or is it above and to the left of the pixel? If you consider the location is above and left of the pixel, then the line truly is zero height. In order to draw what's requested, the line ends up bleeding across 2 pixel-widths to try to accomplish the task.

Check out this canvas:
The two blue rectangles are drawn as are the 'gray' rectangles above them. The left one is a path object, and the right is made of lines. It's obvious by looking that we now have a single-pixel rectangle!

The trick was to draw from (10.5, 10.5) to (20.5, 10.5) instead of (10,10) to (20,10). This allowed the line to fully populate the pixel height instead of bleeding over into the next one.

You can see from this 600% zoom that the blue lines really are single-pixel: `<Canvas    xmlns="http://schemas.microsoft.com/client/2007"   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">    <Rectangle Canvas.Left="50" Canvas.Top="5" Width="50" Height="50" Stroke="Black" StrokeThickness="1" />    <Path Stroke="Black" StrokeThickness="1"          Data="M20,20 L70,20 L70,70 L20,70z"/>    <Line Stroke="Black" StrokeThickness="1" X1="80" Y1="20" X2="130" Y2="20" />    <Line Stroke="Black" StrokeThickness="1" X1="130" Y1="20" X2="130" Y2="70" />    <Line Stroke="Black" StrokeThickness="1" X1="130" Y1="70" X2="80" Y2="70" />    <Line Stroke="Black" StrokeThickness="1" X1="80" Y1="70" X2="80" Y2="20" />    <Path Stroke="Red" StrokeThickness="2"          Data="M35,40 L115,40 L115,85 L35,85z"/>    <Path Stroke="Blue" StrokeThickness="1"          Data="M5.5,45.5 L60.5,45.5 L60.5,95.5 L5.5,95.5z"/>    <Line Stroke="Blue" StrokeThickness="1" X1="90.5" Y1="45.5" X2="145.5" Y2="45.5" />    <Line Stroke="Blue" StrokeThickness="1" X1="145.5" Y1="45.5" X2="145.5" Y2="95.5" />    <Line Stroke="Blue" StrokeThickness="1" X1="145.5" Y1="95.5" X2="90.5" Y2="95.5" />    <Line Stroke="Blue" StrokeThickness="1" X1="90.5" Y1="95.5" X2="90.5" Y2="45.5" /></Canvas>`