2D graphics, shapes and transforms
Shapes versus drawings, Geometry and Path, brushes and gradients, render transforms, and rendering a visual to a bitmap offscreen.
Shapes and geometry
<Canvas Width="200" Height="120">
<Rectangle Canvas.Left="10" Canvas.Top="10" Width="80" Height="50"
Fill="#2F6FED" RadiusX="6" RadiusY="6" />
<Ellipse Canvas.Left="110" Canvas.Top="10" Width="60" Height="60" Stroke="Gray" StrokeThickness="2" />
<!-- Polyline: an open figure. Polygon: closed and filled. -->
<Polyline Points="10,90 40,70 70,100 100,60" Stroke="SeaGreen" StrokeThickness="2" />
<!-- Path with a geometry mini-language: M move, L line, C curve, Z close -->
<Path Data="M 120 90 C 140 60, 170 120, 190 80 Z"
Fill="#EED46A" Stroke="#B08300" StrokeThickness="1.5" />
</Canvas>| Element | Shape model | Notes |
|---|---|---|
Rectangle | A Shape with its own properties | Cheapest for a simple box |
Path | A Geometry | Most flexible; one element for a complex figure |
GeometryDrawing | A drawing in a DrawingBrush | Static, no hit testing, no per-element cost |
DrawingVisual | A drawing with no framework overhead | For thousands of generated shapes |
Canvas children | Free positioning | No automatic layout; you own every position |
<!-- frozen and reused is far cheaper than recreated per item -->
<Path Data="{StaticResource WarningIcon}"
Fill="{StaticResource AccentBrush}"
Stretch="Uniform" Width="16" Height="16" />Brushes, gradients and images
<Border>
<Border.Background>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
<GradientStop Color="#2F6FED" Offset="0" />
<GradientStop Color="#7BA7F5" Offset="1" />
</LinearGradientBrush>
</Border.Background>
</Border>
<!-- a repeating pattern built from a geometry, no image file required -->
<Rectangle Width="120" Height="40">
<Rectangle.Fill>
<DrawingBrush TileMode="Tile" Viewport="0,0,8,8" ViewportUnits="Absolute">
<DrawingBrush.Drawing>
<GeometryDrawing Brush="#EEE">
<GeometryDrawing.Geometry>
<LineGeometry StartPoint="0,8" EndPoint="8,0" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
</DrawingBrush.Drawing>
</DrawingBrush>
</Rectangle.Fill>
</Rectangle>ImageBrushwithStretch="UniformToFill"and aViewboxgives you a cropped background without distorting the image.- Set
RenderOptions.BitmapScalingMode="HighQuality"on scaled images; the default favours speed and looks soft on large downscales. - Call
Freeze()on a brush or geometry that will not change. A frozen object is immutable, so WPF can skip change tracking and share it across threads.
Transforms and rendering to a bitmap
// render any visual to a PNG without showing it on screen
static void RenderToPng(FrameworkElement element, int width, int height, string path)
{
var visual = new DrawingVisual();
using (var dc = visual.RenderOpen())
{
var brush = new VisualBrush(element) { Stretch = Stretch.Uniform };
dc.DrawRectangle(brush, null, new Rect(0, 0, width, height));
}
var bitmap = new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Pbgra32);
bitmap.Render(visual);
var encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(bitmap));
using var stream = File.Create(path);
encoder.Save(stream);
}
// measure and arrange first if the element has never been laid out
element.Measure(new Size(width, height));
element.Arrange(new Rect(0, 0, width, height));
element.UpdateLayout();<!-- transforms do not affect layout; LayoutTransform does -->
<Image Source="/Assets/chart.png" Width="240">
<Image.RenderTransform>
<RotateTransform Angle="-90" />
</Image.RenderTransform>
</Image>
<StackPanel>
<TextBlock Text="Rotated with layout" RenderTransformOrigin="0.5,0.5">
<TextBlock.LayoutTransform>
<RotateTransform Angle="-90" />
</TextBlock.LayoutTransform>
</TextBlock>
</StackPanel>💡
RenderTransform moves pixels after layout, so the element keeps its original footprint - perfect for hover effects, misleading for anything that must push neighbours aside. LayoutTransform runs before layout and reserves the rotated size, which is what you want for a sideways axis label.FAQ
When should I use a DrawingVisual instead of shapes?
Past a few hundred primitives. Shapes are full framework elements with layout and hit testing; a
DrawingVisual inside a VisualHost gives you the same pixels without that overhead.Why is my exported image blurry?
The bitmap is rendered at 96 DPI regardless of the screen DPI, and a
VisualBrush stretch scales the visual. Render at the target pixel size and set the DPI values in the RenderTargetBitmap constructor to match.Related
Animation and visual states WPF performance and testing MVVM applications
Last refreshed 2026-09-18.