Bug in System.Drawing.Graphics.DrawLines

G

Guest

Hi,

I think I've found a bug in the System.Drawing.Graphic.DrawLines method.

The code below fills a list with a number of points and then draws these
points using DrawLines and then using DrawLine within a loop.

The DrawLines() method inserts a superious line that that is not drawn using
the DrawLine() function.

This bug was initially found in a ASP.NET application but has been
replicated in a Windows Form application.


Regards,
Dave.



private void Form1_Paint(object sender, PaintEventArgs e)
{
Graphics g = e.Graphics;

List<System.Drawing.PointF> pointsToDraw = new List<System.Drawing.PointF>();

Pen penCyan = new Pen(Brushes.Cyan, 5);
Pen penRed = new Pen(Brushes.Red, 5);

pointsToDraw.Clear();
pointsToDraw.Add(new PointF((float)513.4, (float)311.3));
pointsToDraw.Add(new PointF((float)517.6, (float)314));
pointsToDraw.Add(new PointF((float)518.5, (float)314.6));
pointsToDraw.Add(new PointF((float)526.4, (float)319.8));
pointsToDraw.Add(new PointF((float)529.1, (float)321.5));
pointsToDraw.Add(new PointF((float)529.25, (float)321.6));
pointsToDraw.Add(new PointF((float)529.25, (float)321.85));
pointsToDraw.Add(new PointF((float)531.6, (float)329.7));
pointsToDraw.Add(new PointF((float)534, (float)337.6));
pointsToDraw.Add(new PointF((float)533.95, (float)337.85));
pointsToDraw.Add(new PointF((float)533.85, (float)338));
pointsToDraw.Add(new PointF((float)533.6, (float)338.4));
pointsToDraw.Add(new PointF((float)529.9, (float)343.75));
pointsToDraw.Add(new PointF((float)526.2, (float)349.15));
pointsToDraw.Add(new PointF((float)524.9, (float)352.55));
pointsToDraw.Add(new PointF((float)524.8, (float)353));
pointsToDraw.Add(new PointF((float)524.85, (float)353.6));
pointsToDraw.Add(new PointF((float)525, (float)355.25));
pointsToDraw.Add(new PointF((float)525.1, (float)355.95));
pointsToDraw.Add(new PointF((float)525.55, (float)364));
pointsToDraw.Add(new PointF((float)525.55, (float)364.05));
pointsToDraw.Add(new PointF((float)525.95, (float)371.55));
pointsToDraw.Add(new PointF((float)526, (float)371.8));
pointsToDraw.Add(new PointF((float)526.05, (float)372.1));
pointsToDraw.Add(new PointF((float)526.05, (float)372.25));
pointsToDraw.Add(new PointF((float)526.1, (float)372.4));
pointsToDraw.Add(new PointF((float)526.45, (float)373.2));
pointsToDraw.Add(new PointF((float)526.45, (float)373.3));
pointsToDraw.Add(new PointF((float)526.5, (float)373.45));
pointsToDraw.Add(new PointF((float)526.45, (float)373.55));
pointsToDraw.Add(new PointF((float)526.4, (float)373.6));
pointsToDraw.Add(new PointF((float)519.4, (float)381.5));
pointsToDraw.Add(new PointF((float)519.6, (float)381.3)); // <- this point
causes the problem
pointsToDraw.Add(new PointF((float)514.75, (float)387.2));
pointsToDraw.Add(new PointF((float)514.5, (float)387.55));
pointsToDraw.Add(new PointF((float)514.45, (float)387.8));
pointsToDraw.Add(new PointF((float)514.4, (float)388));
pointsToDraw.Add(new PointF((float)514.3, (float)388.25));
pointsToDraw.Add(new PointF((float)514.2, (float)388.45));
pointsToDraw.Add(new PointF((float)514.25, (float)388.6));
pointsToDraw.Add(new PointF((float)514.3, (float)388.8));
pointsToDraw.Add(new PointF((float)516.3, (float)391.35));
pointsToDraw.Add(new PointF((float)518.5, (float)394.15));
pointsToDraw.Add(new PointF((float)524.45, (float)401.8));
pointsToDraw.Add(new PointF((float)530.45, (float)409.5));
pointsToDraw.Add(new PointF((float)534.15, (float)415.45));
pointsToDraw.Add(new PointF((float)537.85, (float)421.4));
pointsToDraw.Add(new PointF((float)538.05, (float)421.8));
pointsToDraw.Add(new PointF((float)541.9, (float)431.15));
pointsToDraw.Add(new PointF((float)541.95, (float)431.6));
pointsToDraw.Add(new PointF((float)542, (float)431.9));
pointsToDraw.Add(new PointF((float)542, (float)432.3));
pointsToDraw.Add(new PointF((float)542, (float)432.65));

g.DrawLines(penCyan, pointsToDraw.ToArray());

for (int loop = 1; loop < pointsToDraw.Count; loop++)
{
g.DrawLine(penRed, pointsToDraw[loop - 1], pointsToDraw[loop]);
}
}
 
