Table of Contents

Class Canvas

Namespace
Egami.Drawing
Assembly
Egami.Drawing.dll

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

width float

The drawing surface's width in device units.

height float

The 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

IColorGlyphRenderer

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

RectF

Height

Gets the drawing surface's height in device units.

public float Height { get; }

Property Value

float

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

RectF

MarkedContentDepth

Gets the number of marked-content sequences begun and not yet ended.

public int MarkedContentDepth { get; }

Property Value

int

Matrix

Gets the current transform from local coordinates to device coordinates.

public Matrix3x2 Matrix { get; }

Property Value

Matrix3x2

SaveCount

Gets the number of saved states plus one: 1 before any Save().

public int SaveCount { get; }

Property Value

int

Width

Gets the drawing surface's width in device units.

public float Width { get; }

Property Value

float

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

content MarkedContent

The 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

color Rgba8

The color.

Clear(RgbaF32)

Replaces every pixel inside the clip with a color: DrawPaint(Paint) with Source.

public void Clear(RgbaF32 color)

Parameters

color RgbaF32

The 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

path Path

The path in local coordinates.

op ClipOp

Whether to keep the inside or the outside.

antialias bool

Whether 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

rect RectF

The rectangle in local coordinates.

op ClipOp

Whether to keep the inside or the outside.

antialias bool

Whether 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

roundRect RoundRect

The round rectangle in local coordinates.

op ClipOp

Whether to keep the inside or the outside.

antialias bool

Whether 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

matrix Matrix3x2

The 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

disposing bool

Always true: the canvas has no finalizer.

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

area RectF

The area: the link's clickable rectangle, or for a destination the point at its top-left corner.

annotation Annotation

The 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

area is 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

image Raster

The sprite sheet.

transforms ReadOnlySpan<SpriteTransform>

Where each sprite goes.

sources ReadOnlySpan<RectF>

Each sprite's part of the image, in its pixels.

colors ReadOnlySpan<RgbaF32>

One straight-alpha color per sprite, or empty for none.

colorBlend BlendMode

How a sprite combines with its color.

sampling SamplingOptions

How the image is sampled; Egami's default sampling when null.

cull RectF?

Bounds of all the sprites, in local coordinates, when the caller knows them: a call outside the clip is skipped at once.

paint Paint

The 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

run GlyphRun

The glyphs.

origin Vector2

Where the run's origin goes, in local coordinates.

paint Paint

The 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

run GlyphRun

The glyphs.

origin Vector2

Where the run's origin goes, in local coordinates.

paint Paint

The paint.

Exceptions

ArgumentException

origin is 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

image EncodedImage

The image.

source RectF

The part of the image to draw, in its pixels; clipped to the image.

destination RectF

Where it goes.

sampling SamplingOptions

How the image is sampled; Egami's default sampling when null.

paint Paint

The 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

image EncodedImage

The image.

destination RectF

Where it goes.

sampling SamplingOptions

How the image is sampled; Egami's default sampling when null.

paint Paint

The 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

image Raster

The image.

source RectF

The part of the image to draw, in its pixels; clipped to the image.

destination RectF

Where it goes.

sampling SamplingOptions

How the image is sampled; Egami's default sampling when null.

paint Paint

The 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

image Raster

The image.

destination RectF

Where it goes.

sampling SamplingOptions

How the image is sampled; Egami's default sampling when null.

paint Paint

The 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

image Raster

The image.

position Vector2

Where its top-left corner goes.

sampling SamplingOptions

How the image is sampled when it is transformed; Egami's default sampling when null.

paint Paint

The 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

image Raster

The image.

lattice Lattice

The grid.

destination RectF

Where the image goes, in local coordinates.

sampling SamplingOptions

How the image is sampled; Egami's default sampling when null.

paint Paint

The 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 destination is 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

mask Raster

The mask image.

destination RectF

Where the mask goes, in local coordinates.

paint Paint

What fills the covered pixels, with its blend mode, opacity and antialiasing of the rectangle's edges.

sampling SamplingOptions

How 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

destination is 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

image Raster

The image.

center Rect

The part of the image that stretches both ways, in its pixels.

destination RectF

Where the image goes, in local coordinates.

sampling SamplingOptions

How the image is sampled; Egami's default sampling when null.

paint Paint

The 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

center is empty or lies outside the image, or destination is not finite.

DrawLine(Vector2, Vector2, Stroke, Paint)

Strokes a straight line.

public void DrawLine(Vector2 start, Vector2 end, Stroke stroke, Paint paint)

Parameters

start Vector2

Where it starts.

end Vector2

Where it ends.

stroke Stroke

The pen.

paint Paint

The 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

mesh Mesh

The mesh, in local coordinates.

paint Paint

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

colorBlend BlendMode

How the vertex colors combine with the paint.

valueStops ReadOnlySpan<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

paint Paint

The 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

patch Patch

The patch, in local coordinates.

paint Paint

The paint; an opaque white solid paint when null, which draws the corner colors as they are.

colorBlend BlendMode

How the corner colors combine with the paint.

valueStops ReadOnlySpan<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

picture Picture

The picture.

matrix Matrix3x2?

A transform applied before the canvas's, or null for none.

paint Paint

The opacity and blend mode of the whole picture, or null to replay it directly.

cacheAsImage bool

Whether 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 matrix is 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

mode PointMode

How the points are read.

points ReadOnlySpan<Vector2>

The points.

stroke Stroke

The pen: its width sizes the dots and lines.

paint Paint

The 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

run GlyphRun

The glyphs.

origin Vector2

Where the run's origin goes, in local coordinates.

paint Paint

The 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

path Path

The shape, in local coordinates.

options ShadowOptions

The 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

text ShapedText

The shaped text.

origin Vector2

The left end of the first line's alphabetic baseline, in local coordinates.

paint Paint

The 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

origin is 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

text RichText

The text with its spans and placeholders.

rect RectF

The rectangle, in local coordinates.

style ParagraphStyle

The paragraph's style.

verticalAlign VerticalAlign

Where the lines go across the rectangle.

Returns

TextLayout

The layout drawn, for hit testing and measuring.

Exceptions

ArgumentNullException

text or style is 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

text string

The text.

rect RectF

The rectangle, in local coordinates.

style ParagraphStyle

The paragraph's style.

verticalAlign VerticalAlign

Where 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

text or style is 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

text string

The text; empty text draws nothing.

origin Vector2

The left end of the first line's alphabetic baseline, in local coordinates (for a vertical line, the first glyph's vertical origin).

font Font

The face and size, the size in local units per em.

paint Paint

The paint.

options ShapingOptions

The 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

font has no typeface, or origin is 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

layout TextLayout

The layout.

origin Vector2

Where the layout's top-left corner goes, in local coordinates.

visibleRect RectF?

The part worth drawing, in local coordinates, or null for the clip's bounds alone.

placeholderPainter PlaceholderPainter

Draws 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

layout is null.

ArgumentException

origin is 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

text string

The text; newlines draw nothing.

path Path

The path, in local coordinates.

font Font

The face and size.

paint Paint

The paint.

options TextOnPathOptions

Where 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

font has 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

oval RectF

The rectangle the ellipse fills.

startDegrees float

Where the slice starts.

sweepDegrees float

How far it turns.

paint Paint

The paint.

FillCircle(Vector2, float, Paint)

Fills a circle.

public void FillCircle(Vector2 center, float radius, Paint paint)

Parameters

center Vector2

The center.

radius float

The radius; nothing is drawn when it is 0 or less.

paint Paint

The 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

oval RectF

The rectangle.

paint Paint

The paint.

FillPath(Path, Paint)

Fills a path by its fill rule.

public void FillPath(Path path, Paint paint)

Parameters

path Path

The path.

paint Paint

The paint.

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

rect RectF

The rectangle.

paint Paint

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

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

outer RoundRect

The outer edge.

inner RoundRect

The inner edge: the hole.

paint Paint

The paint.

FillRoundRect(RoundRect, Paint)

Fills a round rectangle.

public void FillRoundRect(RoundRect roundRect, Paint paint)

Parameters

roundRect RoundRect

The round rectangle.

paint Paint

The paint.

OnBeginMarkedContent(MarkedContent)

Begins a marked-content sequence. By default nothing happens.

protected virtual void OnBeginMarkedContent(MarkedContent content)

Parameters

content MarkedContent

The 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

color RgbaF32

The color.

OnClipPath(Path, ClipOp, bool)

Clips by a path's fill.

protected abstract void OnClipPath(Path path, ClipOp op, bool antialias)

Parameters

path Path

The path in local coordinates.

op ClipOp

Whether to keep the inside or the outside.

antialias bool

Whether 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

rect RectF

The rectangle in local coordinates, under Matrix.

op ClipOp

Whether to keep the inside or the outside.

antialias bool

Whether 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

roundRect RoundRect

The round rectangle in local coordinates.

op ClipOp

Whether to keep the inside or the outside.

antialias bool

Whether 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

area RectF

The area, in local coordinates under Matrix.

annotation Annotation

The 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

image Raster

The sprite sheet.

transforms ReadOnlySpan<SpriteTransform>

Where each sprite goes.

sources ReadOnlySpan<RectF>

Each sprite's part of the image.

colors ReadOnlySpan<RgbaF32>

One color per sprite, or empty.

colorBlend BlendMode

How a sprite combines with its color.

sampling SamplingOptions

How the image is sampled.

paint Paint

The 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

image EncodedImage

The image.

source RectF

The part to draw, inside the image.

destination RectF

Where it goes, in local coordinates.

sampling SamplingOptions

How it is sampled.

paint Paint

The 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

run GlyphRun

The glyphs, never empty.

origin Vector2

Where the run's origin goes, in local coordinates.

paint Paint

The 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

image Raster

The image.

source RectF

The part to draw, inside the image.

destination RectF

Where it goes, in local coordinates.

sampling SamplingOptions

How it is sampled.

paint Paint

