在曲线周围绘制一个信封

时间:2012-06-05 11:24:05

标签: c# winforms math

在我的C#WinForms应用程序中,我有一个托管2条曲线的图片框(由电压/电流测量引起)。 X轴是电压,Y轴是电流。电压轴的范围为-5到5,但电流轴的范围要小得多,范围从-10 uA到10 uA。任务是查看第二条曲线是否在第一条曲线的10%范围内。

对于视觉检查,我试图在第一条曲线(蓝色曲线)周围画一个信封。曲线只是一个PointF数组。目前因为我不知道如何在蓝色曲线周围绘制一个正确的包络线,我只绘制了另外两条曲线,这些曲线是实际曲线的X点加上并减去原始曲线的10%。当然这是一种糟糕的方法,但至少对于曲线中明显垂直的部分来说,它是有效的。但是一旦曲线处于非垂直部分,此技巧就不再起作用了,如下图所示:

enter image description here

以下是我用来绘制信封的代码:

public Bitmap DrawEnvelope(double[,] pinData, float vLimit, float iLimit)
{
    g = Graphics.FromImage(box);
    g.SmoothingMode = SmoothingMode.AntiAlias;
    g.PixelOffsetMode = PixelOffsetMode.HighQuality;

    PointF[] u = new PointF[pinData.GetLength(0)]; //Up line
    PointF[] d = new PointF[pinData.GetLength(0)]; //Down Line
    List<PointF> joinedCurves = new List<PointF>();

    float posX = xMaxValue * (vLimit / 100);
    float minX = posX * -1;


    for (int i = 0; i < pinData.GetLength(0); i++)
    {
        u[i] = new PointF(400 * (1 + (((float)pinData[i, 0]) + minX) / (xMaxValue + vExpand)), 400 * (1 - ((float)pinData[i, 1] * GetInvers((yMaxValue + iExpand)))));
    }

    for (int i = 0; i < pinData.GetLength(0); i++)
    {
        d[i] = new PointF(400 * (1 + (((float)pinData[i, 0]) + posX) / (xMaxValue + vExpand)), 400 * (1 - ((float)pinData[i, 1] * GetInvers((yMaxValue + iExpand)))));
    }


    Pen pengraph = new Pen(Color.FromArgb(50, 0 ,0 ,200), 1F);
    pengraph.Alignment = PenAlignment.Center;

    joinedCurves.AddRange(u);
    joinedCurves.AddRange(d.Reverse());

    PointF[] fillPoints = joinedCurves.ToArray();
    SolidBrush fillBrush = new SolidBrush(Color.FromArgb(40, 0, 0, 250));
    FillMode newFillMode = FillMode.Alternate;

    g.FillClosedCurve(fillBrush, fillPoints, newFillMode, 0);

    g.Dispose();
    return box;
}

绿色圆圈由我自己添加,它们表示第二条曲线(红色曲线)可能与原始曲线的差异大于10%的区域。

如果有人以正确的方式把我放在一边会很好,我应该在原始曲线周围找到一个漂亮的信封?

更新 因为我是如此的诺布,我无法找到一种方法来实现这个问题的答案直到现在,所以请一个赏金,看看somone是否可以告诉我至少这个问题的编码方法。

4 个答案:

答案 0 :(得分:3)

您可以尝试在每对点之间找到渐变,并计算通过中点的正交两侧的两个点。

然后,您可以将另外两行定义为可用于绘制信封的一组点。

答案 1 :(得分:1)

这一切都取决于您希望信封的大小。

您可以通过计算下一个点的斜率和前一个点的斜率来计算/估计每个点的曲线斜率,平均这些斜率,然后计算斜率的垂直向量。

将此向量添加到曲线的点;这为你提供了信封的右手边缘。

从曲线点减去此向量;这会给你信封的左边缘。

如果点太远或点出现非常突然的变化,此方法将失败。

答案 2 :(得分:1)

最好的办法是迭代你的点数组并计算每次连续两个点的垂直向量(参见Calculating a 2D Vector's Cross Product的实现线索)。沿着这些垂直向量在任一方向上投影,以生成信封的两个点阵列。

这个函数大致使用段中点生成它们(只要点数很高而你的偏移量不是太小,绘制时应该看起来不错):

private void GetEnvelope(PointF[] curve, out PointF[] left, out PointF[] right, float offset)
        {
            left = new PointF[curve.Length - 1];
            right = new PointF[curve.Length - 1];

            for (int i = 1; i < curve.Length; i++)
            {
                PointF normal = new PointF(curve[i].Y - curve[i - 1].Y, curve[i - 1].X - curve[i].X);
                float length = (float)Math.Sqrt(normal.X * normal.X + normal.Y * normal.Y);
                normal.X /= length;
                normal.Y /= length;

                PointF midpoint = new PointF((curve[i - 1].X + curve[i].X) / 2F, (curve[i - 1].Y + curve[i].Y) / 2F);
                left[i - 1] = new PointF(midpoint.X - (normal.X * offset), midpoint.Y - (normal.Y * offset));
                right[i - 1] = new PointF(midpoint.X + (normal.X * offset), midpoint.Y + (normal.Y * offset));
            }
        }

答案 3 :(得分:0)

这可能是一个愚蠢的建议。或许不是自己画信封,也许你可以让winforms为你做。尝试使用宽度较大的笔将信封绘制成一条线。也许它可能会奏效。

如果你看一下改变笔宽的msdn例子,你可能会看到我的意思。

http://msdn.microsoft.com/en-us/library/3bssbs7z.aspx