0

I have been trying to mimic the acrylic blur of UWP Application into my WPF Application. I have already tried using DWM Blur but it is not so blurry as the Acrylic Brush in UWP & also tried with setWindowCompositionAttribute() which is currently lagging my whole window.

Recently i have seen a code on github on Windows.UI.Composition-Win32-Samples

They Achieved Acrylic blur effect in WPF using Win2D interop.but the problem is that they are actually creating another window and hosting it into the Original window as a user controls which flickers a lot on resizing.

is there any way to implement it directly on to the WPF window without using a HWND Host.


Link to the Github Project

WPF Acrylic Blur Effect using Win2D Interop

This is the code they use:

CompositionHostControl.xaml.cs

sealed partial class CompositionHostControl : UserControl, IDisposable
{
    private readonly CanvasDevice _canvasDevice;
    private readonly IGraphicsEffect _acrylicEffect;
    private readonly CompositionHost _compositionHost;
    private readonly Compositor _compositor;
    private readonly ContainerVisual _containerVisual;

    private static double _rectWidth;
    private static double _rectHeight;
    private static bool _isAcrylicVisible = false;
    private static SpriteVisual _acrylicVisual;
    private static DpiScale _currentDpi;
    private static CompositionGraphicsDevice _compositionGraphicsDevice;
    private static CompositionSurfaceBrush _noiseSurfaceBrush; 

    public CompositionHostControl()
    {
        InitializeComponent();            

        // Get graphics device.
        _canvasDevice = CanvasDevice.GetSharedDevice();

        // Create host and attach root container for hosted tree.
        _compositionHost = new CompositionHost();
        CompositionHostElement.Child = _compositionHost;
        _compositionHost.RegisterForDispose(this);

        _compositor = _compositionHost.Compositor;
        _containerVisual = _compositor.CreateContainerVisual();
        
        // Create effect graph.
        _acrylicEffect = CreateAcrylicEffectGraph();
    }

    private void CompositionHostControl_Loaded(object sender, RoutedEventArgs e)
    {
        _currentDpi = WindowsMedia.VisualTreeHelper.GetDpi(this);

        _rectWidth = CompositionHostElement.ActualWidth / 2;
        _rectHeight = CompositionHostElement.ActualHeight / 2;

        // Get graphics device.
        _compositionGraphicsDevice = CanvasComposition.CreateCompositionGraphicsDevice(_compositor, _canvasDevice);

        // Create surface. 
        var noiseDrawingSurface = _compositionGraphicsDevice.CreateDrawingSurface(
            new Windows.Foundation.Size(_rectWidth, _rectHeight),
            DirectXPixelFormat.B8G8R8A8UIntNormalized,
            DirectXAlphaMode.Premultiplied);

        // Draw to surface and create surface brush.
        var noiseFilePath = AppDomain.CurrentDomain.BaseDirectory + "Assets\\NoiseAsset_256X256.png";
        LoadSurface(noiseDrawingSurface, noiseFilePath);
        _noiseSurfaceBrush = _compositor.CreateSurfaceBrush(noiseDrawingSurface);

        // Add composition content to tree.
        _compositionHost.SetChild(_containerVisual);
        AddCompositionContent();
    }

    protected override void OnDpiChanged(DpiScale oldDpi, DpiScale newDpi)
    {
        base.OnDpiChanged(oldDpi, newDpi);
        _currentDpi = newDpi;
        Vector3 newScale = new Vector3((float)newDpi.DpiScaleX, (float)newDpi.DpiScaleY, 1);

        // Adjust each child visual scale and offset.
        foreach (SpriteVisual child in _containerVisual.Children)
        {
            child.Scale = newScale;
            var newOffsetX = child.Offset.X * ((float)newDpi.DpiScaleX / (float)oldDpi.DpiScaleX);
            var newOffsetY = child.Offset.Y * ((float)newDpi.DpiScaleY / (float)oldDpi.DpiScaleY);
            child.Offset = new Vector3(newOffsetX, newOffsetY, 0);
        }
    }

    public void AddCompositionContent()
    {
        var acrylicVisualOffset = new Vector3(
            (float)(_rectWidth * _currentDpi.DpiScaleX) / 2,
            (float)(_rectHeight * _currentDpi.DpiScaleY) / 2,
            0);

        // Create visual and set brush.
        _acrylicVisual = CreateCompositionVisual(acrylicVisualOffset);         
        _acrylicVisual.Brush = CreateAcrylicEffectBrush();
    }
    
    SpriteVisual CreateCompositionVisual(Vector3 offset)
    {
        var visual = _compositor.CreateSpriteVisual();
        visual.Size = new Vector2((float)_rectWidth, (float)_rectHeight);
        visual.Scale = new Vector3((float)_currentDpi.DpiScaleX, (float)_currentDpi.DpiScaleY, 1);
        visual.Offset = offset;

        return visual;
    }

