.NET Forum / .NET Framework / New Users / May 2007
Bug in System.Drawing.Graphics.DrawLines
|
|
Thread rating:  |
mfr - 23 May 2007 16:05 GMT 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]); } }
Peter Duniho - 23 May 2007 21:42 GMT > 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
mfr - 24 May 2007 08:17 GMT 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.
Peter Duniho - 24 May 2007 09:31 GMT > 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
Linda Liu [MSFT] - 24 May 2007 09:04 GMT 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.
mfr - 24 May 2007 10:12 GMT 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.
> Hi Dave, > [quoted text clipped - 40 lines] > > This posting is provided "AS IS" with no warranties, and confers no rights. Peter Duniho - 24 May 2007 10:47 GMT > 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
Linda Liu [MSFT] - 25 May 2007 05:30 GMT 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
mfr - 30 May 2007 09:45 GMT 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.
> Hi Dave, > [quoted text clipped - 45 lines] > Linda Liu > Microsoft Online Community Support Linda Liu [MSFT] - 30 May 2007 10:26 GMT 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
mfr - 30 May 2007 10:46 GMT As a point to note - the HELP for the line join property is incorrect.
https://msdn2.microsoft.com/en-us/library/system.drawing.drawing2d.linejoin.aspx
The description for the Miter incorrectly says
"Specifies a mitered join. This produces a sharp corner or a clipped corner, depending on whether the length of the miter exceeds the miter limit."
whereas it should read, something like.....
"Specifies a mitered join. This produces a sharp corner."
Regards, Dave.
> Hi Dave, > [quoted text clipped - 12 lines] > Linda Liu > Microsoft Online Community Support Linda Liu [MSFT] - 31 May 2007 10:42 GMT 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
Free MagazinesGet these publications absolutely FREE for up to 12 months. There are no hidden fees and no obligation. Simply choose a title, complete the application form and submit it. Read more ...
|
|
|