Troubleshooting
This page collects common issues you can hit when moving from simple drawing samples to full ImageSharp.Drawing pipelines. Most problems come from three areas: canvas replay lifetime, clipping and fill-rule choices, or text layout state.
If the issue is WebGPU-specific, start with the WebGPU section below and then check the dedicated WebGPU, environment, window, external surface, and render target pages.
Nothing Appears on the Image
If you are drawing through image.Mutate(ctx => ctx.Paint(...)), the processing pipeline owns the canvas lifetime and replays the recorded drawing commands for you.
If you create a canvas manually, make sure the canvas is disposed before you inspect the destination image. Canvas drawing is recorded and replayed in order, so pending commands are not visible until the root canvas replays them. Flush() only seals queued commands into the replay timeline; it does not render them by itself.
using Image<Rgba32> image = new(400, 240, Color.White.ToPixel<Rgba32>());
using (DrawingCanvas canvas = image.CreateCanvas())
{
canvas.Fill(Brushes.Solid(Color.CornflowerBlue), new Rectangle(40, 40, 180, 100));
// Disposing the canvas replays the recorded drawing commands onto the image.
}
image.Save("output.png");
When you use images as drawing sources, keep those source images alive until the canvas has replayed. DrawImage and ImageBrush<TPixel> record the drawing operation; they do not make the source image safe to dispose before replay.
Clipping Removes the Wrong Area
ShapeOptions.BooleanOperation controls how the clip shape combines with the current drawing region. The default value is Difference, which subtracts the supplied shape from the current region. For the usual "draw only inside this shape" behavior, set it to BooleanOperation.Intersection.
DrawingOptions options = new()
{
ShapeOptions = new()
{
// Intersect keeps the part of subsequent drawing inside the clip shape.
BooleanOperation = BooleanOperation.Intersection
}
};
PointF clipCenter = new(200, 120);
SizeF clipSize = new(260, 160);
EllipsePolygon clip = new(clipCenter, clipSize);
canvas.Save(options, clip);
canvas.Fill(Brushes.Solid(Color.HotPink), new Rectangle(0, 0, 400, 240));
canvas.Restore();
Use Save(...) for scoped clipping and state changes. Call Restore() when the scoped operation is complete so later drawing returns to the previous state.
Holes or Overlaps Fill Unexpectedly
The fill rule controls how overlapping contours inside a complex polygon are interpreted. ImageSharp.Drawing defaults to IntersectionRule.NonZero, which matches the default used by SVG and web canvas APIs. With NonZero, contour winding order is meaningful, so holes are normally expressed by reversing the winding of the inner contour.
Use IntersectionRule.EvenOdd when you want parity-based filling where each crossing toggles between inside and outside. This can be convenient for imported geometry that does not carry reliable winding direction.
DrawingOptions options = new()
{
ShapeOptions = new()
{
// EvenOdd treats alternating contours as filled and unfilled regions.
IntersectionRule = IntersectionRule.EvenOdd
}
};
_ = canvas.Save(options);
canvas.Fill(Brushes.Solid(Color.MediumSeaGreen), complexPolygon);
canvas.Restore();
Text Is Not Centered Where Expected
For region-based text layout, use the text alignment options instead of manually subtracting measured text sizes. The Origin is the layout anchor, WrappingLength defines the line width, and HorizontalAlignment / VerticalAlignment place the text block relative to that anchor.
TextAlignment controls how wrapped lines are aligned inside the paragraph. HorizontalAlignment controls how the resulting paragraph bounds are positioned relative to Origin.
Styled Text Affects the Wrong Characters
Rich text runs use grapheme indices, not UTF-16 code unit indices. Start is inclusive and End is exclusive, so the affected range is [Start, End).
This matters for emoji, combining marks, flags, and other user-perceived characters that can contain multiple Unicode scalar values. See the Fonts Unicode page for the same indexing model.
Processors Run Before Earlier Drawing
Canvas operations are ordered, but image processors operate at replay barriers. A processor such as blur, opacity, or a mask operation includes drawing that was recorded before the Apply(...) call.
canvas.Fill(Brushes.Solid(Color.Black), shadowShape);
// Apply seals the shadow geometry before the blur processor is applied.
canvas.Apply(x => x.GaussianBlur(8));
This is most useful when you mix vector drawing with ImageSharp processors in the same canvas sequence.
Images, Brushes, or Masks Stop Working After Disposal
Drawing commands can be replayed later than the point where the command is recorded. Keep any source Image<TPixel> used by DrawImage, masks, or ImageBrush<TPixel> alive until the root canvas has been disposed.
The canvas does not own images passed into it. Dispose those images after the drawing scope that uses them has completed.
WebGPU Produces a Blank Frame
Probe WebGPU support before creating GPU-backed drawing resources. WebGPU depends on the runtime environment, adapter, device, texture format, and native surface or offscreen target.
For window or surface rendering, acquire a frame, draw into its canvas, and dispose the frame. Disposing the frame completes the drawing scope and presents it to the surface.
if (!surface.TryAcquireFrame(out WebGPUSurfaceFrame? frame))
{
return;
}
using (frame)
{
DrawingCanvas canvas = frame.CreateCanvas();
// Drawing commands are presented when the frame is disposed.
canvas.Clear(Brushes.Solid(Color.White));
canvas.Fill(Brushes.Solid(Color.SteelBlue), new Rectangle(40, 40, 180, 120));
}
Resize the WebGPUExternalSurface when the framebuffer size changes. If you need to read pixels back to the CPU, use a pixel type that matches the target texture format, for example Rgba32 with an Rgba8Unorm target.
A Good Debugging Order
- Confirm the root canvas scope is disposed before checking the output.
- Check source image lifetimes when using image brushes, masks, or
DrawImage. - Check
ShapeOptions.BooleanOperationwhen clipping. - Check
ShapeOptions.IntersectionRuleand contour winding for complex polygons. - Check text layout options before doing manual measurement math.
- Check grapheme-based
[Start, End)indices for rich text runs. - Probe WebGPU availability and surface frame acquisition before drawing GPU content.
Related Topics
- Canvas Drawing
- Clipping, Regions, and Layers
- Paths and Shapes
- Drawing Text
- WebGPU
- WebGPU Environment and Support
- WebGPU Window Rendering
- WebGPU External Surfaces
- WebGPU Offscreen Render Targets
Practical Guidance
- Start with lifetime and replay issues before debugging visual details.
- Reduce complex examples to one shape, one clip, or one text block to isolate state.
- Check canvas ordering around
Apply(...)whenever processors see unexpected pixels. - Check font availability and grapheme ranges before changing text drawing code.