Pārlūkot izejas kodu

Merge pull request #4043 from AvaloniaUI/feature/experimental-acrylic-brush

experimental acrylic brush implementation.
danwalmsley 5 gadi atpakaļ
vecāks
revīzija
5a890b9063
27 mainītis faili ar 1072 papildinājumiem un 8 dzēšanām
  1. 3 2
      samples/ControlCatalog/MainView.xaml
  2. 139 0
      samples/ControlCatalog/Pages/AcrylicPage.xaml
  3. 19 0
      samples/ControlCatalog/Pages/AcrylicPage.xaml.cs
  4. 23 0
      src/Avalonia.Controls/AcrylicPlatformCompensationLevels.cs
  5. 3 0
      src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs
  6. 117 0
      src/Avalonia.Controls/ExperimentalAcrylicBorder.cs
  7. 5 0
      src/Avalonia.Controls/Platform/ITopLevelImpl.cs
  8. 2 0
      src/Avalonia.DesignerSupport/Remote/Stubs.cs
  9. 2 0
      src/Avalonia.Native/WindowImplBase.cs
  10. 20 0
      src/Avalonia.Visuals/Media/AcrylicBackgroundSource.cs
  11. 345 0
      src/Avalonia.Visuals/Media/ExperimentalAcrylicMaterial.cs
  12. 1 1
      src/Avalonia.Visuals/Media/IBrush.cs
  13. 33 0
      src/Avalonia.Visuals/Media/IExperimentalAcrylicMaterial.cs
  14. 14 0
      src/Avalonia.Visuals/Media/IMutableExperimentalAcrylicMaterial.cs
  15. 73 0
      src/Avalonia.Visuals/Media/ImmutableExperimentalAcrylicMaterial.cs
  16. 22 0
      src/Avalonia.Visuals/Media/MaterialExtensions.cs
  17. 9 0
      src/Avalonia.Visuals/Platform/IDrawingContextWithAcrylicLikeSupport.cs
  18. 16 1
      src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs
  19. 94 0
      src/Avalonia.Visuals/Rendering/SceneGraph/ExperimentalAcrylicNode.cs
  20. 1 1
      src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs
  21. 2 0
      src/Avalonia.X11/X11Window.cs
  22. 2 0
      src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs
  23. BIN
      src/Skia/Avalonia.Skia/Assets/NoiseAsset_256X256_PNG.png
  24. 3 0
      src/Skia/Avalonia.Skia/Avalonia.Skia.csproj
  25. 118 2
      src/Skia/Avalonia.Skia/DrawingContextImpl.cs
  26. 2 0
      src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs
  27. 4 1
      src/Windows/Avalonia.Win32/WindowImpl.cs

+ 3 - 2
samples/ControlCatalog/MainView.xaml

@@ -2,7 +2,7 @@
         xmlns:pages="clr-namespace:ControlCatalog.Pages"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         x:Class="ControlCatalog.MainView"
-        Background="Transparent"
+        Background="Black"
         Foreground="{DynamicResource ThemeForegroundBrush}"
         FontSize="{DynamicResource FontSizeNormal}">
   <Grid>
@@ -14,6 +14,7 @@
         </Style>
     </Grid.Styles>  
     <TabControl Classes="sidebar" Name="Sidebar">
+      <TabItem Header="Acrylic"><pages:AcrylicPage/></TabItem>
       <TabItem Header="AutoCompleteBox"><pages:AutoCompleteBoxPage/></TabItem>
       <TabItem Header="Border"><pages:BorderPage/></TabItem>
       <TabItem Header="Button"><pages:ButtonPage/></TabItem>
@@ -80,7 +81,7 @@
             <ComboBoxItem>Simple - Light</ComboBoxItem>
             <ComboBoxItem>Simple - Dark</ComboBoxItem>
           </ComboBox>
-          <ComboBox x:Name="TransparencyLevels" SelectedIndex="0">
+          <ComboBox x:Name="TransparencyLevels" SelectedIndex="{Binding TransparencyLevel}">
             <ComboBoxItem>None</ComboBoxItem>
             <ComboBoxItem>Transparent</ComboBoxItem>
             <ComboBoxItem>Blur</ComboBoxItem>

+ 139 - 0
samples/ControlCatalog/Pages/AcrylicPage.xaml

@@ -0,0 +1,139 @@
+<UserControl xmlns="https://github.com/avaloniaui"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+             mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
+             x:Class="ControlCatalog.Pages.AcrylicPage">
+  <Border Padding="20" HorizontalAlignment="Center">
+    <StackPanel Spacing="20">
+
+      <ExperimentalAcrylicBorder Width="660" CornerRadius="5">
+        <ExperimentalAcrylicBorder.Material>
+          <ExperimentalAcrylicMaterial
+            TintColor="White"
+            BackgroundSource="Digger" />
+        </ExperimentalAcrylicBorder.Material>
+        <StackPanel Spacing="5" Margin="40 10">
+          <StackPanel Orientation="Horizontal">
+            <TextBlock Text="TintOpacity" Foreground="Black" />
+            <Slider Name="TintOpacitySlider" Minimum="0" Maximum="1" Value="0.9" Width="400" />
+            <TextBlock Text="{Binding #TintOpacitySlider.Value}" Foreground="Black" />
+          </StackPanel>
+          <StackPanel Orientation="Horizontal">
+            <TextBlock Text="MaterialOpacity" Foreground="Black" />
+            <Slider Name="MaterialOpacitySlider" Minimum="0" Maximum="1" Value="0.8" Width="400" />
+            <TextBlock Text="{Binding #MaterialOpacitySlider.Value}" Foreground="Black" />
+          </StackPanel>
+        </StackPanel>
+      </ExperimentalAcrylicBorder>
+
+
+      <StackPanel Orientation="Horizontal" Spacing="20">
+        <ExperimentalAcrylicBorder Height="200" Width="200" CornerRadius="5">
+          <ExperimentalAcrylicBorder.Material>
+            <ExperimentalAcrylicMaterial
+              TintColor="#FF0000"
+              TintOpacity="{Binding #TintOpacitySlider.Value}"
+              MaterialOpacity="{Binding #MaterialOpacitySlider.Value}"
+              BackgroundSource="Digger" />
+          </ExperimentalAcrylicBorder.Material>
+        </ExperimentalAcrylicBorder>
+
+        <ExperimentalAcrylicBorder Height="200" Width="200" CornerRadius="5">
+          <ExperimentalAcrylicBorder.Material>
+            <ExperimentalAcrylicMaterial
+              TintColor="#00FF00"
+              TintOpacity="{Binding #TintOpacitySlider.Value}"
+              MaterialOpacity="{Binding #MaterialOpacitySlider.Value}"
+              BackgroundSource="Digger" />
+          </ExperimentalAcrylicBorder.Material>
+        </ExperimentalAcrylicBorder>
+
+        <ExperimentalAcrylicBorder Height="200" Width="200" CornerRadius="5">
+          <ExperimentalAcrylicBorder.Material>
+            <ExperimentalAcrylicMaterial
+              TintColor="#000000"
+              TintOpacity="{Binding #TintOpacitySlider.Value}"
+              MaterialOpacity="{Binding #MaterialOpacitySlider.Value}"
+              BackgroundSource="Digger" />
+          </ExperimentalAcrylicBorder.Material>
+        </ExperimentalAcrylicBorder>
+      </StackPanel>
+
+
+      <StackPanel Orientation="Horizontal" Spacing="20">
+        <ExperimentalAcrylicBorder Height="200" Width="200" CornerRadius="5">
+          <ExperimentalAcrylicBorder.Material>
+            <ExperimentalAcrylicMaterial
+              TintColor="#0000FF"
+              TintOpacity="{Binding #TintOpacitySlider.Value}"
+              MaterialOpacity="{Binding #MaterialOpacitySlider.Value}"
+              BackgroundSource="Digger" />
+          </ExperimentalAcrylicBorder.Material>
+        </ExperimentalAcrylicBorder>
+
+        <ExperimentalAcrylicBorder Height="200" Width="200" CornerRadius="5">
+          <ExperimentalAcrylicBorder.Material>
+            <ExperimentalAcrylicMaterial
+              TintColor="#FFFF00"
+              TintOpacity="{Binding #TintOpacitySlider.Value}"
+              MaterialOpacity="{Binding #MaterialOpacitySlider.Value}"
+              BackgroundSource="Digger" />
+          </ExperimentalAcrylicBorder.Material>
+        </ExperimentalAcrylicBorder>
+
+        <ExperimentalAcrylicBorder Height="200" Width="200" CornerRadius="5">
+          <ExperimentalAcrylicBorder.Material>
+            <ExperimentalAcrylicMaterial
+              TintColor="#000000"
+              TintOpacity="{Binding #TintOpacitySlider.Value}"
+              MaterialOpacity="{Binding #MaterialOpacitySlider.Value}"
+              BackgroundSource="Digger" />
+          </ExperimentalAcrylicBorder.Material>
+        </ExperimentalAcrylicBorder>
+      </StackPanel>
+
+      <StackPanel Orientation="Horizontal" Spacing="20">
+        <ExperimentalAcrylicBorder Height="200" Width="200" CornerRadius="5">
+          <ExperimentalAcrylicBorder.Material>
+            <ExperimentalAcrylicMaterial
+              TintColor="White"
+              TintOpacity="{Binding #TintOpacitySlider.Value}"
+              MaterialOpacity="{Binding #MaterialOpacitySlider.Value}"
+              BackgroundSource="Digger" />
+          </ExperimentalAcrylicBorder.Material>
+        </ExperimentalAcrylicBorder>
+
+        <ExperimentalAcrylicBorder Height="200" Width="200" CornerRadius="5">
+          <ExperimentalAcrylicBorder.Material>
+            <ExperimentalAcrylicMaterial
+              TintColor="#3c3c3c"
+              TintOpacity="{Binding #TintOpacitySlider.Value}"
+              MaterialOpacity="{Binding #MaterialOpacitySlider.Value}"
+              BackgroundSource="Digger" />
+          </ExperimentalAcrylicBorder.Material>
+        </ExperimentalAcrylicBorder>
+
+        <ExperimentalAcrylicBorder Height="200" Width="200" CornerRadius="5">
+          <ExperimentalAcrylicBorder.Material>
+            <ExperimentalAcrylicMaterial
+              TintColor="White"
+              TintOpacity="{Binding #TintOpacitySlider.Value}"
+              MaterialOpacity="{Binding #MaterialOpacitySlider.Value}"
+              BackgroundSource="Digger" />
+          </ExperimentalAcrylicBorder.Material>
+        </ExperimentalAcrylicBorder>
+      </StackPanel>
+
+      <ExperimentalAcrylicBorder Height="200" Width="660" CornerRadius="5">
+        <ExperimentalAcrylicBorder.Material>
+          <ExperimentalAcrylicMaterial
+            TintColor="Red"
+            TintOpacity="{Binding #TintOpacitySlider.Value}"
+            MaterialOpacity="{Binding #MaterialOpacitySlider.Value}"
+            BackgroundSource="Digger" />
+        </ExperimentalAcrylicBorder.Material>
+      </ExperimentalAcrylicBorder>
+    </StackPanel>
+  </Border>
+</UserControl>

+ 19 - 0
samples/ControlCatalog/Pages/AcrylicPage.xaml.cs

@@ -0,0 +1,19 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+
+namespace ControlCatalog.Pages
+{
+    public class AcrylicPage : UserControl
+    {
+        public AcrylicPage()
+        {
+            this.InitializeComponent();
+        }
+
+        private void InitializeComponent()
+        {
+            AvaloniaXamlLoader.Load(this);
+        }
+    }
+}

+ 23 - 0
src/Avalonia.Controls/AcrylicPlatformCompensationLevels.cs

@@ -0,0 +1,23 @@
+namespace Avalonia.Controls
+{
+    /// <summary>
+    /// Defines compensation levels for the platform depending on the transparency level.
+    /// It controls the base opacity level of the 'tracing paper' layer that compensates
+    /// for low blur radius.
+    /// </summary>
+    public struct AcrylicPlatformCompensationLevels
+    {
+        public AcrylicPlatformCompensationLevels(double transparent, double blurred, double acrylic)
+        {
+            TransparentLevel = transparent;
+            BlurLevel = blurred;
+            AcrylicBlurLevel = acrylic;
+        }
+
+        public double TransparentLevel { get; }
+
+        public double BlurLevel { get; }
+
+        public double AcrylicBlurLevel { get; }
+    }
+}

+ 3 - 0
src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs

@@ -52,6 +52,9 @@ namespace Avalonia.Controls.Embedding.Offscreen
 
         public Action<WindowTransparencyLevel> TransparencyLevelChanged { get; set; }
 
+        /// <inheritdoc/>
+        public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = new AcrylicPlatformCompensationLevels(1, 1, 1);
+
         public void SetInputRoot(IInputRoot inputRoot) => InputRoot = inputRoot;
 
         public virtual Point PointToClient(PixelPoint point) => point.ToPoint(1);

+ 117 - 0
src/Avalonia.Controls/ExperimentalAcrylicBorder.cs

@@ -0,0 +1,117 @@
+using Avalonia.Controls.Utils;
+using Avalonia.Layout;
+using Avalonia.Media;
+using Avalonia.Platform;
+using System;
+
+namespace Avalonia.Controls
+{
+    public class ExperimentalAcrylicBorder : Decorator
+    {
+        public static readonly StyledProperty<CornerRadius> CornerRadiusProperty =
+            Border.CornerRadiusProperty.AddOwner<ExperimentalAcrylicBorder>();
+
+        public static readonly StyledProperty<ExperimentalAcrylicMaterial> MaterialProperty =
+            AvaloniaProperty.Register<ExperimentalAcrylicBorder, ExperimentalAcrylicMaterial>(nameof(Material));
+
+        private readonly BorderRenderHelper _borderRenderHelper = new BorderRenderHelper();
+
+        private IDisposable _subscription;
+
+        static ExperimentalAcrylicBorder()
+        {
+            AffectsRender<ExperimentalAcrylicBorder>(
+                MaterialProperty,
+                CornerRadiusProperty);
+        }
+
+
+        /// <summary>
+        /// Gets or sets the radius of the border rounded corners.
+        /// </summary>
+        public CornerRadius CornerRadius
+        {
+            get { return GetValue(CornerRadiusProperty); }
+            set { SetValue(CornerRadiusProperty, value); }
+        }
+
+        public ExperimentalAcrylicMaterial Material
+        {
+            get => GetValue(MaterialProperty);
+            set => SetValue(MaterialProperty, value);
+        }
+
+        protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
+        {
+            base.OnAttachedToVisualTree(e);
+
+            var tl = (e.Root as TopLevel);
+
+            _subscription = tl.GetObservable(TopLevel.ActualTransparencyLevelProperty)
+                .Subscribe(x =>
+                {
+                    switch (x)
+                    {
+                        case WindowTransparencyLevel.Transparent:
+                        case WindowTransparencyLevel.None:
+                            Material.PlatformTransparencyCompensationLevel = tl.PlatformImpl.AcrylicCompensationLevels.TransparentLevel;
+                            break;
+
+                        case WindowTransparencyLevel.Blur:
+                            Material.PlatformTransparencyCompensationLevel = tl.PlatformImpl.AcrylicCompensationLevels.BlurLevel;
+                            break;
+
+                        case WindowTransparencyLevel.AcrylicBlur:
+                            Material.PlatformTransparencyCompensationLevel = tl.PlatformImpl.AcrylicCompensationLevels.AcrylicBlurLevel;
+                            break;
+                    }
+                });
+        }
+
+        protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
+        {
+            base.OnDetachedFromVisualTree(e);
+
+            _subscription?.Dispose();
+        }
+
+        public override void Render(DrawingContext context)
+        {
+            if (context.PlatformImpl is IDrawingContextWithAcrylicLikeSupport idc)
+            {
+                var cornerRadius = CornerRadius;
+
+                idc.DrawRectangle(
+                    Material,
+                    new RoundedRect(
+                        new Rect(Bounds.Size),
+                        cornerRadius.TopLeft, cornerRadius.TopRight,
+                        cornerRadius.BottomRight, cornerRadius.BottomLeft));
+            }
+            else
+            {
+                _borderRenderHelper.Render(context, Bounds.Size, new Thickness(), CornerRadius, new SolidColorBrush(Material.FallbackColor), null, default);
+            }
+        }
+
+        /// <summary>
+        /// Measures the control.
+        /// </summary>
+        /// <param name="availableSize">The available size.</param>
+        /// <returns>The desired size of the control.</returns>
+        protected override Size MeasureOverride(Size availableSize)
+        {
+            return LayoutHelper.MeasureChild(Child, availableSize, Padding);
+        }
+
+        /// <summary>
+        /// Arranges the control's child.
+        /// </summary>
+        /// <param name="finalSize">The size allocated to the control.</param>
+        /// <returns>The space taken.</returns>
+        protected override Size ArrangeOverride(Size finalSize)
+        {
+            return LayoutHelper.ArrangeChild(Child, finalSize, Padding);
+        }
+    }
+}

+ 5 - 0
src/Avalonia.Controls/Platform/ITopLevelImpl.cs

@@ -127,5 +127,10 @@ namespace Avalonia.Platform
         /// Gets the current <see cref="WindowTransparencyLevel"/> of the TopLevel.
         /// </summary>
         WindowTransparencyLevel TransparencyLevel { get; }
+
+        /// <summary>
+        /// Gets the <see cref="AcrylicPlatformCompensationLevels"/> for the platform.        
+        /// </summary>
+        AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; }
     }
 }

+ 2 - 0
src/Avalonia.DesignerSupport/Remote/Stubs.cs

@@ -174,6 +174,8 @@ namespace Avalonia.DesignerSupport.Remote
         public bool IsClientAreaExtendedToDecorations { get; }
 
         public bool NeedsManagedDecorations => false;
+        
+        public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = new AcrylicPlatformCompensationLevels(1, 1, 1);
     }
 
     class ClipboardStub : IClipboard

+ 2 - 0
src/Avalonia.Native/WindowImplBase.cs

@@ -439,6 +439,8 @@ namespace Avalonia.Native
 
         public WindowTransparencyLevel TransparencyLevel { get; private set; } = WindowTransparencyLevel.Transparent;
 
+        public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = new AcrylicPlatformCompensationLevels(1, 0, 0);
+
         public IPlatformHandle Handle { get; private set; }
     }
 }

+ 20 - 0
src/Avalonia.Visuals/Media/AcrylicBackgroundSource.cs

@@ -0,0 +1,20 @@
+namespace Avalonia.Media
+{
+    /// <summary>
+    /// Background Sources for Acrylic.
+    /// </summary>
+    public enum AcrylicBackgroundSource
+    {
+        /// <summary>
+        /// The acrylic has no background.
+        /// </summary>
+        None,
+
+        /// <summary>
+        /// Cuts through all render layers to reveal the window background.
+        /// This means if your window is transparent or blurred it 
+        /// will be blended with the material.
+        /// </summary>
+        Digger
+    }
+}

+ 345 - 0
src/Avalonia.Visuals/Media/ExperimentalAcrylicMaterial.cs