    async void LoadSurface(CompositionDrawingSurface surface, string path)
    {
        // Load from stream.
        var storageFile = await StorageFile.GetFileFromPathAsync(path);
        var stream = await storageFile.OpenAsync(FileAccessMode.Read);
        var bitmap = await CanvasBitmap.LoadAsync(_canvasDevice, stream);

        // Draw to surface.
        using (var ds = CanvasComposition.CreateDrawingSession(surface))
        {
            ds.Clear(Colors.Transparent);

            var rect = new Windows.Foundation.Rect(0, 0, _rectWidth, _rectHeight);
            ds.DrawImage(bitmap, 0, 0, rect);
        }

        stream.Dispose();
        bitmap.Dispose();
    }

    IGraphicsEffect CreateAcrylicEffectGraph()
    {
        return new BlendEffect
        {
            Mode = BlendEffectMode.Overlay,
            Background = new CompositeEffect
            {
                Mode = CanvasComposite.SourceOver,
                Sources =
                        {
                        new BlendEffect
                        {
                            Mode = BlendEffectMode.Exclusion,
                            Background = new SaturationEffect
                            {
                                Saturation = 2,
                                Source = new GaussianBlurEffect
                                {
                                    Source = new CompositionEffectSourceParameter("Backdrop"),
                                    BorderMode = EffectBorderMode.Hard,
                                    BlurAmount = 30
                                },
                            },                                
                            Foreground = new ColorSourceEffect()
                            {
                                Color = Color.FromArgb(26, 255, 255, 255)
                            }
                        },
                        new ColorSourceEffect
                        {
                            Color = Color.FromArgb(153, 255, 255, 255)
                        }
                    }
            },
            Foreground = new OpacityEffect
            {
                Opacity = 0.03f,
                Source = new BorderEffect()
                {
                    ExtendX = CanvasEdgeBehavior.Wrap,
                    ExtendY = CanvasEdgeBehavior.Wrap,
                    Source = new CompositionEffectSourceParameter("Noise")
                },
            },
        };
    }

    CompositionEffectBrush CreateAcrylicEffectBrush()
    {
        // Compile the effect.
        var effectFactory = _compositor.CreateEffectFactory(_acrylicEffect);

        // Create Brush.
        var acrylicEffectBrush = effectFactory.CreateBrush();

        // Set sources.
        var destinationBrush = _compositor.CreateBackdropBrush();
        acrylicEffectBrush.SetSourceParameter("Backdrop", destinationBrush);
        acrylicEffectBrush.SetSourceParameter("Noise", _noiseSurfaceBrush);

        return acrylicEffectBrush;
    }

    public void Dispose()
    {
        _acrylicVisual.Dispose();
        _noiseSurfaceBrush.Dispose();

        _canvasDevice.Dispose();
        _compositionGraphicsDevice.Dispose();
}

    internal void ToggleAcrylic()
    {
        // Toggle visibility of acrylic visual by adding or removing from tree.
        if (_isAcrylicVisible)
        {
            _containerVisual.Children.Remove(_acrylicVisual);
        }
        else
        {
            _containerVisual.Children.InsertAtTop(_acrylicVisual);
        }

        _isAcrylicVisible = !_isAcrylicVisible;
    }
}

CompositionHost.cs

sealed class CompositionHost : HwndHost
{
    private readonly object _dispatcherQueue;
    private readonly List<IDisposable> _registeredDisposables = new List<IDisposable>();
    private readonly ICompositorDesktopInterop _compositorDesktopInterop;

    private ICompositionTarget _compositionTarget;

    public IntPtr HwndHost { get; private set; }
    public Compositor Compositor { get; private set; }

    public CompositionHost()
    {
        // Create dispatcher queue.
        _dispatcherQueue = InitializeCoreDispatcher();

        Compositor = new Compositor();
        _compositorDesktopInterop = (ICompositorDesktopInterop)(object)Compositor;
    }

    public void SetChild(Visual v)
    {
        _compositionTarget.Root = v;
    }

    protected override HandleRef BuildWindowCore(HandleRef hwndParent)
    {
        // Create Window
        HwndHost = IntPtr.Zero;
        HwndHost = User32.CreateWindowExW(
                               dwExStyle: User32.WS_EX.WS_EX_TRANSPARENT,
                               lpClassName: "Message",
                               lpWindowName: "CompositionHost",
                               dwStyle: User32.WS.WS_CHILD,
                               x: 0, y: 0,
                               nWidth: 0, nHeight: 0,
                               hWndParent: hwndParent.Handle,
                               hMenu: IntPtr.Zero,
                               hInstance: IntPtr.Zero,
                               lpParam: IntPtr.Zero);

        // Get compositor and target for hwnd.
        _compositorDesktopInterop.CreateDesktopWindowTarget(HwndHost, true, out _compositionTarget);

        return new HandleRef(this, HwndHost);
    }

