首页 > 其他分享 >【WPF】使用RenderTargetBitmap截图的时候位置出现偏移的一些解决办法

【WPF】使用RenderTargetBitmap截图的时候位置出现偏移的一些解决办法

时间:2025-01-15 12:32:05浏览次数:1  
标签:截图 RenderTargetBitmap Visual visual var using WPF null

简介

在WPF中,如果你需要把控件的渲染画面保存到图片,那么唯一的选择就是RenderTargetBitmap。
不过,RenderTargetBitmap是个比较难伺候的主,有时候你以为能工作,但实际上不能;你以为能够正常截图,但实际上截出来的图片是歪的。
所以,我总结一下自己项目中遇到的坑和解决办法吧!

保存的图片是黑色的

这种情况下,通常是WPF的RenderTargetBitmap无法正确获取视觉对象的渲染结果,我比较建议在需要截图的控件之上包装一层Border。

例如:

<ux:RarityRibbon Background="{Binding Background, Mode=OneWay, Converter={x:Static wf:Converters.StringToBrush}}"
                             Foreground="{Binding Foreground, Mode=OneWay, Converter={x:Static wf:Converters.StringToBrush}}"
                             RarityStarColor="{Binding Stroke, Mode=OneWay, Converter={x:Static wf:Converters.StringToBrush}}"
                             Title="{Binding Name, Mode=OneWay}"
                             Value="{Binding Value, Mode=OneWay}"
                             Level="{Binding Level, Mode=OneWay}"
                             Margin="0">
                <ux:RarityRibbon.MaskedBrush>
                    <ImageBrush ImageSource="{Binding Image, Mode=OneWay, Converter={x:Static wf:Converters.Icon}}"
                                Stretch="Uniform"
                                Viewbox="0.1 0.1 0.5 0.5"
                                Opacity="0.15" />
                </ux:RarityRibbon.MaskedBrush>
            </ux:RarityRibbon>

要截图的是ux:RarityRibbon控件,但保存的图片是纯黑的。
这时候,要做的就是在ux:RarityRibbon控件之上,包装一层Border,然后把截图对象改成Border即可。


  <Border x:Name="Container">
            <ux:RarityRibbon Background="{Binding Background, Mode=OneWay, Converter={x:Static wf:Converters.StringToBrush}}"
                             Foreground="{Binding Foreground, Mode=OneWay, Converter={x:Static wf:Converters.StringToBrush}}"
                             RarityStarColor="{Binding Stroke, Mode=OneWay, Converter={x:Static wf:Converters.StringToBrush}}"
                             Title="{Binding Name, Mode=OneWay}"
                             Value="{Binding Value, Mode=OneWay}"
                             Level="{Binding Level, Mode=OneWay}"
                             Margin="0">
                <ux:RarityRibbon.MaskedBrush>
                    <ImageBrush ImageSource="{Binding Image, Mode=OneWay, Converter={x:Static wf:Converters.Icon}}"
                                Stretch="Uniform"
                                Viewbox="0.1 0.1 0.5 0.5"
                                Opacity="0.15" />
                </ux:RarityRibbon.MaskedBrush>
            </ux:RarityRibbon>
        </Border>

控件位置发生了偏移

  1. 要截图的控件必须保证Margin属性没有赋值
  2. 要截图的控件必须保证父级元素没有使用Grid.ColumnDefnitions或者Grid.RowDefnitions属性,这个强制布局的属性会影响RenderTargetBitmap工作。
  3. 要截图的控件必须不适用HorizontalAlignment以及VerticalAlignment,这两个属性会影响RenderTargetBitmap工作。

控件大小无法对应

可以试试Snoop里面截图的帮助类,还挺有用的。


// (c) Copyright Cory Plotts.
// This source is subject to the Microsoft Public License (Ms-PL).
// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
// All other rights reserved.

namespace Snoop.Infrastructure;

using System;
using System.IO;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Media.Imaging;

public static class VisualCaptureUtil
{
    private const double BaseDpi = 96;

    public static void SaveVisual(Visual visual, int dpi, string filename)
    {
        // sometimes RenderTargetBitmap doesn't render the Visual or doesn't render the Visual properly
        // below i am using the trick that jamie rodriguez posted on his blog
        // where he wraps the Visual inside of a VisualBrush and then renders it.
        // http://blogs.msdn.com/b/jaimer/archive/2009/07/03/rendertargetbitmap-tips.aspx

        var visualBrush = CreateVisualBrushSafe(visual);

        if (visual is null
            || visualBrush is null)
        {
            return;
        }

        var renderTargetBitmap = RenderVisualWithHighQuality(visual, dpi, dpi);

        SaveAsPng(renderTargetBitmap, filename);
    }
    
