.NET 控件转图片
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 控件转图片的更多相关文章
- MFC编程入门之二十七(常用控件:图片控件PictureControl)
上一节讲的是滚动条控件,本节主要讲一种简单实用的控件,图片控件Picture Control.我们可以在界面某个位置放入图片控件,显示图片以美化界面. 图片控件简介 图片控件和前面讲到的静态文本框都是 ...
- File控件选择图片的时候在Html5下马上预览
页面HTML <div> <img src="@pic.Path" id="img" style="width:200px;heig ...
- iOS开发UI篇—UIScrollView控件实现图片缩放功能
iOS开发UI篇—UIScrollView控件实现图片缩放功能 一.缩放 1.简单说明: 有些时候,我们可能要对某些内容进行手势缩放,如下图所示 UIScrollView不仅能滚动显示大量内容,还能对 ...
- iOS开发UI篇—UIScrollView控件实现图片轮播
iOS开发UI篇—UIScrollView控件实现图片轮播 一.实现效果 实现图片的自动轮播 二.实现代码 storyboard中布局 代码: #import "YYV ...
- iOS开发——UI高级OC篇&自定义控件之调整按钮中子控件(图片和文字)的位置
自定义控件之调整按钮中子控件(图片和文字)的位置 其实还有一种是在storyBoard中实现的,只需要设置对应空间的左右间距: 这里实现前面两种自定义的方式 一:imageRectForContent ...
- 【转】 iOS开发UI篇—UIScrollView控件实现图片轮播
原文:http://www.cnblogs.com/wendingding/p/3763527.html iOS开发UI篇—UIScrollView控件实现图片轮播 一.实现效果 实现图片的自动轮播 ...
- VS2010/MFC常用控件:图片控件Picture Control
图片控件Picture Control 本节主要讲一种简单实用的控件,图片控件Picture Control.我们可以在界面某个位置放入图片控件,显示图片以美化界面. 图片控件简介 图片控件和前面讲到 ...
- 让DELPHI自带的richedit控件显示图片
让DELPHI自带的richedit控件显示图片 unit RichEx; { 2005-03-04 LiChengbin Added: Insert bitmap or gif into RichE ...
- VS2010/MFC编程入门之二十七(常用控件:图片控件Picture Control)
上一节中鸡啄米讲的是滚动条控件,本节主要讲一种简单实用的控件,图片控件Picture Control.我们可以在界面某个位置放入图片控件,显示图片以美化界面. 图片控件简介 图片控件和前面讲到的静态文 ...
- 任意flex控件导出图片
任意flex控件导出图片 flex导出图片功能通常是: 思路1:客户端将UIComponent转化为BitmapData,再转为ByteArray,将ByteArray上传到服务端,服务端发送文件 ...
随机推荐
- nginx使用lua waf防火墙来做防CC配置
nginx添加lua模块 启动和安装nginx yum install -y nginx systemctl daemon-reload systemctl enable nginx #为了实验方便这 ...
- 使用线程池实现为多个客户端提供Echo服务
import java.io.*; import java.net.ServerSocket; import java.net.Socket; import java.util.Scanner; im ...
- 夜莺监控(Nightingale)上线内置指标功能
Prometheus 生态里如果要查询数据,需要编写 promql,对于普通用户来说,门槛有点高.通常有两种解法,一个是通过 AI 的手段做翻译,你用大白话跟 AI 提出你的诉求,让 AI 帮你写 p ...
- work02
第一题: 看程序说答案 int a = 10; int b = 3; int c = a + b;//13 int d = a - b;//7 int e = a * b; //30 int f = ...
- 地铁查询app 结对作业三
经过今天一下午的奋斗 安卓app 只剩下最难的部分了 最短路径问题 我们考虑用迪杰斯特拉算法 不过 没有做出来 还要继续去学习一下这个代码 并寻求网上代码的帮助
- Java序列化和反序列化 Serializable BeanUtils.copyProperties赋值属性方法
Java序列化和反序列化 Serializable BeanUtils.copyProperties赋值属性方法 package com.example.core.mydemo.java; impor ...
- [笔记]Git常用命令大全
Tips:当你看到这个提示的时候,说明当前的文章是由原emlog博客系统搬迁至此的,文章发布时间已过于久远,编排和内容不一定完整,还请谅解` [笔记]Git常用命令大全 日期:2018-6-16 阿珏 ...
- VMware 虚拟机下载
VMware破解版下载地址: https://www.aliyundrive.com/s/CUr4eaMgxGa 提取码:e0c8 激活码: ZF3R0-FHED2-M80TY-8QYGC-NPKYF ...
- FFmpeg frei0r插件使用学习
背景 ffmpeg做基本的音视频编辑还是比较简单的,但要做一些滤镜及特效就比较麻烦了.接下来看看借用frei0r插件怎么做: 简介 你可以将frei0r看作是一个"视频特效工具箱" ...
- Linux下Oracle11G数据备份恢复(RMAN)
数据库安装参考步骤1--14 https://www.cnblogs.com/baixisuozai/p/17852235.html #rman数据库备份脚本 #!/bin/bash PATH=$PA ...