    protected override void DestroyWindowCore(HandleRef hwnd)
    {
        if (_compositionTarget.Root != null)
        {
            _compositionTarget.Root.Dispose();
        }

        User32.DestroyWindow(hwnd.Handle);

        foreach (var d in _registeredDisposables)
        {
            d.Dispose();
        }
    }

    // Register a given IDisposable object for disposal during cleanup.
    public void RegisterForDispose(IDisposable d)
    {
        _registeredDisposables.Add(d);
    }

    private object InitializeCoreDispatcher()
    {
        DispatcherQueueOptions options = new DispatcherQueueOptions
        {
            apartmentType = DISPATCHERQUEUE_THREAD_APARTMENTTYPE.DQTAT_COM_STA,
            threadType = DISPATCHERQUEUE_THREAD_TYPE.DQTYPE_THREAD_CURRENT,
            dwSize = Marshal.SizeOf(typeof(DispatcherQueueOptions))
        };

        var hresult = CreateDispatcherQueueController(options, out object queue);
        if (hresult != 0)
        {
            Marshal.ThrowExceptionForHR(hresult);
        }

        return queue;
    }

    internal enum DISPATCHERQUEUE_THREAD_APARTMENTTYPE
    {
        DQTAT_COM_NONE = 0,
        DQTAT_COM_ASTA = 1,
        DQTAT_COM_STA = 2
    };

    internal enum DISPATCHERQUEUE_THREAD_TYPE
    {
        DQTYPE_THREAD_DEDICATED = 1,
        DQTYPE_THREAD_CURRENT = 2,
    };

    [StructLayout(LayoutKind.Sequential)]
    internal struct DispatcherQueueOptions
    {
        public int dwSize;

        [MarshalAs(UnmanagedType.I4)]
        public DISPATCHERQUEUE_THREAD_TYPE threadType;

        [MarshalAs(UnmanagedType.I4)]
        public DISPATCHERQUEUE_THREAD_APARTMENTTYPE apartmentType;
    };

    [DllImport("coremessaging.dll")]
    internal static extern int CreateDispatcherQueueController(DispatcherQueueOptions options,
                                            [MarshalAs(UnmanagedType.IUnknown)]
                                           out object dispatcherQueueController);
}

[ComImport]
[Guid("29E691FA-4567-4DCA-B319-D0F207EB6807")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface ICompositorDesktopInterop
{
    void CreateDesktopWindowTarget(IntPtr hwndTarget, bool isTopmost, out ICompositionTarget target);
}

[ComImport]
[Guid("A1BEA8BA-D726-4663-8129-6B5E7927FFA6")]
[InterfaceType(ComInterfaceType.InterfaceIsIInspectable)]
public interface ICompositionTarget
{
    Visual Root
    {
        get;
        set;
    }
}
trickymind
  • 557
  • 5
  • 21
  • Direct2D (here Win2D) is necessary for building a custom effect graph. What do you mean by "flickers a lot on resizing."? the sample can be resized w/o flickering. Or have you changed something in it? – Simon Mourier Aug 02 '20 at 13:30
  • The example does not flicker since its size is not same as the window size.just try resizing the User Control to the window size. – trickymind Aug 02 '20 at 15:48
  • Please provide exactly the change that you apply to the sample. – Simon Mourier Aug 02 '20 at 16:50
  • Currently all i want is to get rid of the flickering while resizing the sample window. – trickymind Aug 02 '20 at 18:44
  • You could use shader effect: https://stackoverflow.com/questions/2204955/handling-alpha-channel-in-wpf-pixel-shader-effect – o_w Aug 03 '20 at 07:01
  • I don't know much about Shader effects. How does it work – trickymind Aug 03 '20 at 09:16
  • Honestly, this is the same situation that happens when Winforms developers ask about to achieve something that WPF can do, but Winforms can't. The answer is basically the same. Move to the framework that allows the thing that you want to accomplish. – Xam Aug 05 '20 at 07:12
  • But what I need to achieve is in wpf. UWP already have it I'm trying to bring it into WPF. Well I'm not going to stop until I figure out a way to do it. – trickymind Aug 05 '20 at 13:35
  • Take a look at upcoming WinUI3 which is supported by Windows App SDK (aka Project Reunion). – aepot Sep 29 '21 at 08:23
  • Ya Win UI looks Good but it is far from being stable, I found another way to bring acrylic to wpf, using .NET5 TFM, Win2D of WinUI, and there is no airspace issue, and zero flickering on resize. it is not backwards compatiable but from build 19041 and above it will support for sure – trickymind Sep 29 '21 at 09:54
  • The method is to create two seperate windows 1) The wpf Window, 2) The Acrylic Window and clip the acrylic Window RootVisual to the size of WPF Window, it avoids flickering and resizes perfectly – trickymind Sep 29 '21 at 09:57

0 Answers0