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>
ElementShape modelNotes
RectangleA Shape with its own propertiesCheapest for a simple box
PathA GeometryMost flexible; one element for a complex figure
GeometryDrawingA drawing in a DrawingBrushStatic, no hit testing, no per-element cost
DrawingVisualA drawing with no framework overheadFor thousands of generated shapes
Canvas childrenFree positioningNo 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>
  • ImageBrush with Stretch="UniformToFill" and a Viewbox gives 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.

Animation and visual states WPF performance and testing MVVM applications

Last refreshed 2026-09-18.