The 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

image Raster

The image.

lattice Lattice

The grid.

destination RectF

Where the image goes, in local coordinates; not empty.

sampling SamplingOptions

How the image is sampled.

paint Paint

The 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

mask Raster

The mask image.

destination RectF

Where the mask goes, in local coordinates; not empty.

paint Paint

What fills the covered pixels.

sampling SamplingOptions

How 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

mesh Mesh

The mesh, in local coordinates.

paint Paint

The paint.

colorBlend BlendMode

How the vertex colors combine with the paint.

valueStops ReadOnlySpan<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

oval RectF

The rectangle the ellipse fills.

stroke Stroke

The pen, or null to fill.

paint Paint

The 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

paint Paint

The 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

patch Patch

The patch, in local coordinates.

paint Paint

The paint.

colorBlend BlendMode

How the corner colors combine with the paint.

valueStops ReadOnlySpan<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

path Path

The path in local coordinates.

stroke Stroke

The pen, or null to fill.

paint Paint

The 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

picture Picture

The picture, with at least one call.

matrix Matrix3x2

The transform added before the canvas's.

paint Paint

The opacity and blend mode of the whole picture, or null.

cacheAsImage bool

Whether 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

mode PointMode

How the points are read.

points ReadOnlySpan<Vector2>

The points.

stroke Stroke

The pen.

paint Paint

The 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

rect RectF

The rectangle.

stroke Stroke

The pen, or null to fill.

paint Paint

The paint.

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

roundRect RoundRect

The round rectangle.

stroke Stroke

The pen, or null to fill.

paint Paint

The 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

options LayerOptions

How 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

matrix Matrix3x2

The transform from local to device coordinates.

QuickReject(Path)

Returns whether a path certainly lies outside the clip.

public bool QuickReject(Path path)

Parameters

path Path

The path in local coordinates.

Returns

bool

true if nothing of it can be drawn.

QuickReject(RectF)

Returns whether a local rectangle certainly lies outside the clip, so drawing it can be skipped.

public bool QuickReject(RectF rect)

Parameters

rect RectF

The rectangle in local coordinates.

Returns

bool

true if nothing inside it can be drawn.

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

draw Action<Canvas>

The call, made on a raster canvas set up with this canvas's transform.

scale float

Raster 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

scale is 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

count int

The save count to return to, at least 1.

Exceptions

ArgumentOutOfRangeException

count is below 1.

Rotate(float)

Turns later drawing clockwise (on a y-down screen) about the local origin.

public void Rotate(float degrees)

Parameters

degrees float

The angle.

Rotate(float, Vector2)

Turns later drawing clockwise about a local point.

public void Rotate(float degrees, Vector2 center)

Parameters

degrees float

The angle.

center Vector2

The point that stays put.

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

options LayerOptions

How 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

opacity float

The 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

opacity is 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

scale float

The factor.

Scale(float, float)

Scales later drawing about the local origin.

public void Scale(float sx, float sy)

Parameters

sx float

The horizontal factor.

sy float

The vertical factor.

SetMatrix(Matrix3x2)

Replaces the current transform.

public void SetMatrix(Matrix3x2 matrix)

Parameters

matrix Matrix3x2

The 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

degreesX float

The horizontal slant.

degreesY float

The vertical slant.

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

oval RectF

The rectangle the ellipse fills.

startDegrees float

Where the arc starts.

sweepDegrees float

How far it turns.

stroke Stroke

The pen.

paint Paint

The paint.

StrokeCircle(Vector2, float, Stroke, Paint)

Outlines a circle.

public void StrokeCircle(Vector2 center, float radius, Stroke stroke, Paint paint)

Parameters

center Vector2

The center.

radius float

The radius.

stroke Stroke

The pen.

paint Paint

The paint.

StrokeOval(RectF, Stroke, Paint)

Outlines the ellipse that fills a rectangle.

public void StrokeOval(RectF oval, Stroke stroke, Paint paint)

Parameters

oval RectF

The rectangle.

stroke Stroke

The pen.

paint Paint

The paint.

StrokePath(Path, Stroke, Paint)

Outlines a path with a pen.

public void StrokePath(Path path, Stroke stroke, Paint paint)

Parameters

path Path

The path.

stroke Stroke

The pen.

paint Paint

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

StrokeRect(RectF, Stroke, Paint)

Outlines a rectangle.

public void StrokeRect(RectF rect, Stroke stroke, Paint paint)

Parameters

rect RectF

The rectangle.

stroke Stroke

The pen.

paint Paint

The paint.

StrokeRoundRect(RoundRect, Stroke, Paint)

Outlines a round rectangle.

public void StrokeRoundRect(RoundRect roundRect, Stroke stroke, Paint paint)

Parameters

roundRect RoundRect

The round rectangle.

stroke Stroke

The pen.

paint Paint

The paint.

Translate(float, float)

Moves the origin: later drawing is shifted by (dx, dy) local units.

public void Translate(float dx, float dy)

Parameters

dx float

The horizontal shift.

dy float

The vertical shift.