13

I was following this article and I got my canvas to be saved, however, I want to extend the code's functionality and save a particular part of my canvas as an image, rather than my entire canvas.

I tried setting the rect.Offset and rect.Location properties but the image is always saved from the upper left corner of my canvas.

Does anyone know how can I achieve my wanted functionality in a similar way?

Thanks!

1

5 Answers 5

24

A simple method would be to use a CroppedBitmap after rendering the whole canvas. You could reuse the same RenderTargetBitmap, if you need multiple images.

RenderTargetBitmap rtb = new RenderTargetBitmap((int)canvas.RenderSize.Width,
    (int)canvas.RenderSize.Height, 96d, 96d, System.Windows.Media.PixelFormats.Default);
rtb.Render(canvas);

var crop = new CroppedBitmap(rtb, new Int32Rect(50, 50, 250, 250));

BitmapEncoder pngEncoder = new PngBitmapEncoder();
pngEncoder.Frames.Add(BitmapFrame.Create(crop));

using (var fs = System.IO.File.OpenWrite("logo.png"))
{
    pngEncoder.Save(fs);
}

If you want to save to a bitmap object instead of a file, you can use:

using (Stream s = new MemoryStream())
{
    pngEncoder.Save(s);
    Bitmap myBitmap = new Bitmap(s);
}
Sign up to request clarification or add additional context in comments.

1 Comment

Make sure to add using System.Windows.Media.Imaging;
7

I know this is an old question, but it took me a while of searching and trying different answers to come up with something that worked reliably well. So to save some time for those in the future, here is a little service to either save a canvas out to a file, or return an ImageSource for display elsewhere in your application.

It should be made more robust for a production application, additional null and error checking, etc..

public static class RenderVisualService
{
    private const double defaultDpi = 96.0;

    public static ImageSource RenderToPNGImageSource(Visual targetControl)
    {
        var renderTargetBitmap = GetRenderTargetBitmapFromControl(targetControl);

        var encoder = new PngBitmapEncoder();
        encoder.Frames.Add(BitmapFrame.Create(renderTargetBitmap));

        var result = new BitmapImage();

        using (var memoryStream = new MemoryStream())
        {
            encoder.Save(memoryStream);
            memoryStream.Seek(0, SeekOrigin.Begin);

            result.BeginInit();
            result.CacheOption = BitmapCacheOption.OnLoad;
            result.StreamSource = memoryStream;
            result.EndInit();
        }

        return result;
    }

    public static void RenderToPNGFile(Visual targetControl, string filename)
    {
        var renderTargetBitmap = GetRenderTargetBitmapFromControl(targetControl);

        var encoder = new PngBitmapEncoder();
        encoder.Frames.Add(BitmapFrame.Create(renderTargetBitmap));

        var result = new BitmapImage();

        try
        {
            using (var fileStream = new FileStream(filename, FileMode.Create))
            {
                encoder.Save(fileStream);
            }
        }
        catch (Exception ex)
        {
            System.Diagnostics.Debug.WriteLine($"There was an error saving the file: {ex.Message}");
        }
    }

    private static BitmapSource GetRenderTargetBitmapFromControl(Visual targetControl, double dpi = defaultDpi)
    {
        if (targetControl == null) return null;

        var bounds = VisualTreeHelper.GetDescendantBounds(targetControl);
        var renderTargetBitmap = new RenderTargetBitmap((int)(bounds.Width * dpi / 96.0),
                                                        (int)(bounds.Height * dpi / 96.0),
                                                        dpi,
                                                        dpi,
                                                        PixelFormats.Pbgra32);

        var drawingVisual = new DrawingVisual();

        using (var drawingContext = drawingVisual.RenderOpen())
        {
            var visualBrush = new VisualBrush(targetControl);
            drawingContext.DrawRectangle(visualBrush, null, new Rect(new Point(), bounds.Size));
        }

        renderTargetBitmap.Render(drawingVisual);
        return renderTargetBitmap;
    }
}

And a sample WPF app demonstrating it's use.

MainWindow.xaml

