用WinForms绘制宽线时发生OutOfMemoryException [英] OutOfMemoryException on drawing wide lines with WinForms

查看:80
本文介绍了用WinForms绘制宽线时发生OutOfMemoryException的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这个人很疯狂。我只是在 OnPaint 处理程序中绘制了几千行。当 pen.Width <= 1 或屏幕上没有多行时,没有任何问题。



好的,我画一张缩放的地图。线宽与地图成比例。当我放大某些地图时,我得到 OutOfMemoryException 。为什么?!



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



发生了什么?这与实际的内存使用没有任何关系。



顺便说一下, pen.Width 我设置的时间大约为2。

代码看起来像 foreach(...)g.DrawLine(...) - 并且崩溃如果我不能找到解决方案,我将不得不放弃线宽缩放,这将大大降低质量介绍。或者我可以做一个丑陋的黑客试图捕捉这个异常(如果它可以被捕获)...

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



我试图将行坐标限制为仅在视口中可见的行坐标。这是多余的,因为 Graphics 对象自己处理它。当然它没有帮助。我尝试了一些较小的窗口大小 - 没有帮助。我试图打开和关闭双缓冲。没有快乐。



编辑:

  private void DrawMap(PaintEventArgs e){
var pens = new [] {
新笔(TrackColor),
新笔(SwitchColor),
新笔(RoadColor),
新笔(RiverColor),
新笔(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 []可见=!f? Splines.GetSubset(ts,_Viewport):null;
var ct = f? Splines.Count:visible.Length;
for(int i = 0; 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);
尝试{
g.DrawBezier(pen,p1,p2,p3,p4);
} catch(OutOfMemoryException){
g.DrawLine(pen,p1,p4);
}
}
}
foreach(var p in pens)p.Dispose();
}

在这里看到这个丑陋的黑客?它完美地工作,我甚至不会看到哪条曲线被线代替。显然 g.DrawBezier 会抛出异常。我不喜欢丑陋的黑客......

解决方案

感谢来自@ var pens = new [] {// TODO:绘制层代替
新笔(TrackColor),
新笔(SwitchColor),
新笔(RoadColor),
新笔(RiverColor),
新笔(CrossColor)
};
var b = Splines.Bounds;
var g = e.Graphics;
var f = true; // OutFull; //(TODO:将矢量限制为可见的)
var tr = GetTransformation(); //获得点的缩放和平移
float ts = tr [0],tx = tr [1],ty = tr [2];
TrackSpline []可见=!f? Splines.GetSubset(ts,_Viewport):null;
var ct = f? Splines.Count:visible.Length;
for(int i = 0; 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();



$ b在他的
linked 答案我们阅读:


这是笔和宽方法。确保您的
路径起点和路径终点不相同。


是的, .NET,向微软报告,显然还没有修复。在这里,它显示了Bezier曲线,它看起来太像直线了;)

我猜零长度的线可能会抛出类似的异常。



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



对于每条曲线和每条线都进行这种检查对于性能来说并不好。不知何故比捕捉错误的例外更好。该检查可能可能会优化一点,或移动到规模变化的处理程序。

在我的代码中, GetTransformation()方法只为所有点带来了缩放,X和Y偏移量。如果您想知道为什么我不使用内置转换并手动执行 - 这是因为内置转换不适用于双缓冲。在.NET中的另一个错误或只是功能?没有双缓冲绘图的速度很慢,所以必须在这里使用。


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:

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();
}

In his linked answer we read:

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.

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.

这篇关于用WinForms绘制宽线时发生OutOfMemoryException的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