【问题标题】:OutOfMemoryException on drawing wide lines with WinForms使用 WinForms 绘制宽线时出现 OutOfMemoryException
【发布时间】:2015-01-16 00:17:48
【问题描述】:

这个太疯狂了。我只是在OnPaint 处理程序中画了几千行。没有问题,pen.Width <= 1,或者屏幕上没有多少行。

好的,我画了一张比例尺的地图。线宽随地图缩放。当我缩放一些地图时,我得到OutOfMemoryException。为什么?!

当我将pen.Width 设置为 1 - 没问题。当我将其设置为对应的轨道宽度时 - 有些地图绘制正常,有些则在某些缩放级别抛出异常。

发生了什么事?它与实际内存使用无关。我已经仔细检查过。

顺便说一句,我设置的 pen.Width 在它发生时约为 2。

代码看起来像foreach (...) g.DrawLine(...) - 在画了几百行后它崩溃了。

如果我找不到解决方案,我将不得不放弃线宽缩放,这会大大降低演示质量。或者我可以做一个丑陋的黑客尝试捕捉这个异常(如果它可以被捕捉到的话)......

注意:我不使用任何位图。我不对巨大的数组进行操作。我在绘图期间不打开任何文件。有一个向量数组(大约 10k 个元素),我只是将它们全部绘制为单独的线,使用一些不同的笔来绘制各种地图对象。当我不触摸pen.Width 时 - 不会发生异常。当我设置 pen.Width - 一些地图在所有缩放级别上都正确显示,但有些则抛出异常。这 5 支笔在进入绘图循环之前在OnPaint 事件中创建,并在退出循环后正确处理。在绘制每条线之前设置其宽度。

我试图将线坐标限制为仅在视口中实际可见的坐标。这是多余的,因为 Graphics 对象会自己处理它。当然没有帮助。我在一些较小的窗口尺寸上尝试过 - 没有帮助。我试图打开和关闭双缓冲。没有喜悦。我没有想法。

编辑:

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 抛出异常。我不喜欢丑陋的黑客...

【问题讨论】:

标签: c# .net winforms graphics


【解决方案1】:

这是解决方案,感谢@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 回答中,我们读到:

这是笔和加宽方法的错误。确保您的 路径的起点和终点不一样。

是的,.NET 中的一个错误,已报告给 Microsoft,但显然尚未修复。在这里它显示了贝塞尔曲线,看起来太像直线了;)

我猜零长度的行可能会引发类似的异常。

请注意,我检查点坐标之间的距离是否大于 0.1f,而不是 0!这一点很重要。如果这些点彼此足够接近,则抛出异常,而不仅仅是当它们相等时。我可以计算点之间的距离,但出于性能原因,最好不要这样做。

对每条曲线和直线进行此类检查对性能不利 - 但它似乎比捕获错误异常更好。检查可能会进行一些优化,或移至“缩放更改”处理程序。

顺便说一句:我的代码中的GetTransformation() 方法只是为所有点带来比例、X 和 Y 偏移。如果您想知道为什么我不使用内置转换并手动进行 - 这是因为内置转换不适用于双缓冲。 .NET 中的另一个错误或只是功能?没有双缓冲的绘制速度非常慢,所以这里必须使用它。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-08-09
    • 2019-11-27
    • 2018-03-25
    • 2013-06-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多