繁体   English   中英

GDI +曲线“溢出”

[英]GDI+ curve “overflowing”

我目前正在使用GDI +绘制线形图,并使用Graphics.DrawCurve平滑线形。 问题是曲线并不总是与我输入的点匹配,这会使曲线在某些点上超出图形框架,如下所示(红色是Graphics.DrawLines ,绿色是Graphics.DrawCurve )。

图形

我将如何解决这个问题?

最简单的解决方案是设置张力:

在此处输入图片说明

绿色曲线是使用默认张力绘制的,蓝色曲线将张力设置为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已经过度绘制了。

对于一个真正好的解决方案,您将需要使用DrawBeziers 这样一来,您就可以绘制出可以通过这些点的曲线,而不会产生任何过度绘制,并且可以完全控制曲线的半径。 但是要做到这一点,您将需要“查找”,即计算好的 control points ,这是不重要的。

在此处输入图片说明

这个结果绝不是完美的,但已经足够复杂了。.我用相同的颜色显示了curve points和它们各自的control points 对于每一个点有一个输入和一个输出的控制点。 为使曲线平滑,它们的曲线点需要具有相同的切线/渐变。

我使用一些辅助函数来计算有关细分的一些信息:

  • 渐变列表
  • 渐变符号列表
  • 段长度列表
  • 点之间的水平和垂直间隙列表

主函数计算bezier points curve points的数组,即curve points以及在每对之间的前一个左 control points下一个右 control points

Paint事件中,它的用法如下:

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

最后是主要功能; 在这里,我要区分一个问题:极限点(最小值和最大值)的控制点应与这些点本身的高度相同。 这样可以防止垂直溢出。 它们很容易找到:它们的渐变迹象总是会减弱。

其他点需要为输入和输出控制点具有相同的坡度。 我使用线段梯度之间的平均值。 (也许加权平均会更好。。)然后根据线段长度对它们的距离进行加权。

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坐标的点,我没有进行编码。 因此,对于“功能图”而言,这是可以的,但对于诸如星形之类的数字而言,则不是。

也许您只是想将“越界”问题看作不是过冲问题,而是边界问题。 在这种情况下,可以使用System.Drawing.Drawing2D.GraphicsPath对象确定曲线的实际边界:

GraphicsPath gp = new GraphicsPath();
gp.AddCurve(listOfPoints);
RectangleF bounds = gp.GetBounds();

您可以直接绘制该GraphicsPath:

graphics.DrawPath(Pens.Black, gp);

就解决边界问题而言,线必定会某些轴上超出顶点。 当线条与边界对齐时,更容易看到这一事实。

鉴于以下几点:

为了使它们弯曲,它们必须以某种方式超出其范围:

如果您不希望超出其垂直范围,则只需确保贝塞尔曲线手柄具有与顶点相同的Y值,但它们会在X上超调:

或相反亦然:

您可以故意使下冲刚好足以避免曲线过冲的方式。 这可以通过将bezier手柄(可能在线中心处)与顶点交换来完成:

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM