20

I have encountered this problem while scaling graph, which is drawn over GIS control Greatmap. But a simple experiment persuades me that problems is somewhere deeper in WPF.

Consider simple WPF application:

This is MainWindow.xaml

<Grid>
    <StackPanel>
        <Slider ValueChanged="Size_Changed" Minimum="0" Maximum="300000"/>
        <TextBox x:Name="Value"></TextBox>
    </StackPanel>
    <Canvas>
        <Path x:Name="MyPath" Stroke="Black" StrokeThickness="2" />
    </Canvas>
</Grid>

And this is its code behind

private void Size_Changed(object sender,
        RoutedPropertyChangedEventArgs<double> e)
    {
        if (MyPath == null) return;

        var g = new StreamGeometry();
        using (var s = g.Open())
        {
            var pointA = new Point(0, 200);

            s.BeginFigure(pointA, false, false);

            var pointB = new Point(e.NewValue, 200);

            s.PolyLineTo(new[] {pointB}, true, true);

            Value.Text = $"zoom = {e.NewValue:0.0} ;  pointA = {pointA.X:#,0} ; pointB = {pointB.X:#,0}";
        }

        g.Freeze();

        MyPath.Data = g;
    }

While I drag slider from 0 to 249999 it’s all right. I can see line on my view. But at the very moment slider’s value becomes 250000 – line disappears.

Is there any limitations in WPF ?

mli
  • 420
  • 1
  • 4
  • 10
  • Could it have something to do with the data type you picked? perhaps you could try to use decimal or float variables instead and see if the problem persist. – Dark Templar Feb 11 '19 at 16:40
  • No, surely I use only double. By the way, just now encoutered the very similar thing in Google Earth. I put the path between points 40,40 and 50,50 and than I start zoom in in the middle of this path and at some scale half of the path disappeared. I don't know what technology Google Earth uses but it was fanny. – mli Feb 12 '19 at 06:46
  • 1
    It's actually related to the proportion between thickness and length of your path/poly line. For stroke thickness 3 line collapses at length 375,000; for stroke thickness 4 it collapses at length 500,000 and so on, at 1/125,000 proportion for thickness/length, for a perfect horizontal line, that is. – jsanalytics Feb 13 '19 at 12:54
  • @jsanalytics But why does it collapse? And only with `Point(0, 200)`, with `Point(0.0000001, 200)` it doesn't. – Rekshino Feb 13 '19 at 13:12
  • 3
    Such as the hazards of floating point math. That WPF uses *double* for sizes and locations was wishful thinking from the designers, the backend is still stuck in float land. Where all graphics engines roam. Very well demonstrated by [this question](https://stackoverflow.com/questions/4009588/unprecise-rendering-of-huge-wpf-visuals-any-solutions). – Hans Passant Feb 13 '19 at 14:41
  • [float.Epsilon](https://learn.microsoft.com/en-us/dotnet/api/system.single.epsilon?view=netframework-4.7.2) seems to be the smallest possible number that can be used as x-coordinate for `pointA` as to avoid the problem. `float.Epsilon / 2` (goes to zero), for instance, will crash it, and so will [double.Epsilon](https://learn.microsoft.com/en-us/dotnet/api/system.double.epsilon?view=netframework-4.7.2), obviously. – jsanalytics Feb 13 '19 at 18:51
  • I believe the call to `MilUtility_PathGeometryBounds` in [PathGeometry.cs,849](https://referencesource.microsoft.com/#PresentationCore/Core/CSharp/System/Windows/Media/PathGeometry.cs,849) might have something to do with this. – l33t Feb 14 '19 at 12:34

4 Answers4

3

For some reason, the underlying algorithms (could be Direct2D or it's ancestor) considers the ratio StrokeThickness / Geometry extent and decides the stroke is invisible at some point (1 vs 125000). It's also somewhat mentioned here in this other question: WPF DrawGeometry does not draw stroke.

But the stroke is a brush, so you can trick the system like this, using a custom brush, for example in this case:

// use pen size instead of StrokeThickness
var geo = new GeometryDrawing(null, new Pen(Brushes.Black, 2), 
              new RectangleGeometry(new Rect(0, 0, e.NewValue, 1))); // use desired width for the geometry
var brush = new DrawingBrush(geo);
brush.Stretch = Stretch.None; // use this brush size, don't stretch to stroke thickness
brush.Freeze();
MyPath.Stroke = brush;

// choose a big number (more than desired thickness * 125000) to fool WPF or its friends
MyPath.StrokeThickness = 1000;
Simon Mourier
  • 132,049
  • 21
  • 248
  • 298
  • Nice idea! But what to do with arbitrary (not horizontal or vertical) path ? – mli Feb 15 '19 at 09:40
  • @mli - I haven't thought about it. I don't think there are good fixes in all cases. I've also tested Direct2D with an UWP app + Win2D and I don't see any problem. Note the question was posted also here: https://stackoverflow.com/questions/13731593/horizontal-or-vertical-wpf-lines-limited-to-125-000-pixels there was a bug reported to connect but now connect has gone and I can't find what was said in that bug report. In any case, it shows Microsoft was aware of this, so it's more a by design limit than a bug... – Simon Mourier Feb 15 '19 at 15:04
1

This appears to be related to the issue described in the the following Horizontal or vertical WPF Lines limited to 125,000 pixels?

There appears to be a maximum length to stroke width to length ratio in WPF relating to how the graphics are rendered - which could be dictated by the graphics rendering. It the stroke width is varied the following can be observed:

  • If the stroke width is 1 : max length allowed is 125,000
  • If the stroke width is 2 : max length allowed is 250,000
  • If the stroke width is 3 : the max length allowed is 375,000
TheMachinist
  • 298
  • 2
  • 10
0

I see no limitations in WPF. For me it looks like a WPF bug in rendering.

I can only offer a workaround, but I don't know why it causes the issue after 250000. I don't know whether this workaround applicable to your real case. The idea is to use LayoutTransform to draw a horizontal line:

private void Size_Changed(object sender, RoutedPropertyChangedEventArgs<double> e)
{
    if (MyPath == null) return;

    var g = new StreamGeometry();
    using (var s = g.Open())
    {
        var pointA = new Point(0, 200);

        s.BeginFigure(pointA, false, false);

        var pointB = new Point(1, 200);

        s.PolyLineTo(new[] { pointB }, true, true);

        MyPath.LayoutTransform = new ScaleTransform(e.NewValue, 1);

        Value.Text = $"zoom = {e.NewValue:0.0} ;  pointA = {pointA.X:#,0} ; pointB = {pointB.X:#,0}";
    }

    g.Freeze();

    MyPath.Data = g;
}
Rekshino
  • 6,954
  • 2
  • 19
  • 44
0

Very exciting issue, I guess it must be a bug. If you changed the pointA as below:

var pointA = new Point(1, 200);

then it would work ... :-)

Chinh Nguyen
  • 583
  • 1
  • 7
  • 14