Class Path
An outline of contours made of lines, quadratic and cubic Bézier curves, with the FillRule that says which points it covers when filled. A path never changes after it is built, so one path can be drawn from many threads and kept by any canvas backend. Coordinates are float pixels, y down. Build one with PathBuilder or a shape helper such as Circle(Vector2, float).
public sealed class Path
- Inheritance
-
Path
- Inherited Members
Examples
// A path is built once and never changes, so it can be drawn many times and from many threads.
Path heart = new PathBuilder()
.MoveTo(150, 60)
.CubicTo(150, 30, 100, 20, 90, 60)
.CubicTo(80, 100, 130, 130, 150, 160)
.CubicTo(170, 130, 220, 100, 210, 60)
.CubicTo(200, 20, 150, 30, 150, 60)
.Close()
.ToPath();
canvas.FillPath(heart, Rgba8.Red);
// SVG's arc command, and the even-odd rule: an inner contour becomes a hole.
Path frame = new PathBuilder()
.AddRect(new RectF(10, 10, 60, 60))
.AddCircle(new Vector2(40, 40), 20)
.MoveTo(10, 120)
.ArcTo(radii: new Vector2(30, 20), rotationDegrees: 0, largeArc: false, clockwise: true, end: new Vector2(70, 120))
.Close()
.ToPath(FillRule.EvenOdd);
canvas.FillPath(frame, Rgba8.Black);
bool hit = heart.Contains(new Vector2(150, 100)); // hit testing on the curves themselves
RectF box = heart.TightBounds; // curves measured at their extremes
Properties
Bounds
Gets the bounds of every point, control points included: cheap, and never smaller than the outline.
public RectF Bounds { get; }
Property Value
Contours
Gets the contours, each as a path of its own with this path's fill rule.
public IReadOnlyList<Path> Contours { get; }
Property Value
Empty
Gets a path with no contours.
public static Path Empty { get; }
Property Value
FillRule
Gets which points the path covers when filled.
public FillRule FillRule { get; }
Property Value
IsConvex
Gets a value indicating whether the path is one convex contour: every turn goes the same way and it winds around once. A path that is a line, a point or empty counts as convex.
public bool IsConvex { get; }
Property Value
IsEmpty
Gets a value indicating whether the path draws nothing: it has no line or curve.
public bool IsEmpty { get; }
Property Value
Points
Gets the points, in order: one for each move and line, two for each quadratic and three for each cubic.
public ReadOnlySpan<Vector2> Points { get; }
Property Value
TightBounds
Gets the exact bounds of the outline: curves are measured at their extremes, not their control points.
public RectF TightBounds { get; }
Property Value
Verbs
Gets the commands, in order.
public ReadOnlySpan<PathVerb> Verbs { get; }
Property Value
Methods
Apply(PathEffect)
Returns the path an effect makes of this one, such as its dashes or its corners rounded.
public Path Apply(PathEffect effect)
Parameters
effectPathEffectThe effect.
Returns
- Path
The new path.
Examples
// A hand-drawn chart line: jittered the same way on every run, then its corners rounded.
Path data = Path.Polygon([new(10, 80), new(50, 30), new(90, 60), new(130, 20), new(170, 50)], close: false);
PathEffect sketchy = PathEffect.Jitter(segmentLength: 6, deviation: 1.5f, seed: 7).Then(PathEffect.Corner(3));
using Raster<Rgba8> chart = Raster.Create(180, 100, Rgba8.White);
using (RasterCanvas<Rgba8> canvas = chart.CreateCanvas())
{
canvas.StrokePath(data, new Stroke(2) { Effect = sketchy }, new Rgba8(40, 40, 40, 255));
// Dots along the line, and hatching inside a circle.
canvas.FillPath(data.Apply(PathEffect.Stamp(Path.Circle(Vector2.Zero, 2), advance: 12, style: StampStyle.Translate)), Rgba8.Black);
canvas.FillPath(Path.Circle(new Vector2(150, 75), 20).Apply(PathEffect.Hatch(Matrix3x2.CreateScale(1, 5) * Matrix3x2.CreateRotation(0.6f), width: 1)), Rgba8.Black);
}
// A line that draws itself: trim it to a growing fraction of its length, frame by frame.
Path frame30 = data.Apply(PathEffect.Trim(0, 0.3f));
Arc(RectF, float, float)
Creates the open arc of an ellipse; 0 degrees is the right end of the horizontal axis and positive angles turn clockwise.
public static Path Arc(RectF oval, float startDegrees, float sweepDegrees)
Parameters
ovalRectFThe rectangle the ellipse fills.
startDegreesfloatWhere the arc starts.
sweepDegreesfloatHow far it turns, at most a full turn either way.
Returns
- Path
The path.
Circle(Vector2, float)
Creates a circle, clockwise.
public static Path Circle(Vector2 center, float radius)
Parameters
Returns
- Path
The path.
Combine(IEnumerable<Path>, PathOp, float)
Combines any number of paths with one operation in a single pass, as if it were applied in order, left to right: ((a op b) op c) and so on. Far faster than chaining Op(Path, PathOp, float) for many paths, since every outline is cut and sorted once. The result is as Op(Path, PathOp, float) describes.
public static Path Combine(IEnumerable<Path> paths, PathOp op, float tolerance = 0.05)
Parameters
pathsIEnumerable<Path>The paths; each keeps its own fill rule.
opPathOpThe operation.
tolerancefloatHow close, in path units, points must be to count as the same, as Op(Path, PathOp, float) describes.
Returns
- Path
The combined path; empty when there are no paths.
Examples
// A frame: one round rectangle cut out of another, kept as curves, so a PDF or SVG backend writes it exactly.
Path frame = Path.RoundRect(new RoundRect(new RectF(0, 0, 200, 140), 24))
.Difference(Path.RoundRect(new RoundRect(new RectF(20, 20, 160, 100), 12)));
// Many shapes in one pass, applied left to right; overlaps and self-intersections removed; an area grown by 4 units.
Path dots = Path.Combine([Path.Circle(new Vector2(40, 40), 30), Path.Circle(new Vector2(80, 40), 30), Path.Circle(new Vector2(60, 75), 30)], PathOp.Union);
Path clean = Path.Star(new Vector2(50, 50), 45, 20, 5).Simplify();
Path halo = dots.Inflate(4, LineJoin.Round);
Exceptions
- ArgumentOutOfRangeException
opis not defined, ortoleranceis not positive and finite.
Contains(Vector2)
Returns whether the filled path covers a point, by the winding number of the outline itself, curves included, and the fill rule. Open contours count as closed. A point exactly on the outline counts as inside, as in Skia.
public bool Contains(Vector2 point)
Parameters
pointVector2The point.
Returns
Difference(Path, float)
Returns the area of this path without the other, as Op(Path, PathOp, float) describes.
public Path Difference(Path other, float tolerance = 0.05)
Parameters
Returns
- Path
The difference.
Inflate(float, LineJoin, float)
Returns the area grown outwards by a distance, or for a negative distance shrunk inwards: the union of the path and the outline of a pen twice the distance wide, or the difference. Corners grow by the join: round keeps the distance everywhere, miter keeps them sharp up to the miter limit. Open contours count as closed, as fills close them. The grown or cut parts are straight pieces within a hundredth of the distance (at most a tenth of a unit); the rest keeps its curves.
public Path Inflate(float distance, LineJoin join = LineJoin.Round, float miterLimit = 4)
Parameters
distancefloatHow far to grow; negative to shrink.
joinLineJoinHow grown corners look.
miterLimitfloatThe longest miter, in multiples of twice the distance, as MiterLimit defines it.
Returns
- Path
The offset path, simplified; empty when it shrinks away.
Examples
// A frame: one round rectangle cut out of another, kept as curves, so a PDF or SVG backend writes it exactly.
Path frame = Path.RoundRect(new RoundRect(new RectF(0, 0, 200, 140), 24))
.Difference(Path.RoundRect(new RoundRect(new RectF(20, 20, 160, 100), 12)));
// Many shapes in one pass, applied left to right; overlaps and self-intersections removed; an area grown by 4 units.
Path dots = Path.Combine([Path.Circle(new Vector2(40, 40), 30), Path.Circle(new Vector2(80, 40), 30), Path.Circle(new Vector2(60, 75), 30)], PathOp.Union);
Path clean = Path.Star(new Vector2(50, 50), 45, 20, 5).Simplify();
Path halo = dots.Inflate(4, LineJoin.Round);
Exceptions
- ArgumentOutOfRangeException
A value is not finite,
joinis not defined, ormiterLimitis below 1.
Intersect(Path, float)
Returns the area of both this path and the other, as Op(Path, PathOp, float) describes.
public Path Intersect(Path other, float tolerance = 0.05)
Parameters
Returns
- Path
The intersection.
Line(Vector2, Vector2)
Creates a straight line, for stroking.
public static Path Line(Vector2 start, Vector2 end)
Parameters
Returns
- Path
The path.
Op(Path, PathOp, float)
Combines this path's area with another's. Each keeps its own fill rule; open contours count as closed, as fills close them. The result keeps the inputs' lines, quadratics and cubics, split exactly where outlines cross, so it is exact for PDF and SVG output; its outer contours run clockwise on screen and its holes counterclockwise, with the nonzero fill rule, and none of them cross.
public Path Op(Path other, PathOp op, float tolerance = 0.05)
Parameters
otherPathThe other path.
opPathOpThe operation.
tolerancefloatHow close, in path units, points must be to count as the same: crossings are found to well within it, and ends, crossings and curves closer than it merge, which keeps nearly touching or overlapping outlines from making slivers. A caller who will draw the result ten times larger passes a tenth of it.
Returns
- Path
The combined path.
Examples
// A frame: one round rectangle cut out of another, kept as curves, so a PDF or SVG backend writes it exactly.
Path frame = Path.RoundRect(new RoundRect(new RectF(0, 0, 200, 140), 24))
.Difference(Path.RoundRect(new RoundRect(new RectF(20, 20, 160, 100), 12)));
// Many shapes in one pass, applied left to right; overlaps and self-intersections removed; an area grown by 4 units.
Path dots = Path.Combine([Path.Circle(new Vector2(40, 40), 30), Path.Circle(new Vector2(80, 40), 30), Path.Circle(new Vector2(60, 75), 30)], PathOp.Union);
Path clean = Path.Star(new Vector2(50, 50), 45, 20, 5).Simplify();
Path halo = dots.Inflate(4, LineJoin.Round);
Exceptions
- ArgumentOutOfRangeException
opis not defined, ortoleranceis not positive and finite.
Oval(RectF)
Creates the ellipse that fills a rectangle, clockwise.
public static Path Oval(RectF bounds)
Parameters
boundsRectFThe rectangle.
Returns
- Path
The path.
ParseSvg(ReadOnlySpan<char>, FillRule, bool)
Reads SVG path data, the grammar of an SVG d attribute (SVG 1.1 and SVG 2): every command (M, L, H, V, C, S, Q, T,
A, Z and their relative forms), implicit repeats, compact numbers such as 0.5.5 and arc flags without separators.
Arcs become cubic curves, as ArcTo(Vector2, float, bool, bool, Vector2) makes them. Empty or
blank data is an empty path.
public static Path ParseSvg(ReadOnlySpan<char> data, FillRule fillRule = FillRule.NonZero, bool lenient = false)
Parameters
dataReadOnlySpan<char>The path data.
fillRuleFillRuleThe fill rule of the path; SVG's
fill-ruleis not part of the data.lenientboolWhether to keep every complete command before the first error instead of throwing, as SVG tells renderers to draw a path with an error in it.
Returns
- Path
The path.
Examples
// An icon from SVG path data, drawn four times larger, and written back as compact relative data.
Path heart = Path.ParseSvg("M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09" +
"C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z");
string d = heart.Transform(Matrix3x2.CreateScale(4)).ToSvg(new SvgPathFormat { Relative = true, Compact = true });
// Untrusted data: TryParseSvg says whether it is valid; lenient keeps what comes before an error, as browsers draw it.
bool valid = Path.TryParseSvg("M0 0 L10 10 L20", out Path? strict);
Path drawn = Path.ParseSvg("M0 0 L10 10 L20", lenient: true);
try
{
Path.ParseSvg("M0 0 X");
}
catch (SvgPathException e)
{
Console.WriteLine($"{e.Problem} at character {e.Position}");
}
Exceptions
- SvgPathException
The data breaks the grammar and
lenientis false.- ArgumentOutOfRangeException
fillRuleis not defined.
Pie(RectF, float, float)
Creates a pie slice: the arc of an ellipse closed through its center.
public static Path Pie(RectF oval, float startDegrees, float sweepDegrees)
Parameters
ovalRectFThe rectangle the ellipse fills.
startDegreesfloatWhere the arc starts.
sweepDegreesfloatHow far it turns.
Returns
- Path
The path.
Polygon(ReadOnlySpan<Vector2>, bool)
Creates a polygon through the points.
public static Path Polygon(ReadOnlySpan<Vector2> points, bool close = true)
Parameters
pointsReadOnlySpan<Vector2>The corners, in order.
closeboolWhether the last point joins the first.
Returns
- Path
The path.
Rect(RectF)
Creates a rectangle, clockwise from its top-left corner.
public static Path Rect(RectF rect)
Parameters
rectRectFThe rectangle.
Returns
- Path
The path.
Examples
canvas.FillRect(new RectF(10, 10, 80, 40), Rgba8.Red);
canvas.FillRoundRect(new RoundRect(new RectF(100, 10, 80, 40), 12), new Rgba8(0, 128, 0, 255));
// Each corner can have its own radii; radii that would overlap are scaled down together, as CSS does.
CornerRadii tab = new(topLeft: 16, topRight: 16, bottomRight: 0, bottomLeft: 0);
canvas.FillRoundRect(new RoundRect(new RectF(190, 10, 100, 40), tab), Rgba8.Black);
canvas.FillOval(new RectF(10, 60, 120, 60), new Rgba8(255, 200, 0, 255));
canvas.FillCircle(new Vector2(170, 90), 30, new Rgba8(80, 80, 200, 255));
canvas.FillArc(new RectF(210, 60, 60, 60), startDegrees: -90, sweepDegrees: 120, Rgba8.Red);
canvas.FillRing(new RoundRect(new RectF(10, 130, 60, 60), 30), new RoundRect(new RectF(25, 145, 30, 30), 15), Rgba8.Black);
// The same shapes as paths, to build on or to reuse.
Path hexagon = Path.RegularPolygon(new Vector2(120, 160), 30, sides: 6);
Path star = Path.Star(new Vector2(200, 160), outerRadius: 30, innerRadius: 12, points: 5);
canvas.FillPath(hexagon, new Rgba8(0, 150, 150, 255));
canvas.FillPath(star, new Rgba8(220, 160, 0, 255));
RegularPolygon(Vector2, float, int, float)
Creates a regular polygon with its first corner straight up from the center, turned clockwise by rotationDegrees.
public static Path RegularPolygon(Vector2 center, float radius, int sides, float rotationDegrees = 0)
Parameters
centerVector2The center.
radiusfloatThe distance from the center to each corner.
sidesintThe number of sides, at least 3.
rotationDegreesfloatThe clockwise turn.
Returns
- Path
The path.
Exceptions
- ArgumentOutOfRangeException
sidesis below 3.
Reverse()
Returns the path with each contour running the other way; contours keep their order and closed state.
public Path Reverse()
Returns
- Path
The reversed path.
Ring(RoundRect, RoundRect)
Creates the ring between two round rectangles: outer clockwise and inner counterclockwise,
with EvenOdd, which is what Skia's drawDRRect draws and what PDF writes directly. The inner shape
should lie inside the outer one.
public static Path Ring(RoundRect outer, RoundRect inner)
Parameters
Returns
- Path
The path.
RoundRect(RoundRect)
Creates a round rectangle, clockwise.
public static Path RoundRect(RoundRect roundRect)
Parameters
roundRectRoundRectThe round rectangle.
Returns
- Path
The path.
Simplify(float)
Returns the same area as this path, by its fill rule, as contours that neither overlap nor cross themselves or each other: outer contours clockwise on screen, holes counterclockwise, with the nonzero fill rule, curves kept as Op(Path, PathOp, float) keeps them.
public Path Simplify(float tolerance = 0.05)
Parameters
tolerancefloatHow close points must be to count as the same, as Op(Path, PathOp, float) describes.
Returns
- Path
The simplified path.
Examples
// A frame: one round rectangle cut out of another, kept as curves, so a PDF or SVG backend writes it exactly.
Path frame = Path.RoundRect(new RoundRect(new RectF(0, 0, 200, 140), 24))
.Difference(Path.RoundRect(new RoundRect(new RectF(20, 20, 160, 100), 12)));
// Many shapes in one pass, applied left to right; overlaps and self-intersections removed; an area grown by 4 units.
Path dots = Path.Combine([Path.Circle(new Vector2(40, 40), 30), Path.Circle(new Vector2(80, 40), 30), Path.Circle(new Vector2(60, 75), 30)], PathOp.Union);
Path clean = Path.Star(new Vector2(50, 50), 45, 20, 5).Simplify();
Path halo = dots.Inflate(4, LineJoin.Round);
Exceptions
- ArgumentOutOfRangeException
toleranceis not positive and finite.
Star(Vector2, float, float, int, float)
Creates a star whose points lie on outerRadius and whose inner corners lie on innerRadius,
the first point straight up, turned clockwise by rotationDegrees.
public static Path Star(Vector2 center, float outerRadius, float innerRadius, int points, float rotationDegrees = 0)
Parameters
centerVector2The center.
outerRadiusfloatThe distance to each point.
innerRadiusfloatThe distance to each inner corner.
pointsintThe number of points, at least 2.
rotationDegreesfloatThe clockwise turn.
Returns
- Path
The path.
Exceptions
- ArgumentOutOfRangeException
pointsis below 2.
ToString()
Returns a string that represents the current object.
public override string ToString()
Returns
- string
A string that represents the current object.
ToSvg(SvgPathFormat?)
Writes the path as SVG path data, for a d attribute. Lines, quadratics and cubics are written as L, Q and C (H and V
for straight lines along an axis when compact); arcs were cubics already. The fill rule is not part of path data.
public string ToSvg(SvgPathFormat? format = null)
Parameters
formatSvgPathFormatHow to write it; null for absolute commands with three decimals.
Returns
- string
The path data.
Examples
// An icon from SVG path data, drawn four times larger, and written back as compact relative data.
Path heart = Path.ParseSvg("M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09" +
"C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z");
string d = heart.Transform(Matrix3x2.CreateScale(4)).ToSvg(new SvgPathFormat { Relative = true, Compact = true });
// Untrusted data: TryParseSvg says whether it is valid; lenient keeps what comes before an error, as browsers draw it.
bool valid = Path.TryParseSvg("M0 0 L10 10 L20", out Path? strict);
Path drawn = Path.ParseSvg("M0 0 L10 10 L20", lenient: true);
try
{
Path.ParseSvg("M0 0 X");
}
catch (SvgPathException e)
{
Console.WriteLine($"{e.Problem} at character {e.Position}");
}
Transform(Homography)
Returns the path under a perspective transform. Lines stay lines; curves are split until each piece's projected cubic stays within a hundredth of a unit of the true projection, since a projected Bézier curve is no longer polynomial.
public Path Transform(Homography homography)
Parameters
homographyHomographyThe transform.
Returns
- Path
The transformed path.
Exceptions
- ArgumentException
A point maps to infinity or behind the viewer.
Transform(Matrix3x2)
Returns the path with every point transformed; the fill rule is kept.
public Path Transform(Matrix3x2 matrix)
Parameters
matrixMatrix3x2The affine transform.
Returns
- Path
The transformed path.
TryParseSvg(ReadOnlySpan<char>, out Path?)
Reads SVG path data as ParseSvg(ReadOnlySpan<char>, FillRule, bool) does, returning whether it was valid instead of throwing.
public static bool TryParseSvg(ReadOnlySpan<char> data, out Path? path)
Parameters
dataReadOnlySpan<char>The path data.
pathPathThe path, with the nonzero fill rule, or null when the data breaks the grammar.
Returns
Examples
// An icon from SVG path data, drawn four times larger, and written back as compact relative data.
Path heart = Path.ParseSvg("M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09" +
"C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z");
string d = heart.Transform(Matrix3x2.CreateScale(4)).ToSvg(new SvgPathFormat { Relative = true, Compact = true });
// Untrusted data: TryParseSvg says whether it is valid; lenient keeps what comes before an error, as browsers draw it.
bool valid = Path.TryParseSvg("M0 0 L10 10 L20", out Path? strict);
Path drawn = Path.ParseSvg("M0 0 L10 10 L20", lenient: true);
try
{
Path.ParseSvg("M0 0 X");
}
catch (SvgPathException e)
{
Console.WriteLine($"{e.Problem} at character {e.Position}");
}
Union(Path, float)
Returns the area of this path or the other, as Op(Path, PathOp, float) describes.
public Path Union(Path other, float tolerance = 0.05)
Parameters
Returns
- Path
The union.
WithFillRule(FillRule)
Returns the same outline with another fill rule.
public Path WithFillRule(FillRule fillRule)
Parameters
fillRuleFillRuleThe fill rule.
Returns
- Path
The path; this one when the rule is the same.
Exceptions
- ArgumentOutOfRangeException
fillRuleis not defined.
Xor(Path, float)
Returns the area of exactly one of this path and the other, as Op(Path, PathOp, float) describes.
public Path Xor(Path other, float tolerance = 0.05)
Parameters
Returns
- Path
The exclusive or.