.NET 控件轉(zhuǎn)圖片
當(dāng)前位置:點(diǎn)晴教程→知識(shí)管理交流
→『 技術(shù)文檔交流 』
Windows應(yīng)用開(kāi)發(fā)有很多場(chǎng)景需要?jiǎng)討B(tài)獲取控件顯示的圖像,即控件轉(zhuǎn)圖片,用于其它界面的顯示、傳輸圖片數(shù)據(jù)流、保存為本地圖片等用途。 下面分別介紹下一些實(shí)現(xiàn)方式以及主要使用場(chǎng)景 RenderTargetBitmap控件轉(zhuǎn)圖片BitmapImage/BitmapSource,在WPF中可以使用RenderTargetBitmap獲取捕獲控件的圖像。 RenderTargetBitmap是用于將任何 Visual 元素內(nèi)容渲染為位圖的主要工具 下面我們展示下簡(jiǎn)單快速的獲取控件圖片: ? 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轉(zhuǎn)圖片 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)}值無(wú)效:${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 } 獲取當(dāng)前窗口所在屏幕DPI,使用控件已經(jīng)渲染的尺寸,就可以捕獲到指定控件的渲染圖片。捕獲到圖片BitmapSource,即可以將位圖分配給Image的Source屬性來(lái)顯示。 DPI獲取可以參考 C# 獲取當(dāng)前屏幕DPI - 唐宋元明清2188 - 博客園 (cnblogs.com) 上面方法獲取的是BitmapSource,BitmapSource是WPF位圖的的抽象基類,繼承自ImageSource,因此可以直接用作WPF控件如Image的圖像源。RenderTargetBitmap以及BitmapImage均是BitmapSource的派生實(shí)現(xiàn)類 RenderTargetBitmap此處用于渲染Visual對(duì)象生成位圖,RenderTargetBitmap它可以用于拼接、合并(上下層疊加)、縮放圖像等。BitmapImage主要用于從文件、URL及流中加載位圖。 而捕獲返回的基類BitmapSource可以用于通用位圖的一些操作(如渲染、轉(zhuǎn)成流數(shù)據(jù)、保存),BitmapSource如果需要轉(zhuǎn)成可以支持支持更高層次圖像加載功能和延遲加載機(jī)制的BitmapImage,可以按如下操作: 1 /// <summary> 2 /// WPF位圖轉(zhuǎn)換 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轉(zhuǎn)換成圖片流,然后再解碼為BitmapImage。 圖片編碼器有很多種用途,上面是將流轉(zhuǎn)成內(nèi)存流,也可以轉(zhuǎn)成文件流保存本地文件: 1 var encoder = new PngBitmapEncoder(); 2 encoder.Frames.Add(BitmapFrame.Create(bitmapSource)); 3 using Stream stream = File.Create(imagePath); 4 encoder.Save(stream); 回到控件圖片捕獲,上方操作是在界面控件渲染后的場(chǎng)景。如果控件未加載,需要更新布局下: 1 //未加載到視覺(jué)樹的,按指定大小布局 2 //按size顯示,如果設(shè)計(jì)寬高大于size則按sie裁剪,如果設(shè)計(jì)寬度小于size則按size放大顯示。 3 element.Measure(size); 4 element.Arrange(new Rect(size)); 另外也存在場(chǎng)景:控件不確定它的具體尺寸,只是想單純捕獲圖像,那代碼整理后如下: 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 //計(jì)算元素的渲染尺寸 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 //未加載到視覺(jué)樹的,按指定大小布局 19 //按size顯示,如果設(shè)計(jì)寬高大于size則按sie裁剪,如果設(shè)計(jì)寬度小于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 } 控件未加載時(shí),可以使用DesiredSize來(lái)臨時(shí)替代操作,這類方案獲取的圖片寬高比例可能不太準(zhǔn)確。已加載完的控件,可以通過(guò)VisualTreeHelper.GetDescendantBounds獲取視覺(jué)樹子元素集的坐標(biāo)矩形區(qū)域Bounds。 kybs00/VisualImageDemo: RenderTargetBitmap獲取控件圖片 (github.com) 所以控件轉(zhuǎn)BitmapSource、保存等,可以使用RenderTargetBitmap來(lái)實(shí)現(xiàn) VisualBrush如果只是程序內(nèi)其它界面同步展示此控件,就不需要RenderTargetBitmap了,可以直接使用VisualBrush VisualBrush是非常強(qiáng)大的類,允許使用另一個(gè)Visual對(duì)象(界面顯示控件最底層的UI元素基類)作為畫刷的內(nèi)容,并將其繪制在其它UI元素上(當(dāng)然,不是直接掛到其它視覺(jué)樹上,WPF也不支持元素同時(shí)存在于倆個(gè)視覺(jué)樹的設(shè)計(jì)) 具體的可以看下官網(wǎng)VisualBrush 類 (System.Windows.Media) | Microsoft Learn,這里做一個(gè)簡(jiǎn)單的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="截圖測(cè)試" 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) 左側(cè)操作一個(gè)控件移動(dòng),右側(cè)區(qū)域動(dòng)態(tài)同步顯示左側(cè)視覺(jué)。VisualBrush.Visual可以直接綁定指定控件,一次綁定、后續(xù)同步界面變更,延時(shí)超低 同步界面變更是如何操作的?下面是部分代碼,我們看到,VisualBrush內(nèi)有監(jiān)聽(tīng)元素的內(nèi)容變更,內(nèi)容變更后VisualBrush也會(huì)自動(dòng)同步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內(nèi)部是通過(guò)元素Visual.Render方法將圖像給到渲染上下文: 1 RenderContext rc = new RenderContext(); 2 rc.Initialize(channel, DUCE.ResourceHandle.Null); 3 vVisual.Render(rc, 0); 其內(nèi)部是將Visual的快照拿來(lái)顯示輸出。VisualBrush基于這種渲染快照的機(jī)制,不會(huì)影響原始視覺(jué)元素在原來(lái)視覺(jué)樹的位置,所以并不會(huì)導(dǎo)致不同視覺(jué)樹之間的沖突。 此類VisualBrush方案,適合制作預(yù)覽顯示,比如打印預(yù)覽、PPT頁(yè)面預(yù)覽列表等。 下面是我們團(tuán)隊(duì)開(kāi)發(fā)的會(huì)議白板-頁(yè)面列表預(yù)覽效果: 作者:唐宋元明清2188
本文版權(quán)歸作者和博客園共有,歡迎轉(zhuǎn)載,但未經(jīng)作者同意必須在文章頁(yè)面給出原文連接,否則保留追究法律責(zé)任的權(quán)利。 該文章在 2024/10/16 10:15:00 編輯過(guò) |
關(guān)鍵字查詢
相關(guān)文章
正在查詢... |