Search code examples
c#graphicsgdi

Draw an ellipse with a specified "fatness" between 2 points


I have a C# bitmap object, and i am able to draw a line from point A to point B.

I have the 2 points on the edges of the diagram, and I would like to draw an ellipse from A to B. The basic g.DrawEllipse() only draws ellipses either perfectly horizontally or vertically, however I need the ellipse to be kind of diagonal from the one end of the image to the other.

My bitmap:    200 tall by 500 wide
Point A:      Column 0, Row 20   (0,20)
Point B:      Column 499, Row 60 (499, 60)
Widest Point: 30  - Narrow Radius of the ellipse

Here is what I have so far, the draw ellipse doesnt have the overload I need, so help there please:

    using (Graphics g = Graphics.FromImage(bmp))
    {
        g.DrawLine(pen, new Point(20,0), new Point(499,60));
        g.DrawEllipse(pen, 20, 0, someWidth, someHeight);
    }

Solution

  • Here is how to use the DrawEllipse method from a rotation, the minor axis and two vertices.

    First we calculate the Size of the bounding Rectangle:

    Given the Points A and B sitting on the short sides of length smallSize we get the long side with a little Pythagoras:

    int longSide = (int)(Math.Sqrt((A.Y - B.Y) * (A.Y - B.Y) + (B.X - A.X) * (B.X - A.X)));
    

    So :

    Size size = new System.Drawing.Size(longSide, smallSize);
    

    Next we need the rotation angle:

    float angle = -(float)(Math.Atan2(A.Y - B.Y, B.X - A.X) * 180f / Math.PI);
    

    And it will make things easier to also get the center Point C:

    Point C = new Point((A.X + B.X)/ 2, (A.Y + B.Y)/ 2);
    

    The last thing we want is a routine that draws an ellipse of a given Size, rotated around C at an angle:

    void DrawEllipse(Graphics G, Pen pen, Point center, Size size, float angle)
    {
        int h2 = size.Height / 2;
        int w2 = size.Width / 2;
        Rectangle rect = new Rectangle( new Point(center.X - w2, center.Y - h2), size );
    
        G.TranslateTransform(center.X, center.Y);
        G.RotateTransform(angle);
        G.TranslateTransform(-center.X, -center.Y);
        G.DrawEllipse(pen, rect);
        G.ResetTransform();
    }
    

    enter image description here

    Here is a little testbed that brings it all together:

    Point A = new Point(200, 200); // *
    Point B = new Point(500, 250);
    int smallSize = 50;
    
    
    void doTheDraw(PictureBox pb)
    {
        Bitmap bmp = new Bitmap(pb.Width, pb.Height);
    
        float angle = -(float)(Math.Atan2(A.Y - B.Y, B.X - A.X) * 180f / Math.PI);
        int longSide = (int)(Math.Sqrt((A.Y - B.Y) * (A.Y - B.Y) + (B.X - A.X) * (B.X - A.X)));
        Point C = new Point((A.X + B.X) / 2, (A.Y + B.Y) / 2);
        Size size = new System.Drawing.Size((int)longSide, smallSize);
    
        using (Pen pen = new Pen(Color.Orange, 3f))
        using (Graphics g = Graphics.FromImage(bmp))
        {
            // a nice background grid (optional):
            DrawGrid(g, 0, 0, 100, 50, 10,
                Color.LightSlateGray, Color.DarkGray, Color.Gainsboro);
    
            // show the points we use (optional):
            g.FillEllipse(Brushes.Red, A.X - 4, A.Y - 4, 8, 8);
            g.FillRectangle(Brushes.Red, B.X - 3, B.Y - 3, 7, 7);
            g.FillEllipse(Brushes.Red, C.X - 5, C.Y - 5, 11, 11);
    
            // show the connection line (optional):
            g.DrawLine(Pens.Orange, A, B);
    
            // here comes the ellipse:
            DrawEllipse(g, pen, C, size, angle);
        }
        pb.Image = bmp;
    }
    

    The grid is a nice helper:

    void DrawGrid(Graphics G, int ox, int oy, 
                  int major, int medium, int minor, Color c1, Color c2, Color c3)
    {
        using (Pen pen1 = new Pen(c1, 1f))
        using (Pen pen2 = new Pen(c2, 1f))
        using (Pen pen3 = new Pen(c3, 1f))
        {
            pen2.DashStyle = DashStyle.Dash;
            pen3.DashStyle = DashStyle.Dot;
            for (int x = ox; x < G.VisibleClipBounds.Width; x += major)
                G.DrawLine(pen1, x, 0, x, G.VisibleClipBounds.Height);
            for (int y = oy; y < G.VisibleClipBounds.Height; y += major)
                G.DrawLine(pen1, 0, y, G.VisibleClipBounds.Width, y);
    
            for (int x = ox; x < G.VisibleClipBounds.Width; x += medium)
                G.DrawLine(pen2, x, 0, x, G.VisibleClipBounds.Height);
            for (int y = oy; y < G.VisibleClipBounds.Height; y += medium)
                G.DrawLine(pen2, 0, y, G.VisibleClipBounds.Width, y); 
    
            for (int x = ox; x < G.VisibleClipBounds.Width; x += minor)
                G.DrawLine(pen3, x, 0, x, G.VisibleClipBounds.Height);
            for (int y = oy; y < G.VisibleClipBounds.Height; y += minor)
                G.DrawLine(pen3, 0, y, G.VisibleClipBounds.Width, y);
        }
    }
    

    Note that I made A, B, smallSide class level variables so I can modify them during my tests, (and I did *)..

    As you can see I have added a TrackBar to make the smallside dynamic; for even more fun I have added this MouseClick event:

    private void pictureBox1_MouseClick(object sender, MouseEventArgs e)
    {
        if (e.Button.HasFlag(MouseButtons.Left)) A = e.Location;
        else B = e.Location;
        doTheDraw(pictureBox1);
    }
    

    Note that I didn't care for disposing of the old Bitmap; you should, of course..!