@@ -0,0 +1,345 @@
+using System;
+
+namespace Avalonia.Media
+{
+    public class ExperimentalAcrylicMaterial : AvaloniaObject, IMutableExperimentalAcrylicMaterial
+    {
+        private Color _effectiveTintColor;
+        private Color _effectiveLuminosityColor;
+
+        static ExperimentalAcrylicMaterial()
+        {
+            AffectsRender<ExperimentalAcrylicMaterial>(
+                TintColorProperty,
+                BackgroundSourceProperty,
+                TintOpacityProperty,
+                MaterialOpacityProperty,
+                PlatformTransparencyCompensationLevelProperty);
+
+            TintColorProperty.Changed.AddClassHandler<ExperimentalAcrylicMaterial>((b, e) =>
+            {
+                b._effectiveTintColor = GetEffectiveTintColor(b.TintColor, b.TintOpacity);
+                b._effectiveLuminosityColor = b.GetEffectiveLuminosityColor();
+            });
+
+            TintOpacityProperty.Changed.AddClassHandler<ExperimentalAcrylicMaterial>((b, e) =>
+            {
+                b._effectiveTintColor = GetEffectiveTintColor(b.TintColor, b.TintOpacity);
+                b._effectiveLuminosityColor = b.GetEffectiveLuminosityColor();
+            });
+
+            MaterialOpacityProperty.Changed.AddClassHandler<ExperimentalAcrylicMaterial>((b, e) =>
+            {
+                b._effectiveTintColor = GetEffectiveTintColor(b.TintColor, b.TintOpacity);
+                b._effectiveLuminosityColor = b.GetEffectiveLuminosityColor();
+            });
+
+            PlatformTransparencyCompensationLevelProperty.Changed.AddClassHandler<ExperimentalAcrylicMaterial>((b, e) =>
+            {
+                b._effectiveTintColor = GetEffectiveTintColor(b.TintColor, b.TintOpacity);
+                b._effectiveLuminosityColor = b.GetEffectiveLuminosityColor();
+            });
+        }
+
+        /// <summary>
+        /// Defines the <see cref="TintColor"/> property.
+        /// </summary>
+        public static readonly StyledProperty<Color> TintColorProperty =
+            AvaloniaProperty.Register<ExperimentalAcrylicMaterial, Color>(nameof(TintColor));
+
+        /// <summary>
+        /// Defines the <see cref="BackgroundSource"/> property.
+        /// </summary>
+        public static readonly StyledProperty<AcrylicBackgroundSource> BackgroundSourceProperty =
+            AvaloniaProperty.Register<ExperimentalAcrylicMaterial, AcrylicBackgroundSource>(nameof(BackgroundSource));
+
+        /// <summary>
+        /// Defines the <see cref="TintOpacity"/> property.
+        /// </summary>
+        public static readonly StyledProperty<double> TintOpacityProperty =
+            AvaloniaProperty.Register<ExperimentalAcrylicMaterial, double>(nameof(TintOpacity), 0.8);
+
+        /// <summary>
+        /// Defines the <see cref="MaterialOpacity"/> property.
+        /// </summary>
+        public static readonly StyledProperty<double> MaterialOpacityProperty =
+            AvaloniaProperty.Register<ExperimentalAcrylicMaterial, double>(nameof(MaterialOpacity), 0.5);
+
+        /// <summary>
+        /// Defines the <see cref="PlatformTransparencyCompensationLevel"/> property.
+        /// </summary>
+        public static readonly StyledProperty<double> PlatformTransparencyCompensationLevelProperty =
+            AvaloniaProperty.Register<ExperimentalAcrylicMaterial, double>(nameof(PlatformTransparencyCompensationLevel), 0.0);
+
+        /// <summary>
+        /// Defines the <see cref="FallbackColor"/> property.
+        /// </summary>
+        public static readonly StyledProperty<Color> FallbackColorProperty =
+            AvaloniaProperty.Register<ExperimentalAcrylicMaterial, Color>(nameof(FallbackColor));
+
+        /// <inheritdoc/>
+        public event EventHandler Invalidated;
+
+        /// <summary>
+        /// Gets or Sets the BackgroundSource <seealso cref="AcrylicBackgroundSource"/>.
+        /// </summary>
+        public AcrylicBackgroundSource BackgroundSource
+        {
+            get => GetValue(BackgroundSourceProperty);
+            set => SetValue(BackgroundSourceProperty, value);
+        }
+
+        /// <summary>
+        /// Gets or Sets the TintColor.
+        /// </summary>
+        public Color TintColor
+        {
+            get => GetValue(TintColorProperty);
+            set => SetValue(TintColorProperty, value);
+        }
+
+        /// <summary>
+        /// Gets or Sets the Tint Opacity.
+        /// </summary>
+        public double TintOpacity
+        {
+            get => GetValue(TintOpacityProperty);
+            set => SetValue(TintOpacityProperty, value);
+        }
+
+        /// <summary>
+        /// Gets or Sets the Fallback Color.
+        /// This is used on rendering plaforms that dont support acrylic.
+        /// </summary>
+        public Color FallbackColor
+        {
+            get => GetValue(FallbackColorProperty);
+            set => SetValue(FallbackColorProperty, value);
+        }
+
+        /// <summary>
+        /// Gets or Sets the MaterialOpacity.
+        /// This makes the material more or less opaque.
+        /// </summary>
+        public double MaterialOpacity
+        {
+            get => GetValue(MaterialOpacityProperty);
+            set => SetValue(MaterialOpacityProperty, value);
+        }
+
+        /// <summary>
+        /// Gets or Sets the PlatformTransparencyCompensationLevel.
+        /// This value defines the minimum <see cref="MaterialOpacity"/> that can be used.
+        /// It means material opacity is re-scaled from this value to 1.
+        /// </summary>
+        public double PlatformTransparencyCompensationLevel
+        {
+            get => GetValue(PlatformTransparencyCompensationLevelProperty);
+            set => SetValue(PlatformTransparencyCompensationLevelProperty, value);
+        }
+
+        Color IExperimentalAcrylicMaterial.MaterialColor => _effectiveLuminosityColor;
+
+        Color IExperimentalAcrylicMaterial.TintColor => _effectiveTintColor;
+
+        private struct HsvColor
+        {
+            public float Hue { get; set; }
+            public float Saturation { get; set; }
+            public float Value { get; set; }
+        }
+
+        private static HsvColor RgbToHsv(Color color)
+        {
+            var r = color.R / 255.0f;
+            var g = color.G / 255.0f;
+            var b = color.B / 255.0f;
+            var max = Math.Max(r, Math.Max(g, b));
+            var min = Math.Min(r, Math.Min(g, b));
+
+            float h, s, v;
+            h = v = max;
+
+            var d = max - min;
+            s = max == 0 ? 0 : d / max;
+
+            if (max == min)
+            {
+                h = 0; // achromatic
+            }
+            else
+            {
+                if (max == r)
+                {
+                    h = (g - b) / d + (g < b ? 6 : 0);
+                }
+                else if (max == g)
+                {
+                    h = (b - r) / d + 2;
+                }
+                else if (max == b)
+                {
+                    h = (r - g) / d + 4;
+                }
+
+                h /= 6;
+            }
+
+            return new HsvColor { Hue = h, Saturation = s, Value = v };
+        }
+
+        private static Color GetEffectiveTintColor(Color tintColor, double tintOpacity)
+        {
+            // Update tintColor's alpha with the combined opacity value
+            double tintOpacityModifier = GetTintOpacityModifier(tintColor);
+
+            return new Color((byte)(255 * ((255.0 / tintColor.A) * tintOpacity) * tintOpacityModifier), tintColor.R, tintColor.G, tintColor.B);
+        }
+
+        private static double GetTintOpacityModifier(Color tintColor)
+        {
+            // This method supresses the maximum allowable tint opacity depending on the luminosity and saturation of a color by 
+            // compressing the range of allowable values - for example, a user-defined value of 100% will be mapped to 45% for pure 
+            // white (100% luminosity), 85% for pure black (0% luminosity), and 90% for pure gray (50% luminosity).  The intensity of 
+            // the effect increases linearly as luminosity deviates from 50%.  After this effect is calculated, we cancel it out
+            // linearly as saturation increases from zero.
+
+            const double midPoint = 0.5; // Mid point of HsvV range that these calculations are based on. This is here for easy tuning.
+
+            const double whiteMaxOpacity = 0.2; // 100% luminosity
+            const double midPointMaxOpacity = 0.45; // 50% luminosity
+            const double blackMaxOpacity = 0.45; // 0% luminosity
+
+            var hsv = RgbToHsv(tintColor);
+
+            double opacityModifier = midPointMaxOpacity;
+
+            if (hsv.Value != midPoint)
+            {
+                // Determine maximum suppression amount
+                double lowestMaxOpacity = midPointMaxOpacity;
+                double maxDeviation = midPoint;
+
+                if (hsv.Value > midPoint)
+                {
+                    lowestMaxOpacity = whiteMaxOpacity; // At white (100% hsvV)
+                    maxDeviation = 1 - maxDeviation;
+                }
+                else if (hsv.Value < midPoint)
+                {
+                    lowestMaxOpacity = blackMaxOpacity; // At black (0% hsvV)
+                }
+
+                double maxOpacitySuppression = midPointMaxOpacity - lowestMaxOpacity;
+
+                // Determine normalized deviation from the midpoint
+                double deviation = Math.Abs(hsv.Value - midPoint);
+                double normalizedDeviation = deviation / maxDeviation;
+
+                // If we have saturation, reduce opacity suppression to allow that color to come through more
+                if (hsv.Saturation > 0)
+                {
+                    // Dampen opacity suppression based on how much saturation there is
+                    maxOpacitySuppression *= Math.Max(1 - (hsv.Saturation * 2), 0.0);
+                }
+
+                double opacitySuppression = maxOpacitySuppression * normalizedDeviation;
+
+                opacityModifier = midPointMaxOpacity - opacitySuppression;
+            }
+
+            return opacityModifier;
+        }
+
+        private Color GetEffectiveLuminosityColor()
+        {
+            double? luminosityOpacity = MaterialOpacity;
+
+            return GetLuminosityColor(luminosityOpacity);
+        }
+
+        private static byte Trim(double value)
+        {
+            value = Math.Min(Math.Floor(value * 256), 255);
+
+            if (value < 0)
+            {
+                return 0;
+            }
+            else if (value > 255)
+            {
+                return 255;
+            }
+
+            return (byte)value;
+        }
+
+        private static float RGBMax(Color color)
+        {
+            if (color.R > color.G)
+                return (color.R > color.B) ? color.R : color.B;
+            else
+                return (color.G > color.B) ? color.G : color.B;
+        }
+
+        private static float RGBMin(Color color)
+        {
+            if (color.R < color.G)
+                return (color.R < color.B) ? color.R : color.B;
+            else
+                return (color.G < color.B) ? color.G : color.B;
+        }
+
+        // The tintColor passed into this method should be the original, unmodified color created using user values for TintColor + TintOpacity
+        private Color GetLuminosityColor(double? luminosityOpacity)
+        {
+            // Calculate the HSL lightness value of the color.
+            var max = (float)RGBMax(TintColor) / 255.0f;
+            var min = (float)RGBMin(TintColor) / 255.0f;
+
+            var lightness = (max + min) / 2.0;
+
+            lightness = 1 - ((1 - lightness) * luminosityOpacity.Value);
+
+            lightness = 0.13 + (lightness * 0.74);
+
+            var luminosityColor = new Color(255, Trim(lightness), Trim(lightness), Trim(lightness));
+
+            var compensationMultiplier = 1 - PlatformTransparencyCompensationLevel;
+            return new Color((byte)(255 * Math.Max(Math.Min(PlatformTransparencyCompensationLevel + (luminosityOpacity.Value * compensationMultiplier), 1.0), 0.0)), luminosityColor.R, luminosityColor.G, luminosityColor.B);
+        }
+
+        /// <summary>
+        /// Marks a property as affecting the brush's visual representation.
+        /// </summary>
+        /// <param name="properties">The properties.</param>
+        /// <remarks>
+        /// After a call to this method in a brush's static constructor, any change to the
+        /// property will cause the <see cref="Invalidated"/> event to be raised on the brush.
+        /// </remarks>
+        protected static void AffectsRender<T>(params AvaloniaProperty[] properties)
+            where T : ExperimentalAcrylicMaterial
+        {
+            static void Invalidate(AvaloniaPropertyChangedEventArgs e)
+            {
+                (e.Sender as T)?.RaiseInvalidated(EventArgs.Empty);
+            }
+
+            foreach (var property in properties)
+            {
+                property.Changed.Subscribe(e => Invalidate(e));
+            }
+        }
+
+        /// <summary>
+        /// Raises the <see cref="Invalidated"/> event.
+        /// </summary>
+        /// <param name="e">The event args.</param>
+        protected void RaiseInvalidated(EventArgs e) => Invalidated?.Invoke(this, e);
+
+        public IExperimentalAcrylicMaterial ToImmutable()
+        {
+            return new ImmutableExperimentalAcrylicMaterial(this);
+        }
+    }
+}

