Page 1 of 1

WPF Blurriness - SnapsToDevicePixels

Posted: Tue Nov 04, 2008 5:07 pm
by 13048566
I am upgrading my code from the version 3 to version 3 in WPF. One of the main issues I've run into is that the shapes and text are pretty blurry.

I know that this is how WPF renders, but by setting SnapToDevicePixels to true it should fix the blurriness. I set it to True on the TChart itself, but it seems to have no effect on the shapes that TChart draws. Is there another property I need to set through TChart, and if not, then can it be fixed so that if I do set it to true it draws all the shapes and text with that property on.

Thanks,
T-Mac

Posted: Wed Nov 05, 2008 12:54 pm
by narcis
Hi T-Mac,

Could you send us an image of what you see as the problem? We can then try experimenting with SnapToDevicePixels in the source to see if that makes any difference.

You can either post your files at news://www.steema.net/steema.public.attachments newsgroup or at our upload page.

Thanks in advance.

Posted: Wed Nov 05, 2008 2:59 pm
by 13048566
I posted the file as BlurryChart.zip. The difference can be seen mainly around the candlesticks. The WinForms version has very distinct edges while the WPF version is pretty blurry on the edges.

I was also able to reproduce this effect in a test project by drawing two WPF rectangles on a black background. Turn SnapsToDevicePixels to true for one of them, and you can clearly see the difference between them.

Thanks,
T-Mac

Posted: Thu Nov 06, 2008 5:35 pm
by Chris
T-Mac,
T-Mac wrote: I was also able to reproduce this effect in a test project by drawing two WPF rectangles on a black background. Turn SnapsToDevicePixels to true for one of them, and you can clearly see the difference between them.
An interesting issue. Please let me explain.

I've created a little UserControl to try and illustrate the issue. Here's the code behind:

Code: Select all

namespace WpfControlLibrary1
{
  /// <summary>
  /// Interaction logic for UserControl1.xaml
  /// </summary>
  public partial class UserControl1 : UserControl
  {
    public UserControl1()
    {
      InitializeComponent();
      InitializeCanvas();
      InitializeOther();
    }

    private void InitializeOther()
    {
      this.SnapsToDevicePixels = true;
    }

    private void InitializeCanvas()
    {
      canvas1.Background = new SolidColorBrush(Colors.Black);

      Rectangle rect = new Rectangle();
      rect.Width = 100;
      rect.Height = 100;
      rect.SnapsToDevicePixels = false;
      rect.Fill = new SolidColorBrush(Colors.Yellow);
      rect.Stroke = new SolidColorBrush(Colors.Red);
      Canvas.SetLeft(rect, 10);
      Canvas.SetTop(rect, 10);
      canvas1.Children.Add(rect);

      Rectangle rect1 = new Rectangle();
      rect1.Width = 100;
      rect1.Height = 100;
      rect1.SnapsToDevicePixels = true;
      rect1.Fill = new SolidColorBrush(Colors.Yellow);
      rect1.Stroke = new SolidColorBrush(Colors.Red);
      Canvas.SetLeft(rect1, 150);
      Canvas.SetTop(rect1, 10);
      canvas1.Children.Add(rect1);
    }


    protected override void OnRender(DrawingContext drawingContext)
    {
      drawingContext.DrawRectangle(new SolidColorBrush(Colors.Black), null, new Rect(RenderSize));

      drawingContext.DrawRectangle(new SolidColorBrush(Colors.Yellow), new Pen(new SolidColorBrush(Colors.Red), 1), new Rect(10, 150, 100, 100));
    }
  }
}
And here's the XAML:

Code: Select all

<UserControl x:Class="WpfControlLibrary1.UserControl1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Height="300" Width="300">
    <Grid>
    <Canvas Margin="0,0,0,150" Name="canvas1" />
  </Grid>
</UserControl>
With any luck you should easily be able to drop this code into to a default-created UserControl and then drop the UserControl into a standard WPF Application.

Please understand that TChart does not paint objects by adding Shapes to a canvas, as can be seen in InitializeCanvas(). Rather, it draws directly to the DrawingContext, as in the code in the OnRender() overload.

