Iii Means to Depict an Arc

Download Sample Download the sample

Learn how to use SkiaSharp to define arcs in three dissimilar ways

An arc is a curve on the circumference of an ellipse, such as the rounded parts of this infinity sign:

Infinity sign

Despite the simplicity of that definition, there is no style to define an arc-drawing function that satisfies every need, and hence, no consensus among graphics systems of the all-time mode to depict an arc. For this reason, the SKPath class does not restrict itself to just 1 approach.

SKPath defines an AddArc method, v dissimilar ArcTo methods, and ii relative RArcTo methods. These methods fall into iii categories, representing three very different approaches to specifying an arc. Which 1 you utilize depends on the information available to ascertain the arc, and how this arc fits in with the other graphics that you're cartoon.

The Angle Arc

The angle arc arroyo to drawing arcs requires that you specify a rectangle that bounds an ellipse. The arc on the circumference of this ellipse is indicated past angles from the middle of the ellipse that indicate the beginning of the arc and its length. Two unlike methods draw angle arcs. These are the AddArc method and the ArcTo method:

              public void AddArc (SKRect oval, Single startAngle, Single sweepAngle)  public void ArcTo (SKRect oval, Unmarried startAngle, Single sweepAngle, Boolean forceMoveTo)                          

These methods are identical to the Android AddArc and [ArcTo]xref:Android.Graphics.Path.ArcTo*) methods. The iOS AddArc method is like but is restricted to arcs on the circumference of a circle rather than generalized to an ellipse.

Both methods begin with an SKRect value that defines both the location and size of an ellipse:

The oval that begins an angle arc

The arc is a function of the circumference of this ellipse.

The startAngle argument is a clockwise angle in degrees relative to a horizontal line drawn from the centre of the ellipse to the correct. The sweepAngle argument is relative to the startAngle. Here are startAngle and sweepAngle values of sixty degrees and 100 degrees, respectively:

The angles that define an angle arc

The arc begins at the offset angle. Its length is governed by the sweep angle. The arc is shown hither in red:

The highlighted angle arc

The bend added to the path with the AddArc or ArcTo method is simply that part of the ellipse's circumference:

The angle arc by itself

The startAngle or sweepAngle arguments can be negative: The arc is clockwise for positive values of sweepAngle and counter-clockwise for negative values.

However, AddArc does non ascertain a closed contour. If you call LineTo afterward AddArc, a line is drawn from the end of the arc to the bespeak in the LineTo method, and the same is true of ArcTo.

AddArc automatically starts a new contour and is functionally equivalent to a phone call to ArcTo with a final argument of true:

              path.ArcTo (oval, startAngle, sweepAngle, truthful);                          

That last statement is called forceMoveTo, and it effectively causes a MoveTo call at the start of the arc. That begins a new contour. That is not the case with a concluding statement of simulated:

              path.ArcTo (oval, startAngle, sweepAngle, false);                          

This version of ArcTo draws a line from the electric current position to the showtime of the arc. This ways that the arc can be somewhere in the middle of a larger profile.

The Angle Arc page lets y'all use 2 sliders to specify the start and sweep angles. The XAML file instantiates two Slider elements and an SKCanvasView. The PaintCanvas handler in the AngleArcPage.xaml.cs file draws both the oval and the arc using two SKPaint objects defined equally fields:

              void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args) {     SKImageInfo info = args.Info;     SKSurface surface = args.Surface;     SKCanvas canvas = surface.Canvas;      sheet.Articulate();      SKRect rect = new SKRect(100, 100, info.Width - 100, info.Summit - 100);     float startAngle = (float)startAngleSlider.Value;     float sweepAngle = (bladder)sweepAngleSlider.Value;      canvas.DrawOval(rect, outlinePaint);      using (SKPath path = new SKPath())     {         path.AddArc(rect, startAngle, sweepAngle);         canvass.DrawPath(path, arcPaint);     } }                          

Every bit you can see, both the beginning angle and the sweep angle tin take on negative values:

Triple screenshot of the Angle Arc page