    public static RenderTargetBitmap SaveVisual(Visual visual, int dpi)
    {
        // sometimes RenderTargetBitmap doesn't render the Visual or doesn't render the Visual properly
        // below i am using the trick that jamie rodriguez posted on his blog
        // where he wraps the Visual inside of a VisualBrush and then renders it.
        // http://blogs.msdn.com/b/jaimer/archive/2009/07/03/rendertargetbitmap-tips.aspx

        var visualBrush = CreateVisualBrushSafe(visual);

        if (visual is null || visualBrush is null)
        {
            return null;
        }

        return RenderVisualWithHighQuality(visual, dpi, dpi);
    }

    public static VisualBrush CreateVisualBrushSafe(Visual visual)
    {
        return IsSafeToVisualize(visual)
            ? new VisualBrush(visual)
            : null;
    }

    public static bool IsSafeToVisualize(Visual visual)
    {
        if (visual is null)
        {
            return false;
        }

        if (visual is Window)
        {
            var source = PresentationSource.FromVisual(visual) as HwndSource;
            return source?.CompositionTarget is not null;
        }

        return true;
    }

    private static void SaveAsPng(RenderTargetBitmap bitmap, string filename)
    {
        var pngBitmapEncoder = new PngBitmapEncoder();
        pngBitmapEncoder.Frames.Add(BitmapFrame.Create(bitmap));

        using (var fileStream = File.Create(filename))
        {
            pngBitmapEncoder.Save(fileStream);
        }
    }

    /// <summary>
    /// Draws <paramref name="visual"/> in smaller tiles using multiple <see cref="VisualBrush"/>.
    /// </summary>
    /// <remarks>
    /// This way we workaround a limitation in <see cref="VisualBrush"/> which causes poor quality for larger visuals.
    /// </remarks>
    public static RenderTargetBitmap RenderVisualWithHighQuality(Visual visual, int dpiX, int dpiY, PixelFormat? pixelFormat = null, Viewport3D viewport3D = null)
    {
        var size = GetSize(visual);

        var drawingVisual = new DrawingVisual();
        using (var drawingContext = drawingVisual.RenderOpen())
        {
            DrawVisualInTiles(visual, drawingContext, size);
        }

        return RenderVisual(drawingVisual, size, dpiX, dpiY, pixelFormat, viewport3D);
    }

    public static RenderTargetBitmap RenderVisual(Visual visual, Size bounds, int dpiX, int dpiY, PixelFormat? pixelFormat = null, Viewport3D viewport3D = null)
    {
        var scaleX = dpiX / BaseDpi;
        var scaleY = dpiY / BaseDpi;

        pixelFormat ??= PixelFormats.Pbgra32;

        var renderTargetBitmap = new RenderTargetBitmap((int)Math.Ceiling(scaleX * bounds.Width), (int)Math.Ceiling(scaleY * bounds.Height), dpiX, dpiY, pixelFormat.Value);

        if (viewport3D is not null)
        {
            typeof(RenderTargetBitmap)
                .GetMethod("RenderForBitmapEffect", BindingFlags.Instance | BindingFlags.NonPublic)
                ?.Invoke(renderTargetBitmap, new object[] { visual, Matrix.Identity, Rect.Empty });
        }
        else
        {
            renderTargetBitmap.Render(visual);
        }

        return renderTargetBitmap;
    }

    private static Size GetSize(Visual visual)
    {
        if (visual is UIElement uiElement)
        {
            return uiElement.RenderSize;
        }

        var descendantBounds = VisualTreeHelper.GetDescendantBounds(visual);
        return new Size(descendantBounds.Width, descendantBounds.Height);
    }