P

Peter Duniho

I think I've found a bug in the System.Drawing.Graphic.DrawLines method.

I'm not so sure.
The code below fills a list with a number of points and then draws these
points using DrawLines and then using DrawLine within a loop.

The DrawLines() method inserts a superious line that that is not drawn
using the DrawLine() function.

As near as I tell, it's not so much a "spurious" line as it a consequence
of trying to fill in the pen in between the endpoints you gave DrawLines().

I took your code and added some UI to allow me to modify how much of the
lines are drawn and in what order, as well as scale and move the drawing
within the form, so that I could see things better (on my computer
monitor, the absolute positions you've provided are fairly small and well
to the right and bottom of the application window). (New code available
on request...I wrote it mostly as an exercise in learning, and have no
idea if anyone else would want it).

I noticed a couple of things. The first is that the problem only happens
when you provide DrawLines() with more than two points. If it only draws
a single line between two endpoints, it works just fine. The second is
that the point that is "problematic" doesn't follow the sequence of the
lines. That is, within the overall line, it doubles back.

I'm no expert in the math that DrawLines() uses to deal with fitting a
curve onto the sequence of points you've provided it, but given that it
does have to do something along those lines to implement its output, it's
not surprising to me that when there's a sharp discontinuity in the input
data, you get some anomalous results. I would not use DrawLines() with
data that I do not know ahead of time to represent a reasonably smooth,
continuous function.

For what it's worth, if you move the "problematic" point by swapping it
with the previous point in the array, the line draws just fine both ways.
Doing that rectifies the region of discontinuity in your input data, and
allows DrawLines() to produce smoother results.

Pete
 
G

Guest

Hi Peter, Thanks for looking at this.

According to the documentationon MSDN (quoted below) there is no mention of
curve fitting, and indeed there shouldn't be as I want to plot the exact
points and not a smoothed curve. The data points are actually extracted from
a route that is being overlayed onto a map.

Quote from MSDN....

"This method draws a series of lines connecting an array of ending points.
The first two points in the array specify the first line. Each additional
point specifies the end of a line segment whose starting point is the ending
point of the previous line segment."


Even if a line segment doubles back on itself, DrawLines() should handle
this correctly.


Regards,
Dave.
 
L

Linda Liu [MSFT]

Hi Dave,

I performed a test based on your sample code and did see the problem on my
side.

If I change the size of the 'penCyan' in your sample code to 1, the problem
doesn't exist.

I think the key point that causes the problem is that the "problematic"
point doesn't follow the sequence of the line, just as Peter mentioned.

It seems that there's an error when there's such a "problematic" point in
the point array and this error is enlarged when the size of the pen used to
draw the lines becomes bigger.

A workaround to this issue is to diminish the disorder in the point array.
To do this, you may simply swap the "problematic" point with its previous
piont the list.

If you have any concern, please feel free to let me know.

Sincerely,
Linda Liu
Microsoft Online Community Support

==================================================
Get notification to my posts through email? Please refer to
http://msdn.microsoft.com/subscriptions/managednewsgroups/default.aspx#notif
ications.

Note: The MSDN Managed Newsgroup support offering is for non-urgent issues
where an initial response from the community or a Microsoft Support
Engineer within 1 business day is acceptable. Please note that each follow
up response may take approximately 2 business days as the support
professional working with you may need further investigation to reach the
most efficient resolution. The offering is not appropriate for situations
that require urgent, real-time or phone-based interactions or complex
project analysis and dump analysis issues. Issues of this nature are best
handled working with a dedicated Microsoft Support Engineer by contacting
Microsoft Customer Support Services (CSS) at
http://msdn.microsoft.com/subscriptions/support/default.aspx.
==================================================

This posting is provided "AS IS" with no warranties, and confers no rights.
 
P

Peter Duniho

According to the documentationon MSDN (quoted below) there is no mention
of curve fitting, and indeed there shouldn't be as I want to plot the
exact
points and not a smoothed curve.

You can tell just by looking at the output that *some* sort of curve
fitting must take place. You'll note that when you draw simply a sequence
of line segments, there are gaps in the drawing graphics. This is because
Windows just takes the pen of the width you specify, and extends a line
using that pen from the start point to the finish point. If you then draw
another line in a different direction, the new line segment heads off with
the end not matching up exactly with the first line segment.

Contrast that to the output of DrawLines(), which makes sure that every
joint between each line segment is smooth and filled in. A very simple
way to accomplish this is to put circles at every joint, but then you get
rounded corners everywhere, which is not necessarily what is desired when
drawing lines. Instead, Windows fills in with straight connections
between the gaps.

As I said, I don't know the specifics of the math that's going into
calculating what pixels need to get set, but it's clear to me that it is a
non-trivial calculation and given that, it's not too surprising that when
you provide data that has tight, reversing corners, the output gets a
little odd.
The data points are actually extracted from
a route that is being overlayed onto a map.

