GDI +曲线"四溢" [英] GDI+ curve "overflowing"
问题描述
我目前使用GDI +绘制线图,并使用 Graphics.DrawCurve
来理顺线。问题是,该曲线不总是匹配我给它的点,这使得曲线生长出在某些点图形帧的,如下所示(红色是 Graphics.DrawLines
,绿色是 Graphics.DrawCurve
)
I'm currently using GDI+ to draw a line graph, and using Graphics.DrawCurve
to smooth out the line. The problem is that the curve doesn't always match the points I feed it, and that makes the curve grow out of the graph frame in some points, as seen below(red is Graphics.DrawLines
, green is Graphics.DrawCurve
).
我将如何去解决呢?
推荐答案
最简单的办法是设置一个张力:
The simplest solution is to set a tension:
绿色曲线绘制默认紧张,蓝某设定张力
The green curve is drawn with the default tension, the blue one set a tension of 0.1f
:
private void panel1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
e.Graphics.DrawLines(Pens.Red, points.ToArray());
e.Graphics.DrawCurve(Pens.Green, points.ToArray());
e.Graphics.DrawCurve(Pens.Blue, points.ToArray(), 0.1f);
}
您将需要测试什么是最好的妥协, 0.2F
仍然是确定, 0.3f
已经透支了不少。
You will need to test what is the best compromise, 0.2f
is still ok, 0.3f
is already overdrawing quite a bit..
有关一个很好的解决方案,您将需要使用的 DrawBeziers
。这将让你绘制曲线可以通过没有任何透支和曲线半径的全面控制点;但对这样你就需要发现,即计算的好 控制点
,这是什么,但平凡..:
For a really good solution you will need to use DrawBeziers
. This will let you draw curves that can go through the points without any overdrawing and with full control of the radius of the curves; but to to so you will need to 'find', i.e. calculate good control points
, which is anything but trivial..:
这结果绝不是完美的,但已经够复杂了。我已经显示曲线点
及其各自的控制点
在相同的颜色。每个点有一个接收和发送控制点。对于一个平滑的曲线,他们需要有自己的曲线点相同的切线/梯度
This result is by no means perfect but already complicated enough.. I have displayed the curve points
and their respective control points
in the same color. For each point there is an incoming and an outgoing control point. For a smooth curve they need to have the same tangents/gradients in their curve points.
我使用了一些辅助函数来计算对段的几件事情:
I use a few helper functions to calculate a few things about the segments:
- 渐变
- 梯度的迹象名单
- 段长度 列表
- 的水平和点之间的垂直间距
名单
- A list of gradients
- A list of signs of the gradients
- A list of segment lengths
- Lists of horizontal and of vertical gaps between points
主函数计算的贝塞尔点
阵列,即曲线点
和每一对之间的先前的左的和的下一个右的控制点
。
The main function calculates the array of bezier points
, that is the curve points
and between each pair the previous left and the next right control points
.
在油漆
事件是这样使用:
List<PointF> bezz = getBezz(points);
using (Pen pen = new Pen(Color.Black, 2f))
e.Graphics.DrawBeziers(pen, bezz.ToArray());
下面是我使用的功能:
List<float> getGradients(List<PointF> p)
{
List<float> grads = new List<float>();
for (int i = 0; i < p.Count - 1; i++)
{
float dx = p[i + 1].X - p[i].X;
float dy = p[i + 1].Y - p[i].Y;
if (dx == 0) grads.Add(dy == 0 ? 0 : dy > 0 ?
float.PositiveInfinity : float.NegativeInfinity);
else grads.Add(dy / dx);
}
return grads;
}
List<float> getLengths(List<PointF> p)
{
List<float> lengs = new List<float>();
for (int i = 0; i < p.Count - 1; i++)
{
float dx = p[i + 1].X - p[i].X;
float dy = p[i + 1].Y - p[i].Y;
lengs.Add((float)Math.Sqrt(dy * dy + dx * dx));
}
return lengs;
}
List<float> getGaps(List<PointF> p, bool horizontal)
{
List<float> gaps = new List<float>();
for (int i = 0; i < p.Count - 1; i++)
{
float dx = p[i + 1].X - p[i].X;
float dy = p[i + 1].Y - p[i].Y;
gaps.Add(horizontal ? dx : dy);
}
return gaps;
}
List<int> getSigns(List<float> g)
{
return g.Select(x => x > 0 ? 1 : x == 0 ? 0 : -1).ToList();
}
和最后的主要功能;在这里我做一个区别:极端点(最低和放大器;最大值)应该有相同的高度点自己的控制点。这将防止垂直四溢。他们很容易找到。他们的梯度永远altenate
And finally the main function; here I make a distinction: Extreme points ( minima & maxima) should have their control points on the same height as the points themselves. This will prevent vertical overflowing. They are easy to find: The signs of their gradients will always altenate.
其他景点的迹象需要有传入和outcoming控制点相同的梯度。我用的是段梯度之间的平均水平。 (也许加权平均会更好。)和我根据线段长度..
Other points need to have the same gradient for incoming and outcoming control points. I use the average between the segments' gradients. (Maybe a weighed average would be better..) And I weigh their distance according to the segment lengths..
List<PointF> getBezz(List<PointF> points)
{
List<PointF> bezz = new List<PointF>();
int pMax = points.Count;
List<float> hGaps = getGaps(points, true);
List<float> vGaps = getGaps(points, false);
List<float> grads = getGradients(points);
List<float> lengs = getLengths(points);
List<int> signs = getSigns(grads);
PointF[] bezzA = new PointF[pMax * 3 - 2];
// curve points
for (int i = 0; i < pMax; i++) bezzA[i * 3] = points[i];
// left control points
for (int i = 1; i < pMax; i++)
{
float x = points[i].X - hGaps[i - 1] / 2f;
float y = points[i].Y;
if (i < pMax - 1 && signs[i - 1] == signs[i])
{
float m = (grads[i-1] + grads[i]) / 2f;
y = points[i].Y - hGaps[i-1] / 2f * m * vGaps[i-1] / lengs[i-1];
}
bezzA[i * 3 - 1] = new PointF(x, y);
}
// right control points
for (int i = 0; i < pMax - 1; i++)
{
float x = points[i].X + hGaps[i] / 2f;
float y = points[i].Y;
if (i > 0 && signs[i-1] == signs[i])
{
float m = (grads[i-1] + grads[i]) / 2f;
y = points[i].Y + hGaps[i] / 2f * m * vGaps[i] / lengs[i];
}
bezzA[i * 3 + 1] = new PointF(x, y);
}
return bezzA.ToList();
}
请注意,我没有代码点具有相同x的情况下-坐标。因此,这是确定'功能图',但不是,说的数字,例如像明星..
Note that I didn't code for the case of points with the same x-coordinate. So this is ok for 'functional graphs' but not for, say figures, like e.g. stars..
这篇关于GDI +曲线"四溢"的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!