Having said that, try as I might I cannot see a difference between the top two rectangles, both derived from System.Windows.Shapes.Shape, one with SnapsToDevicePixels set to true and the other set to false. Even commenting out the call to SnapsToDevicePixels in InitializeOther(), which sets the property on the UserControl itself and which should make the difference obvious, doesn't seem to make a visible difference to me.

On the other hand, there is a case that the rectangle drawn directly to the DrawingContext is blurrier than the other two. This rendering does not seem affected at all by SnapsToDevicePixels. Reading the following:
http://msdn.microsoft.com/en-us/library ... S.85).aspx (sorry, this link is not parsed correctly by this version of phpBB)
makes things clearer as to what is going on here. As it says in that article "To set guidelines on Drawing and DrawingContext objects, the GuidelineSet class is used." Well, at the moment we do not use the GuidelineSet class in TChart. We will definitely look into doing so, however, for future releases.

Posted: Thu Nov 06, 2008 8:08 pm
by 13048566
I was looking into the way that you guys are drawing and think I might have found a solution for it. Here's how I edited your code:

Code: Select all

<UserControl x:Class="WpfControlLibrary1.UserControl1" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Grid>
        <Canvas Name="canvas1" />
    </Grid>
</UserControl>

Code: Select all

namespace WpfControlLibrary1
{
    /// <summary> 
    /// Interaction logic for UserControl1.xaml 
    /// </summary> 
    public partial class UserControl1 : UserControl
    {
        public UserControl1()
        {
            InitializeComponent();
        }

        protected override void OnRender(DrawingContext drawingContext)
        {
            // Uncomment this to remove blurriness.
            // RenderOptions.SetEdgeMode(this, EdgeMode.Aliased);

            drawingContext.DrawRectangle(new SolidColorBrush(Colors.Black), null, new Rect(RenderSize));
            drawingContext.DrawRectangle(new SolidColorBrush(Colors.LightBlue), null, new Rect(RenderSize.Width / 2, RenderSize.Height / 2, 10, 50));
        }
    }
}
I found that when the rectangles in a specific location it is harder to recreate the blurring. If you allow it to move as you resize the window, it is much easier to see, it also seemed easier when the rectangle is smaller.

If you run the changes I made and resize the window, you will see that sometimes the Rectangle gets a little blurry. Using the Vista Magnifier tool, you can really see the difference between when it is blurry and not blurry, it creates a line of gradient pixels between the black and blue.

Uncommenting the RenderOptions.SetEdgeMode(this, EdgeMode.Aliased) line of code makes it show up perfectly. I went back to my original app and added this line of code to the parent of the TChart and it worked perfectly without any changes in the TChart code.

Let me know if you see any problems with this solution.

Thanks,
T-Mac

Posted: Fri Nov 07, 2008 8:01 am
by Chris
T-Mac,
T-Mac wrote: Uncommenting the RenderOptions.SetEdgeMode(this, EdgeMode.Aliased) line of code makes it show up perfectly. I went back to my original app and added this line of code to the parent of the TChart and it worked perfectly without any changes in the TChart code.

Let me know if you see any problems with this solution.
Most certainly not. If the solution works for you, then that's great!

Thank you for putting some of your time in to looking into this problem for us. Your input is most appreciated!

As it happens, I had also run into this solution while searching for information on this issue and had given it a quick run. Again, I could see no visible difference and so rather dismissed it in the hope that the GuidelineSet class would give better results! When I have a little more time to look into this issue more thoroughly then I will evaluate these two options in more detail.

Posted: Fri Nov 07, 2008 3:05 pm
by Chris
T-Mac
Chris wrote:When I have a little more time to look into this issue more thoroughly then I will evaluate these two options in more detail.
Just a quick update, FYI.

I'm now running with the following code:

Code: Select all

  public partial class UserControl1 : UserControl
  {
    public UserControl1()
    {
      InitializeComponent();
      InitializeCanvas();
    }

    private void InitializeCanvas()
    {
      this.SnapsToDevicePixels = false;

      canvas1.Background = new SolidColorBrush(Colors.Black);

      Rectangle rect = new Rectangle();
      rect.Width = 25;
      rect.Height = 25;
      rect.SnapsToDevicePixels = false;
      rect.Fill = new SolidColorBrush(Colors.Yellow);
      rect.Stroke = new SolidColorBrush(Colors.Red);
      Canvas.SetLeft(rect, 10);
      Canvas.SetTop(rect, 10);
      canvas1.Children.Add(rect);

      Rectangle rect1 = new Rectangle();
      rect1.Width = 25;
      rect1.Height = 25;
      rect1.SnapsToDevicePixels = true;
      rect1.Fill = new SolidColorBrush(Colors.Yellow);
      rect1.Stroke = new SolidColorBrush(Colors.Red);
      Canvas.SetLeft(rect1, 150);
      Canvas.SetTop(rect1, 10);
      canvas1.Children.Add(rect1);
    }


    protected override void OnRender(DrawingContext drawingContext)
    {
      RenderOptions.SetEdgeMode(this, EdgeMode.Unspecified);
      drawingContext.DrawRectangle(new SolidColorBrush(Colors.Black), null, new Rect(RenderSize));

      drawingContext.DrawRectangle(new SolidColorBrush(Colors.Yellow), new Pen(new SolidColorBrush(Colors.Red), 1), new Rect(10, 150, 25, 25));
    }
As the code is, I get the following:
1. top left rectangle
Image

2. top right rectangle
Image

3. bottom left rectangle
Image

Changing the EdgeMode to Aliased, I get:
1. bottom left rectangle
Image

From this test I summize:
1. Setting SnapsToDevicePixels in the way I have done doesn't make any difference to the outcome.
2. Setting EdgeMode to Aliased is an improvement, but still does not draw as sharply as the System.Windows.Shapes.Rectangle does.

Do you get similar results on your machine? I wonder if the video card has a bearing on what is rendered here.

Anyhow, at least on my machine, I think I'll have to look at the other option, the use of the GuidelineSet class, to get optimal results. This is a shame, as the use of this class seems somewhat more complicated than a one-liner :)

Posted: Fri Nov 07, 2008 3:36 pm
by Chris
T-Mac,
Chris wrote: Anyhow, at least on my machine, I think I'll have to look at the other option, the use of the GuidelineSet class, to get optimal results. This is a shame, as the use of this class seems somewhat more complicated than a one-liner :)
Changing the OnRender() overload to this:

Code: Select all

    protected override void OnRender(DrawingContext drawingContext)
    {
      RenderOptions.SetEdgeMode(this, EdgeMode.Unspecified);
      drawingContext.DrawRectangle(new SolidColorBrush(Colors.Black), null, new Rect(RenderSize));

      double halfPenWidth = 1.0 / 2.0;
      Rect rect = new Rect(10, 150, 25, 25);

      GuidelineSet guidelines = new GuidelineSet();
      guidelines.GuidelinesX.Add(rect.Left + halfPenWidth);
      guidelines.GuidelinesX.Add(rect.Right + halfPenWidth);
      guidelines.GuidelinesY.Add(rect.Top + halfPenWidth);
      guidelines.GuidelinesY.Add(rect.Bottom + halfPenWidth);

      drawingContext.PushGuidelineSet(guidelines);
      drawingContext.DrawRectangle(new SolidColorBrush(Colors.Yellow), new Pen(new SolidColorBrush(Colors.Red), halfPenWidth * 2), rect);
      drawingContext.Pop();
    }
gives me the following for the bottom left rectangle:
Image

That seems to be exactly the same as what the System.Windows.Shapes.Rectangle gives me. So, I'll try and incorporate this as an option into the WPF chart. A reasonable amount of work to get it up and running for all TChart drawing primitives. Shame really :wink:

Posted: Fri Nov 07, 2008 9:50 pm
by 13048566
That looks pretty good with the GuideLines. I can't create the blurriness on my machine with the EdgeMode set to Aliased, but I have a ridiculously powerful video card on this machine. That might make the difference.

Could you let me know when the GuideLines option becomes available....

Thanks!
T-Mac

Posted: Mon Nov 10, 2008 8:26 am
by Chris
T-Mac,
T-Mac wrote: Could you let me know when the GuideLines option becomes available....
This new feature has now been implemented and will become available in the next maintenance release, due out at the beginning of week 51. You will be able to activate guidelines with the following line of code:

Code: Select all

tChart1.Aspect.UseGuidelines = true;