Why does the route have a sharp reversal in it then?
[...]
Even if a line segment doubles back on itself, DrawLines() should handle
this correctly.

Sometimes, you have to accept that the built-in functionality is there to
provide basic needs, and if your needs are more complex than that, you
have to "roll your own". This seems to me to be such a situation. I have
seen situations in which an actual bug has been reported and Microsoft has
acknowledged it as such, but I would be surprised if they would consider
this to be an actual bug. I suspect that they would respond by saying
that the algorithm in DrawLines() is working as designed, and that your
scenario is a known limitation, and not one that they feel warrants
complicating the code further to solve.

For what it's worth, you've got at least a couple of reasonable
workarounds, IMHO:

* Draw the lines yourself. It's not hard...you can implement your own
DrawLines() easily as long as you don't mind rounded corners.

* Do some data smoothing before you pass your points off to
DrawLines(). Throw out any point that involves a course reversal of more
than some threshold amount (135 degrees, for example). That sort of
pre-processing should eliminate the kinds of discontinuities that give
DrawLines() problems, and will allow you to continue using DrawLines() and
take advantage of its other positive benefits.

Pete
 
G

Guest

Linda,

I can't go swapping the data points round! This is drawing a pipeline route
onto a map. Perhaps the pipelines should be relaid to match the microsoft
drawing function!


Since it does appear that this a bug in the DrawLines() code - will it be
raised as a bug report and will it get fixed?

Regards,
Dave.
 
P

Peter Duniho

I can't go swapping the data points round! This is drawing a pipeline
route
onto a map. Perhaps the pipelines should be relaid to match the
microsoft
drawing function!

What kind of pipeline makes two immediate 180-degree turns like that?
Every bend in a pipeline is drag, limiting flow. It's really not a good
idea to design a pipeline like that. So yes, maybe the pipelines *should*
be relaid.

;)
Since it does appear that this a bug in the DrawLines() code - will it be
raised as a bug report and will it get fixed?

So far, you are the only person saying it's a bug. Personally, I'd call
it a "limitation". You're feeding DrawLines() data that it's just not
designed to deal with.

I can't speak for Microsoft, but I doubt that a bug report will get filed
unless you do it, and even if you do it, I doubt anything will be done to
change the behavior of DrawLines().

But good luck with that. :)

Pete
 
L

Linda Liu [MSFT]

Hi Dave,

Thank you for your prompt response.

I do more research on this issue and get more information on the
Graphics.DrawLines method.

The reason of this issue should not be the calculation error, but the
drawing style in the internal implementation of the DrawLines method.

When connecting two segments of lines, the DrawLines method gets the
intersection of the two lines at the conjunction. The more sharp the angle
formed by the two lines and more big the size of the pen used to draw the
lines, the more large the intersection part.

I will illuminate this with a sample. The following is the Paint event
handler of a form.

void Form1_Paint(object sender, PaintEventArgs e)
{
Point[] points = new Point[3];
points[0]= new Point(30,40);
points[1] = new Point(50,30);
points[2] = new Point(20,60);
// draw the lines with a pen of size 10
using (Pen p = new Pen(Color.Black,10))
{
e.Graphics.DrawLines(p,points);
}
// draw the lines with a pen of size 1
using (Pen p = new Pen(Color.White, 1))
{
e.Graphics.DrawLines(p, points);
}
}

Run the application and you should see the conjunction of the two lines
drawn by the black color is larger than that drawn by the white color.

Due to this characteristic of the DrawLines method, I suggest that you use
DrawLine method instead.

Hope I make some clarifications.

If you have any question, please feel free to let me know.

Sincerely,
Linda Liu
Microsoft Online Community Support
 
G

Guest

Linda,

Thank you for your reply. However a collegue of mine has found the solution.

Setting the pen's lineJoin property as below fixes the problem.

penCyan.LineJoin = System.Drawing.Drawing2D.LineJoin.MiterClipped;

Regards,
Dave.
 
L

Linda Liu [MSFT]

Hi Dave,

Thank you for your feedback on how you successfully solved the problem!

Sorry that I wasn't aware of the LineJoin property of the Pen class.

I perform a test setting the LineJoin property of the penCyan to
MiterClipped and see it works. I also find it works when I set this
property to Bevel or Round.

If you have any other questions in the future, please don't hesitate to
contact us.

Sincerely,
Linda Liu
Microsoft Online Community Support
 
L

Linda Liu [MSFT]

Hi Dave,

IMHO, I don't think the description for the Miter enumeration value in MSDN
document is incorrect.

The document says that this produces a sharp corner or a clipped corner,
DEPENDING on whether the lengh of the miter exceeds the miter limit.

Nevertheless, it is always safe to use the Bevel enumeration value, which
always reproduces a beveled join.


Sincerely,
Linda Liu
Microsoft Online Community Support
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Similar Threads


Top