    /// <summary>
    /// Draws <paramref name="visual"/> in smaller tiles using multiple <see cref="VisualBrush"/> to <paramref name="drawingContext"/>.
    /// This way we workaround a limitation in <see cref="VisualBrush"/> which causes poor quality for larger visuals.
    /// </summary>
    /// <param name="visual">The visual to be drawn.</param>
    /// <param name="drawingContext">The <see cref="DrawingContext"/> to use.</param>
    /// <param name="visualSize">The size of <paramref name="visual"/>.</param>
    /// <param name="tileWidth">The width of one tile.</param>
    /// <param name="tileHeight">The height of one tile.</param>
    /// <remarks>
    /// Original version of this method was copied from https://srndolha.wordpress.com/2012/10/16/exported-drawingvisual-quality-when-using-visualbrush/
    ///
    /// A tile size of 32x32 turned out deliver the best quality while not increasing computation time too much.
    /// </remarks>
    private static void DrawVisualInTiles(Visual visual, DrawingContext drawingContext, Size visualSize, double tileWidth = 32, double tileHeight = 32)
    {
        var visualWidth = visualSize.Width;
        var visualHeight = visualSize.Height;

        var verticalTileCount = visualHeight / tileHeight;
        var horizontalTileCount = visualWidth / tileWidth;

        for (var i = 0; i <= verticalTileCount; i++)
        {
            for (var j = 0; j <= horizontalTileCount; j++)
            {
                var width = tileWidth;
                var height = tileHeight;

                // Check if we would exceed the width of the visual and limit it by the remaining
                if ((j + 1) * tileWidth > visualWidth)
                {
                    width = visualWidth - (j * tileWidth);
                }

                // Check if we would exceed the height of the visual and limit it by the remaining
                if ((i + 1) * tileHeight > visualHeight)
                {
                    height = visualHeight - (i * tileHeight);
                }

                var x = j * tileWidth;
                var y = i * tileHeight;

                var rectangle = new Rect(x, y, width, height);

                var contentBrush = new VisualBrush(visual)
                {
                    Stretch = Stretch.None,
                    AlignmentX = AlignmentX.Left,
                    AlignmentY = AlignmentY.Top,
                    Viewbox = rectangle,
                    ViewboxUnits = BrushMappingMode.Absolute
                };

                drawingContext.DrawRectangle(contentBrush, null, rectangle);
            }
        }
    }
}

标签:截图,RenderTargetBitmap,Visual,visual,var,using,WPF,null
From: https://www.cnblogs.com/luoyisi/p/18672776

相关文章

  • 解决htmlcanvas遇到图片较多的复杂首页,保存截图特别慢的问题
    先说问题:在首页新增个保存部分dom截图的功能,但首页加载接口较多,图片跨域加载比较慢,而htmlcanvas保存截图前会将整个页面渲染一遍,这就导致有些图片没加载完成,dom渲染不然,canvas保存就会延迟四五秒之久 解决方法:增加这个参数ignoreElements:function(element){......
  • DevExpress WPF 中文教程:Grid - 如何创建列并将其绑定到数据属性?
    DevExpressWPF拥有120+个控件和库,将帮助您交付满足甚至超出企业需求的高性能业务应用程序。通过DevExpressWPF能创建有着强大互动功能的XAML基础应用程序,这些应用程序专注于当代客户的需求和构建未来新一代支持触摸的解决方案。无论是Office办公软件的衍伸产品,还是以数据为中心......
  • WPF中后端bool是否可见字段转换为前端Visibility
    1.编写转换类Bool2VisibilityConverter,继承自IValueConverter(usingSystem.Windows.Data;)publicclassBool2VisibilityConverter:IValueConverter{publicobjectConvert(objectvalue,TypetargetType,objectparameter,CultureInfoculture){......
  • WPF命令模式深度解析:从RelayCommand到命令自动刷新机制
    引言 在WPF应用程序开发中,命令模式是一个非常重要的设计模式,它帮助我们将UI交互与业务逻辑解耦。本文将深入探讨WPF命令模式的实现机制,特别是通过RelayCommand的实现来理解命令模式的核心概念。 1.命令的基础概念1.1什么是命令?命令是将用户操作(如按钮点击)转换为具体行为......
  • WPF Prism框架INavigationAware接口的一个bug记录
    Prism中使用INavigationAware进行页面切换的时候,需要实现IsNavigationTarget、OnNavigatedFrom、OnNavigatedTo这三个方法,具体如下:regionINavigationAware接口方法publicboolIsNavigationTarget(NavigationContextnavigationContext){//是否允许重复导航进来//返回True,......
  • 一个超经典 WinForm,WPF 卡死问题的终极反思
    一:背景1.讲故事写这篇文章起源于训练营里一位朋友最近在微信聊到他对这个问题使用了一种非常切实可行,简单粗暴的方式,并且也成功解决了公司里几个这样的卡死dump,如今在公司已是灵魂级人物,让我也尝到了什么叫反哺!对,这个东西叫Harmony,github网址:https://github.com/pardeike/H......
  • WPF ListBox ItemTemplate DataTemplate
    <Windowx:Class="WpfApp137.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d="http://schemas.microsoft......
  • WPF ListBoxItem ControlTemplate
    <Windowx:Class="WpfApp136.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d="http://schemas.microsoft......
  • wpf mvvm(prism)
     mainwindow.xml.cspublicpartialclassMainWindow:MetroWindow{privatereadonlyIRegionManagerregionManager;publicMainWindow(IRegionManagerregionManager){InitializeComponent();this.regionManager=regionMana......
  • wpf 打包成单文件
     FolderProfile.pubxml<Project><PropertyGroup><TargetFramework>net6.0-windows7.0</TargetFramework><PublishSingleFile>true</PublishSingleFile> <Configuration>Release</Configuration> <I......