+ 1 - 1
src/Avalonia.Visuals/Media/IBrush.cs

@@ -13,4 +13,4 @@ namespace Avalonia.Media
         /// </summary>
         double Opacity { get; }
     }
-}
+}

+ 33 - 0
src/Avalonia.Visuals/Media/IExperimentalAcrylicMaterial.cs

@@ -0,0 +1,33 @@
+namespace Avalonia.Media
+{
+    /// <summary>
+    /// Experimental Interface for producing Acrylic-like materials.
+    /// </summary>
+    public interface IExperimentalAcrylicMaterial
+    {
+        /// <summary>
+        /// Gets the <see cref="AcrylicBackgroundSource"/> of the material.
+        /// </summary>
+        AcrylicBackgroundSource BackgroundSource { get; }
+
+        /// <summary>
+        /// Gets the TintColor of the material.
+        /// </summary>
+        Color TintColor { get; }
+
+        /// <summary>
+        /// Gets the TintOpacity of the material.
+        /// </summary>
+        double TintOpacity { get; }
+
+        /// <summary>
+        /// Gets the effective material color.
+        /// </summary>
+        Color MaterialColor { get; }        
+
+        /// <summary>
+        /// Gets the fallback color.
+        /// </summary>
+        Color FallbackColor { get; }
+    }
+}

