Click here to Skip to main content
15,907,906 members
Please Sign up or sign in to vote.
1.00/5 (1 vote)
Goal: To clip and draw the same geometry.
Problem: Clipped geometry is not equal to drawn geometry.
Notes: Shapes are used and EdgeMode is Aliased.
Warning: The next code lines doesn't fix the problem:
C#
RenderOptions.SetBitmapScalingMode(canvas, BitmapScalingMode.NearestNeighbor);
canvas.SnapsToDevicePixels = true;
canvas.UseLayoutRounding = true;

Question: How to solve the problem?
Additional question: What does clip take as region?
Code:
C#
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        this.Loaded += delegate
        {
            this.KeyDown += MainWindow_KeyDown;

            canvas = new Canvas();
            this.Content = canvas;
            canvas.HorizontalAlignment = System.Windows.HorizontalAlignment.Left;
            canvas.VerticalAlignment = System.Windows.VerticalAlignment.Top;
            canvas.Width = Math.Round(500d);
            canvas.Height = Math.Round(500d);

            RenderOptions.SetEdgeMode(canvas, EdgeMode.Aliased);

            canvas.Background = Brushes.Black;
            canvas.MouseLeftButtonDown += (sender, e) =>
            {
                down = true;

                lines = new Polyline();
                lines.Stroke = Brushes.White;
                canvas.Children.Add(lines);
            };

            canvas.MouseRightButtonDown += (sender, e) =>
            {
                RenderOptions.SetEdgeMode(canvas, EdgeMode.Unspecified);
            };
            canvas.MouseUp += delegate
            {
                RenderTargetBitmap rtb = new RenderTargetBitmap((int)Math.Round(500d), (int)Math.Round(500d), 96, 96, PixelFormats.Default);
                rtb.Render(canvas);
                canvas.Children.Clear();
                ImageBrush image = new ImageBrush();
                image.ImageSource = rtb;
                canvas.Background = image;

                down = false;
                thickness = 1;
            };
            canvas.MouseMove += canvas_MouseMove;
        };
    }

    Canvas canvas;
    Polyline lines;
    List<LineGeometry> linegeoms = new List<LineGeometry>();
    bool down;
    double thickness = 1;

    void canvas_MouseMove(object sender, MouseEventArgs e)
    {
        if (down)
            lines.Points.Add(new Point(e.GetPosition(canvas).X, e.GetPosition(canvas).Y));
    }

    int add = 5;

    void MainWindow_KeyDown(object sender, KeyEventArgs e)
    {
        if (e.Key == Key.Up)
            thickness += add;

        if (e.Key == Key.Down)
            thickness -= add;

        if (thickness <= 0)
            thickness = 1;

        Light();
    }

    void Light()
    {
        canvas.Children.Clear();
        Geometry linesgeom = this.lines.RenderedGeometry;

        Color color = Colors.White;
        double add = 255 / (thickness);
        color.A = 127;

        for (double i = thickness; i > 0; )
        {
            Geometry geom1 = linesgeom.GetWidenedPathGeometry(new Pen(Brushes.Transparent, i--));
            Geometry geom2 = linesgeom.GetWidenedPathGeometry(new Pen(Brushes.Transparent, i));
            CombinedGeometry combined = new CombinedGeometry(GeometryCombineMode.Exclude, geom1, geom2);

            Path path = new Path();
            path.Data = geom1;
            path.Fill = new SolidColorBrush(color);
            path.Clip = combined;

            canvas.Children.Add(path);
        }
    }
}
Posted
Updated 31-Jul-15 7:36am
v11

1 solution

Answered by Mike Danes on msdn:
https://social.msdn.microsoft.com/Forums/en-US/cf584e19-af0d-42d4-8aa5-bc9a2d06be3a/why-clipped-geometry-is-not-equal-to-drawn-geometry[^]

But it's not necessarily related to drawing, geometry operations like Exclude play a part in this. Try the following example:
C#
void Light()
{
    canvas.Children.Clear();
    Geometry linesgeom = this.lines.RenderedGeometry;

    for (double i = thickness; i > 0; )
    {
        var geom1 = linesgeom.GetWidenedPathGeometry(new Pen(Brushes.Transparent, i--));
        var geom2 = linesgeom.GetWidenedPathGeometry(new Pen(Brushes.Transparent, i));
        var combined = Geometry.Combine(geom1, geom2, GeometryCombineMode.Exclude, null);
        combined = Geometry.Combine(combined, geom1, GeometryCombineMode.Exclude, null);

        if (!combined.IsEmpty())
        {
            canvas.Children.Add(new Path {
                Data = combined,
                Stroke = Brushes.Blue,
                Fill = Brushes.Red
            });
        }
    }
}

In this example geom2 is exclude from geom1 and then geom1 is excluded from the result. Theoretically the final result should be empty and if you try this with a straight line you'll see that nothing is displayed. But as soon you try it with a more complex line blue lines start to show up. Those lines reflect the inexact result of geometric operations.

Think about it, any such operations will require computing line intersection points. The coordinates of the intersection point of 2 lines are rational numbers even if the points that determine those 2 lines have integer coordinates. For example, lines (0,0)-(3,1) and (1,0)-(1,1) intersect at (1,1/3). That 1/3 cannot be represented exactly using standard numeric types like int, float and double.

It's technically possible to use custom rational types to perform such computations but in the end you still have to convert to float (because that's what DirectX understands) and ultimately to integer (because screen pixels have integer coordinates). Somewhere along the way some inaccuracies will sneak in.
 
Share this answer
 

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900