c# - 将 WPF 视觉对象异步渲染为位图

标签 c# wpf asynchronous async-await rendertargetbitmap

我看到 WinRT RenderTargetBitmap 能够通过“RenderAsync(visual);”异步渲染 Visual。方法。 不幸的是.net RendertargetBitmap没有 RenderAsync 方法。 .net RenderTargetBitmap 是否有任何解决方法或扩展以允许异步渲染 WPF 视觉效果? 提前感谢您的帮助!

最佳答案

我编写了一种通过 BitBlt 函数将视觉效果渲染为位图的方法。在像我这样的重型 UI 中,此方法比 RenderTargetBitmap 快 20 倍。

给你。

 public static class VisualRender
{
    [DllImport("user32.dll")]
    static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int x, int y, int cx, int cy, uint uFlags);

    static readonly IntPtr _HwndBottom = new IntPtr(1);
    const UInt32 _SWP_NOSIZE = 0x0001;
    const UInt32 _SWP_NOMOVE = 0x0002;
    const UInt32 _SWP_NOACTIVATE = 0x0010;


    public static async Task<BitmapSource> RenderAsync(this UIElement uiElement)
    {
        var brush = new VisualBrush(uiElement);
        var topLeft = uiElement.PointToScreen(new System.Windows.Point(0, 0));
        var bottomRight = uiElement.PointToScreen(new System.Windows.Point(uiElement.RenderSize.Width, uiElement.RenderSize.Height));

        var helperWindowLocation = Rect.Empty;
        helperWindowLocation.Union(topLeft);
        helperWindowLocation.Union(bottomRight);

        var width = helperWindowLocation.Width;
        var height = helperWindowLocation.Height;

        //
        var windowsScaleTransform = uiElement.GetWindowsScaleTransform();
        helperWindowLocation.X /= windowsScaleTransform;
        helperWindowLocation.Y /= windowsScaleTransform;
        helperWindowLocation.Width /= windowsScaleTransform;
        helperWindowLocation.Height /= windowsScaleTransform;

        BitmapSource bitmapSourceT = await RenderAsync(brush, helperWindowLocation, new System.Windows.Size(width, height));
        bitmapSourceT.Freeze();
        return bitmapSourceT;
    }

    private static async Task<BitmapSource> RenderAsync(System.Windows.Media.Brush brush, Rect helperWindowLocation, System.Windows.Size snapshotSize)
    {
        // Create a tmp window with its own hwnd to render it later
        var window = new Window { WindowStyle = WindowStyle.None, ResizeMode = ResizeMode.NoResize, ShowInTaskbar = false, Background = System.Windows.Media.Brushes.White };
        window.Left = helperWindowLocation.X;
        window.Top = helperWindowLocation.Y;
        window.Width = helperWindowLocation.Width;
        window.Height = helperWindowLocation.Height;
        window.ShowActivated = false;
        var content = new Grid() { Background = brush };
        RenderOptions.SetBitmapScalingMode(content, BitmapScalingMode.HighQuality);
        window.Content = content;
        var handle = new WindowInteropHelper(window).EnsureHandle();
        window.Show();
        // Make sure the tmp window is under our mainwindow to hide it
        SetWindowPos(handle, _HwndBottom, 0, 0, 0, 0, _SWP_NOMOVE | _SWP_NOSIZE | _SWP_NOACTIVATE);

        //Wait for the element to render
        //await popupChild.WaitForLoaded();
        await window.WaitForFullyRendered();

        ////Why we have to wait here fore the visualbrush to finish its lazy rendering Process? 
        //// Todo: It seems like very complex uielements does not finish its rendering process within one renderloop
        //// Check https://stackoverflow.com/questions/2851236/rendertargetbitmap-resourced-visualbrush-incomplete-image

        // Async BitBlt the tmp Window
        var bitmapSourceT = await Task.Run(() =>
        {
            Bitmap bitmap = VisualToBitmapConverter.GetBitmap(handle,
            (int)snapshotSize.Width, (int)snapshotSize.Height);

            var bitmapSource = new SharedBitmapSource(bitmap);
            bitmapSource.Freeze();
            return bitmapSource;
        });
        // Close the Window
        window.Close();

        return bitmapSourceT;
    }

    public static class VisualToBitmapConverter
    {
        private enum TernaryRasterOperations : uint
        {
            SRCCOPY = 0x00CC0020,
            SRCPAINT = 0x00EE0086,
            SRCAND = 0x008800C6,
            SRCINVERT = 0x00660046,
            SRCERASE = 0x00440328,
            NOTSRCCOPY = 0x00330008,
            NOTSRCERASE = 0x001100A6,
            MERGECOPY = 0x00C000CA,
            MERGEPAINT = 0x00BB0226,
            PATCOPY = 0x00F00021,
            PATPAINT = 0x00FB0A09,
            PATINVERT = 0x005A0049,
            DSTINVERT = 0x00550009,
            BLACKNESS = 0x00000042,
            WHITENESS = 0x00FF0062,
            CAPTUREBLT = 0x40000000
        }

        [DllImport("gdi32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool BitBlt(IntPtr hdc, int nXDest, int nYDest, int nWidth, int nHeight, IntPtr hdcSrc, int nXSrc, int nYSrc, TernaryRasterOperations dwRop);

        public static Bitmap GetBitmap(IntPtr hwnd, int width, int height)
        {
            var bitmap = new Bitmap(width, height);
            using (Graphics graphicsFromVisual = Graphics.FromHwnd(hwnd))
            {
                using (Graphics graphicsFromImage = Graphics.FromImage(bitmap))
                {
                    IntPtr source = graphicsFromVisual.GetHdc();
                    IntPtr destination = graphicsFromImage.GetHdc();

                    BitBlt(destination, 0, 0, bitmap.Width, bitmap.Height, source, 0, 0, TernaryRasterOperations.SRCCOPY);

                    graphicsFromVisual.ReleaseHdc(source);
                    graphicsFromImage.ReleaseHdc(destination);
                }
            }

            return bitmap;
        }
    }
}

static class Extensions
{
    public static Task WaitForLoaded(this FrameworkElement element)
    {
        var tcs = new TaskCompletionSource<object>();
        RoutedEventHandler handler = null;
        handler = (s, e) =>
        {
            element.Loaded -= handler;
            tcs.SetResult(null);
        };
        element.Loaded += handler;
        return tcs.Task;
    }


    public static Task WaitForFullyRendered(this Window window)
    {
        var tcs = new TaskCompletionSource<object>();
        EventHandler handler = null;
        handler = (s, e) =>
        {
            window.ContentRendered -= handler;
            tcs.SetResult(null);
        };
        window.ContentRendered += handler;
        return tcs.Task;
    }

    public static double GetWindowsScaleTransform(this Visual visual)
    {
        Matrix m = Matrix.Identity;
        var presentationSource = PresentationSource.FromVisual(visual);
        if (presentationSource != null)
        {
            if (presentationSource.CompositionTarget != null) m = presentationSource.CompositionTarget.TransformToDevice;
        }
        double totalTransform = m.M11;
        return totalTransform;
    }

}

您将在此处找到 SharedBitmapSource。 https://stackoverflow.com/a/32841840/690656

关于c# - 将 WPF 视觉对象异步渲染为位图,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36402011/

相关文章:

c# - 用于数字的正确数据类型是什么

c# - PInvokeStackImbalance 不是由 CallingConvention 引起的

c# - Mode=TwoWay、UpdateSourceTrigger=PropertyChanged 或 LostFocus?

wpf - 如何在 WPF Canvas 上绘制滚动条

Angular 自定义异步验证器返回 {__zone_symbol__state : null, __zone_symbol__value: Array(0)}

JavaScript:4 个异步函数在继续之前按顺序等待彼此完成?

c# - 我们应该散列密码重置 token 并将它们存储在数据库中吗?

c# - 我可以使用什么来与 PLC 的 HMS AnyBus AB7007 网关进行通信

c# - 是否可以在 WPF 中使用反射绑定(bind)到属性?

ios - 如何在 Swift 中使函数成为原子函数?