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

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

RenderTargetBitmap

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

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

 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 类 (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 }

左侧操作一个控件移动,右侧区域动态同步显示左侧视觉

VisualBrush.Visual可以直接绑定指定控件,一次绑定、后续同步界面变更

下面是部分代码,我们看到,VisualBrush内有监听元素的内容变更:

 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);

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

.NET 控件转图片的更多相关文章

  1. MFC编程入门之二十七(常用控件:图片控件PictureControl)

    上一节讲的是滚动条控件,本节主要讲一种简单实用的控件,图片控件Picture Control.我们可以在界面某个位置放入图片控件,显示图片以美化界面. 图片控件简介 图片控件和前面讲到的静态文本框都是 ...

  2. File控件选择图片的时候在Html5下马上预览

    页面HTML <div> <img src="@pic.Path" id="img" style="width:200px;heig ...

  3. iOS开发UI篇—UIScrollView控件实现图片缩放功能

    iOS开发UI篇—UIScrollView控件实现图片缩放功能 一.缩放 1.简单说明: 有些时候,我们可能要对某些内容进行手势缩放,如下图所示 UIScrollView不仅能滚动显示大量内容,还能对 ...

  4. iOS开发UI篇—UIScrollView控件实现图片轮播

    iOS开发UI篇—UIScrollView控件实现图片轮播 一.实现效果 实现图片的自动轮播            二.实现代码 storyboard中布局 代码: #import "YYV ...

  5. iOS开发——UI高级OC篇&自定义控件之调整按钮中子控件(图片和文字)的位置

    自定义控件之调整按钮中子控件(图片和文字)的位置 其实还有一种是在storyBoard中实现的,只需要设置对应空间的左右间距: 这里实现前面两种自定义的方式 一:imageRectForContent ...

  6. 【转】 iOS开发UI篇—UIScrollView控件实现图片轮播

    原文:http://www.cnblogs.com/wendingding/p/3763527.html iOS开发UI篇—UIScrollView控件实现图片轮播 一.实现效果 实现图片的自动轮播 ...

  7. VS2010/MFC常用控件:图片控件Picture Control

    图片控件Picture Control 本节主要讲一种简单实用的控件,图片控件Picture Control.我们可以在界面某个位置放入图片控件,显示图片以美化界面. 图片控件简介 图片控件和前面讲到 ...

  8. 让DELPHI自带的richedit控件显示图片

    让DELPHI自带的richedit控件显示图片 unit RichEx; { 2005-03-04 LiChengbin Added: Insert bitmap or gif into RichE ...

  9. VS2010/MFC编程入门之二十七(常用控件:图片控件Picture Control)

    上一节中鸡啄米讲的是滚动条控件,本节主要讲一种简单实用的控件,图片控件Picture Control.我们可以在界面某个位置放入图片控件,显示图片以美化界面. 图片控件简介 图片控件和前面讲到的静态文 ...

  10. 任意flex控件导出图片

    任意flex控件导出图片   flex导出图片功能通常是: 思路1:客户端将UIComponent转化为BitmapData,再转为ByteArray,将ByteArray上传到服务端,服务端发送文件 ...

随机推荐

  1. 无需搭建环境,零门槛带你体验Open-Sora文生视频应用

    本文分享自华为云社区<Open-Sora 文生视频原来在AI Gallery上也能体验了>,作者:码上开花_Lancer. 体验链接:Open-Sora 文生视频案例体验 不久前,Open ...

  2. LeetCode 295. Find Median from Data Stream数据流的中位数 (C++/Java)

    题目: Median is the middle value in an ordered integer list. If the size of the list is even, there is ...

  3. redis 远程连接

    redis-cli -h host -p port -a password -h 服务器地址 -p 端口号 -a 密码

  4. Kafka多维度调优

    优化金字塔 应用程序层面 框架层面(Broker层面) JVM层面 操作系统层面 应用程序层面:应当优化业务代码合理使用kafka,合理规划主题,合理规划分区,合理设计数据结构: 框架层面:在不改动源 ...

  5. k8s使用rbd作为存储

    k8s使用rbd作为存储 如果需要使用rbd作为后端存储的话,需要先安装ceph-common 1. ceph集群创建rbd 需要提前在ceph集群上创建pool,然后创建image [root@ce ...

  6. Masonry的进阶使用技巧

    Masonry是iOS开发中常见的视图约束框架,但是有人对他的使用还是浅尝辄止,接下来会提出几点比较少见但是又十分便捷的使用技巧. mas_greaterThanOrEqualTo mas_great ...

  7. hdu4135题解 容斥

    Problem Description Given a number N, you are asked to count the number of integers between A and B ...

  8. Web运作原理探析

    Web运作原理探析 1.1 web的 概念 Web是一种分布式的应用架构,旨在共享分布在网络上的各个Web服务器中的所有互相链接的信息. 1.2 HTML是指超文本标记语言. 1.3 URL简介 UR ...

  9. 如何用matlab求隐式函数的导数

    如何用matlab求隐式函数的导数 隐函数求导的例子 假设有一个圆 \(x^2+y^2=5\) , 要求在某个点上的切线的斜率. 我们可以把式\(x^2+y^2=5\)中的每一项对\(x\)求导, 可 ...

  10. SERSYNC服务

    inotiry图片参考 sersync图片参考 inotify文字教程 该软件对系统有要求,内核2.6以上,并且有如下目录,后面会讲解三个文件用途 [root@jokerpro ~]# uname - ...