Search code examples
c#.netwinformsdrawing2dmiter

Create Lines around a guiding line using Miterjoin


I am drawing graphs into a WinForms Picturebox. Now I am searching for a possibility to 'duplicate' a line (an array of points), so that the two resulting lines are positioned a fixed distance away from the original one. Like in this picture, I have the red line and want to get the black ones:

Picture of lines http://img227.imageshack.us/img227/2341/linesb.png

I thought about just moving the line a few pixels up/right/up-right, but that leads to strange overlapping lines.

Is there any other approach that does what I want? Any ideas would be greatly appreciated. Thanks!


Solution

  • I have created a function that does exactly what you need a few months ago as a part of a graph layouting algorithm. I wrote that in python and PyQt. I just pasted the code here at codepad. That should be very easily translateable to c#.

    Update:

    Translated it one-to-one from my python snippet (Love to do that graphics stuff :) ). As my original code was designed for more than two output lines, I just took that into the c# version as well. For two black lines being 20 pixels away from the red one, just pass width = 40 and num = 2. The returned jagged array represents an array of lines (outer array), with each line represented by an array of points (inner).

    public PointF[][] MultiplyLine(PointF[] line, int width, int num)
    {
        if (num == 1) return new PointF[][] { line };
        if (num < 1) throw new ArgumentOutOfRangeException();
        if (line.Length < 2) return Enumerable.Range(0, num)
                      .Select(x => line).ToArray();
    
        Func<float, float, PointF> normVec = (x, y) => {
            float len = (float)Math.Sqrt((double)(x * x + y * y));
            return len == 0 ? new PointF(1f, 0f) : new PointF(x / len, y / len);
        };
    
        PointF[][] newLines = Enumerable.Range(0, num)
                      .Select(x => new PointF[line.Length]).ToArray();
    
        float numinv = 1f / (float)(num - 1), cor = 0f;
        PointF vec1 = PointF.Empty, vec2 = PointF.Empty, vec3 = PointF.Empty;
    
        int j = -1, i = -1;
        foreach (PointF p in line)
        {
            bool first = j == -1, last = j == line.Length - 2; j++;
    
            if (!last)
                vec1 = normVec(line[j + 1].Y - p.Y, -line[j + 1].X + p.X);
            if (!first)
                vec2 = normVec(-line[j - 1].Y + p.Y, line[j - 1].X - p.X);
            if (!first && !last)
            {
                vec3 = normVec(vec1.X + vec2.X, vec1.Y + vec2.Y);
                cor = (float)Math.Sin((Math.PI - 
                      Math.Acos(vec1.X * vec2.X + vec1.Y * vec2.Y)) / 2);
                cor = cor == 0 ? 1 : cor;
                vec3 = new PointF(vec3.X / cor, vec3.Y / cor);
            }
    
            i = -1;
            foreach (PointF[] newLine in newLines)
            {
                i++; cor = (float)width * ((float)i * numinv - 0.5f);
                vec1 = first ? vec1 : last ? vec2 : vec3;
                newLine[j] = new PointF(vec1.X * cor + p.X, vec1.Y * cor + p.Y);
            }
        }
    
        return newLines;
    }
    

    To try it out I took this small sample (The same sample as in my PyQt code):

    PointF[] pts = new PointF[] { 
        new PointF(100f, 100f), new PointF(300f, 200f), 
        new PointF(500f, 200f), new PointF(300f, 500f), 
        new PointF(600f, 450f), new PointF(650f, 180f), 
        new PointF(800f, 180f), new PointF(800f, 500f), 
        new PointF(200f, 700f)
    };
    
    pictureBox1.Image = new Bitmap(pictureBox1.Width, pictureBox1.Height);
    using(Graphics g = Graphics.FromImage(pictureBox1.Image)){
        g.DrawLines(new Pen(Color.Red), pts);
    
        foreach (PointF[] line in MultiplyLine(pts, 80, 14))
            g.DrawLines(new Pen(Color.Black), line);
    }
    

    Which resulted in this graphic:

    outlines around line http://img41.imageshack.us/img41/8606/lines2.th.png