<Window x:Class="CanvasToBitmapDemo.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:CanvasToBitmapDemo"
    mc:Ignorable="d"
    Title="MainWindow" Height="450" Width="800">
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto" />
        <RowDefinition Height="Auto" />
        <RowDefinition Height="Auto" />
    </Grid.RowDefinitions>

    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="1*" />
        <ColumnDefinition Width="1*" />
    </Grid.ColumnDefinitions>

    <StackPanel Grid.Row="0" Grid.ColumnSpan="2" Orientation="Horizontal" HorizontalAlignment="Center">
        <Button Click="Button_Click" Content="Capture Image" Width="100"/>
        <Button Click="Button_Click_1" Content="Save To Disk" Width="100"/>
    </StackPanel>

    <Canvas x:Name="PART_Canvas" Grid.Row="1" Grid.Column="0">
        <Ellipse Canvas.Top="50"
                 Canvas.Left="60"
                 Fill="Gold"
                 Width="250"
                 Height="250" />

        <Polyline Stroke="#FF853D00"
                  StrokeThickness="10"
                  StrokeEndLineCap="Round"
                  StrokeStartLineCap="Round"
                  Points="110,100 120,97 130,95 140,94 150,95 160,97 170,100" />

        <Ellipse Canvas.Top="115"
                 Canvas.Left="114"
                 Fill="#FF853D00"
                 Width="45"
                 Height="50" />

        <Polyline Stroke="#FF853D00"
                  StrokeThickness="10"
                  StrokeEndLineCap="Round"
                  StrokeStartLineCap="Round"
                  Points="205,120 215,117 225,115 235,114 245,115 255,117 265,120" />

        <Ellipse Canvas.Top="120"
                 Canvas.Left="208"
                 Fill="#FF853D00"
                 Width="45"
                 Height="50" />

        <Polyline Stroke="#FF853D00"
                  StrokeThickness="10"
                  StrokeEndLineCap="Round"
                  StrokeStartLineCap="Round"
                  Points="150,220 160,216 170,215 180,215 190,216 202,218 215,221" />

    </Canvas>

    <Image x:Name="PART_Image" Grid.Row="1" Grid.Column="1" Stretch="None"/>
</Grid>

And the code behind making the calls into the service.

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        PART_Image.Source = RenderVisualService.RenderToPNGImageSource(PART_Canvas);
    }

    private void Button_Click_1(object sender, RoutedEventArgs e)
    {
        RenderVisualService.RenderToPNGFile(PART_Canvas, "myawesomeimage.png");
    }
}

2 Comments

Gave you a +1 for old time's sake, thanks for investigating this :)
I can export the .png file, but it's content is rather blur. If I make a screenshot of the Canvas containing some TextBlocks and export it to a png file, it is completely sharp. I tried to increase the DPI and RenderToPNGFile() created a bigger file. One displayed character used now more pixels, but was blurred as before.
0

Looking at the link you posted, obviously you can choose the rendered target coordinates here.

RenderTargetBitmap rtb = new RenderTargetBitmap((int)rect.Right,
     (int)rect.Bottom, 96d, 96d, System.Windows.Media.PixelFormats.Default);

1 Comment

This does not allow the specification of the X and Y starting coordinates - only the desired width and height.
0

See if this solution works for you.

Size size = new Size(width, height);
canvas.Measure(size);
canvas.Arrange(new Rect(X, Y, width, height));

//Save Image
...  
...

// Revert old position
canvas.Measure(new Size());

5 Comments

This won't achieve what the OP requested as the problem is that the X,Y coordinates cannot be specified. Only the desired width and height.
Samuel is right, I'm able to change my rectangle size, however I cannot position it anywhere else but the default starting point of the top left corner...
What did you set as your new size?
I've placed the code in the article inside a function, and I call that function three times, each time with a different size (200x200, 300x300 and 600x600), but I want the rectangle to be centered about a certain point on the canvas.
Still doesn't work - it still keeps saving the image from the top left corner
0

In my case, I needed to apply a size constraint for the resulting image, since the images needed to be stored and later used as a small icon.

I was able to scale the image down to a reasonable size using CreateScaledRect below in combination with GetScaledRenderTargetBitmapFromControl (thanks to code from @Andy Stagg's post).

Then, to store the image for later use, I used the SaveImageOfControlToStream method below.

private static Rect CreateScaledRect(Visual targetControl)
{
    Rect scaledRect;
    var bounds = VisualTreeHelper.GetDescendantBounds(targetControl);

    // maintain aspect ratio and make sure scaledRect is at least 64 wide or 64 high
    if (bounds.Width < bounds.Height)
    {
       scaledRect = new Rect(new Point(), new Size(64, bounds.Height / bounds.Width * 64));
    }
    else
    {
       scaledRect = new Rect(new Point(), new Size(bounds.Width / bounds.Height * 64, 64));
    }

    return scaledRect;
}

private static BitmapSource GetScaledRenderTargetBitmapFromControl(Visual targetControl, double dpi = defaultDpi)
{
    if (targetControl == null) return null;

    // Calling CreateScaledRect here to reduce image size
    var bounds = CreateScaledRect(targetControl);
    var renderTargetBitmap = new RenderTargetBitmap((int)(bounds.Width * dpi / 96.0),
                                                    (int)(bounds.Height * dpi / 96.0),
                                                    dpi,
                                                    dpi,
                                                    PixelFormats.Pbgra32);

    var drawingVisual = new DrawingVisual();

    using (var drawingContext = drawingVisual.RenderOpen())
    {
        var visualBrush = new VisualBrush(targetControl);
        drawingContext.DrawRectangle(visualBrush, null, new Rect(new Point(), bounds.Size));
    }

    renderTargetBitmap.Render(drawingVisual);
    return renderTargetBitmap;
}

public static void SaveImageOfControlToStream(Stream outputStream, Visual targetControl)
{
    var bitmapSource = GetScaledRenderTargetBitmapFromControl(targetControl);
            
    PngBitmapEncoder encoder = new PngBitmapEncoder();
    encoder.Frames.Add(BitmapFrame.Create(bitmapSource ));
    encoder.Save(outputStream);
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.