+ 14 - 0
src/Avalonia.Visuals/Media/IMutableExperimentalAcrylicMaterial.cs

@@ -0,0 +1,14 @@
+namespace Avalonia.Media
+{
+    /// <summary>
+    /// Represents a mutable brush which can return an immutable clone of itself.
+    /// </summary>
+    public interface IMutableExperimentalAcrylicMaterial : IExperimentalAcrylicMaterial, IAffectsRender
+    {
+        /// <summary>
+        /// Creates an immutable clone of the brush.
+        /// </summary>
+        /// <returns>The immutable clone.</returns>
+        IExperimentalAcrylicMaterial ToImmutable();
+    }
+}

+ 73 - 0
src/Avalonia.Visuals/Media/ImmutableExperimentalAcrylicMaterial.cs

@@ -0,0 +1,73 @@
+using System;
+
+namespace Avalonia.Media
+{
+    public readonly struct ImmutableExperimentalAcrylicMaterial : IExperimentalAcrylicMaterial, IEquatable<ImmutableExperimentalAcrylicMaterial>
+    {
+        public ImmutableExperimentalAcrylicMaterial(IExperimentalAcrylicMaterial brush)
+        {
+            BackgroundSource = brush.BackgroundSource;
+            TintColor = brush.TintColor;
+            TintOpacity = brush.TintOpacity;
+            FallbackColor = brush.FallbackColor;
+            MaterialColor = brush.MaterialColor;
+        }
+
+        public AcrylicBackgroundSource BackgroundSource { get; }
+
+        public Color TintColor { get; }
+
+        public Color MaterialColor { get; }
+
+        public double TintOpacity { get; }
+
+        public Color FallbackColor { get; }
+
+        public bool Equals(ImmutableExperimentalAcrylicMaterial other)
+        {
+            // ReSharper disable once CompareOfFloatsByEqualityOperator
+            return
+                TintColor == other.TintColor &&                
+                TintOpacity == other.TintOpacity &&
+                BackgroundSource == other.BackgroundSource &&
+                FallbackColor == other.FallbackColor && MaterialColor == other.MaterialColor;
+
+        }
+
+        public override bool Equals(object obj)
+        {
+            return obj is ImmutableExperimentalAcrylicMaterial other && Equals(other);
+        }
+
+        public Color GetEffectiveTintColor()
+        {
+            return TintColor;
+        }
+
+        public override int GetHashCode()
+        {
+            unchecked
+            {
+                int hash = 17;
+
+                hash = (hash * 23) + TintColor.GetHashCode();                
+                hash = (hash * 23) + TintOpacity.GetHashCode();
+                hash = (hash * 23) + BackgroundSource.GetHashCode();
+                hash = (hash * 23) + FallbackColor.GetHashCode();
+                hash = (hash * 23) + MaterialColor.GetHashCode();
+
+                return hash;
+            }
+        }
+
+        public static bool operator ==(ImmutableExperimentalAcrylicMaterial left, ImmutableExperimentalAcrylicMaterial right)
+        {
+            return left.Equals(right);
+        }
+
+        public static bool operator !=(ImmutableExperimentalAcrylicMaterial left, ImmutableExperimentalAcrylicMaterial right)
+        {
+            return !left.Equals(right);
+        }
+    }
+}

+ 22 - 0
src/Avalonia.Visuals/Media/MaterialExtensions.cs

@@ -0,0 +1,22 @@
+using System;
+
+namespace Avalonia.Media
+{
+    public static class MaterialExtensions
+    {
+        /// <summary>
+        /// Converts a brush to an immutable brush.
+        /// </summary>
+        /// <param name="material">The brush.</param>
+        /// <returns>
+        /// The result of calling <see cref="IMutableBrush.ToImmutable"/> if the brush is mutable,
+        /// otherwise <paramref name="material"/>.
+        /// </returns>
+        public static IExperimentalAcrylicMaterial ToImmutable(this IExperimentalAcrylicMaterial material)
+        {
+            Contract.Requires<ArgumentNullException>(material != null);
+
+            return (material as IMutableExperimentalAcrylicMaterial)?.ToImmutable() ?? material;
+        }
+    }
+}

+ 9 - 0
src/Avalonia.Visuals/Platform/IDrawingContextWithAcrylicLikeSupport.cs

@@ -0,0 +1,9 @@
+using Avalonia.Media;
+
+namespace Avalonia.Platform
+{
+    public interface IDrawingContextWithAcrylicLikeSupport
+    {
+        void DrawRectangle(IExperimentalAcrylicMaterial material, RoundedRect rect);
+    }
+}

+ 16 - 1
src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs

@@ -11,7 +11,7 @@ namespace Avalonia.Rendering.SceneGraph
     /// <summary>
     /// A drawing context which builds a scene graph.
     /// </summary>
-    internal class DeferredDrawingContextImpl : IDrawingContextImpl
+    internal class DeferredDrawingContextImpl : IDrawingContextImpl, IDrawingContextWithAcrylicLikeSupport
     {
         private readonly ISceneBuilder _sceneBuilder;
         private VisualNode _node;
@@ -164,6 +164,21 @@ namespace Avalonia.Rendering.SceneGraph
             }
         }
 
+        /// <inheritdoc/>
+        public void DrawRectangle(IExperimentalAcrylicMaterial material, RoundedRect rect)
+        {
+            var next = NextDrawAs<ExperimentalAcrylicNode>();
+
+            if (next == null || !next.Item.Equals(Transform, material, rect))
+            {
+                Add(new ExperimentalAcrylicNode(Transform, material, rect));
+            }
+            else
+            {
+                ++_drawOperationindex;
+            }
+        }
+
         public void Custom(ICustomDrawOperation custom)
         {
             var next = NextDrawAs<CustomDrawOperation>();

+ 94 - 0
src/Avalonia.Visuals/Rendering/SceneGraph/ExperimentalAcrylicNode.cs

@@ -0,0 +1,94 @@
+using System;
+using Avalonia.Media;
+using Avalonia.Media.Immutable;
+using Avalonia.Platform;
+
+namespace Avalonia.Rendering.SceneGraph
+{
+    /// <summary>
+    /// A node in the scene graph which represents a rectangle draw.
+    /// </summary>
+    internal class ExperimentalAcrylicNode : DrawOperation
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="RectangleNode"/> class.
+        /// </summary>
+        /// <param name="transform">The transform.</param>        
+        /// <param name="rect">The rectangle to draw.</param>
+        /// <param name="boxShadow">The box shadow parameters</param>
+        /// <param name="childScenes">Child scenes for drawing visual brushes.</param>
+        public ExperimentalAcrylicNode(
+            Matrix transform,
+            IExperimentalAcrylicMaterial material,
+            RoundedRect rect)
+            : base(rect.Rect, transform)
+        {
+            Transform = transform;
+            Material = material?.ToImmutable();
+            Rect = rect;
+        }
+
+        /// <summary>
+        /// Gets the transform with which the node will be drawn.
+        /// </summary>
+        public Matrix Transform { get; }
+
+        public IExperimentalAcrylicMaterial Material { get; }        
+
+        /// <summary>
+        /// Gets the rectangle to draw.
+        /// </summary>
+        public RoundedRect Rect { get; }
+
+        /// <summary>
+        /// Determines if this draw operation equals another.
+        /// </summary>
+        /// <param name="transform">The transform of the other draw operation.</param>
+        /// <param name="brush">The fill of the other draw operation.</param>
+        /// <param name="rect">The rectangle of the other draw operation.</param>        
+        /// <returns>True if the draw operations are the same, otherwise false.</returns>
+        /// <remarks>
+        /// The properties of the other draw operation are passed in as arguments to prevent
+        /// allocation of a not-yet-constructed draw operation object.
+        /// </remarks>
+        public bool Equals(Matrix transform, IExperimentalAcrylicMaterial material, RoundedRect rect)
+        {
+            return transform == Transform &&
+                   Equals(material, Material) &&
+                   rect.Equals(Rect);
+        }
+
+        /// <inheritdoc/>
+        public override void Render(IDrawingContextImpl context)
+        {
+            context.Transform = Transform;
+
+            if(context is IDrawingContextWithAcrylicLikeSupport idc)
+            {
+                idc.DrawRectangle(Material, Rect);
+            }
+            else
+            {
+                context.DrawRectangle(new ImmutableSolidColorBrush(Material.FallbackColor), null, Rect);
+            }            
+        }
+
+        /// <inheritdoc/>
+        public override bool HitTest(Point p)
+        {
+            // TODO: This doesn't respect CornerRadius yet.
+            if (Transform.HasInverse)
+            {
+                p *= Transform.Invert();
+
+                if (Material != null)
+                {
+                    var rect = Rect.Rect;
+                    return rect.Contains(p);
+                }
+            }
+
+            return false;
+        }
+    }
+}

+ 1 - 1
src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs

@@ -19,7 +19,7 @@ namespace Avalonia.Rendering.SceneGraph
         /// <param name="brush">The fill brush.</param>
         /// <param name="pen">The stroke pen.</param>
         /// <param name="rect">The rectangle to draw.</param>
-        /// <param name="boxShadow">The box shadow parameters</param>
+        /// <param name="boxShadows">The box shadow parameters</param>
         /// <param name="childScenes">Child scenes for drawing visual brushes.</param>
         public RectangleNode(
             Matrix transform,

+ 2 - 0
src/Avalonia.X11/X11Window.cs

@@ -1128,6 +1128,8 @@ namespace Avalonia.X11
 
         public WindowTransparencyLevel TransparencyLevel => _transparencyHelper.CurrentLevel;
 
+        public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = new AcrylicPlatformCompensationLevels(1, 0.8, 0.8);
+
         public bool NeedsManagedDecorations => false;
     }
 }

+ 2 - 0
src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs

@@ -82,5 +82,7 @@ namespace Avalonia.LinuxFramebuffer
         public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel) { }
 
         public WindowTransparencyLevel TransparencyLevel { get; private set; }
+
+        public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = new AcrylicPlatformCompensationLevels(1, 1, 1);
     }
 }

BIN
src/Skia/Avalonia.Skia/Assets/NoiseAsset_256X256_PNG.png


+ 3 - 0
src/Skia/Avalonia.Skia/Avalonia.Skia.csproj

@@ -7,6 +7,9 @@
     <IncludeLinuxSkia>true</IncludeLinuxSkia>
     <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
   </PropertyGroup>
+  <ItemGroup>
+    <EmbeddedResource Include="Assets\NoiseAsset_256X256_PNG.png" />
+  </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="..\..\..\packages\Avalonia\Avalonia.csproj" />
   </ItemGroup>

+ 118 - 2
src/Skia/Avalonia.Skia/DrawingContextImpl.cs

@@ -17,7 +17,7 @@ namespace Avalonia.Skia
     /// <summary>
     /// Skia based drawing context.
     /// </summary>
-    internal class DrawingContextImpl : IDrawingContextImpl, ISkiaDrawingContextImpl
+    internal class DrawingContextImpl : IDrawingContextImpl, ISkiaDrawingContextImpl, IDrawingContextWithAcrylicLikeSupport
     {
         private IDisposable[] _disposables;
         private readonly Vector _dpi;
@@ -34,6 +34,7 @@ namespace Avalonia.Skia
         private readonly SKPaint _strokePaint = new SKPaint();
         private readonly SKPaint _fillPaint = new SKPaint();
         private readonly SKPaint _boxShadowPaint = new SKPaint();
+        private static SKShader s_acrylicNoiseShader;
 
         /// <summary>
         /// Context create info.
@@ -227,6 +228,41 @@ namespace Avalonia.Skia
             return bounds;
         }
 
