This one is crazy. I just draw a couple of thousands of lines in OnPaint
handler. There is no problem, when pen.Width <= 1
, or when there aren't many lines on the screen.
OK, I draw a scaled map. Line width scales with the map. When I zoom SOME maps, I get OutOfMemoryException
. WHY?!
When I set pen.Width
to 1 - no problem. When I set it to correspond tracks widths - some maps draw OK, some throw the exception AT CERTAIN ZOOM LEVELS.
What's going on? It's has NOTHING to do with actual memory usage. I've double checked this.
BTW, the pen.Width
I set is around 2 when it happens.
The code looks like foreach (...) g.DrawLine(...)
- and it crashes after drawing a couple of hundreds of lines.
If I won't find solution to this, I'll have to drop line width scaling which would greatly degrade the quality of presentation. Or I can do an ugly hack trying to catch this exception (if it can be caught)...
NOTE: I don't use any bitmaps. I don't operate on huge arrays. I don't open any files during drawing. There is an array of vectors (about 10k elements), I just draw all of them as separate lines, using some different pens for various map objects. When I don't touch pen.Width
- no exception occurs. When I set pen.Width - some maps are displayed correctly with all zoom levels, but some throw the exception. The 5 pens are created in OnPaint
event before entering the drawing loop and are properly disposed after exiting the loop. Before drawing each line its width is set.
I tried to limit line coordinates to only ones actually visible in viewport. It's redundant, since Graphics
object takes care of it by itself. Of course it didn't help. I tried it on some smaller window sizes - didn't help. I tried to switch double buffering on and off. No joy. I'm out of ideas.
EDIT:
private void DrawMap(PaintEventArgs e) {
var pens = new[] {
new Pen(TrackColor),
new Pen(SwitchColor),
new Pen(RoadColor),
new Pen(RiverColor),
new Pen(CrossColor)
};
var b = Splines.Bounds;
var g = e.Graphics;
var f = true; // OutFull;
var tr = GetTransformation();
float ts = tr[0], tx = tr[1], ty = tr[2];
TrackSpline[] visible = !f ? Splines.GetSubset(ts, _Viewport) : null;
var ct = f ? Splines.Count : visible.Length;
for (int i = 0; i < ct; i++) {
TrackSpline s = f ? Splines[i] : visible[i];
var pen = pens[s.T];
pen.Width = ts * s.W;
if (ts < 0.01 || s.L) {
var p1 = new PointF(s.A.X * ts + tx, s.A.Y * ts + ty);
var p2 = new PointF(s.D.X * ts + tx, s.D.Y * ts + ty);
g.DrawLine(pen, p1, p2);
} else {
var p1 = new PointF(s.A.X * ts + tx, s.A.Y * ts + ty);
var p2 = new PointF(s.B.X * ts + tx, s.B.Y * ts + ty);
var p3 = new PointF(s.C.X * ts + tx, s.C.Y * ts + ty);
var p4 = new PointF(s.D.X * ts + tx, s.D.Y * ts + ty);
try {
g.DrawBezier(pen, p1, p2, p3, p4);
} catch (OutOfMemoryException) {
g.DrawLine(pen, p1, p4);
}
}
}
foreach (var p in pens) p.Dispose();
}
See the ugly hack here? It works flawlessly and I don't even see which curves are replaced with lines. Obviously g.DrawBezier
throws the exception. I don't like ugly hacks...
Here's the solution, thanks to hint from @LarsTech:
In his linked answer we read:
Yes, a bug in .NET, reported to Microsoft and apparently not fixed yet. And here it shows with Bezier curves, which look too much like straight lines ;)
I guess zero-lenght lines could throw similar exception.
Note that I check the distance between points coordinates is greater than 0.1f, not 0! It's important. The exception is thrown if the points are close enough to each other, not only when they equal. I could calculate the distance between points, but for performance reasons it's better not to.
It's not good for performance to have such checks for every curve and line - but it seems somehow better than catching the false exception. The check could be probably optimized a little, or moved into "scale changed" handler.
BTW:
GetTransformation()
method in my code just brings scale, X and Y offsets for all points. If you wonder why I don't use built in transformation and do it manually - it's because built in transformation doesn't work with double buffering. Another bug in .NET or just feature? Without double buffering drawing is painfully slow, so it has to be used here.