user230910
user230910

Reputation: 2372

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

Upvotes: 0

Views: 2470

Answers (2)

TaW
TaW

Reputation: 54433

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..!

Upvotes: 7

Razko
Razko

Reputation: 581

If you wish to use Graphics to create a diagonal ellipse, perhaps you can use DrawBezier() method. Here is some code that does it:

// Draws an ellipse using 2 beziers.
private void DrawEllipse(Graphics g, PointF center, float width, float height, double rotation)
{
  // Unrotated ellipse frame
  float left   = center.X - width / 2;
  float right  = center.X + width / 2;
  float top      = center.Y - height / 2;
  float bottom   = center.Y + height / 2;
  PointF p1 = new PointF(left, center.Y);
  PointF p2 = new PointF(left, top);
  PointF p3 = new PointF(right, top);
  PointF p4 = new PointF(right, center.Y);
  PointF p5 = new PointF(right, bottom);
  PointF p6 = new PointF(left, bottom);

  // Draw ellipse with rotated points.
  g.DrawBezier(Pens.Black, Rotate(p1, center, rotation), Rotate(p2, center, rotation), Rotate(p3, center, rotation), Rotate(p4, center, rotation));
  g.DrawBezier(Pens.Black, Rotate(p4, center, rotation), Rotate(p5, center, rotation), Rotate(p6, center, rotation), Rotate(p1, center, rotation));
}

// Rotating a given point by given angel around a given pivot.
private PointF Rotate(PointF point, PointF pivot, double angle)
{
  float x = point.X - pivot.X;
  float y = point.Y - pivot.Y;
  double a = Math.Atan(y / x);
  if (x < 0)
  {
    a += Math.PI;
  }
  float size = (float)Math.Sqrt(x * x + y * y);

  double newAngel = a + angle;
  float newX = ((float)Math.Cos(newAngel) * size);
  float newY = ((float)Math.Sin(newAngel) * size);
  return pivot + new SizeF(newX, newY);
}

The above code computes the frame of the ellipse (proir to the rotation) at points p1, p2, ..., p6. And then, draws the ellipse as 2 beziers with the ellipse frame rotated points.

Upvotes: 1

Related Questions