使用 WinForms 绘制宽线时出现 OutOfMemoryException
OutOfMemoryException on drawing wide lines with WinForms
这个太疯狂了。我只是在 OnPaint
处理程序中绘制了几千行。当pen.Width <= 1
,或者屏幕上的行数不多时,都没有问题。
好的,我画了比例尺地图。线宽随地图缩放。当我缩放一些地图时,我得到 OutOfMemoryException
。为什么?!
当我将 pen.Width
设置为 1 时 - 没问题。当我将它设置为对应轨道宽度时 - 有些地图绘制正常,有些地图在某些缩放级别抛出异常。
这是怎么回事?它与实际内存使用无关。我已经仔细检查过了。
顺便说一句,我设置的 pen.Width
发生时大约是 2。
代码看起来像 foreach (...) g.DrawLine(...)
- 在绘制了几百行后它崩溃了。
如果找不到解决方案,我将不得不放弃会大大降低演示质量的线宽缩放。或者我可以做一个丑陋的 hack 来尝试捕获这个异常(如果它可以被捕获)...
注意:我不使用任何位图。我不对大型阵列进行操作。我在绘图期间不打开任何文件。有一个矢量数组(大约 10k 个元素),我只是将它们全部绘制为单独的线,使用不同的笔绘制不同的地图对象。当我不触摸 pen.Width
- 没有异常发生。当我设置 pen.Width 时 - 一些地图在所有缩放级别下都可以正确显示,但有些地图会抛出异常。 5支笔在进入绘图循环前在OnPaint
事件中创建,退出循环后妥善处理。在绘制每条线之前设置其宽度。
我试图将线坐标限制为仅在视口中实际可见的坐标。这是多余的,因为 Graphics
对象会自己处理它。当然没有用。我在一些较小的 window 尺寸上试过 - 没有帮助。我试图打开和关闭双缓冲。没有快乐。我没主意了。
编辑:
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();
}
看到这里丑陋的骇客了吗?它完美地工作,我什至看不到哪些曲线被线条替换了。显然 g.DrawBezier
抛出异常。我不喜欢丑陋的黑客...
这是解决方案,感谢@LarsTech的提示:
private void DrawMap(PaintEventArgs e) {
var pens = new[] { // TODO: draw layers instead
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; // (TODO: limiting vectors to visible ones)
var tr = GetTransformation(); // gets scale and translation for points
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);
var b1c = Math.Abs(p1.X - p2.X) >= 0.1f || Math.Abs(p1.Y - p2.Y) > 0.1f;
var b2c = Math.Abs(p3.X - p4.X) >= 0.1f || Math.Abs(p3.Y - p4.Y) > 0.1f;
if (b1c && b2c) g.DrawBezier(pen, p1, p2, p3, p4); else g.DrawLine(pen, p1, p4);
}
}
foreach (var p in pens) p.Dispose();
}
在他的 linked 回答中我们读到:
That's a bug with the pen and the widen method. Make sure your
startpoint of the path and the endpoint of the path are not the same.
是的,.NET 中的一个错误,已报告给 Microsoft,但显然尚未修复。这里显示的是贝塞尔曲线,看起来太像直线了 ;)
我想零长度的行可能会抛出类似的异常。
注意我检查点坐标之间的距离是大于0.1f,不是0!这一点很重要。如果点彼此足够接近,则抛出异常,而不仅仅是当它们相等时。我可以计算点之间的距离,但出于性能原因,最好不要计算。
对每条曲线和直线都进行这样的检查对性能没有好处 - 但它似乎比捕获错误异常要好。检查可能会被优化一点,或者移到 "scale changed" 处理程序中。
顺便说一句:我的代码中的 GetTransformation()
方法只是为所有点带来比例、X 和 Y 偏移。如果您想知道为什么我不使用内置转换并手动执行 - 这是因为内置转换不适用于双缓冲。 .NET 中的另一个错误或只是功能?没有双缓冲画图速度慢得要命,所以这里不得不用
这个太疯狂了。我只是在 OnPaint
处理程序中绘制了几千行。当pen.Width <= 1
,或者屏幕上的行数不多时,都没有问题。
好的,我画了比例尺地图。线宽随地图缩放。当我缩放一些地图时,我得到 OutOfMemoryException
。为什么?!
当我将 pen.Width
设置为 1 时 - 没问题。当我将它设置为对应轨道宽度时 - 有些地图绘制正常,有些地图在某些缩放级别抛出异常。
这是怎么回事?它与实际内存使用无关。我已经仔细检查过了。
顺便说一句,我设置的 pen.Width
发生时大约是 2。
代码看起来像 foreach (...) g.DrawLine(...)
- 在绘制了几百行后它崩溃了。
如果找不到解决方案,我将不得不放弃会大大降低演示质量的线宽缩放。或者我可以做一个丑陋的 hack 来尝试捕获这个异常(如果它可以被捕获)...
注意:我不使用任何位图。我不对大型阵列进行操作。我在绘图期间不打开任何文件。有一个矢量数组(大约 10k 个元素),我只是将它们全部绘制为单独的线,使用不同的笔绘制不同的地图对象。当我不触摸 pen.Width
- 没有异常发生。当我设置 pen.Width 时 - 一些地图在所有缩放级别下都可以正确显示,但有些地图会抛出异常。 5支笔在进入绘图循环前在OnPaint
事件中创建,退出循环后妥善处理。在绘制每条线之前设置其宽度。
我试图将线坐标限制为仅在视口中实际可见的坐标。这是多余的,因为 Graphics
对象会自己处理它。当然没有用。我在一些较小的 window 尺寸上试过 - 没有帮助。我试图打开和关闭双缓冲。没有快乐。我没主意了。
编辑:
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();
}
看到这里丑陋的骇客了吗?它完美地工作,我什至看不到哪些曲线被线条替换了。显然 g.DrawBezier
抛出异常。我不喜欢丑陋的黑客...
这是解决方案,感谢@LarsTech的提示:
private void DrawMap(PaintEventArgs e) {
var pens = new[] { // TODO: draw layers instead
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; // (TODO: limiting vectors to visible ones)
var tr = GetTransformation(); // gets scale and translation for points
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);
var b1c = Math.Abs(p1.X - p2.X) >= 0.1f || Math.Abs(p1.Y - p2.Y) > 0.1f;
var b2c = Math.Abs(p3.X - p4.X) >= 0.1f || Math.Abs(p3.Y - p4.Y) > 0.1f;
if (b1c && b2c) g.DrawBezier(pen, p1, p2, p3, p4); else g.DrawLine(pen, p1, p4);
}
}
foreach (var p in pens) p.Dispose();
}
在他的 linked 回答中我们读到:
That's a bug with the pen and the widen method. Make sure your startpoint of the path and the endpoint of the path are not the same.
是的,.NET 中的一个错误,已报告给 Microsoft,但显然尚未修复。这里显示的是贝塞尔曲线,看起来太像直线了 ;)
我想零长度的行可能会抛出类似的异常。
注意我检查点坐标之间的距离是大于0.1f,不是0!这一点很重要。如果点彼此足够接近,则抛出异常,而不仅仅是当它们相等时。我可以计算点之间的距离,但出于性能原因,最好不要计算。
对每条曲线和直线都进行这样的检查对性能没有好处 - 但它似乎比捕获错误异常要好。检查可能会被优化一点,或者移到 "scale changed" 处理程序中。
顺便说一句:我的代码中的 GetTransformation()
方法只是为所有点带来比例、X 和 Y 偏移。如果您想知道为什么我不使用内置转换并手动执行 - 这是因为内置转换不适用于双缓冲。 .NET 中的另一个错误或只是功能?没有双缓冲画图速度慢得要命,所以这里不得不用