Draw a smiley face with WPF in C#

[example]

Often I need to use images for demonstration purposes or for a book, and a smiley face makes a neutral, pleasant image. This example shows how to draw a smiley face with XAML and WPF, and then save an image of the face into a PNG file.

The following code shows how the program draws the smiley face on the left.

<Grid Margin="10">
    <Grid.RowDefinitions>
        <RowDefinition Height="100"/>
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="100"/>
        <ColumnDefinition Width="10"/>
        <ColumnDefinition Width="100"/>
    </Grid.ColumnDefinitions>
    <Grid Grid.Row="0" Grid.Column="0">
        <Canvas Name="canvas1" Background="Transparent">
            <Ellipse Width="100" Height="100"
                Fill="Yellow" Stroke="Red" StrokeThickness="3"/>

            <Ellipse Width="20" Height="30"
                Canvas.Left="20" Canvas.Top="20"
                Fill="White" Stroke="Black" StrokeThickness="1"/>
            <Ellipse Width="10" Height="20"
                Canvas.Left="30" Canvas.Top="25"
                Fill="Black" Stroke="Black" StrokeThickness="1"/>

            <Ellipse Width="20" Height="30"
                Canvas.Left="60" Canvas.Top="20"
                Fill="White" Stroke="Black" StrokeThickness="1"/>
            <Ellipse Width="10" Height="20"
                Canvas.Left="70" Canvas.Top="25"
                Fill="Black" Stroke="Black" StrokeThickness="1"/>

            <Ellipse Width="15" Height="25"
                Canvas.Left="42.5" Canvas.Top="45"
                Fill="LightBlue" Stroke="Blue" StrokeThickness="1"/>

            <Path Stroke="Black" StrokeThickness="2"  
                Data="M 15,50 A 35,35 0 1 0 85,50" />
        </Canvas>
    </Grid>

    ...
</Grid>

The code first creates a Grid control that defines three columns: two for smiley face drawings and one to form a margin between them.

Inside that Grid control’s first cell is another Grid that contains a Canvas control named canvas1.

The Canvas holds ellipses of various colors to make up most of the smiley face. The final control inside the Canvas is a Path object. Its Data property uses the path minilanguage to create an arc to represent the smiley face’s smile.

After this inner Grid is another Grid that contains controls to draw the smiley face on the right. Its Canvas control is named canvas2. The only difference is that the second Canvas control has its Background property set to a LinearGradientBrush instead of the color Transparent.

When the program starts, it executes the following code.

// Save the canvas images.
private void Window_Loaded(object sender, RoutedEventArgs e)
{
    SaveControlImage(canvas1, "Smiley no bg.png");
    SaveControlImage(canvas2, "Smiley with bg.png");
}

// Save a control's image.
private void SaveControlImage(FrameworkElement control,
    string filename)
{
    // Get the size of the Visual and its descendants.
    Rect rect = VisualTreeHelper.GetDescendantBounds(control);

    // Make a DrawingVisual to make a screen
    // representation of the control.
    DrawingVisual dv = new DrawingVisual();

    // Fill a rectangle the same size as the control
    // with a brush containing images of the control.
    using (DrawingContext ctx = dv.RenderOpen())
    {
        VisualBrush brush = new VisualBrush(control);
        ctx.DrawRectangle(brush, null, new Rect(rect.Size));
    }

    // Make a bitmap and draw on it.
    int width = (int)control.ActualWidth;
    int height = (int)control.ActualHeight;
    RenderTargetBitmap rtb = new RenderTargetBitmap(
        width, height, 96, 96, PixelFormats.Pbgra32);
    rtb.Render(dv);

    // Make a PNG encoder.
    PngBitmapEncoder encoder = new PngBitmapEncoder();
    encoder.Frames.Add(BitmapFrame.Create(rtb));

    // Save the file.
    using (FileStream fs = new FileStream(filename,
        FileMode.Create, FileAccess.Write, FileShare.None))
    {
        encoder.Save(fs);
    }
}

The Window_Loaded event handler simply calls the SaveControlImage method, passing it the canvas controls and the file names where their images should be saved.

The SaveControlImage method first gets the bounds of the Canvas control’s child hierarchy.

Next the method creates a DrawingVisual on which to draw. It then opens the DrawingVisual and creates a VisualBrush that can fill areas with images of the Canvas control. It uses the DrawRectangle method to fill the control’s child hierarchy bounds with the brush. The result of this is to create a rectangle filled with an image of the Canvas control containing the smiley face image.

Next the method gets the size of the Canvas control and creates a RenderTargetBitmap big enough to hold it. It then renders the DrawingVisual on the RenderTargetBitmap. The result of this is that the RenderTargetBitmap now holds the image of the Canvas control.

The method then creates a PngBitmapEncoder and adds the RenderTargetBitmap to it. It finishes by opening a FileStream to the file where we want to save the image and then calling the encoder’s Save method to save the image into the file.

If you run the program and then look in its executable directory, you will find the two smiley face image files.

If this all seems very roundabout and confusing, you’re right. This is in keeping with WPF’s unofficial slogan, “Twice as flexible and only five times as hard.”

Despite WPF’s non-intuitive nature, the program lets you easily create pictures in WPF and save them into PNG files. You can modify the XAML code to generate other images if you like, and the code behind will save the result into a file.


Download Example   Follow me on Twitter   RSS feed   Donate




About RodStephens

Rod Stephens is a software consultant and author who has written more than 30 books and 250 magazine articles covering C#, Visual Basic, Visual Basic for Applications, Delphi, and Java.
This entry was posted in drawing, graphics, image processing, wpf, XAML and tagged , , , , , , , , , , , , , , . Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *