首页 星云 工具 资源 星选 资讯 热门工具
:

PDF转图片 完全免费 小红书视频下载 无水印 抖音视频下载 无水印 数字星空

.NET 控件转图片

编程知识
2024年07月26日 16:08

Windows应用开发有很多场景需要动态获取控件显示的图像,即控件转图片,用于其它界面的显示、传输图片数据流、保存为本地图片等用途。

下面分别介绍下一些实现方式以及主要使用场景

RenderTargetBitmap

控件转图片BitmapImage/BitmapSource,在WPF中可以使用RenderTargetBitmap获取捕获控件的图像。

RenderTargetBitmap 是用于将任何 Visual 元素内容渲染为位图的主要工具

下面我们展示下简单快速的获取控件图片:

 1     private void CaptureButton_OnClick(object sender, RoutedEventArgs e)
 2     {
 3         var dpi = GetAppStartDpi();
 4         var bitmapSource = ToImageSource(Grid1, Grid1.RenderSize, dpi.X, dpi.Y);
 5         CaptureImage.Source = bitmapSource;
 6     }
 7     /// <summary>
 8     /// Visual转图片
 9     /// </summary>
10     public static BitmapSource ToImageSource(Visual visual, Size size, double dpiX, double dpiY)
11     {
12         var validSize = size.Width > 0 && size.Height > 0;
13         if (!validSize) throw new ArgumentException($"{nameof(size)}值无效:${size.Width},${size.Height}");
14         if (Math.Abs(size.Width) > 0.0001 && Math.Abs(size.Height) > 0.0001)
15         {
16             RenderTargetBitmap bitmap = new RenderTargetBitmap((int)(size.Width * dpiX), (int)(size.Height * dpiY), dpiX * 96, dpiY * 96, PixelFormats.Pbgra32);
17             bitmap.Render(visual);
18             return bitmap;
19         }
20         return new BitmapImage();
21     }

获取当前窗口所在屏幕DPI,使用控件已经渲染的尺寸,就可以捕获到指定控件的渲染图片。捕获到图片BitmapSource,即可以将位图分配给Image的Source属性来显示。

DPI获取可以参考 C# 获取当前屏幕DPI - 唐宋元明清2188 - 博客园 (cnblogs.com)

上面方法获取的是BitmapSource,BitmapSource是WPF位图的的抽象基类,继承自ImageSource,因此可以直接用作WPF控件如Image的图像源。RenderTargetBitmap以及BitmapImage均是BitmapSource的派生实现类

RenderTargetBitmap此处用于渲染Visual对象生成位图,RenderTargetBitmap它可以用于拼接、合并(上下层叠加)、缩放图像等。BitmapImage主要用于从文件、URL及流中加载位图。

而捕获返回的基类BitmapSource可以用于通用位图的一些操作(如渲染、转成流数据、保存),BitmapSource如果需要转成可以支持支持更高层次图像加载功能和延迟加载机制的BitmapImage,可以按如下操作:

 1     /// <summary>
 2     /// WPF位图转换
 3     /// </summary>
 4     private static BitmapImage ToBitmapImage(BitmapSource bitmap,Size size,double dpiX,double dpiY)
 5     {
 6         MemoryStream memoryStream = new MemoryStream();
 7         BitmapEncoder encoder = new PngBitmapEncoder();
 8         encoder.Frames.Add(BitmapFrame.Create(bitmap));
 9         encoder.Save(memoryStream);
10         memoryStream.Seek(0L, SeekOrigin.Begin);
11 
12         BitmapImage bitmapImage = new BitmapImage();
13         bitmapImage.BeginInit();
14         bitmapImage.DecodePixelWidth = (int)(size.Width * dpiX);
15         bitmapImage.DecodePixelHeight = (int)(size.Height * dpiY);
16         bitmapImage.StreamSource = memoryStream;
17         bitmapImage.EndInit();
18         bitmapImage.Freeze();
19         return bitmapImage;
20     }

这里选择了Png编码器,先将bitmapSource转换成图片流,然后再解码为BitmapImage。

图片编码器有很多种用途,上面是将流转成内存流,也可以转成文件流保存本地文件:

1     var encoder = new PngBitmapEncoder();
2     encoder.Frames.Add(BitmapFrame.Create(bitmapSource));
3     using Stream stream = File.Create(imagePath);
4     encoder.Save(stream);

回到控件图片捕获,上方操作是在界面控件渲染后的场景。如果控件未加载,需要更新布局下:

1     //未加载到视觉树的,按指定大小布局
2     //按size显示,如果设计宽高大于size则按sie裁剪,如果设计宽度小于size则按size放大显示。
3     element.Measure(size);
4     element.Arrange(new Rect(size));

另外也存在场景:控件不确定它的具体尺寸,只是想单纯捕获图像,那代码整理后如下:

 1     public BitmapSource ToImageSource(Visual visual, Size size = default)
 2     {
 3         if (!(visual is FrameworkElement element))
 4         {
 5             return null;
 6         }
 7         if (!element.IsLoaded)
 8         {
 9             if (size == default)
10             {
11                 //计算元素的渲染尺寸
12                 element.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
13                 element.Arrange(new Rect(new Point(), element.DesiredSize));
14                 size = element.DesiredSize;
15             }
16             else
17             {
18                 //未加载到视觉树的,按指定大小布局
19                 //按size显示,如果设计宽高大于size则按sie裁剪,如果设计宽度小于size则按size放大显示。
20                 element.Measure(size);
21                 element.Arrange(new Rect(size));
22             }
23         }
24         else if (size == default)
25         {
26             Rect rect = VisualTreeHelper.GetDescendantBounds(visual);
27             if (rect.Equals(Rect.Empty))
28             {
29                 return null;
30             }
31             size = rect.Size;
32         }
33 
34         var dpi = GetAppStartDpi();
35         return ToImageSource(visual, size, dpi.X, dpi.Y);
36     }

控件未加载时,可以使用DesiredSize来临时替代操作,这类方案获取的图片宽高比例可能不太准确。已加载完的控件,可以通过VisualTreeHelper.GetDescendantBounds获取视觉树子元素集的坐标矩形区域Bounds。

kybs00/VisualImageDemo: RenderTargetBitmap获取控件图片 (github.com)

所以控件转BitmapSource、保存等,可以使用RenderTargetBitmap来实现

VisualBrush

如果只是程序内其它界面同步展示此控件,就不需要RenderTargetBitmap了,可以直接使用VisualBrush

VisualBrush是非常强大的类,允许使用另一个Visual对象(界面显示控件最底层的UI元素基类)作为画刷的内容,并将其绘制在其它UI元素上(当然,不是直接挂到其它视觉树上,WPF也不支持元素同时存在于俩个视觉树的设计)

具体的可以看下官网VisualBrush 类 (System.Windows.Media) | Microsoft Learn,这里做一个简单的DEMO:

 1 <Window x:Class="VisualBrushDemo.MainWindow"
 2         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 3         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 4         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
 5         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
 6         xmlns:local="clr-namespace:VisualBrushDemo"
 7         mc:Ignorable="d" Title="MainWindow" Height="450" Width="800">
 8     <Grid>
 9         <Grid.ColumnDefinitions>
10             <ColumnDefinition Width="*"/>
11             <ColumnDefinition Width="10"/>
12             <ColumnDefinition/>
13         </Grid.ColumnDefinitions>
14         <Canvas x:Name="Grid1" Background="BlueViolet">
15             <TextBlock x:Name="TestTextBlock" Text="截图测试" VerticalAlignment="Center" HorizontalAlignment="Center" 
16                        Width="100" Height="30" Background="Red" TextAlignment="Center" LineHeight="30" Padding="0 6 0 0"
17                        MouseDown="TestTextBlock_OnMouseDown" 
18                        MouseMove="TestTextBlock_OnMouseMove"
19                        MouseUp="TestTextBlock_OnMouseUp"/>
20         </Canvas>
21         <Grid x:Name="Grid2" Grid.Column="2">
22             <Grid.Background>
23                 <VisualBrush Stretch="UniformToFill"
24                              AlignmentX="Center" AlignmentY="Center"
25                              Visual="{Binding ElementName=Grid1}"/>
26             </Grid.Background>
27         </Grid>
28     </Grid>
29 </Window>

CS代码:

 1     private bool _isDown;
 2     private Point _relativeToBlockPosition;
 3     private void TestTextBlock_OnMouseDown(object sender, MouseButtonEventArgs e)
 4     {
 5         _isDown = true;
 6         _relativeToBlockPosition = e.MouseDevice.GetPosition(TestTextBlock);
 7         TestTextBlock.CaptureMouse();
 8     }
 9 
10     private void TestTextBlock_OnMouseMove(object sender, MouseEventArgs e)
11     {
12         if (_isDown)
13         {
14             var position = e.MouseDevice.GetPosition(Grid1);
15             Canvas.SetTop(TestTextBlock, position.Y - _relativeToBlockPosition.Y);
16             Canvas.SetLeft(TestTextBlock, position.X - _relativeToBlockPosition.X);
17         }
18     }
19 
20     private void TestTextBlock_OnMouseUp(object sender, MouseButtonEventArgs e)
21     {
22         TestTextBlock.ReleaseMouseCapture();
23         _isDown = false;
24     }

kybs00/VisualImageDemo: RenderTargetBitmap获取控件图片 (github.com) 

左侧操作一个控件移动,右侧区域动态同步显示左侧视觉。VisualBrush.Visual可以直接绑定指定控件,一次绑定、后续同步界面变更,延时超低

同步界面变更是如何操作的?下面是部分代码,我们看到,VisualBrush内有监听元素的内容变更,内容变更后VisualBrush也会自动同步DoLayout(element)一次:

 1     // We need 2 ways of initiating layout on the VisualBrush root.
 2     // 1. We add a handler such that when the layout is done for the
 3     // main tree and LayoutUpdated is fired, then we do layout for the
 4     // VisualBrush tree.
 5     // However, this can fail in the case where the main tree is composed
 6     // of just Visuals and never does layout nor fires LayoutUpdated. So
 7     // we also need the following approach.
 8     // 2. We do a BeginInvoke to start layout on the Visual. This approach 
 9     // alone, also falls short in the scenario where if we are already in 
10     // MediaContext.DoWork() then we will do layout (for main tree), then look
11     // at Loaded callbacks, then render, and then finally the Dispather will 
12     // fire us for layout. So during loaded callbacks we would not have done
13     // layout on the VisualBrush tree.
14     //
15     // Depending upon which of the two layout passes comes first, we cancel
16     // the other layout pass.
17     element.LayoutUpdated += OnLayoutUpdated;
18     _DispatcherLayoutResult = Dispatcher.BeginInvoke(
19         DispatcherPriority.Normal,
20         new DispatcherOperationCallback(LayoutCallback),
21         element);
22     _pendingLayout = true;

而显示绑定元素,VisualBrush内部是通过元素Visual.Render方法将图像给到渲染上下文:

1     RenderContext rc = new RenderContext();
2     rc.Initialize(channel, DUCE.ResourceHandle.Null);
3     vVisual.Render(rc, 0);

其内部是将Visual的快照拿来显示输出。VisualBrush基于这种渲染快照的机制,不会影响原始视觉元素在原来视觉树的位置,所以并不会导致不同视觉树之间的冲突。

此类VisualBrush方案,适合制作预览显示,比如打印预览、PPT页面预览列表等。

下面是我们团队开发的会议白板-页面列表预览效果:

 

 关键字:RenderTargetBitmap、VisualBrush、控件转图片/控件截图

From:https://www.cnblogs.com/kybs0/p/18324167
本文地址: http://shuzixingkong.net/article/464
0评论
提交 加载更多评论
其他文章 【工具】IDEA怎么查看maven依赖链路?
当我在SpringBoot项目中想加个依赖,但是不确定现有依赖的依赖的依赖.....有没有添加过这个依赖,怎么办呢?如果添加过了但是不知道我需要的这个依赖属于哪个依赖的下面,怎么查呢? IDEA中提供了很方便的视图可以满足我们的需求 第一步点击项目右侧的maven 第二步选中Dependencies
【工具】IDEA怎么查看maven依赖链路? 【工具】IDEA怎么查看maven依赖链路? 【工具】IDEA怎么查看maven依赖链路?
Jetpack Compose学习(12)——Material Theme的主题色切换
原文:Jetpack Compose学习(12)——Material Theme的主题色切换-Stars-One的杂货小窝 闲着无事研究了下Jetpack Compose M3 主题切换效果 本系列以往文章请查看此分类链接Jetpack compose学习 如何生成主题 首先,我们需要知道的是,M3
Jetpack Compose学习(12)——Material Theme的主题色切换 Jetpack Compose学习(12)——Material Theme的主题色切换 Jetpack Compose学习(12)——Material Theme的主题色切换
空间反演对称性 (Spatial Inversion Symmetry) 和非线性响应 (Non-linear Response)
我们定义一次宇称变换 (parity transformation) 为反转所有坐标: \[\mathcal{P}: \begin{pmatrix} x \\ y \\ z \end{pmatrix} \rightarrow \begin{pmatrix} -x \\ -y \\ -z \end{p
空间反演对称性 (Spatial Inversion Symmetry) 和非线性响应 (Non-linear Response) 空间反演对称性 (Spatial Inversion Symmetry) 和非线性响应 (Non-linear Response) 空间反演对称性 (Spatial Inversion Symmetry) 和非线性响应 (Non-linear Response)
《最新出炉》系列入门篇-Python+Playwright自动化测试-54- 上传文件(input控件) - 上篇
1.简介 在实际工作中,我们进行web自动化的时候,文件上传是很常见的操作,例如上传用户头像,上传身份证信息等。所以宏哥打算按上传文件的分类对其进行一下讲解和分享。 2.上传文件的API(input控件) Playwright是一个现代化的自动化测试工具,它支持多种浏览器和操作系统,可以帮助开发人员
《最新出炉》系列入门篇-Python+Playwright自动化测试-54- 上传文件(input控件) - 上篇 《最新出炉》系列入门篇-Python+Playwright自动化测试-54- 上传文件(input控件) - 上篇 《最新出炉》系列入门篇-Python+Playwright自动化测试-54- 上传文件(input控件) - 上篇
微服务:解决复杂业务的妙方
1 微服务介绍 1)什么是微服务 ​ 微服务(Microservices)是一种软件架构风格,它将一个大型应用程序拆分成许多较小的、松散耦合的、独立运行的服务。这些服务通常围绕特定功能或业务领域组织,可以独立开发、部署、扩展和更新。微服务之间通过轻量级的通信协议(如HTTP/REST、消息队列等)相
微服务:解决复杂业务的妙方 微服务:解决复杂业务的妙方 微服务:解决复杂业务的妙方
矩阵的奇异值分解(SVD)及其应用
该博客针对矩阵的奇异值分解(SVD)展开介绍,主要介绍了奇异值分解的计算及其几何意义,并基于C++编程语言举例说明了SVD分解的一些应用。
矩阵的奇异值分解(SVD)及其应用 矩阵的奇异值分解(SVD)及其应用 矩阵的奇异值分解(SVD)及其应用
后端说,单页面SPA和前端路由是怎么回事
没有请求的路由 在传统开发中,浏览器点击一个超链接,就会像后端web服务器发送一个html文档请求,然后页面刷新。但开始单页面开发后,就完全不同了。 单页面?这个概念难以理解。我用一个js作为整个web应用,然后再这个js中操作dom变化,以此来实现页面变化。这不叫单页面吗?这叫!但不完善,因为这种
后端说,单页面SPA和前端路由是怎么回事
Asp .Net Core 系列:详解授权以及实现角色、策略、自定义三种授权和自定义响应
什么是授权(Authorization)? 在 ASP.NET Core 中,授权(Authorization)是控制对应用资源的访问的过程。它决定了哪些用户或用户组可以访问特定的资源或执行特定的操作。授权通常与身份验证(Authentication)一起使用,身份验证是验证用户身份的过程,授权与身
Asp .Net Core 系列:详解授权以及实现角色、策略、自定义三种授权和自定义响应