This approach to generating an arc is algorithmically the simplest, and it's like shooting fish in a barrel to derive the parametric equations that describe the arc. Knowing the size and location of the ellipse, and the start and sweep angles, the beginning and end points of the arc tin can be calculated using elementary trigonometry:

10 = oval.MidX + (oval.Width / 2) * cos(angle)

y = oval.MidY + (oval.Height / two) * sin(bending)

The angle value is either startAngle or startAngle + sweepAngle.

The use of ii angles to define an arc is all-time for cases where you know the angular length of the arc that you desire to draw, for case, to make a pie nautical chart. The Exploded Pie Nautical chart page demonstrates this. The ExplodedPieChartPage class uses an internal form to define some fabricated data and colors:

              class ChartData {     public ChartData(int value, SKColor color)     {         Value = value;         Colour = colour;     }      public int Value { private set; get; }      public SKColor Color { private prepare; get; } }  ChartData[] chartData = {     new ChartData(45, SKColors.Cherry),     new ChartData(13, SKColors.Green),     new ChartData(27, SKColors.Blue),     new ChartData(19, SKColors.Magenta),     new ChartData(40, SKColors.Cyan),     new ChartData(22, SKColors.Brownish),     new ChartData(29, SKColors.Grey) };                          

The PaintSurface handler first loops through the items to calculate a totalValues number. From that, it tin determine each particular's size as the fraction of the total, and convert that to an angle:

              void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args) {     SKImageInfo info = args.Info;     SKSurface surface = args.Surface;     SKCanvas canvas = surface.Sheet;      canvas.Clear();      int totalValues = 0;      foreach (ChartData item in chartData)     {         totalValues += item.Value;     }      SKPoint center = new SKPoint(info.Width / 2, info.Height / two);     float explodeOffset = 50;     float radius = Math.Min(info.Width / 2, info.Height / 2) - 2 * explodeOffset;     SKRect rect = new SKRect(center.X - radius, heart.Y - radius,                              centre.10 + radius, center.Y + radius);      float startAngle = 0;      foreach (ChartData item in chartData)     {         float sweepAngle = 360f * item.Value / totalValues;          using (SKPath path = new SKPath())         using (SKPaint fillPaint = new SKPaint())         using (SKPaint outlinePaint = new SKPaint())         {             path.MoveTo(center);             path.ArcTo(rect, startAngle, sweepAngle, false);             path.Close();              fillPaint.Style = SKPaintStyle.Fill;             fillPaint.Color = item.Color;              outlinePaint.Style = SKPaintStyle.Stroke;             outlinePaint.StrokeWidth = v;             outlinePaint.Color = SKColors.Black;              // Calculate "explode" transform             float angle = startAngle + 0.5f * sweepAngle;             bladder x = explodeOffset * (float)Math.Cos(Math.PI * angle / 180);             bladder y = explodeOffset * (float)Math.Sin(Math.PI * angle / 180);              canvas.Save();             sail.Translate(x, y);              // Fill and stroke the path             canvass.DrawPath(path, fillPaint);             sail.DrawPath(path, outlinePaint);             sail.Restore();         }          startAngle += sweepAngle;     } }                          

A new SKPath object is created for each pie slice. The path consists of a line from the eye, then an ArcTo to depict the arc, and another line back to the center results from the Shut telephone call. This plan displays "exploded" pie slices by moving them all out from the eye by 50 pixels. That task requires a vector in the direction of the midpoint of the sweep angle for each piece:

Triple screenshot of the Exploded Pie Chart page

To see what it looks like without the "explosion," just comment out the Translate call:

Triple screenshot of the Exploded Pie Chart page without the explosion

The Tangent Arc

The second type of arc supported past SKPath is the tangent arc, so called because the arc is the circumference of a circle that is tangent to 2 continued lines.

A tangent arc is added to a path with a phone call to the ArcTo method with ii SKPoint parameters, or the ArcTo overload with separate Unmarried parameters for the points:

              public void ArcTo (SKPoint point1, SKPoint point2, Single radius)  public void ArcTo (Unmarried x1, Unmarried y1, Single x2, Unmarried y2, Single radius)                          

This ArcTo method is similar to the PostScript arct (folio 532) function and the iOS AddArcToPoint method.

The ArcTo method involves 3 points:

  • The current betoken of the contour, or the point (0, 0) if MoveTo has not been called
  • The first point argument to the ArcTo method, called the corner signal
  • The second point argument to ArcTo, chosen the destination betoken:

Three points that begin a tangent arc

These three points define two connected lines:

Lines connecting the three points of a tangent arc

If the 3 points are colinear — that is, if they lie on the same direct line — no arc will exist drawn.

The ArcTo method too includes a radius parameter. This defines the radius of a circle:

The circle of a tangent arc

The tangent arc is not generalized for an ellipse.

If the two lines meet at any bending, that circle tin can be inserted between those lines then that it is tangent to both lines:

The tangent arc circle between the two lines

The curve that is added to the contour does not bear on either of the points specified in the ArcTo method. It consists of a straight line from the current indicate to the first tangent point, and an arc that ends at the 2nd tangent point, shown here in red:

Diagram shows the previous diagram annotated with a red line that shows the highlighted tangent arc between the two lines.

Hither'due south the final straight line and arc that is added to the contour:

The highlighted tangent arc between the two lines

The contour can exist continued from the second tangent signal.

The Tangent Arc page allows you to experiment with the tangent arc. This is the first of several pages that derive from InteractivePage, which defines a few handy SKPaint objects and performs TouchPoint processing:

              public grade InteractivePage : ContentPage {     protected SKCanvasView baseCanvasView;     protected TouchPoint[] touchPoints;      protected SKPaint strokePaint = new SKPaint     {         Style = SKPaintStyle.Stroke,         Color = SKColors.Black,         StrokeWidth = 3     };      protected SKPaint redStrokePaint = new SKPaint     {         Style = SKPaintStyle.Stroke,         Color = SKColors.Red,         StrokeWidth = xv     };      protected SKPaint dottedStrokePaint = new SKPaint     {         Style = SKPaintStyle.Stroke,         Color = SKColors.Black,         StrokeWidth = 3,         PathEffect = SKPathEffect.CreateDash(new float[] { 7, seven }, 0)     };      protected void OnTouchEffectAction(object sender, TouchActionEventArgs args)     {         bool touchPointMoved = false;          foreach (TouchPoint touchPoint in touchPoints)         {             float calibration = baseCanvasView.CanvasSize.Width / (float)baseCanvasView.Width;             SKPoint signal = new SKPoint(scale * (float)args.Location.X,                                         scale * (float)args.Location.Y);             touchPointMoved |= touchPoint.ProcessTouchEvent(args.Id, args.Type, point);         }          if (touchPointMoved)         {             baseCanvasView.InvalidateSurface();         }     } }                          

The TangentArcPage class derives from InteractivePage. The constructor in the TangentArcPage.xaml.cs file is responsible for instantiating and initializing the touchPoints array, and setting baseCanvasView (in InteractivePage) to the SKCanvasView object instantiated in the TangentArcPage.xaml file:

              public partial class TangentArcPage : InteractivePage {     public TangentArcPage()     {         touchPoints = new TouchPoint[three];          for (int i = 0; i < 3; i++)         {             TouchPoint touchPoint = new TouchPoint             {                 Center = new SKPoint(i == 0 ? 100 : 500,                                      i != two ? 100 : 500)             };             touchPoints[i] = touchPoint;         }          InitializeComponent();          baseCanvasView = canvasView;         radiusSlider.Value = 100;     }      void sliderValueChanged(object sender, ValueChangedEventArgs args)     {         if (canvasView != zero)         {             canvasView.InvalidateSurface();         }     }     ... }                          

The PaintSurface handler uses the ArcTo method to draw the arc based on the bear upon points and a Slider, but also algorithmically calculates the circumvolve that the angle is based on:

              public partial course TangentArcPage : InteractivePage {     ...     void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)     {         SKImageInfo info = args.Info;         SKSurface surface = args.Surface;         SKCanvas canvas = surface.Canvas;          canvas.Clear();          // Draw the 2 lines that encounter at an bending         using (SKPath path = new SKPath())         {             path.MoveTo(touchPoints[0].Center);             path.LineTo(touchPoints[1].Center);             path.LineTo(touchPoints[2].Center);             canvas.DrawPath(path, dottedStrokePaint);         }          // Draw the circle that the arc wraps around         float radius = (bladder)radiusSlider.Value;          SKPoint v1 = Normalize(touchPoints[0].Center - touchPoints[1].Center);         SKPoint v2 = Normalize(touchPoints[2].Center - touchPoints[ane].Center);          double dotProduct = v1.X * v2.10 + v1.Y * v2.Y;         double angleBetween = Math.Acos(dotProduct);         float hypotenuse = radius / (float)Math.Sin(angleBetween / 2);         SKPoint vMid = Normalize(new SKPoint((v1.X + v2.10) / 2, (v1.Y + v2.Y) / 2));         SKPoint eye = new SKPoint(touchPoints[i].Heart.X + vMid.10 * hypotenuse,                                      touchPoints[one].Center.Y + vMid.Y * hypotenuse);          canvas.DrawCircle(center.X, center.Y, radius, this.strokePaint);          // Draw the tangent arc         using (SKPath path = new SKPath())         {             path.MoveTo(touchPoints[0].Centre);             path.ArcTo(touchPoints[one].Eye, touchPoints[2].Center, radius);             canvas.DrawPath(path, redStrokePaint);         }          foreach (TouchPoint touchPoint in touchPoints)         {             touchPoint.Paint(canvas);         }     }      // Vector methods     SKPoint Normalize(SKPoint v)     {         float magnitude = Magnitude(v);         return new SKPoint(v.10 / magnitude, v.Y / magnitude);     }      bladder Magnitude(SKPoint v)     {         render (float)Math.Sqrt(v.10 * five.X + v.Y * v.Y);     } }                          

Here'due south the Tangent Arc page running:

Triple screenshot of the Tangent Arc page

The tangent arc is ideal for creating rounded corners, such equally a rounded rectangle. Considering SKPath already includes an AddRoundedRect method, the Rounded Heptagon page demonstrates how to utilize ArcTo for rounding the corners of a seven-sided polygon. (The code is generalized for whatsoever regular polygon.)

The PaintSurface handler of the RoundedHeptagonPage class contains 1 for loop to calculate the coordinates of the vii vertices of the heptagon, and a 2d to calculate the midpoints of the vii sides from these vertices. These midpoints are then used to construct the path:

              void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args) {     SKImageInfo info = args.Info;     SKSurface surface = args.Surface;     SKCanvas canvas = surface.Sail;      canvas.Articulate();      float cornerRadius = 100;     int numVertices = 7;     float radius = 0.45f * Math.Min(info.Width, info.Height);      SKPoint[] vertices = new SKPoint[numVertices];     SKPoint[] midPoints = new SKPoint[numVertices];      double vertexAngle = -0.5f * Math.PI;       // straight upward      // Coordinates of the vertices of the polygon     for (int vertex = 0; vertex < numVertices; vertex++)     {         vertices[vertex] = new SKPoint(radius * (float)Math.Cos(vertexAngle),                                        radius * (bladder)Math.Sin(vertexAngle));         vertexAngle += two * Math.PI / numVertices;     }      // Coordinates of the midpoints of the sides connecting the vertices     for (int vertex = 0; vertex < numVertices; vertex++)     {         int prevVertex = (vertex + numVertices - ane) % numVertices;         midPoints[vertex] = new SKPoint((vertices[prevVertex].X + vertices[vertex].Ten) / 2,                                         (vertices[prevVertex].Y + vertices[vertex].Y) / 2);     }      // Create the path     using (SKPath path = new SKPath())     {         // Brainstorm at the start midpoint         path.MoveTo(midPoints[0]);          for (int vertex = 0; vertex < numVertices; vertex++)         {             SKPoint nextMidPoint = midPoints[(vertex + 1) % numVertices];              // Draws a line from the electric current indicate, and so the arc             path.ArcTo(vertices[vertex], nextMidPoint, cornerRadius);              // Connect the arc with the next midpoint             path.LineTo(nextMidPoint);         }         path.Shut();          // Render the path in the center of the screen         using (SKPaint paint = new SKPaint())         {             paint.Style = SKPaintStyle.Stroke;             paint.Color = SKColors.Blue;             pigment.StrokeWidth = 10;              sail.Translate(info.Width / 2, info.Height / two);             canvas.DrawPath(path, pigment);         }     } }                          

Here's the programme running:

Triple screenshot of the Rounded Heptagon page

The Elliptical Arc

The elliptical arc is added to a path with a call to the ArcTo method that has ii SKPoint parameters, or the ArcTo overload with divide X and Y coordinates:

              public void ArcTo (SKPoint r, Single xAxisRotate, SKPathArcSize largeArc, SKPathDirection sweep, SKPoint xy)  public void ArcTo (Single rx, Single ry, Unmarried xAxisRotate, SKPathArcSize largeArc, SKPathDirection sweep, Unmarried 10, Single y)                          

The elliptical arc is consistent with the elliptical arc included in Scalable Vector Graphics (SVG) and the Universal Windows Platform ArcSegment class.

These ArcTo methods draw an arc between 2 points, which are the current point of the contour, and the final parameter to the ArcTo method (the xy parameter or the separate x and y parameters):

The two points that defined an elliptical arc

The first point parameter to the ArcTo method (r, or rx and ry) is not a indicate at all but instead specifies the horizontal and vertical radii of an ellipse;

The ellipse that defined an elliptical arc

The xAxisRotate parameter is the number of clockwise degrees to rotate this ellipse:

The tilted ellipse that defined an elliptical arc

If this tilted ellipse is then positioned and so that it touches the two points, the points are continued by two different arcs:

The first set of elliptical arcs

These two arcs can be distinguished in ii means: The top arc is larger than the bottom arc, and as the arc is drawn from left to right, the top arc is drawn in a clockwise direction while the bottom arc is fatigued in a counter-clockwise direction.

It is also possible to fit the ellipse betwixt the two points in another fashion:

The second set of elliptical arcs

Now there's a smaller arc on top that's drawn clockwise, and a larger arc on the bottom that'southward drawn counter-clockwise.

These ii points can therefore be connected by an arc defined by the tilted ellipse in a full of iv means:

All four elliptical arcs

These iv arcs are distinguished by the four combinations of the SKPathArcSize and SKPathDirection enumeration type arguments to the ArcTo method:

  • red: SKPathArcSize.Large and SKPathDirection.Clockwise
  • dark-green: SKPathArcSize.Small-scale and SKPathDirection.Clockwise
  • bluish: SKPathArcSize.Small-scale and SKPathDirection.CounterClockwise
  • magenta: SKPathArcSize.Large and SKPathDirection.CounterClockwise

If the tilted ellipse is not big enough to fit between the two points, and so it is uniformly scaled until it is large enough. Simply two unique arcs connect the two points in that case. These can be distinguished with the SKPathDirection parameter.

Although this arroyo to defining an arc sounds circuitous on first encounter, it is the only approach that allows defining an arc with a rotated ellipse, and it is often the easiest approach when you lot need to integrate arcs with other parts of the contour.

The Elliptical Arc folio allows you to interactively set the 2 points, and the size and rotation of the ellipse. The EllipticalArcPage grade derives from InteractivePage, and the PaintSurface handler in the EllipticalArcPage.xaml.cs code-behind file draws the four arcs:

              void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args) {     SKImageInfo info = args.Info;     SKSurface surface = args.Surface;     SKCanvas canvas = surface.Canvas;      canvass.Clear();      using (SKPath path = new SKPath())     {         int colorIndex = 0;         SKPoint ellipseSize = new SKPoint((bladder)xRadiusSlider.Value,                                           (float)yRadiusSlider.Value);         bladder rotation = (float)rotationSlider.Value;          foreach (SKPathArcSize arcSize in Enum.GetValues(typeof(SKPathArcSize)))             foreach (SKPathDirection direction in Enum.GetValues(typeof(SKPathDirection)))             {                 path.MoveTo(touchPoints[0].Center);                 path.ArcTo(ellipseSize, rotation,                            arcSize, direction,                            touchPoints[one].Center);                  strokePaint.Color = colors[colorIndex++];                 canvass.DrawPath(path, strokePaint);                 path.Reset();             }     }      foreach (TouchPoint touchPoint in touchPoints)     {         touchPoint.Paint(canvas);     } }                          

Hither information technology is running:

Triple screenshot of the Elliptical Arc page

The Arc Infinity page uses the elliptical arc to draw an infinity sign. The infinity sign is based on two circles with radii of 100 units separated by 100 units:

Two circles

Two lines crossing each other are tangent to both circles:

Two circles with tangent lines

The infinity sign is a combination of parts of these circles and the ii lines. To use the elliptical arc to draw the infinity sign, the coordinates where the two lines are tangent to the circles must be determined.

Construct a correct rectangle in one of the circles:

Two circles with tangent lines and embedded circle

The radius of the circle is 100 units, and the hypotenuse of the triangle is 150 units, then the angle α is the arcsine (changed sine) of 100 divided past 150, or 41.8 degrees. The length of the other side of the triangle is 150 times the cosine of 41.8 degrees, or 112, which can also be calculated by the Pythagorean theorem.

The coordinates of the tangent point tin then be calculated using this data:

10 = 112·cos(41.8) = 83

y = 112·sin(41.viii) = 75

The four tangent points are all that'southward necessary to depict an infinity sign centered on the signal (0, 0) with circle radii of 100:

Two circles with tangent lines and coordinates

The PaintSurface handler in the ArcInfinityPage class positions the infinity sign so that the (0, 0) signal is positioned in the center of the page, and scales the path to the screen size:

              void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args) {     SKImageInfo info = args.Info;     SKSurface surface = args.Surface;     SKCanvas canvas = surface.Sail;      canvass.Clear();      using (SKPath path = new SKPath())     {         path.LineTo(83, 75);         path.ArcTo(100, 100, 0, SKPathArcSize.Big, SKPathDirection.CounterClockwise, 83, -75);         path.LineTo(-83, 75);         path.ArcTo(100, 100, 0, SKPathArcSize.Large, SKPathDirection.Clockwise, -83, -75);         path.Close();          // Use path.TightBounds for coordinates without control points         SKRect pathBounds = path.Bounds;          canvass.Interpret(info.Width / two, info.Meridian / ii);         sheet.Scale(Math.Min(info.Width / pathBounds.Width,                               info.Height / pathBounds.Height));          using (SKPaint paint = new SKPaint())         {             paint.Mode = SKPaintStyle.Stroke;             paint.Color = SKColors.Blue;             pigment.StrokeWidth = five;              canvas.DrawPath(path, paint);         }     } }                          

The lawmaking uses the Bounds belongings of SKPath to determine the dimensions of the infinity sine to scale it to the size of the canvas:

Triple screenshot of the Arc Infinity page

The outcome seems a little small, which suggests that the Bounds holding of SKPath is reporting a size larger than the path.

Internally, Skia approximates the arc using multiple quadratic Bézier curves. These curves (as you lot'll meet in the side by side department) contain command points that govern how the curve is drawn merely are not part of the rendered bend. The Premises property includes those command points.

To go a tighter fit, use the TightBounds property, which excludes the control points. Here's the program running in landscape mode, and using the TightBounds property to obtain the path bounds:

Triple screenshot of the Arc Infinity page with tight bounds

Although the connections betwixt the arcs and direct lines are mathematically smooth, the change from arc to direct line might seem a little precipitous. A better infinity sign is presented in the side by side commodity on Three Types of Bézier Curves.

  • SkiaSharp APIs
  • SkiaSharpFormsDemos (sample)