Class Canvas
A surface to draw paths, shapes, strokes and images on, through a current transform and clip. The canvas itself is abstract:
RasterCanvas<TPixel> draws into pixels, and other backends (a PDF page, a recorder) keep the calls as vectors.
Every public call is checked and tracked here (the transform, the clip bounds, the save stack) and then ends in exactly one
protected On hook, which is all a backend overrides; hooks added in later versions get default bodies, so adding them
never breaks a backend.
Coordinates are float pixels, y down; the pixel at (0, 0) covers the square from 0 to 1. Calls draw when they are made. A canvas is for one thread at a time; one large call may still spread its rows over several cores.
public abstract class Canvas : IDisposable
- Inheritance
-
Canvas
- Implements
- Derived
-
RasterCanvas<TPixel>
- Inherited Members
- Extension Methods
Examples
// A canvas draws onto a raster. Calls draw when they are made; dispose the canvas when done.
using Raster<Rgba8> raster = Raster.Create(400, 300, Rgba8.White);
using (RasterCanvas<Rgba8> canvas = raster.CreateCanvas())
{
canvas.FillRect(new RectF(20, 20, 160, 100), new Rgba8(30, 90, 200, 255));
// Save and Restore bracket transform and clip changes.
canvas.Save();
canvas.Translate(260, 150);
canvas.Rotate(30);
canvas.FillRect(new RectF(-50, -30, 100, 60), new Rgba8(200, 60, 30, 200));
canvas.Restore();
canvas.FillCircle(new Vector2(100, 220), 50, Rgba8.Black);
}
// Blend edges in linear light, which keeps thin lines and translucent colors from darkening.
using RasterCanvas<Rgba8> linear = raster.CreateCanvas(new CanvasOptions { ColorSpace = ColorSpace.Linear });
Constructors
Canvas(float, float)
Initializes a new instance of the Canvas class.
protected Canvas(float width, float height)
Parameters
widthfloatThe drawing surface's width in device units.
heightfloatThe drawing surface's height in device units.
Exceptions
- ArgumentOutOfRangeException
A size is not positive and finite.
Properties
ColorGlyphRenderer
Gets the renderer for color glyph formats the canvas does not draw itself, such as OpenType SVG glyphs; none by default. A backend overrides it to draw those glyphs, as the raster backend returns ColorGlyphRenderer.
protected virtual IColorGlyphRenderer? ColorGlyphRenderer { get; }
Property Value
DeviceClipBounds
Gets the bounds of the current clip in device coordinates. Every clip so far lies inside them; a backend may draw less (a clip by a round shape, or one that cuts a hole, keeps the bounds of what it could reach).
public RectF DeviceClipBounds { get; }
Property Value
Height
Gets the drawing surface's height in device units.
public float Height { get; }
Property Value
LocalClipBounds
Gets the bounds of the current clip in local coordinates: the device clip bounds mapped back through the transform.
public RectF LocalClipBounds { get; }
Property Value
MarkedContentDepth
Gets the number of marked-content sequences begun and not yet ended.
public int MarkedContentDepth { get; }
Property Value
Matrix
Gets the current transform from local coordinates to device coordinates.
public Matrix3x2 Matrix { get; }
Property Value
SaveCount
Gets the number of saved states plus one: 1 before any Save().
public int SaveCount { get; }
Property Value
Width
Gets the drawing surface's width in device units.
public float Width { get; }
Property Value
Methods
BeginMarkedContent(MarkedContent)
Begins a marked-content sequence: everything drawn until the matching EndMarkedContent() carries the tag, as PDF's BMC or BDC. Sequences nest; keep them nested with Save() and Restore(), as PDF requires.
public void BeginMarkedContent(MarkedContent content)
Parameters
contentMarkedContentThe tag and its properties.
Examples
// A layout engine marks links, destinations and structure while it draws; pixels show none of it.
canvas.BeginMarkedContent(new MarkedContent("P") { NodeId = 12 });
canvas.DrawAnnotation(new RectF(20, 130, 120, 16), Annotation.Url("https://example.com"));
canvas.DrawAnnotation(new RectF(20, 150, 0, 0), Annotation.NamedDestination("chapter-2"));
canvas.EndMarkedContent();
Clear(Rgba8)
Replaces every pixel inside the clip with a color.
public void Clear(Rgba8 color)
Parameters
colorRgba8The color.
Clear(RgbaF32)
Replaces every pixel inside the clip with a color: DrawPaint(Paint) with Source.
public void Clear(RgbaF32 color)
Parameters
colorRgbaF32The color.
ClipPath(Path, ClipOp, bool)
Clips later drawing to the area a path covers by its fill rule, or to outside it.
public void ClipPath(Path path, ClipOp op = ClipOp.Intersect, bool antialias = true)
Parameters
pathPathThe path in local coordinates.
opClipOpWhether to keep the inside or the outside.
antialiasboolWhether the clip's edges are antialiased.
Examples
// Clips are antialiased and last until the matching Restore.
canvas.Save();
canvas.ClipPath(Path.Circle(new Vector2(100, 100), 80));
canvas.ClipRect(new RectF(60, 60, 80, 80), ClipOp.Difference); // a square hole
canvas.DrawPaint(new Rgba8(30, 90, 200, 255)); // fills the whole clip
canvas.Restore();
// Only whole pixels inside the clip are drawn after a pixel-aligned rectangle clip, at no extra cost.
canvas.ClipRect(new RectF(0, 0, 100, 200));
canvas.FillCircle(new Vector2(100, 190), 40, Rgba8.Black);
ClipRect(RectF, ClipOp, bool)
Clips later drawing to a rectangle, or to outside it, until the matching Restore().
public void ClipRect(RectF rect, ClipOp op = ClipOp.Intersect, bool antialias = true)
Parameters
rectRectFThe rectangle in local coordinates.
opClipOpWhether to keep the inside or the outside.
antialiasboolWhether the clip's edges are antialiased.
Examples
// Clips are antialiased and last until the matching Restore.
canvas.Save();
canvas.ClipPath(Path.Circle(new Vector2(100, 100), 80));
canvas.ClipRect(new RectF(60, 60, 80, 80), ClipOp.Difference); // a square hole
canvas.DrawPaint(new Rgba8(30, 90, 200, 255)); // fills the whole clip
canvas.Restore();
// Only whole pixels inside the clip are drawn after a pixel-aligned rectangle clip, at no extra cost.
canvas.ClipRect(new RectF(0, 0, 100, 200));
canvas.FillCircle(new Vector2(100, 190), 40, Rgba8.Black);
ClipRoundRect(RoundRect, ClipOp, bool)
Clips later drawing to a round rectangle, or to outside it.
public void ClipRoundRect(RoundRect roundRect, ClipOp op = ClipOp.Intersect, bool antialias = true)
Parameters
roundRectRoundRectThe round rectangle in local coordinates.
opClipOpWhether to keep the inside or the outside.
antialiasboolWhether the clip's edges are antialiased.
Concat(Matrix3x2)
Applies matrix before the current transform: local points go through it first.
public void Concat(Matrix3x2 matrix)
Parameters
matrixMatrix3x2The transform to add.
Exceptions
- ArgumentException
An element is not finite.
Dispose()
Restores every saved state and releases the backend's resources. Drawing afterwards throws.
public void Dispose()
Dispose(bool)
Releases the backend's resources; called once by Dispose() after every state is restored.
protected virtual void Dispose(bool disposing)
Parameters
DrawAnnotation(RectF, Annotation)
Marks an area with a link or a destination, as Skia's drawAnnotation: a PDF page writes a link annotation or a named destination; pixels show nothing. The area is in local coordinates, under the current transform.
public void DrawAnnotation(RectF area, Annotation annotation)
Parameters
areaRectFThe area: the link's clickable rectangle, or for a destination the point at its top-left corner.
annotationAnnotationThe link or destination.
Examples
// A layout engine marks links, destinations and structure while it draws; pixels show none of it.
canvas.BeginMarkedContent(new MarkedContent("P") { NodeId = 12 });
canvas.DrawAnnotation(new RectF(20, 130, 120, 16), Annotation.Url("https://example.com"));
canvas.DrawAnnotation(new RectF(20, 150, 0, 0), Annotation.NamedDestination("chapter-2"));
canvas.EndMarkedContent();
Exceptions
- ArgumentException
areais not finite.
DrawAtlas(Raster, ReadOnlySpan<SpriteTransform>, ReadOnlySpan<RectF>, ReadOnlySpan<RgbaF32>, BlendMode, SamplingOptions?, RectF?, Paint?)
Draws many sprites from one image in one call, as Skia's drawAtlas: sprite i is sources[i] of the image,
placed by transforms[i], and, when there are colors, combined with colors[i] by
colorBlend, the color as the destination and the sprite as the source. Each sprite blends with what lies
below on its own, so overlapping particles add up. Samples near a sprite's edge may read the image just outside its source
rectangle, as in Skia; leave a pixel of space between sprites when that matters.
public void DrawAtlas(Raster image, ReadOnlySpan<SpriteTransform> transforms, ReadOnlySpan<RectF> sources, ReadOnlySpan<RgbaF32> colors = default, BlendMode colorBlend = BlendMode.Modulate, SamplingOptions? sampling = null, RectF? cull = null, Paint? paint = null)
Parameters
imageRasterThe sprite sheet.
transformsReadOnlySpan<SpriteTransform>Where each sprite goes.
sourcesReadOnlySpan<RectF>Each sprite's part of the image, in its pixels.
colorsReadOnlySpan<RgbaF32>One straight-alpha color per sprite, or empty for none.
colorBlendBlendModeHow a sprite combines with its color.
samplingSamplingOptionsHow the image is sampled; Egami's default sampling when null.
cullRectF?Bounds of all the sprites, in local coordinates, when the caller knows them: a call outside the clip is skipped at once.
paintPaintThe blend mode, opacity and antialiasing; its color is not used.
Examples
// Pack the sprites once, then draw thousands of them in one call: each with its own place, turn, scale and tint.
using SpriteSheet sheet = SpriteSheet.Pack([spark, star], new SpritePackOptions { Padding = 1 });
Random random = new(7);
SpriteTransform[] transforms = new SpriteTransform[2000];
RectF[] sources = new RectF[transforms.Length];
RgbaF32[] tints = new RgbaF32[transforms.Length];
for (int i = 0; i < transforms.Length; i++)
{
Rect frame = sheet.Frames[i % 2];
sources[i] = frame;
transforms[i] = SpriteTransform.FromRotationScale(
scale: 0.3f + random.NextSingle(),
degrees: random.NextSingle() * 360,
position: new Vector2(random.NextSingle() * 320, random.NextSingle() * 200),
anchor: new Vector2(frame.Width / 2f, frame.Height / 2f));
tints[i] = new RgbaF32(1, 0.5f + (random.NextSingle() / 2), random.NextSingle(), 0.8f);
}
using (RasterCanvas<Rgba8> canvas = raster.CreateCanvas())
{
// Plus adds the sprites up, as light does.
canvas.DrawAtlas(sheet.Image, transforms, sources, tints, BlendMode.Modulate, paint: new SolidPaint(Rgba8.White) { BlendMode = BlendMode.Plus });
}
Exceptions
- ArgumentException
The lists' lengths differ, or a number is not finite.
DrawColorGlyphs(GlyphRun, Vector2, Paint)
Draws a run's color glyphs (COLR, sbix, CBDT) with canvas calls (clips, fills, gradients, layers and images) and returns the run of the glyphs left to draw as outlines.
protected GlyphRun DrawColorGlyphs(GlyphRun run, Vector2 origin, Paint paint)
Parameters
runGlyphRunThe glyphs.
originVector2Where the run's origin goes, in local coordinates.
paintPaintThe paint, whose alpha color glyphs use.
Returns
- GlyphRun
The run's other glyphs, or the run itself when it has no color glyphs.
DrawGlyphRun(GlyphRun, Vector2, Paint)
Draws positioned glyphs, such as a run from Shape(string, ShapingOptions?) or a layout engine.
public void DrawGlyphRun(GlyphRun run, Vector2 origin, Paint paint)
Parameters
runGlyphRunThe glyphs.
originVector2Where the run's origin goes, in local coordinates.
paintPaintThe paint.
Exceptions
- ArgumentException
originis not finite.
DrawImage(EncodedImage, RectF, RectF, SamplingOptions?, Paint?)
Draws part of an encoded image, such as a JPEG, scaled to fill a local rectangle. A document backend may store the file's bytes as they are; other backends draw the decoded pixels, as DrawImage(Raster, RectF, RectF, SamplingOptions?, Paint?).
public void DrawImage(EncodedImage image, RectF source, RectF destination, SamplingOptions? sampling = null, Paint? paint = null)
Parameters
imageEncodedImageThe image.
sourceRectFThe part of the image to draw, in its pixels; clipped to the image.
destinationRectFWhere it goes.
samplingSamplingOptionsHow the image is sampled; Egami's default sampling when null.
paintPaintThe blend mode, opacity and antialiasing; its color is not used.
Examples
// A JPEG kept as its bytes: a PDF backend embeds them as they are; a raster canvas draws the decoded pixels.
EncodedImage jpeg = EncodedImage.Load("photo.jpg");
canvas.DrawImage(jpeg, new RectF(20, 20, 128, 96));
Exceptions
- ArgumentException
A rectangle is not finite.
DrawImage(EncodedImage, RectF, SamplingOptions?, Paint?)
Draws a whole encoded image scaled to fill a local rectangle.
public void DrawImage(EncodedImage image, RectF destination, SamplingOptions? sampling = null, Paint? paint = null)
Parameters
imageEncodedImageThe image.
destinationRectFWhere it goes.
samplingSamplingOptionsHow the image is sampled; Egami's default sampling when null.
paintPaintThe blend mode, opacity and antialiasing; its color is not used.
Examples
// A JPEG kept as its bytes: a PDF backend embeds them as they are; a raster canvas draws the decoded pixels.
EncodedImage jpeg = EncodedImage.Load("photo.jpg");
canvas.DrawImage(jpeg, new RectF(20, 20, 128, 96));
DrawImage(Raster, RectF, RectF, SamplingOptions?, Paint?)
Draws part of an image scaled to fill a local rectangle, under the current transform and clip. Samples near the source rectangle's edges read only pixels inside it.
public void DrawImage(Raster image, RectF source, RectF destination, SamplingOptions? sampling = null, Paint? paint = null)
Parameters
imageRasterThe image.
sourceRectFThe part of the image to draw, in its pixels; clipped to the image.
destinationRectFWhere it goes.
samplingSamplingOptionsHow the image is sampled; Egami's default sampling when null.
paintPaintThe blend mode, opacity and antialiasing; its color is not used.
Examples
using Raster photo = Raster.Load("duck.png");
using Raster<Rgba8> page = Raster.Create(800, 600, Rgba8.White);
using (RasterCanvas<Rgba8> canvas = page.CreateCanvas())
{
// At its own size: whole-pixel placement blends exactly as DrawImage does.
canvas.DrawImage(photo, new Vector2(10, 10));
// Scaled into a rectangle and turned, sampled with any of Egami's filters.
canvas.Save();
canvas.Rotate(-8, new Vector2(500, 300));
canvas.DrawImage(photo, new RectF(350, 150, 300, 300 * photo.Height / photo.Width), new SamplingOptions { Filter = ResampleFilter.Lanczos3 });
canvas.Restore();
// A part of the image, at half opacity.
canvas.DrawImage(photo, source: new RectF(0, 0, 100, 100), destination: new RectF(10, 450, 140, 140), paint: new SolidPaint(Rgba8.Black) { Opacity = 0.5f });
}
Exceptions
- ArgumentException
A rectangle is not finite.
DrawImage(Raster, RectF, SamplingOptions?, Paint?)
Draws a whole image scaled to fill a local rectangle.
public void DrawImage(Raster image, RectF destination, SamplingOptions? sampling = null, Paint? paint = null)
Parameters
imageRasterThe image.
destinationRectFWhere it goes.
samplingSamplingOptionsHow the image is sampled; Egami's default sampling when null.
paintPaintThe blend mode, opacity and antialiasing; its color is not used.
Examples
using Raster photo = Raster.Load("duck.png");
using Raster<Rgba8> page = Raster.Create(800, 600, Rgba8.White);
using (RasterCanvas<Rgba8> canvas = page.CreateCanvas())
{
// At its own size: whole-pixel placement blends exactly as DrawImage does.
canvas.DrawImage(photo, new Vector2(10, 10));
// Scaled into a rectangle and turned, sampled with any of Egami's filters.
canvas.Save();
canvas.Rotate(-8, new Vector2(500, 300));
canvas.DrawImage(photo, new RectF(350, 150, 300, 300 * photo.Height / photo.Width), new SamplingOptions { Filter = ResampleFilter.Lanczos3 });
canvas.Restore();
// A part of the image, at half opacity.
canvas.DrawImage(photo, source: new RectF(0, 0, 100, 100), destination: new RectF(10, 450, 140, 140), paint: new SolidPaint(Rgba8.Black) { Opacity = 0.5f });
}
DrawImage(Raster, Vector2, SamplingOptions?, Paint?)
Draws a whole image with its top-left corner at a local point, at its own size in local units.
public void DrawImage(Raster image, Vector2 position, SamplingOptions? sampling = null, Paint? paint = null)
Parameters
imageRasterThe image.
positionVector2Where its top-left corner goes.
samplingSamplingOptionsHow the image is sampled when it is transformed; Egami's default sampling when null.
paintPaintThe blend mode, opacity and antialiasing of the image's edges; its color is not used. null draws source-over.
Examples
using Raster photo = Raster.Load("duck.png");
using Raster<Rgba8> page = Raster.Create(800, 600, Rgba8.White);
using (RasterCanvas<Rgba8> canvas = page.CreateCanvas())
{
// At its own size: whole-pixel placement blends exactly as DrawImage does.
canvas.DrawImage(photo, new Vector2(10, 10));
// Scaled into a rectangle and turned, sampled with any of Egami's filters.
canvas.Save();
canvas.Rotate(-8, new Vector2(500, 300));
canvas.DrawImage(photo, new RectF(350, 150, 300, 300 * photo.Height / photo.Width), new SamplingOptions { Filter = ResampleFilter.Lanczos3 });
canvas.Restore();
// A part of the image, at half opacity.
canvas.DrawImage(photo, source: new RectF(0, 0, 100, 100), destination: new RectF(10, 450, 140, 140), paint: new SolidPaint(Rgba8.Black) { Opacity = 0.5f });
}
DrawImageLattice(Raster, Lattice, RectF, SamplingOptions?, Paint?)
Draws an image through a Lattice: fixed columns and rows keep their size, stretchable ones share the rest of the destination, and cells may be left out or filled with a color. Neighboring cells meet without seams: the raster backend draws the cells as one textured mesh, with antialiasing on its outer edge only.
public void DrawImageLattice(Raster image, Lattice lattice, RectF destination, SamplingOptions? sampling = null, Paint? paint = null)
Parameters
imageRasterThe image.
latticeLatticeThe grid.
destinationRectFWhere the image goes, in local coordinates.
samplingSamplingOptionsHow the image is sampled; Egami's default sampling when null.
paintPaintThe blend mode, opacity and antialiasing; its color is not used.
Examples
using (RasterCanvas<Rgba8> canvas = raster.CreateCanvas())
{
// An Android .9.png: its one-pixel border marks what stretches. The corners stay sharp at any size.
(Raster inner, Lattice lattice, Rect? padding) = NinePatch.FromAndroid(file);
canvas.DrawImageLattice(inner, lattice, new RectF(10, 10, 180, 48));
inner.Dispose();
// A nine-patch from a plain image: the 8-pixel corners keep their size, the edges and center stretch.
canvas.DrawImageNinePatch(frame, new Rect(8, 8, 8, 8), new RectF(10, 70, 120, 40));
// A lattice can leave cells out or fill them with a color: here the center becomes a flat panel.
LatticeCellKind[] kinds = [.. Enumerable.Repeat(LatticeCellKind.Default, 9)];
kinds[4] = LatticeCellKind.FixedColor;
RgbaF32[] colors = new RgbaF32[9];
colors[4] = new RgbaF32(0.95f, 0.95f, 0.9f, 1);
canvas.DrawImageLattice(frame, new Lattice([8, 16], [8, 16]) { CellKinds = kinds, CellColors = colors }, new RectF(150, 70, 140, 40));
}
Exceptions
- ArgumentException
The lattice does not fit the image, or
destinationis not finite.
DrawImageMask(Raster, RectF, Paint, SamplingOptions?)
Paints through an image used as a mask, as PDF's stencil masks (/ImageMask): the mask, scaled to fill a local rectangle, gives each pixel's coverage, and the paint fills what it covers. A mask with alpha covers by its alpha; one without, a 1-bit or gray image, covers by its gray level, so white paints and black does not. Scanned text and icons are drawn this way.
public void DrawImageMask(Raster mask, RectF destination, Paint paint, SamplingOptions? sampling = null)
Parameters
maskRasterThe mask image.
destinationRectFWhere the mask goes, in local coordinates.
paintPaintWhat fills the covered pixels, with its blend mode, opacity and antialiasing of the rectangle's edges.
samplingSamplingOptionsHow the mask is sampled; Egami's default sampling when null. Nearest keeps a 1-bit mask hard.
Examples
// A 1-bit scan as a stencil: its white pixels paint in ink, its black ones leave the page as it is.
using Raster<Gray8> scan = Raster.Create(32, 16, new Gray8(255));
canvas.DrawImageMask(scan, new RectF(160, 20, 64, 32), new Rgba8(20, 40, 120, 255), new SamplingOptions { Filter = ResampleFilter.Nearest });
// A knockout group: each shape composites with what the group started as, so overlapping translucent shapes do not darken.
canvas.SaveLayer(new LayerOptions { Knockout = true });
canvas.FillCircle(new Vector2(180, 100), 20, new RgbaF32(1, 0, 0, 0.5f));
canvas.FillCircle(new Vector2(200, 100), 20, new RgbaF32(0, 0, 1, 0.5f));
canvas.Restore();
Exceptions
- ArgumentException
destinationis not finite.
DrawImageNinePatch(Raster, Rect, RectF, SamplingOptions?, Paint?)
Draws an image stretched to a rectangle while its corners keep their size: the edges stretch one way and the center both ways, as Skia's drawImageNine. When the destination is smaller than the corners, they shrink in proportion. It is DrawImageLattice(Raster, Lattice, RectF, SamplingOptions?, Paint?) with NinePatch(Rect).
public void DrawImageNinePatch(Raster image, Rect center, RectF destination, SamplingOptions? sampling = null, Paint? paint = null)
Parameters
imageRasterThe image.
centerRectThe part of the image that stretches both ways, in its pixels.
destinationRectFWhere the image goes, in local coordinates.
samplingSamplingOptionsHow the image is sampled; Egami's default sampling when null.
paintPaintThe blend mode, opacity and antialiasing; its color is not used.
Examples
using (RasterCanvas<Rgba8> canvas = raster.CreateCanvas())
{
// An Android .9.png: its one-pixel border marks what stretches. The corners stay sharp at any size.
(Raster inner, Lattice lattice, Rect? padding) = NinePatch.FromAndroid(file);
canvas.DrawImageLattice(inner, lattice, new RectF(10, 10, 180, 48));
inner.Dispose();
// A nine-patch from a plain image: the 8-pixel corners keep their size, the edges and center stretch.
canvas.DrawImageNinePatch(frame, new Rect(8, 8, 8, 8), new RectF(10, 70, 120, 40));
// A lattice can leave cells out or fill them with a color: here the center becomes a flat panel.
LatticeCellKind[] kinds = [.. Enumerable.Repeat(LatticeCellKind.Default, 9)];
kinds[4] = LatticeCellKind.FixedColor;
RgbaF32[] colors = new RgbaF32[9];
colors[4] = new RgbaF32(0.95f, 0.95f, 0.9f, 1);
canvas.DrawImageLattice(frame, new Lattice([8, 16], [8, 16]) { CellKinds = kinds, CellColors = colors }, new RectF(150, 70, 140, 40));
}
Exceptions
- ArgumentException
centeris empty or lies outside the image, ordestinationis not finite.
DrawLine(Vector2, Vector2, Stroke, Paint)
Strokes a straight line.
public void DrawLine(Vector2 start, Vector2 end, Stroke stroke, Paint paint)
Parameters
startVector2Where it starts.
endVector2Where it ends.
strokeStrokeThe pen.
paintPaintThe paint.
Examples
// The pen is data: width, caps, joins and miter limit. Width 1, butt caps and miter joins by default.
Stroke thick = new(12) { Cap = LineCap.Round, Join = LineJoin.Round };
canvas.DrawLine(new Vector2(20, 20), new Vector2(280, 20), thick, new Rgba8(30, 90, 200, 255));
Path zigzag = Path.Polygon([new(20, 120), new(80, 60), new(140, 120), new(200, 60), new(260, 120)], close: false);
canvas.StrokePath(zigzag, new Stroke(8) { Join = LineJoin.Miter, MiterLimit = 4 }, Rgba8.Black);
// A hairline is one device pixel wide whatever the transform.
canvas.StrokeCircle(new Vector2(150, 160), 30, Stroke.Hairline, Rgba8.Red);
DrawMesh(Mesh, Paint?, BlendMode, ReadOnlySpan<GradientStop>)
Draws a triangle mesh. The triangles' union is filled once, with antialiasing on its outer edges only, so meshes show no
cracks between triangles; where triangles overlap, the later one's color shows. Each pixel's color comes from its
triangle: the paint (mapped through the texture coordinates when the mesh has them), combined with the interpolated vertex
color by colorBlend, the vertex color as the destination and the paint as the source. A solid paint's color
is not used when the mesh has colors or values, as in Skia: its alpha scales them. Vertex colors interpolate in the canvas's
color space, premultiplied; values interpolate and then look up valueStops.
public void DrawMesh(Mesh mesh, Paint? paint = null, BlendMode colorBlend = BlendMode.Modulate, ReadOnlySpan<GradientStop> valueStops = default)
Parameters
meshMeshThe mesh, in local coordinates.
paintPaintThe paint, with the blend mode, opacity and antialiasing of the whole mesh; an opaque white solid paint when null, which draws the vertex colors as they are.
colorBlendBlendModeHow the vertex colors combine with the paint.
valueStopsReadOnlySpan<GradientStop>The stops vertex values map through, as a gradient's, clamped to 0 to 1; needed when the mesh has values.
Examples
using (RasterCanvas<Rgba8> canvas = raster.CreateCanvas())
{
// Gouraud shading: a color at each corner, blended across each triangle without cracks between them.
Vector2[] quad = [new(10, 10), new(140, 20), new(130, 140), new(20, 120)];
RgbaF32[] colors = [new(1, 0.2f, 0.2f, 1), new(1, 0.9f, 0.2f, 1), new(0.2f, 0.6f, 1, 1), new(0.6f, 0.2f, 0.9f, 1)];
canvas.DrawMesh(new Mesh(MeshMode.Fan, quad, colors), Rgba8.White);
// A textured mesh: each vertex says which point of the paint lands on it, here bending a photo into a trapezoid.
Vector2[] trapezoid = [new(170, 20), new(280, 30), new(290, 140), new(160, 130)];
Vector2[] corners = [new(0, 0), new(64, 0), new(64, 64), new(0, 64)];
canvas.DrawMesh(new Mesh(MeshMode.Fan, trapezoid, texCoords: corners), new ImagePaint(photo));
}
Exceptions
- ArgumentException
The mesh has values and no stops are given.
DrawPaint(Paint)
Fills the whole clip with a paint.
public void DrawPaint(Paint paint)
Parameters
paintPaintThe paint.
DrawPatch(Patch, Paint?, BlendMode, ReadOnlySpan<GradientStop>)
Draws a Coons or tensor-product patch: the surface is cut into triangles under the current transform until each strays less than a quarter of a device pixel from it, and drawn as a mesh, so DrawMesh(Mesh, Paint?, BlendMode, ReadOnlySpan<GradientStop>)'s rules apply. Where the patch folds over itself, larger v covers smaller v, and for equal v larger u covers smaller u, as PDF requires.
public void DrawPatch(Patch patch, Paint? paint = null, BlendMode colorBlend = BlendMode.Modulate, ReadOnlySpan<GradientStop> valueStops = default)
Parameters
patchPatchThe patch, in local coordinates.
paintPaintThe paint; an opaque white solid paint when null, which draws the corner colors as they are.
colorBlendBlendModeHow the corner colors combine with the paint.
valueStopsReadOnlySpan<GradientStop>The stops corner values map through; needed when the patch has values.
Examples
// A Coons patch: four cubic edges, clockwise from the top-left corner, and a color at each corner.
Vector2[] edges =
[
new(20, 20), new(80, 0), new(150, 40), new(220, 20), // top, left to right
new(200, 80), new(240, 140), new(220, 180), // right, down
new(150, 160), new(80, 200), new(20, 180), // bottom, right to left
new(40, 120), new(0, 60), // left, up
];
Patch patch = Patch.Coons(edges, [new RgbaF32(1, 0, 0, 1), new RgbaF32(1, 1, 0, 1), new RgbaF32(0, 0, 1, 1), new RgbaF32(0, 1, 0, 1)]);
using (RasterCanvas<Rgba8> canvas = raster.CreateCanvas())
{
canvas.DrawPatch(patch);
// PDF's function-based shadings give each corner a value instead, mapped through a gradient's stops.
Patch shaded = Patch.Tensor(patch.ToTensorNet().Span, cornerValues: [0, 0.3f, 1, 0.6f]);
canvas.Translate(10, 10);
canvas.Scale(0.3f);
canvas.DrawPatch(shaded, valueStops: [new GradientStop(0, Rgba8.Black), new GradientStop(1, Rgba8.White)]);
}
Exceptions
- ArgumentException
The patch has values and no stops are given.
DrawPicture(Picture, Matrix3x2?, Paint?, bool)
Replays a recorded Picture: inside a Save() and Restore(), with matrix
added to the transform, so it stays sharp at any scale. A paint with an opacity below 1 or a blend mode other than
source-over merges the picture as one layer bounded by its Bounds, as Skia's drawPicture does.
public void DrawPicture(Picture picture, Matrix3x2? matrix = null, Paint? paint = null, bool cacheAsImage = false)
Parameters
picturePictureThe picture.
matrixMatrix3x2?A transform applied before the canvas's, or null for none.
paintPaintThe opacity and blend mode of the whole picture, or null to replay it directly.
cacheAsImageboolWhether to draw the picture as an image rendered at the current scale and kept with the picture, for a picture repeated many times at one scale (a map's symbols, a game's sprites). The image is resampled under rotation and skew.
Examples
// Record any picture code once...
PictureRecorder recorder = new(new RectF(0, 0, 40, 40));
DrawLogo(recorder);
Picture logo = recorder.Finish();
using (RasterCanvas<Rgba8> canvas = raster.CreateCanvas())
{
// ...and replay it anywhere, at any scale, still sharp: replay is vector.
canvas.DrawPicture(logo, Matrix3x2.CreateTranslation(10, 10));
canvas.DrawPicture(logo, Matrix3x2.CreateScale(4) * Matrix3x2.CreateTranslation(60, 10));
// A paint with an opacity merges the whole picture as one layer.
canvas.DrawPicture(logo, Matrix3x2.CreateTranslation(240, 10), new SolidPaint(Rgba8.White) { Opacity = 0.4f });
// Drawn many times at one scale, a cached image is cheaper than replaying.
for (int i = 0; i < 6; i++)
{
canvas.DrawPicture(logo, Matrix3x2.CreateTranslation(230 + (i * 25), 150), cacheAsImage: true);
}
}
using Raster<Rgba8> icon = logo.ToRaster<Rgba8>(scale: 2);
Exceptions
- ArgumentException
An element of
matrixis not finite.
DrawPoints(PointMode, ReadOnlySpan<Vector2>, Stroke, Paint)
Draws points as dots, pairs as lines, or all of them as one polyline, with a pen.
public void DrawPoints(PointMode mode, ReadOnlySpan<Vector2> points, Stroke stroke, Paint paint)
Parameters
modePointModeHow the points are read.
pointsReadOnlySpan<Vector2>The points.
strokeStrokeThe pen: its width sizes the dots and lines.
paintPaintThe paint.
DrawRendererGlyphs(GlyphRun, Vector2, Paint)
Draws the glyphs of a run that ColorGlyphRenderer draws (those without a COLR version 1 glyph, which comes first) and returns the run of the rest.
protected GlyphRun DrawRendererGlyphs(GlyphRun run, Vector2 origin, Paint paint)
Parameters
runGlyphRunThe glyphs.
originVector2Where the run's origin goes, in local coordinates.
paintPaintThe paint.
Returns
- GlyphRun
The run's other glyphs, or the run itself when the renderer draws none of them.
DrawShadow(Path, ShadowOptions?)
Draws the shadow a shape casts when it floats above the canvas, after Skia's shadow utilities: an ambient shadow (the shape grown and blurred by its elevation) and a spot shadow (the shape moved away from the light and scaled, blurred by the light's size), each in its own color. Draw the shape itself afterwards. Every backend gets it through ordinary calls: a layer with a blur filter for the ambient shadow and a blurred fill for the spot shadow.
public void DrawShadow(Path path, ShadowOptions? options = null)
Parameters
pathPathThe shape, in local coordinates.
optionsShadowOptionsThe elevation, light and colors; Material Design's defaults when null.
Examples
using (RasterCanvas<Rgba8> canvas = raster.CreateCanvas())
{
// A card floating 12 pixels up: its ambient and spot shadows, then the card itself.
RoundRect card = new(new RectF(60, 40, 180, 110), 10);
canvas.DrawShadow(Path.RoundRect(card), new ShadowOptions { Elevation = 12 });
canvas.FillRoundRect(card, Rgba8.White);
// A whole group's drop shadow: a layer with a drop shadow filter that keeps the layer's size.
canvas.SaveLayer(new LayerOptions { Filter = ops => ops.DropShadow(3, 4, 3, new Rgba8(0, 0, 0, 90), new DropShadowOptions { Expand = false }) });
canvas.FillCircle(new Vector2(110, 95), 20, new Rgba8(230, 80, 40, 255));
canvas.FillRect(new RectF(140, 80, 60, 30), new Rgba8(40, 120, 220, 255));
canvas.Restore();
}
DrawShapedText(ShapedText, Vector2, Paint)
Draws text shaped once by Shape(string, Font, ShapingOptions?), so it can be measured, kept and drawn again without shaping it twice: each line one line height below the one before (a vertical line one line height to the left), its runs from left to right, each run a GlyphRun in its own font.
public void DrawShapedText(ShapedText text, Vector2 origin, Paint paint)
Parameters
textShapedTextThe shaped text.
originVector2The left end of the first line's alphabetic baseline, in local coordinates.
paintPaintThe paint.
Examples
// Shape once, measure the result, draw it centred: no second shaping pass.
using Raster<Rgba8> banner = Raster.Create(640, 120, Rgba8.White);
Egami.Fonts.Shaping.ShapedText title = Egami.Fonts.Shaping.TextShaper.Shape("Office hours: 9 to 17", font);
var origin = new Vector2((banner.Width - title.Advance) / 2, 70);
using (RasterCanvas<Rgba8> canvas = banner.CreateCanvas())
{
canvas.DrawShapedText(title, origin, Rgba8.Black);
}
Exceptions
- ArgumentException
originis not finite.
DrawText(RichText, RectF, ParagraphStyle, VerticalAlign)
Lays rich text out to the width of rect (its height for vertical lines) and draws it there, placed across
the lines by verticalAlign: the same as Layout(float) and
DrawTextLayout(TextLayout, Vector2, RectF?, PlaceholderPainter?). Nothing is clipped; lines past the rectangle are drawn unless the style limits them.
public TextLayout DrawText(RichText text, RectF rect, ParagraphStyle style, VerticalAlign verticalAlign = VerticalAlign.Top)
Parameters
textRichTextThe text with its spans and placeholders.
rectRectFThe rectangle, in local coordinates.
styleParagraphStyleThe paragraph's style.
verticalAlignVerticalAlignWhere the lines go across the rectangle.
Returns
- TextLayout
The layout drawn, for hit testing and measuring.
Exceptions
- ArgumentNullException
textorstyleis null.- ArgumentException
Some text has no font families, or a style's paint is not an Egami.Drawing Paint.
DrawText(string, RectF, ParagraphStyle, VerticalAlign)
Lays text out to the width of rect (its height for vertical lines) and draws it there, placed across the
lines by verticalAlign: the same as Layout(float) and
DrawTextLayout(TextLayout, Vector2, RectF?, PlaceholderPainter?). Nothing is clipped; lines past the rectangle are drawn unless the style limits them.
public TextLayout DrawText(string text, RectF rect, ParagraphStyle style, VerticalAlign verticalAlign = VerticalAlign.Top)
Parameters
textstringThe text.
rectRectFThe rectangle, in local coordinates.
styleParagraphStyleThe paragraph's style.
verticalAlignVerticalAlignWhere the lines go across the rectangle.
Returns
- TextLayout
The layout drawn, for hit testing and measuring.
Examples
ParagraphStyle style = new()
{
DefaultStyle = new TextStyle
{
FontFamilies = [family],
Size = 18,
Color = new Rgba8(30, 30, 60, 255),
Shadows = [new TextShadow(new System.Numerics.Vector2(1, 1), 2, new Rgba8(0, 0, 0, 80))],
},
Align = TextAlign.Justify,
};
// One call: wrap into a box, centred across the lines.
TextLayout boxed = canvas.DrawText("Paragraphs wrap, justify and drop shadows.", new RectF(10, 10, 300, 80), style, VerticalAlign.Center);
// Or lay out once and draw where needed, skipping lines outside the visible part.
TextLayout layout = Paragraph.Create("Laid out once, drawn many times.", style).Layout(300);
canvas.DrawTextLayout(layout, new System.Numerics.Vector2(10, 110), visibleRect: new RectF(0, 0, 320, 200));
// Inline placeholders (an icon, a badge) are drawn by a painter called with each one's box.
canvas.DrawTextLayout(layout, new System.Numerics.Vector2(10, 130), placeholderPainter: (c, index, box) => c.FillRoundRect(new RoundRect(box, 3), new Rgba8(200, 60, 40, 255)));
// The same text as one path: glyphs and decorations.
Egami.Drawing.Path outline = layout.ToPath(new System.Numerics.Vector2(10, 150));
Exceptions
- ArgumentNullException
textorstyleis null.- ArgumentException
The text has no font families, or a style's paint is not an Egami.Drawing Paint.
DrawText(string, Vector2, Font, Paint, ShapingOptions?)
Draws text: Shape(string, ShapingOptions?) shapes it (ligatures, kerning, marks, the script rules, bidi
order, one line per hard break), then DrawGlyphRun(GlyphRun, Vector2, Paint) draws the run. With fallback or
span fonts in options, the text is shaped by Shape(string, Font, ShapingOptions?)
and drawn as DrawShapedText(ShapedText, Vector2, Paint) draws it. Nothing wraps; without fallback a character
the face lacks draws as its .notdef glyph.
public void DrawText(string text, Vector2 origin, Font font, Paint paint, ShapingOptions? options = null)
Parameters
textstringThe text; empty text draws nothing.
originVector2The left end of the first line's alphabetic baseline, in local coordinates (for a vertical line, the first glyph's vertical origin).
fontFontThe face and size, the size in local units per em.
paintPaintThe paint.
optionsShapingOptionsThe direction, language, script, features, fallback and spans, or null for Default.
Examples
// A caption on a photo: white text with a dark shadow, its baseline 24 pixels above the bottom.
using Raster<Rgba8> photo = Raster.Create(640, 360, new Rgba8(90, 120, 160, 255));
var font = new Font(Typeface.FromFile(FontFile), 28);
using (RasterCanvas<Rgba8> canvas = photo.CreateCanvas())
{
var origin = new Vector2(24, photo.Height - 24);
canvas.DrawText("Lofoten, March", origin + new Vector2(2, 2), font, new Rgba8(0, 0, 0, 160));
canvas.DrawText("Lofoten, March", origin, font, Rgba8.White);
}
Exceptions
- ArgumentException
fonthas no typeface, ororiginis not finite.
DrawTextLayout(TextLayout, Vector2, RectF?, PlaceholderPainter?)
Draws a laid-out paragraph with its top-left corner at origin, line by line in CSS's order: text shadows
(glyphs and decorations again, offset and blurred), run backgrounds, underlines and overlines, glyphs and placeholders,
then line-throughs. Each run uses its style's Paint or Color (black when
neither is set), each decoration its own paint or color. Lines outside visibleRect and the clip are
skipped. An ellipsis is drawn as marked content whose ActualText is the text it stands for on its line, and a soft
hyphen's hyphen as one whose ActualText is the soft hyphen, and the tatweels Kashida put in as one
whose ActualText is empty, so text extracted from a PDF stays the paragraph's.
public void DrawTextLayout(TextLayout layout, Vector2 origin, RectF? visibleRect = null, PlaceholderPainter? placeholderPainter = null)
Parameters
layoutTextLayoutThe layout.
originVector2Where the layout's top-left corner goes, in local coordinates.
visibleRectRectF?The part worth drawing, in local coordinates, or null for the clip's bounds alone.
placeholderPainterPlaceholderPainterDraws each placeholder, or null to leave them empty.
Examples
ParagraphStyle style = new()
{
DefaultStyle = new TextStyle
{
FontFamilies = [family],
Size = 18,
Color = new Rgba8(30, 30, 60, 255),
Shadows = [new TextShadow(new System.Numerics.Vector2(1, 1), 2, new Rgba8(0, 0, 0, 80))],
},
Align = TextAlign.Justify,
};
// One call: wrap into a box, centred across the lines.
TextLayout boxed = canvas.DrawText("Paragraphs wrap, justify and drop shadows.", new RectF(10, 10, 300, 80), style, VerticalAlign.Center);
// Or lay out once and draw where needed, skipping lines outside the visible part.
TextLayout layout = Paragraph.Create("Laid out once, drawn many times.", style).Layout(300);
canvas.DrawTextLayout(layout, new System.Numerics.Vector2(10, 110), visibleRect: new RectF(0, 0, 320, 200));
// Inline placeholders (an icon, a badge) are drawn by a painter called with each one's box.
canvas.DrawTextLayout(layout, new System.Numerics.Vector2(10, 130), placeholderPainter: (c, index, box) => c.FillRoundRect(new RoundRect(box, 3), new Rgba8(200, 60, 40, 255)));
// The same text as one path: glyphs and decorations.
Egami.Drawing.Path outline = layout.ToPath(new System.Numerics.Vector2(10, 150));
Exceptions
- ArgumentNullException
layoutis null.- ArgumentException
originis not finite, or a style's paint is not an Egami.Drawing Paint.
DrawTextOnPath(string, Path, Font, Paint, TextOnPathOptions?)
Draws text along a path by SVG's textPath rules, through ShapeOnPath(Font, string, Path, TextOnPathOptions?) and DrawGlyphRun(GlyphRun, Vector2, Paint): each glyph's midpoint sits on the path, turned to its direction, and glyphs past either end are left out. The result is a glyph run, so backends that write text keep it as text.
public void DrawTextOnPath(string text, Path path, Font font, Paint paint, TextOnPathOptions? options = null)
Parameters
textstringThe text; newlines draw nothing.
pathPathThe path, in local coordinates.
fontFontThe face and size.
paintPaintThe paint.
optionsTextOnPathOptionsWhere the text starts and sits, or null for the defaults.
Examples
// A label along a curve: each glyph's midpoint on the path, turned to its direction, as SVG's textPath. The result is
// still a glyph run, so a PDF backend writes it as searchable text.
Path curve = new PathBuilder().MoveTo(20, 150).CubicTo(80, 20, 240, 20, 300, 150).ToPath();
var font = new Font(face, 18);
using (RasterCanvas<Rgba8> canvas = image.CreateCanvas())
{
canvas.DrawTextOnPath("Text that follows the curve", curve, font, Rgba8.Black,
new TextOnPathOptions { StartFraction = 0.5f, Anchor = TextAnchor.Center, BaselineShift = 4 });
}
// Or place glyphs yourself, each with its own turn and scale.
GlyphRun dial = GlyphRun.CreateTransformed(font, [face.GetGlyph('N'), face.GetGlyph('E')],
[GlyphTransform.FromRotationScale(1, 0, new Vector2(150, 30)), GlyphTransform.FromRotationScale(1, 90, new Vector2(290, 90))]);
Exceptions
- ArgumentException
fonthas no typeface.
EndMarkedContent()
Ends the marked-content sequence begun last, as PDF's EMC.
public void EndMarkedContent()
Exceptions
- InvalidOperationException
No sequence is open.
FillArc(RectF, float, float, Paint)
Fills a pie slice of an ellipse: 0 degrees is the right end of its horizontal axis, and positive angles turn clockwise.
public void FillArc(RectF oval, float startDegrees, float sweepDegrees, Paint paint)
Parameters
ovalRectFThe rectangle the ellipse fills.
startDegreesfloatWhere the slice starts.
sweepDegreesfloatHow far it turns.
paintPaintThe paint.
FillCircle(Vector2, float, Paint)
Fills a circle.
public void FillCircle(Vector2 center, float radius, Paint paint)
Parameters
centerVector2The center.
radiusfloatThe radius; nothing is drawn when it is 0 or less.
paintPaintThe paint.
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));
FillOval(RectF, Paint)
Fills the ellipse that fills a rectangle.
public void FillOval(RectF oval, Paint paint)
Parameters
FillPath(Path, Paint)
Fills a path by its fill rule.
public void FillPath(Path path, Paint paint)
Parameters
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
FillRect(RectF, Paint)
Fills a rectangle.
public void FillRect(RectF rect, Paint paint)
Parameters
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));
FillRing(RoundRect, RoundRect, Paint)
Fills the ring between two round rectangles, as Ring(RoundRect, RoundRect) describes.
public void FillRing(RoundRect outer, RoundRect inner, Paint paint)
Parameters
FillRoundRect(RoundRect, Paint)
Fills a round rectangle.
public void FillRoundRect(RoundRect roundRect, Paint paint)
Parameters
OnBeginMarkedContent(MarkedContent)
Begins a marked-content sequence. By default nothing happens.
protected virtual void OnBeginMarkedContent(MarkedContent content)
Parameters
contentMarkedContentThe tag and its properties.
OnClear(RgbaF32)
Replaces everything inside the clip with a color. A PDF page can write it as a filled rectangle over a white or transparent page; by default it is OnDrawPaint(Paint) with a solid paint in Source.
protected virtual void OnClear(RgbaF32 color)
Parameters
colorRgbaF32The color.
OnClipPath(Path, ClipOp, bool)
Clips by a path's fill.
protected abstract void OnClipPath(Path path, ClipOp op, bool antialias)
Parameters
pathPathThe path in local coordinates.
opClipOpWhether to keep the inside or the outside.
antialiasboolWhether the edges are antialiased.
OnClipRect(RectF, ClipOp, bool)
Clips by a rectangle. By default it goes to OnClipPath(Path, ClipOp, bool).
protected virtual void OnClipRect(RectF rect, ClipOp op, bool antialias)
Parameters
rectRectFThe rectangle in local coordinates, under Matrix.
opClipOpWhether to keep the inside or the outside.
antialiasboolWhether the edges are antialiased.
OnClipRoundRect(RoundRect, ClipOp, bool)
Clips by a round rectangle. By default it goes to OnClipPath(Path, ClipOp, bool).
protected virtual void OnClipRoundRect(RoundRect roundRect, ClipOp op, bool antialias)
Parameters
roundRectRoundRectThe round rectangle in local coordinates.
opClipOpWhether to keep the inside or the outside.
antialiasboolWhether the edges are antialiased.
OnDrawAnnotation(RectF, Annotation)
Marks an area with a link or a destination. By default nothing happens: pixels show no annotations.
protected virtual void OnDrawAnnotation(RectF area, Annotation annotation)
Parameters
areaRectFThe area, in local coordinates under Matrix.
annotationAnnotationThe link or destination.
OnDrawAtlas(Raster, ReadOnlySpan<SpriteTransform>, ReadOnlySpan<RectF>, ReadOnlySpan<RgbaF32>, BlendMode, SamplingOptions, Paint?)
Draws sprites from one image; the lists have equal lengths and colors is empty or as long. By default a sprite without a color is a DrawImage(Raster, RectF, RectF, SamplingOptions?, Paint?) under its transform, and a tinted one a two-triangle textured OnDrawMesh(Mesh, Paint, BlendMode, ReadOnlySpan<GradientStop>) call, one sprite at a time.
protected virtual void OnDrawAtlas(Raster image, ReadOnlySpan<SpriteTransform> transforms, ReadOnlySpan<RectF> sources, ReadOnlySpan<RgbaF32> colors, BlendMode colorBlend, SamplingOptions sampling, Paint? paint)
Parameters
imageRasterThe sprite sheet.
transformsReadOnlySpan<SpriteTransform>Where each sprite goes.
sourcesReadOnlySpan<RectF>Each sprite's part of the image.
colorsReadOnlySpan<RgbaF32>One color per sprite, or empty.
colorBlendBlendModeHow a sprite combines with its color.
samplingSamplingOptionsHow the image is sampled.
paintPaintThe blend mode, opacity and antialiasing, or null.
Examples
// Pack the sprites once, then draw thousands of them in one call: each with its own place, turn, scale and tint.
using SpriteSheet sheet = SpriteSheet.Pack([spark, star], new SpritePackOptions { Padding = 1 });
Random random = new(7);
SpriteTransform[] transforms = new SpriteTransform[2000];
RectF[] sources = new RectF[transforms.Length];
RgbaF32[] tints = new RgbaF32[transforms.Length];
for (int i = 0; i < transforms.Length; i++)
{
Rect frame = sheet.Frames[i % 2];
sources[i] = frame;
transforms[i] = SpriteTransform.FromRotationScale(
scale: 0.3f + random.NextSingle(),
degrees: random.NextSingle() * 360,
position: new Vector2(random.NextSingle() * 320, random.NextSingle() * 200),
anchor: new Vector2(frame.Width / 2f, frame.Height / 2f));
tints[i] = new RgbaF32(1, 0.5f + (random.NextSingle() / 2), random.NextSingle(), 0.8f);
}
using (RasterCanvas<Rgba8> canvas = raster.CreateCanvas())
{
// Plus adds the sprites up, as light does.
canvas.DrawAtlas(sheet.Image, transforms, sources, tints, BlendMode.Modulate, paint: new SolidPaint(Rgba8.White) { BlendMode = BlendMode.Plus });
}
OnDrawEncodedImage(EncodedImage, RectF, RectF, SamplingOptions, Paint?)
Draws part of an encoded image into a local rectangle; the source lies inside the image. A PDF backend can embed a JPEG's bytes as they are. By default the image is decoded once and drawn with OnDrawImage(Raster, RectF, RectF, SamplingOptions, Paint?).
protected virtual void OnDrawEncodedImage(EncodedImage image, RectF source, RectF destination, SamplingOptions sampling, Paint? paint)
Parameters
imageEncodedImageThe image.
sourceRectFThe part to draw, inside the image.
destinationRectFWhere it goes, in local coordinates.
samplingSamplingOptionsHow it is sampled.
paintPaintThe blend mode, opacity and antialiasing, or null for source-over.
Examples
// A JPEG kept as its bytes: a PDF backend embeds them as they are; a raster canvas draws the decoded pixels.
EncodedImage jpeg = EncodedImage.Load("photo.jpg");
canvas.DrawImage(jpeg, new RectF(20, 20, 128, 96));
OnDrawGlyphRun(GlyphRun, Vector2, Paint)
Draws a glyph run. The default turns the glyphs into one path, with the font's synthetic bold and italic, and calls OnDrawPath(Path, Stroke?, Paint), aliased for Alias, so every backend draws text; a backend with text of its own, such as a PDF page writing text operators, overrides it and can use Text and Clusters to keep the text searchable.
protected virtual void OnDrawGlyphRun(GlyphRun run, Vector2 origin, Paint paint)
Parameters
runGlyphRunThe glyphs, never empty.
originVector2Where the run's origin goes, in local coordinates.
paintPaintThe paint.
OnDrawImage(Raster, RectF, RectF, SamplingOptions, Paint?)
Draws part of an image into a local rectangle.
protected abstract void OnDrawImage(Raster image, RectF source, RectF destination, SamplingOptions sampling, Paint? paint)
Parameters
imageRasterThe image.
sourceRectFThe part to draw, inside the image.
destinationRectFWhere it goes, in local coordinates.
samplingSamplingOptionsHow it is sampled.
paintPaintThe blend mode, opacity and antialiasing, or null for source-over.
Examples
// A backend overrides hooks; the base class has already checked the arguments and tracks the transform and clip bounds.
// This one writes SVG elements, keeping rectangles as rectangles and strokes as stroke settings.
public sealed class SvgCanvas(float width, float height) : Canvas(width, height)
{
private readonly System.Text.StringBuilder _body = new();
public string Document => $"<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"{Width}\" height=\"{Height}\">{_body}</svg>";
protected override void OnDrawRect(RectF rect, Stroke? stroke, Paint paint)
=> _body.Append(FormattableString.Invariant($"<rect x=\"{rect.X}\" y=\"{rect.Y}\" width=\"{rect.Width}\" height=\"{rect.Height}\" {Style(stroke, paint)}/>"));
protected override void OnDrawPath(Path path, Stroke? stroke, Paint paint)
{
System.Text.StringBuilder d = new();
int p = 0;
foreach (PathVerb verb in path.Verbs)
{
int count = verb switch { PathVerb.Quad => 2, PathVerb.Cubic => 3, PathVerb.Close => 0, _ => 1 };
d.Append(verb switch { PathVerb.Move => "M", PathVerb.Line => "L", PathVerb.Quad => "Q", PathVerb.Cubic => "C", _ => "Z" });
foreach (Vector2 point in path.Points.Slice(p, count))
{
d.Append(FormattableString.Invariant($"{point.X},{point.Y} "));
}
p += count;
}
_body.Append(System.Globalization.CultureInfo.InvariantCulture, $"<path d=\"{d}\" {Style(stroke, paint)}/>");
}
protected override void OnClipPath(Path path, ClipOp op, bool antialias)
{
// Clips would become <clipPath> elements; this sketch leaves them out.
}
protected override void OnDrawImage(Raster image, RectF source, RectF destination, SamplingOptions sampling, Paint? paint)
{
// An image would be embedded as a PNG data URI.
}
private string Style(Stroke? stroke, Paint paint)
{
Rgba8 c = paint is SolidPaint solid ? solid.Color.ToRgba8() : Rgba8.Black;
string color = $"#{c.R:x2}{c.G:x2}{c.B:x2}";
string transform = Matrix.IsIdentity ? "" : FormattableString.Invariant($" transform=\"matrix({Matrix.M11} {Matrix.M12} {Matrix.M21} {Matrix.M22} {Matrix.M31} {Matrix.M32})\"");
return stroke is null
? $"fill=\"{color}\"{transform}"
: FormattableString.Invariant($"fill=\"none\" stroke=\"{color}\" stroke-width=\"{stroke.Width}\" stroke-linecap=\"{stroke.Cap.ToString().ToLowerInvariant()}\"{transform}");
}
}
OnDrawImageLattice(Raster, Lattice, RectF, SamplingOptions, Paint?)
Draws an image through a lattice; the lattice fits the image. By default each cell is an OnDrawImage(Raster, RectF, RectF, SamplingOptions, Paint?) call of its part of the image, sampled only inside that part, and each fixed-color cell an OnDrawRect(RectF, Stroke?, Paint) call; a PDF backend gets one image per cell this way. The raster backend draws the cells as one mesh instead, so they meet without seams.
protected virtual void OnDrawImageLattice(Raster image, Lattice lattice, RectF destination, SamplingOptions sampling, Paint? paint)
Parameters
imageRasterThe image.
latticeLatticeThe grid.
destinationRectFWhere the image goes, in local coordinates; not empty.
samplingSamplingOptionsHow the image is sampled.
paintPaintThe blend mode, opacity and antialiasing, or null.
Examples
using (RasterCanvas<Rgba8> canvas = raster.CreateCanvas())
{
// An Android .9.png: its one-pixel border marks what stretches. The corners stay sharp at any size.
(Raster inner, Lattice lattice, Rect? padding) = NinePatch.FromAndroid(file);
canvas.DrawImageLattice(inner, lattice, new RectF(10, 10, 180, 48));
inner.Dispose();
// A nine-patch from a plain image: the 8-pixel corners keep their size, the edges and center stretch.
canvas.DrawImageNinePatch(frame, new Rect(8, 8, 8, 8), new RectF(10, 70, 120, 40));
// A lattice can leave cells out or fill them with a color: here the center becomes a flat panel.
LatticeCellKind[] kinds = [.. Enumerable.Repeat(LatticeCellKind.Default, 9)];
kinds[4] = LatticeCellKind.FixedColor;
RgbaF32[] colors = new RgbaF32[9];
colors[4] = new RgbaF32(0.95f, 0.95f, 0.9f, 1);
canvas.DrawImageLattice(frame, new Lattice([8, 16], [8, 16]) { CellKinds = kinds, CellColors = colors }, new RectF(150, 70, 140, 40));
}
OnDrawImageMask(Raster, RectF, Paint, SamplingOptions)
Paints through an image mask. A PDF backend writes it as a stencil mask. By default the paint fills the rectangle in a layer and the mask's coverage, as an image drawn with DestinationIn, cuts it out.
protected virtual void OnDrawImageMask(Raster mask, RectF destination, Paint paint, SamplingOptions sampling)
Parameters
maskRasterThe mask image.
destinationRectFWhere the mask goes, in local coordinates; not empty.
paintPaintWhat fills the covered pixels.
samplingSamplingOptionsHow the mask is sampled.
Examples
// A 1-bit scan as a stencil: its white pixels paint in ink, its black ones leave the page as it is.
using Raster<Gray8> scan = Raster.Create(32, 16, new Gray8(255));
canvas.DrawImageMask(scan, new RectF(160, 20, 64, 32), new Rgba8(20, 40, 120, 255), new SamplingOptions { Filter = ResampleFilter.Nearest });
// A knockout group: each shape composites with what the group started as, so overlapping translucent shapes do not darken.
canvas.SaveLayer(new LayerOptions { Knockout = true });
canvas.FillCircle(new Vector2(180, 100), 20, new RgbaF32(1, 0, 0, 0.5f));
canvas.FillCircle(new Vector2(200, 100), 20, new RgbaF32(0, 0, 1, 0.5f));
canvas.Restore();
OnDrawMesh(Mesh, Paint, BlendMode, ReadOnlySpan<GradientStop>)
Draws a mesh with at least one triangle; a mesh with values comes with stops. A PDF backend can write it as a free-form or lattice-form shading (types 4 and 5). By default it is drawn through RasterizeFallback(Action<Canvas>, float) at one raster pixel per device unit, over the mesh's bounds, and placed with OnDrawImage(Raster, RectF, RectF, SamplingOptions, Paint?).
protected virtual void OnDrawMesh(Mesh mesh, Paint paint, BlendMode colorBlend, ReadOnlySpan<GradientStop> valueStops)
Parameters
meshMeshThe mesh, in local coordinates.
paintPaintThe paint.
colorBlendBlendModeHow the vertex colors combine with the paint.
valueStopsReadOnlySpan<GradientStop>The stops vertex values map through, or empty.
OnDrawOval(RectF, Stroke?, Paint)
Fills or strokes an ellipse. By default it goes to OnDrawPath(Path, Stroke?, Paint).
protected virtual void OnDrawOval(RectF oval, Stroke? stroke, Paint paint)
Parameters
ovalRectFThe rectangle the ellipse fills.
strokeStrokeThe pen, or null to fill.
paintPaintThe paint.
OnDrawPaint(Paint)
Fills the clip. By default it fills the device clip bounds mapped back to local coordinates.
protected virtual void OnDrawPaint(Paint paint)
Parameters
paintPaintThe paint.
OnDrawPatch(Patch, Paint, BlendMode, ReadOnlySpan<GradientStop>)
Draws a patch. A PDF backend can write it as a Coons or tensor-product shading (types 6 and 7). By default it is cut into triangles under the current transform and drawn with OnDrawMesh(Mesh, Paint, BlendMode, ReadOnlySpan<GradientStop>).
protected virtual void OnDrawPatch(Patch patch, Paint paint, BlendMode colorBlend, ReadOnlySpan<GradientStop> valueStops)
Parameters
patchPatchThe patch, in local coordinates.
paintPaintThe paint.
colorBlendBlendModeHow the corner colors combine with the paint.
valueStopsReadOnlySpan<GradientStop>The stops corner values map through, or empty.
OnDrawPath(Path, Stroke?, Paint)
Fills a path by its fill rule, or strokes it.
protected abstract void OnDrawPath(Path path, Stroke? stroke, Paint paint)
Parameters
pathPathThe path in local coordinates.
strokeStrokeThe pen, or null to fill.
paintPaintThe paint.
Examples
// A backend overrides hooks; the base class has already checked the arguments and tracks the transform and clip bounds.
// This one writes SVG elements, keeping rectangles as rectangles and strokes as stroke settings.
public sealed class SvgCanvas(float width, float height) : Canvas(width, height)
{
private readonly System.Text.StringBuilder _body = new();
public string Document => $"<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"{Width}\" height=\"{Height}\">{_body}</svg>";
protected override void OnDrawRect(RectF rect, Stroke? stroke, Paint paint)
=> _body.Append(FormattableString.Invariant($"<rect x=\"{rect.X}\" y=\"{rect.Y}\" width=\"{rect.Width}\" height=\"{rect.Height}\" {Style(stroke, paint)}/>"));
protected override void OnDrawPath(Path path, Stroke? stroke, Paint paint)
{
System.Text.StringBuilder d = new();
int p = 0;
foreach (PathVerb verb in path.Verbs)
{
int count = verb switch { PathVerb.Quad => 2, PathVerb.Cubic => 3, PathVerb.Close => 0, _ => 1 };
d.Append(verb switch { PathVerb.Move => "M", PathVerb.Line => "L", PathVerb.Quad => "Q", PathVerb.Cubic => "C", _ => "Z" });
foreach (Vector2 point in path.Points.Slice(p, count))
{
d.Append(FormattableString.Invariant($"{point.X},{point.Y} "));
}
p += count;
}
_body.Append(System.Globalization.CultureInfo.InvariantCulture, $"<path d=\"{d}\" {Style(stroke, paint)}/>");
}
protected override void OnClipPath(Path path, ClipOp op, bool antialias)
{
// Clips would become <clipPath> elements; this sketch leaves them out.
}
protected override void OnDrawImage(Raster image, RectF source, RectF destination, SamplingOptions sampling, Paint? paint)
{
// An image would be embedded as a PNG data URI.
}
private string Style(Stroke? stroke, Paint paint)
{
Rgba8 c = paint is SolidPaint solid ? solid.Color.ToRgba8() : Rgba8.Black;
string color = $"#{c.R:x2}{c.G:x2}{c.B:x2}";
string transform = Matrix.IsIdentity ? "" : FormattableString.Invariant($" transform=\"matrix({Matrix.M11} {Matrix.M12} {Matrix.M21} {Matrix.M22} {Matrix.M31} {Matrix.M32})\"");
return stroke is null
? $"fill=\"{color}\"{transform}"
: FormattableString.Invariant($"fill=\"none\" stroke=\"{color}\" stroke-width=\"{stroke.Width}\" stroke-linecap=\"{stroke.Cap.ToString().ToLowerInvariant()}\"{transform}");
}
}
OnDrawPicture(Picture, Matrix3x2, Paint?, bool)
Draws a recorded picture. A PDF backend can write it once as a form XObject and reference it each time. By default it saves the state, adds the matrix, starts a layer when the paint asks for one, replays the picture through the public calls (or draws its cached image) and restores.
protected virtual void OnDrawPicture(Picture picture, Matrix3x2 matrix, Paint? paint, bool cacheAsImage)
Parameters
picturePictureThe picture, with at least one call.
matrixMatrix3x2The transform added before the canvas's.
paintPaintThe opacity and blend mode of the whole picture, or null.
cacheAsImageboolWhether to draw the picture's cached image instead of its calls.
OnDrawPoints(PointMode, ReadOnlySpan<Vector2>, Stroke, Paint)
Draws points, lines or a polyline. By default each dot or line goes to OnDrawPath(Path, Stroke?, Paint), the polyline as one path.
protected virtual void OnDrawPoints(PointMode mode, ReadOnlySpan<Vector2> points, Stroke stroke, Paint paint)
Parameters
modePointModeHow the points are read.
pointsReadOnlySpan<Vector2>The points.
strokeStrokeThe pen.
paintPaintThe paint.
OnDrawRect(RectF, Stroke?, Paint)
Fills or strokes a rectangle. By default it goes to OnDrawPath(Path, Stroke?, Paint).
protected virtual void OnDrawRect(RectF rect, Stroke? stroke, Paint paint)
Parameters
OnDrawRoundRect(RoundRect, Stroke?, Paint)
Fills or strokes a round rectangle. By default it goes to OnDrawPath(Path, Stroke?, Paint).
protected virtual void OnDrawRoundRect(RoundRect roundRect, Stroke? stroke, Paint paint)
Parameters
roundRectRoundRectThe round rectangle.
strokeStrokeThe pen, or null to fill.
paintPaintThe paint.
OnEndMarkedContent()
Ends the marked-content sequence begun last. By default nothing happens.
protected virtual void OnEndMarkedContent()
OnRestore()
Called by Restore() after the state is popped; Matrix and DeviceClipBounds are already the restored ones.
protected virtual void OnRestore()
OnRestoreLayer()
Called by Restore() for a state saved by SaveLayer(LayerOptions?), after it is popped, so Matrix and DeviceClipBounds are those the layer was started with: the raster backend merges the layer through that clip. By default it calls OnRestore().
protected virtual void OnRestoreLayer()
OnSave()
Called by Save() after the state is pushed.
protected virtual void OnSave()
OnSaveLayer(LayerOptions)
Called by SaveLayer(LayerOptions?) after the state is pushed; DeviceClipBounds is already limited by the options' bounds. The raster backend allocates the layer; a PDF backend would begin a transparency group. By default it calls OnSave(), so a backend without layers draws their contents directly.
protected virtual void OnSaveLayer(LayerOptions options)
Parameters
optionsLayerOptionsHow the layer is merged.
Examples
using (RasterCanvas<Rgba8> canvas = raster.CreateCanvas())
{
// Group opacity: the overlapping circles fade as one shape, with no darker overlap.
canvas.SaveLayer(0.5f);
canvas.FillCircle(new Vector2(80, 100), 60, new Rgba8(200, 30, 30, 255));
canvas.FillCircle(new Vector2(140, 100), 60, new Rgba8(200, 30, 30, 255));
canvas.Restore();
// A group blended with Multiply and blurred as a whole; the layer grows by the blur's reach, so nothing is cut.
canvas.SaveLayer(new LayerOptions { BlendMode = BlendMode.Multiply, Filter = ops => ops.Blur(4) });
canvas.FillRect(new RectF(180, 40, 90, 120), new Rgba8(40, 120, 220, 255));
canvas.FillCircle(new Vector2(225, 100), 30, Rgba8.White);
canvas.Restore();
}
OnSetMatrix(Matrix3x2)
Called whenever the transform changes, with the new transform (also Matrix).
protected virtual void OnSetMatrix(Matrix3x2 matrix)
Parameters
matrixMatrix3x2The transform from local to device coordinates.
QuickReject(Path)
Returns whether a path certainly lies outside the clip.
public bool QuickReject(Path path)
Parameters
pathPathThe path in local coordinates.
Returns
QuickReject(RectF)
Returns whether a local rectangle certainly lies outside the clip, so drawing it can be skipped.
public bool QuickReject(RectF rect)
Parameters
rectRectFThe rectangle in local coordinates.
Returns
RasterizeFallback(Action<Canvas>, float)
Draws one call into a temporary raster instead, for a backend that cannot express it (a blend mode or paint its format lacks):
the result covers the device clip bounds at scale device pixels per unit and holds what the call draws,
clipped to those bounds, over transparency; the backend then draws it as an image. Skia's PDF backend falls back the same way.
A paint's Blur, whose sigma is in device units, is scaled with the raster, so it keeps its size. A layer's
filter and backdrop chains run on the raster's pixels as they are: a chain with sizes in pixels (a blur radius, an offset)
should be built for the scale.
protected FallbackImage? RasterizeFallback(Action<Canvas> draw, float scale = 1)
Parameters
drawAction<Canvas>The call, made on a raster canvas set up with this canvas's transform.
scalefloatRaster pixels per device unit, such as 300 / 72 for a PDF page at 300 dpi.
Returns
- FallbackImage
The image and the device rectangle it covers, or null when the clip is empty.
Exceptions
- ArgumentOutOfRangeException
scaleis not positive and finite.
ResetMatrix()
Resets the transform to the identity: local coordinates are device coordinates.
public void ResetMatrix()
Restore()
Brings back the transform and clip of the matching Save(), or ends the layer of the matching SaveLayer(LayerOptions?) by merging it into what lies below.
public void Restore()
Exceptions
- InvalidOperationException
There is no saved state.
RestoreToCount(int)
Restores until SaveCount is count; nothing when it already is or is lower.
public void RestoreToCount(int count)
Parameters
countintThe save count to return to, at least 1.
Exceptions
- ArgumentOutOfRangeException
countis below 1.
Rotate(float)
Turns later drawing clockwise (on a y-down screen) about the local origin.
public void Rotate(float degrees)
Parameters
degreesfloatThe angle.
Rotate(float, Vector2)
Turns later drawing clockwise about a local point.
public void Rotate(float degrees, Vector2 center)
Parameters
Save()
Saves the transform and clip; the matching Restore() brings them back.
public int Save()
Returns
- int
The save count before this call, to pass to RestoreToCount(int).
Examples
// A canvas draws onto a raster. Calls draw when they are made; dispose the canvas when done.
using Raster<Rgba8> raster = Raster.Create(400, 300, Rgba8.White);
using (RasterCanvas<Rgba8> canvas = raster.CreateCanvas())
{
canvas.FillRect(new RectF(20, 20, 160, 100), new Rgba8(30, 90, 200, 255));
// Save and Restore bracket transform and clip changes.
canvas.Save();
canvas.Translate(260, 150);
canvas.Rotate(30);
canvas.FillRect(new RectF(-50, -30, 100, 60), new Rgba8(200, 60, 30, 200));
canvas.Restore();
canvas.FillCircle(new Vector2(100, 220), 50, Rgba8.Black);
}
// Blend edges in linear light, which keeps thin lines and translucent colors from darkening.
using RasterCanvas<Rgba8> linear = raster.CreateCanvas(new CanvasOptions { ColorSpace = ColorSpace.Linear });
SaveLayer(LayerOptions?)
Starts a layer: later drawing goes into a temporary image, which the matching Restore() filters, masks and merges into what lies below with the layer's opacity and blend mode, through the clip of this call. That gives group opacity (two overlapping shapes at half opacity look like one), group blend modes, filters on a group and soft masks, which PDF calls transparency groups. Like Save(), it also saves the transform and clip.
public int SaveLayer(LayerOptions? options = null)
Parameters
optionsLayerOptionsHow the layer is merged; an isolated, opaque, source-over layer when null.
Returns
- int
The save count before this call, to pass to RestoreToCount(int).
Examples
using (RasterCanvas<Rgba8> canvas = raster.CreateCanvas())
{
// Group opacity: the overlapping circles fade as one shape, with no darker overlap.
canvas.SaveLayer(0.5f);
canvas.FillCircle(new Vector2(80, 100), 60, new Rgba8(200, 30, 30, 255));
canvas.FillCircle(new Vector2(140, 100), 60, new Rgba8(200, 30, 30, 255));
canvas.Restore();
// A group blended with Multiply and blurred as a whole; the layer grows by the blur's reach, so nothing is cut.
canvas.SaveLayer(new LayerOptions { BlendMode = BlendMode.Multiply, Filter = ops => ops.Blur(4) });
canvas.FillRect(new RectF(180, 40, 90, 120), new Rgba8(40, 120, 220, 255));
canvas.FillCircle(new Vector2(225, 100), 30, Rgba8.White);
canvas.Restore();
}
SaveLayer(float)
Starts a layer merged with an opacity: group opacity, the most common layer.
public int SaveLayer(float opacity)
Parameters
opacityfloatThe opacity the layer is merged with, 0 to 1.
Returns
- int
The save count before this call.
Examples
using (RasterCanvas<Rgba8> canvas = raster.CreateCanvas())
{
// Group opacity: the overlapping circles fade as one shape, with no darker overlap.
canvas.SaveLayer(0.5f);
canvas.FillCircle(new Vector2(80, 100), 60, new Rgba8(200, 30, 30, 255));
canvas.FillCircle(new Vector2(140, 100), 60, new Rgba8(200, 30, 30, 255));
canvas.Restore();
// A group blended with Multiply and blurred as a whole; the layer grows by the blur's reach, so nothing is cut.
canvas.SaveLayer(new LayerOptions { BlendMode = BlendMode.Multiply, Filter = ops => ops.Blur(4) });
canvas.FillRect(new RectF(180, 40, 90, 120), new Rgba8(40, 120, 220, 255));
canvas.FillCircle(new Vector2(225, 100), 30, Rgba8.White);
canvas.Restore();
}
Exceptions
- ArgumentOutOfRangeException
opacityis outside 0 to 1.
Scale(float)
Scales later drawing by the same factor on both axes about the local origin.
public void Scale(float scale)
Parameters
scalefloatThe factor.
Scale(float, float)
Scales later drawing about the local origin.
public void Scale(float sx, float sy)
Parameters
SetMatrix(Matrix3x2)
Replaces the current transform.
public void SetMatrix(Matrix3x2 matrix)
Parameters
matrixMatrix3x2The new transform from local to device coordinates.
Exceptions
- ArgumentException
An element is not finite.
Skew(float, float)
Slants later drawing: x moves by y·tan(degreesX) and y by x·tan(degreesY).
public void Skew(float degreesX, float degreesY)
Parameters
StrokeArc(RectF, float, float, Stroke, Paint)
Strokes the open arc of an ellipse.
public void StrokeArc(RectF oval, float startDegrees, float sweepDegrees, Stroke stroke, Paint paint)
Parameters
ovalRectFThe rectangle the ellipse fills.
startDegreesfloatWhere the arc starts.
sweepDegreesfloatHow far it turns.
strokeStrokeThe pen.
paintPaintThe paint.
StrokeCircle(Vector2, float, Stroke, Paint)
Outlines a circle.
public void StrokeCircle(Vector2 center, float radius, Stroke stroke, Paint paint)
Parameters
StrokeOval(RectF, Stroke, Paint)
Outlines the ellipse that fills a rectangle.
public void StrokeOval(RectF oval, Stroke stroke, Paint paint)
Parameters
StrokePath(Path, Stroke, Paint)
Outlines a path with a pen.
public void StrokePath(Path path, Stroke stroke, Paint paint)
Parameters
Examples
// The pen is data: width, caps, joins and miter limit. Width 1, butt caps and miter joins by default.
Stroke thick = new(12) { Cap = LineCap.Round, Join = LineJoin.Round };
canvas.DrawLine(new Vector2(20, 20), new Vector2(280, 20), thick, new Rgba8(30, 90, 200, 255));
Path zigzag = Path.Polygon([new(20, 120), new(80, 60), new(140, 120), new(200, 60), new(260, 120)], close: false);
canvas.StrokePath(zigzag, new Stroke(8) { Join = LineJoin.Miter, MiterLimit = 4 }, Rgba8.Black);
// A hairline is one device pixel wide whatever the transform.
canvas.StrokeCircle(new Vector2(150, 160), 30, Stroke.Hairline, Rgba8.Red);
StrokeRect(RectF, Stroke, Paint)
Outlines a rectangle.
public void StrokeRect(RectF rect, Stroke stroke, Paint paint)
Parameters
StrokeRoundRect(RoundRect, Stroke, Paint)
Outlines a round rectangle.
public void StrokeRoundRect(RoundRect roundRect, Stroke stroke, Paint paint)
Parameters
Translate(float, float)
Moves the origin: later drawing is shifted by (dx, dy) local units.
public void Translate(float dx, float dy)