将 WPF 画布另存为图像
Posted
技术标签:
【中文标题】将 WPF 画布另存为图像【英文标题】:Saving a WPF canvas as an image 【发布时间】:2021-06-20 16:37:30 【问题描述】:我关注了this 文章,我保存了我的画布,但是,我想扩展代码的功能并将画布的特定部分保存为图像,而不是整个画布。
我尝试设置 rect.Offset
和 rect.Location
属性,但图像总是从画布的左上角保存。
有谁知道我怎样才能以类似的方式实现我想要的功能?
谢谢!
【问题讨论】:
替代:***.com/questions/67472818/… 【参考方案1】:一个简单的方法是在渲染整个画布后使用CroppedBitmap
。如果您需要多张图片,您可以重复使用相同的 RenderTargetBitmap
。
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);
如果要保存到位图对象而不是文件,可以使用:
using (Stream s = new MemoryStream())
pngEncoder.Save(s);
Bitmap myBitmap = new Bitmap(s);
【讨论】:
确保添加using System.Windows.Media.Imaging;
【参考方案2】:
我知道这是一个老问题,但我花了一段时间搜索和尝试不同的答案,才想出一些可靠的、运行良好的方法。因此,为了为将来节省一些时间,这里有一个小服务,可以将画布保存到文件中,或者返回 ImageSource 以在应用程序的其他位置显示。
对于生产应用程序,额外的 null 和错误检查等应该更加健壮。
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;
还有一个示例 WPF 应用程序演示了它的使用。
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>
以及调用服务背后的代码。
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");
【讨论】:
看在老旧的份上,给了你一个 +1,感谢您对此进行调查 :) 谢谢,这真的很有帮助。 我可以导出 .png 文件,但它的内容相当模糊。如果我制作包含一些 TextBlocks 的 Canvas 的屏幕截图并将其导出为 png 文件,它会非常清晰。我试图增加 DPI 并且 RenderToPNGFile() 创建了一个更大的文件。一个显示的字符现在使用了更多像素,但像以前一样模糊了。【参考方案3】:看你贴的链接,显然这里可以选择渲染的目标坐标。
RenderTargetBitmap rtb = new RenderTargetBitmap((int)rect.Right,
(int)rect.Bottom, 96d, 96d, System.Windows.Media.PixelFormats.Default);
【讨论】:
这不允许指定 X 和 Y 起始坐标 - 只有所需的宽度和高度。【参考方案4】:看看这个解决方案是否适合你。
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());
【讨论】:
这将无法实现 OP 的要求,因为问题是无法指定 X、Y 坐标。只有所需的宽度和高度。 Samuel 是对的,我可以更改我的矩形大小,但是除了左上角的默认起点之外,我无法将其放置在其他任何位置... 你设置的新尺寸是多少? 我将文章中的代码放在一个函数中,我调用了该函数三次,每次使用不同的大小(200x200、300x300 和 600x600),但我希望矩形为以画布上的某个点为中心。 仍然无法正常工作 - 它仍然从左上角保存图像以上是关于将 WPF 画布另存为图像的主要内容,如果未能解决你的问题,请参考以下文章