+        /// <inheritdoc />
+        public void DrawRectangle(IExperimentalAcrylicMaterial material, RoundedRect rect)
+        {
+            if (rect.Rect.Height <= 0 || rect.Rect.Width <= 0)
+                return;
+            
+            var rc = rect.Rect.ToSKRect();
+            var isRounded = rect.IsRounded;
+            var needRoundRect = rect.IsRounded;
+            using var skRoundRect = needRoundRect ? new SKRoundRect() : null;
+
+            if (needRoundRect)
+                skRoundRect.SetRectRadii(rc,
+                    new[]
+                    {
+                        rect.RadiiTopLeft.ToSKPoint(), rect.RadiiTopRight.ToSKPoint(),
+                        rect.RadiiBottomRight.ToSKPoint(), rect.RadiiBottomLeft.ToSKPoint(),
+                    });
+
+            if (material != null)
+            {
+                using (var paint = CreateAcrylicPaint(_fillPaint, material))
+                {
+                    if (isRounded)
+                    {
+                        Canvas.DrawRoundRect(skRoundRect, paint.Paint);
+                    }
+                    else
+                    {
+                        Canvas.DrawRect(rc, paint.Paint);
+                    }
+
+                }
+            }
+        }
 
         /// <inheritdoc />
         public void DrawRectangle(IBrush brush, IPen pen, RoundedRect rect, BoxShadows boxShadows = default)
@@ -659,6 +695,86 @@ namespace Avalonia.Skia
             }
         }
 
+        static SKColorFilter CreateAlphaColorFilter(double opacity)
+        {
+            if (opacity > 1)
+                opacity = 1;
+            var c = new byte[256];
+            var a = new byte[256];
+            for (var i = 0; i < 256; i++)
+            {
+                c[i] = (byte)i;
+                a[i] = (byte)(i * opacity);
+            }
+
+            return SKColorFilter.CreateTable(a, c, c, c);
+        }
+
+        static byte Blend(byte leftColor, byte leftAlpha, byte rightColor, byte rightAlpha)
+        {
+            var ca = leftColor / 255d;
+            var aa = leftAlpha / 255d;
+            var cb = rightColor / 255d;
+            var ab = rightAlpha / 255d;
+            var r = (ca * aa + cb * ab * (1 - aa)) / (aa + ab * (1 - aa));
+            return (byte)(r * 255);
+        }
+
+        static Color Blend(Color left, Color right)
+        {
+            var aa = left.A / 255d;
+            var ab = right.A / 255d;
+            return new Color(
+                (byte)((aa + ab * (1 - aa)) * 255),
+                Blend(left.R, left.A, right.R, right.A),
+                Blend(left.G, left.A, right.G, right.A),
+                Blend(left.B, left.A, right.B, right.A)                
+            );
+        }
+
+        internal PaintWrapper CreateAcrylicPaint (SKPaint paint, IExperimentalAcrylicMaterial material, bool disposePaint = false)
+        {
+            var paintWrapper = new PaintWrapper(paint, disposePaint);
+
+            paint.IsAntialias = true;
+
+            double opacity = _currentOpacity;
+
+            var tintOpacity =
+                material.BackgroundSource == AcrylicBackgroundSource.Digger ?
+                material.TintOpacity : 1;
+
+            const double noiseOpcity = 0.0225;
+
+            var tintColor = material.TintColor;
+            var tint = new SKColor(tintColor.R, tintColor.G, tintColor.B, tintColor.A);
+
+            if (s_acrylicNoiseShader == null)
+            {
+                using (var stream = typeof(DrawingContextImpl).Assembly.GetManifestResourceStream("Avalonia.Skia.Assets.NoiseAsset_256X256_PNG.png"))
+                using (var bitmap = SKBitmap.Decode(stream))
+                {
+                    s_acrylicNoiseShader = SKShader.CreateBitmap(bitmap, SKShaderTileMode.Repeat, SKShaderTileMode.Repeat)
+                        .WithColorFilter(CreateAlphaColorFilter(noiseOpcity));
+                }
+            }
+
+            using (var backdrop = SKShader.CreateColor(new SKColor(material.MaterialColor.R, material.MaterialColor.G, material.MaterialColor.B, material.MaterialColor.A)))
+            using (var tintShader = SKShader.CreateColor(tint))
+            using (var effectiveTint = SKShader.CreateCompose(backdrop, tintShader))
+            using (var compose = SKShader.CreateCompose(effectiveTint, s_acrylicNoiseShader))
+            {
+                paint.Shader = compose;
+
+                if (material.BackgroundSource == AcrylicBackgroundSource.Digger)
+                {
+                    paint.BlendMode = SKBlendMode.Src;
+                }
+
+                return paintWrapper;
+            }
+        }
+
         /// <summary>
         /// Creates paint wrapper for given brush.
         /// </summary>
@@ -811,7 +927,7 @@ namespace Avalonia.Skia
             };
 
             return new SurfaceRenderTarget(createInfo);
-        }
+        }        
 
         /// <summary>
         /// Skia cached paint state.

+ 2 - 0
src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs

@@ -256,5 +256,7 @@ namespace Avalonia.Win32.Interop.Wpf
         public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel) { }
 
         public WindowTransparencyLevel TransparencyLevel { get; private set; }
+
+        public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = new AcrylicPlatformCompensationLevels(1, 1, 1);
     }
 }

+ 4 - 1
src/Windows/Avalonia.Win32/WindowImpl.cs

@@ -277,7 +277,7 @@ namespace Avalonia.Win32
             }
 
             accent.AccentFlags = 2;
-            accent.GradientColor = 0x00FFFFFF;
+            accent.GradientColor = 0x01000000;
 
             var accentPtr = Marshal.AllocHGlobal(accentStructSize);
             Marshal.StructureToPtr(accent, accentPtr, false);
@@ -1071,6 +1071,9 @@ namespace Avalonia.Win32
         /// <inheritdoc/>
         public Thickness OffScreenMargin => _offScreenMargin;
 
+        /// <inheritdoc/>
+        public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = new AcrylicPlatformCompensationLevels(1, 0.8, 0);
+
         private struct SavedWindowInfo
         {
             public WindowStyles Style { get; set; }