Reputation: 3053
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!
Upvotes: 13
Views: 40734
Reputation: 41
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);
}
Upvotes: 0
Reputation: 7170
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);
}
Upvotes: 24
Reputation: 413
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");
}
}
Upvotes: 7
Reputation: 5325
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());
Upvotes: 0
Reputation: 12346
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);
Upvotes: 0