简体   繁体   English

异步将WPF视觉呈现到位图

[英]Asynchronously render a WPF visual to a bitmap

I saw that the WinRT RenderTargetBitmap is able to render a Visual asynchronously via the "RenderAsync(visual);" 我看到WinRT RenderTargetBitmap能够通过“ RenderAsync(visual);”异步呈现Visual。 method. 方法。 Unfortunately the .net RendertargetBitmap does not have a RenderAsync method. 不幸的是,.net RendertargetBitmap没有RenderAsync方法。 Is there any workaround or entension for the .net RenderTargetBitmap to allow async rendering of WPF visuals? .net RenderTargetBitmap是否有任何变通办法或扩展名,以允许WPF视觉效果的异步呈现? Thanks for help in advance! 预先感谢您的帮助!

I wrote a method to render a visual to bitmap via the BitBlt function. 我编写了一种通过BitBlt函数将视觉效果呈现到位图的方法。 In heavy UIs like in my case this method is 20 times fastern than RenderTargetBitmap. 在像我这样的沉重UI中,此方法比RenderTargetBitmap快20倍。

Here you go. 干得好。

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

}

The SharedBitmapSource you will find here. 您将在此处找到SharedBitmapSource。 https://stackoverflow.com/a/32841840/690656 https://stackoverflow.com/a/32841840/690656

I don't think you can. 我认为你不能。

You can try to perform the render on a background thread using something like await Task.Run(() => bitmap.Render(Button)); 您可以尝试使用诸如await Task.Run(() => bitmap.Render(Button));类的东西在后台线程上执行渲染await Task.Run(() => bitmap.Render(Button)); , but it's not going to work: the background thread won't be allowed to access the Button . ,但无法正常工作:不允许后台线程访问Button

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM