From e4fd1ad4f3dbfca9f713e0faf0d07734dac4275a Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sat, 30 May 2020 14:41:12 -0300 Subject: [PATCH 01/47] experimental acrylic brush implementation. --- .../Media/AcrylicBackgroundSource.cs | 8 ++ .../Media/ExperimentalAcrylicBrush.cs | 17 +++++ src/Avalonia.Visuals/Media/IBrush.cs | 2 +- .../Media/IExperimentalAcrylicBrush.cs | 15 ++++ src/Skia/Avalonia.Skia/DrawingContextImpl.cs | 74 +++++++++++++++++++ 5 files changed, 115 insertions(+), 1 deletion(-) create mode 100644 src/Avalonia.Visuals/Media/AcrylicBackgroundSource.cs create mode 100644 src/Avalonia.Visuals/Media/ExperimentalAcrylicBrush.cs create mode 100644 src/Avalonia.Visuals/Media/IExperimentalAcrylicBrush.cs diff --git a/src/Avalonia.Visuals/Media/AcrylicBackgroundSource.cs b/src/Avalonia.Visuals/Media/AcrylicBackgroundSource.cs new file mode 100644 index 0000000000..d902752f85 --- /dev/null +++ b/src/Avalonia.Visuals/Media/AcrylicBackgroundSource.cs @@ -0,0 +1,8 @@ +namespace Avalonia.Media +{ + public enum AcrylicBackgroundSource + { + None, + Digger + } +} diff --git a/src/Avalonia.Visuals/Media/ExperimentalAcrylicBrush.cs b/src/Avalonia.Visuals/Media/ExperimentalAcrylicBrush.cs new file mode 100644 index 0000000000..2ab2701c9d --- /dev/null +++ b/src/Avalonia.Visuals/Media/ExperimentalAcrylicBrush.cs @@ -0,0 +1,17 @@ +namespace Avalonia.Media +{ + public class ExperimentalAcrylicBrush : IExperimentalAcrylicBrush + { + public AcrylicBackgroundSource BackgroundSource { get; set; } + + public Color TintColor { get; set; } + + public double TintOpacity { get; set; } + + public Color FallbackColor { get; set; } + + public double TintLuminosityOpacity { get; set; } + + public double Opacity { get; set; } + } +} diff --git a/src/Avalonia.Visuals/Media/IBrush.cs b/src/Avalonia.Visuals/Media/IBrush.cs index 7756e94598..15b7681be4 100644 --- a/src/Avalonia.Visuals/Media/IBrush.cs +++ b/src/Avalonia.Visuals/Media/IBrush.cs @@ -13,4 +13,4 @@ namespace Avalonia.Media /// double Opacity { get; } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Visuals/Media/IExperimentalAcrylicBrush.cs b/src/Avalonia.Visuals/Media/IExperimentalAcrylicBrush.cs new file mode 100644 index 0000000000..ae202b58ae --- /dev/null +++ b/src/Avalonia.Visuals/Media/IExperimentalAcrylicBrush.cs @@ -0,0 +1,15 @@ +namespace Avalonia.Media +{ + public interface IExperimentalAcrylicBrush : IBrush + { + AcrylicBackgroundSource BackgroundSource { get; set; } + + Color TintColor { get; set; } + + double TintOpacity { get; set; } + + double TintLuminosityOpacity { get; set; } + + Color FallbackColor { get; set; } + } +} diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index 26fdb08a4b..0d628d6b21 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -227,6 +227,13 @@ namespace Avalonia.Skia return bounds; } + private static bool IsLight (Color c) + { + double lightness = (0.299 * c.R + 0.587 * c.G + 0.114 * c.B) / 255; + + return lightness > 0.5; + } + /// public void DrawRectangle(IBrush brush, IPen pen, RoundedRect rect, BoxShadows boxShadows = default) @@ -238,6 +245,17 @@ namespace Avalonia.Skia if (rect.Rect.Height > 8192 || rect.Rect.Width > 8192) boxShadows = default; + if (brush is IExperimentalAcrylicBrush acrylic && IsLight(acrylic.TintColor)) + { + boxShadows = new BoxShadows(new BoxShadow + { + Blur = 2, + OffsetX = 1, + Color = Colors.White, + IsInset = true + }); + } + var rc = rect.Rect.ToSKRect(); var isRounded = rect.IsRounded; var needRoundRect = rect.IsRounded || (boxShadows.HasInsetShadows); @@ -653,6 +671,36 @@ namespace Avalonia.Skia } } + static SKColor SimpleColorBurn(SKColor bg, SKColor fg) + { + using (var bmp = new SKBitmap(1, 1)) + { + bmp.SetPixel(0, 0, bg); + using (var canvas = new SKCanvas(bmp)) + using (var paint = new SKPaint + { + Color = fg + }) + canvas.DrawRect(-1, -1, 3, 3, paint); + return bmp.GetPixel(0, 0); + } + } + + 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] = 255; + a[i] = (byte)(i * opacity); + } + + return SKColorFilter.CreateTable(a, c, c, c); + } + /// /// Creates paint wrapper for given brush. /// @@ -676,6 +724,32 @@ namespace Avalonia.Skia return paintWrapper; } + if(brush is IExperimentalAcrylicBrush acrylicBrush) + { + var tintOpacity = acrylicBrush.TintOpacity; + var noiseOpcity = 0.12; + + var excl = new SKColor(255, 255, 255, (byte)(255 * acrylicBrush.TintLuminosityOpacity)); + var tint = new SKColor(acrylicBrush.TintColor.R, acrylicBrush.TintColor.G, acrylicBrush.TintColor.B, (byte)(255 * tintOpacity)); + + tint = SimpleColorBurn(excl, tint); + + var tintShader = SKShader.CreateColor(tint); + var noiseShader = + SKShader.CreatePerlinNoiseTurbulence(12.876f, 12.876f, 2, 0.76829314f) + .WithColorFilter(CreateAlphaColorFilter(noiseOpcity)); + + var compose = SKShader.CreateCompose(tintShader, noiseShader); + paint.Shader = compose; + + if (acrylicBrush.BackgroundSource == AcrylicBackgroundSource.Digger) + { + paint.BlendMode = SKBlendMode.Src; + } + + return paintWrapper; + } + paint.Color = new SKColor(255, 255, 255, (byte) (255 * opacity)); if (brush is IGradientBrush gradient) From 29984b1d45684403723e790dce235fa0bb73c97a Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sat, 30 May 2020 15:12:23 -0300 Subject: [PATCH 02/47] drop light mode inset shadow. --- src/Skia/Avalonia.Skia/DrawingContextImpl.cs | 24 ++++---------------- 1 file changed, 4 insertions(+), 20 deletions(-) diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index 0d628d6b21..307f01ad1a 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -227,14 +227,6 @@ namespace Avalonia.Skia return bounds; } - private static bool IsLight (Color c) - { - double lightness = (0.299 * c.R + 0.587 * c.G + 0.114 * c.B) / 255; - - return lightness > 0.5; - } - - /// public void DrawRectangle(IBrush brush, IPen pen, RoundedRect rect, BoxShadows boxShadows = default) { @@ -245,17 +237,6 @@ namespace Avalonia.Skia if (rect.Rect.Height > 8192 || rect.Rect.Width > 8192) boxShadows = default; - if (brush is IExperimentalAcrylicBrush acrylic && IsLight(acrylic.TintColor)) - { - boxShadows = new BoxShadows(new BoxShadow - { - Blur = 2, - OffsetX = 1, - Color = Colors.White, - IsInset = true - }); - } - var rc = rect.Rect.ToSKRect(); var isRounded = rect.IsRounded; var needRoundRect = rect.IsRounded || (boxShadows.HasInsetShadows); @@ -726,7 +707,10 @@ namespace Avalonia.Skia if(brush is IExperimentalAcrylicBrush acrylicBrush) { - var tintOpacity = acrylicBrush.TintOpacity; + var tintOpacity = + acrylicBrush.BackgroundSource == AcrylicBackgroundSource.Digger ? + acrylicBrush.TintOpacity : 1; + var noiseOpcity = 0.12; var excl = new SKColor(255, 255, 255, (byte)(255 * acrylicBrush.TintLuminosityOpacity)); From bdfc4deb6a7ed262eaa83f023dd34a3bbf71d885 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sat, 30 May 2020 15:41:53 -0300 Subject: [PATCH 03/47] implement immutable acrylic and avalonia properties. --- .../Media/ExperimentalAcrylicBrush.cs | 64 ++++++++++++++-- .../Media/IExperimentalAcrylicBrush.cs | 10 +-- .../ImmutableExperimentalAcrylicBrush.cs | 73 +++++++++++++++++++ 3 files changed, 135 insertions(+), 12 deletions(-) create mode 100644 src/Avalonia.Visuals/Media/ImmutableExperimentalAcrylicBrush.cs diff --git a/src/Avalonia.Visuals/Media/ExperimentalAcrylicBrush.cs b/src/Avalonia.Visuals/Media/ExperimentalAcrylicBrush.cs index 2ab2701c9d..3c093b1919 100644 --- a/src/Avalonia.Visuals/Media/ExperimentalAcrylicBrush.cs +++ b/src/Avalonia.Visuals/Media/ExperimentalAcrylicBrush.cs @@ -1,17 +1,67 @@ namespace Avalonia.Media { - public class ExperimentalAcrylicBrush : IExperimentalAcrylicBrush + public class ExperimentalAcrylicBrush : Brush, IExperimentalAcrylicBrush { - public AcrylicBackgroundSource BackgroundSource { get; set; } + static ExperimentalAcrylicBrush() + { + AffectsRender( + TintColorProperty, + BackgroundSourceProperty, + TintOpacityProperty, + TintLuminosityOpacityProperty); + } - public Color TintColor { get; set; } + /// + /// Defines the property. + /// + public static readonly StyledProperty TintColorProperty = + AvaloniaProperty.Register(nameof(TintColor)); - public double TintOpacity { get; set; } + public static readonly StyledProperty BackgroundSourceProperty = + AvaloniaProperty.Register(nameof(BackgroundSource)); - public Color FallbackColor { get; set; } + public static readonly StyledProperty TintOpacityProperty = + AvaloniaProperty.Register(nameof(TintOpacity)); - public double TintLuminosityOpacity { get; set; } + public static readonly StyledProperty TintLuminosityOpacityProperty = + AvaloniaProperty.Register(nameof(TintLuminosityOpacity)); - public double Opacity { get; set; } + public static readonly StyledProperty FallbackColorProperty = + AvaloniaProperty.Register(nameof(FallbackColor)); + + public AcrylicBackgroundSource BackgroundSource + { + get => GetValue(BackgroundSourceProperty); + set => SetValue(BackgroundSourceProperty, value); + } + + public Color TintColor + { + get => GetValue(TintColorProperty); + set => SetValue(TintColorProperty, value); + } + + public double TintOpacity + { + get => GetValue(TintOpacityProperty); + set => SetValue(TintOpacityProperty, value); + } + + public Color FallbackColor + { + get => GetValue(FallbackColorProperty); + set => SetValue(FallbackColorProperty, value); + } + + public double TintLuminosityOpacity + { + get => GetValue(TintLuminosityOpacityProperty); + set => SetValue(TintLuminosityOpacityProperty, value); + } + + public override IBrush ToImmutable() + { + return new ImmutableExperimentalAcrylicBrush(this); + } } } diff --git a/src/Avalonia.Visuals/Media/IExperimentalAcrylicBrush.cs b/src/Avalonia.Visuals/Media/IExperimentalAcrylicBrush.cs index ae202b58ae..adc9335eab 100644 --- a/src/Avalonia.Visuals/Media/IExperimentalAcrylicBrush.cs +++ b/src/Avalonia.Visuals/Media/IExperimentalAcrylicBrush.cs @@ -2,14 +2,14 @@ { public interface IExperimentalAcrylicBrush : IBrush { - AcrylicBackgroundSource BackgroundSource { get; set; } + AcrylicBackgroundSource BackgroundSource { get; } - Color TintColor { get; set; } + Color TintColor { get; } - double TintOpacity { get; set; } + double TintOpacity { get; } - double TintLuminosityOpacity { get; set; } + double TintLuminosityOpacity { get; } - Color FallbackColor { get; set; } + Color FallbackColor { get; } } } diff --git a/src/Avalonia.Visuals/Media/ImmutableExperimentalAcrylicBrush.cs b/src/Avalonia.Visuals/Media/ImmutableExperimentalAcrylicBrush.cs new file mode 100644 index 0000000000..ad91b5fa42 --- /dev/null +++ b/src/Avalonia.Visuals/Media/ImmutableExperimentalAcrylicBrush.cs @@ -0,0 +1,73 @@ +using System; + +namespace Avalonia.Media +{ + public readonly struct ImmutableExperimentalAcrylicBrush : IExperimentalAcrylicBrush, IEquatable + { + public ImmutableExperimentalAcrylicBrush(IExperimentalAcrylicBrush brush) + { + BackgroundSource = brush.BackgroundSource; + TintColor = brush.TintColor; + TintOpacity = brush.TintOpacity; + TintLuminosityOpacity = brush.TintLuminosityOpacity; + FallbackColor = brush.FallbackColor; + Opacity = brush.Opacity; + } + + public AcrylicBackgroundSource BackgroundSource { get; } + + public Color TintColor { get; } + + public double TintOpacity { get; } + + public double TintLuminosityOpacity { get; } + + public Color FallbackColor { get; } + + public double Opacity { get; } + + public bool Equals(ImmutableExperimentalAcrylicBrush other) + { + // ReSharper disable once CompareOfFloatsByEqualityOperator + return + TintColor == other.TintColor && + Opacity == other.Opacity && + TintOpacity == other.TintOpacity && + BackgroundSource == other.BackgroundSource && + TintLuminosityOpacity == other.TintLuminosityOpacity && + FallbackColor == other.FallbackColor; + } + + public override bool Equals(object obj) + { + return obj is ImmutableExperimentalAcrylicBrush other && Equals(other); + } + + public override int GetHashCode() + { + unchecked + { + int hash = 17; + + hash = (hash * 23) + TintColor.GetHashCode(); + hash = (hash * 23) + TintLuminosityOpacity.GetHashCode(); + hash = (hash * 23) + Opacity.GetHashCode(); + hash = (hash * 23) + TintOpacity.GetHashCode(); + hash = (hash * 23) + BackgroundSource.GetHashCode(); + hash = (hash * 23) + FallbackColor.GetHashCode(); + + return hash; + } + } + + public static bool operator ==(ImmutableExperimentalAcrylicBrush left, ImmutableExperimentalAcrylicBrush right) + { + return left.Equals(right); + } + + public static bool operator !=(ImmutableExperimentalAcrylicBrush left, ImmutableExperimentalAcrylicBrush right) + { + return !left.Equals(right); + } + } +} From f329918116cde7c191e22386b0577e95072dd1ff Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sat, 30 May 2020 16:14:09 -0300 Subject: [PATCH 04/47] AcrylicPage. --- samples/ControlCatalog/MainView.xaml | 1 + samples/ControlCatalog/MainWindow.xaml | 1 + samples/ControlCatalog/Pages/AcrylicPage.xaml | 64 +++++++++++++++++++ .../ControlCatalog/Pages/AcrylicPage.xaml.cs | 19 ++++++ 4 files changed, 85 insertions(+) create mode 100644 samples/ControlCatalog/Pages/AcrylicPage.xaml create mode 100644 samples/ControlCatalog/Pages/AcrylicPage.xaml.cs diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml index add5dbde84..30c1586ce3 100644 --- a/samples/ControlCatalog/MainView.xaml +++ b/samples/ControlCatalog/MainView.xaml @@ -14,6 +14,7 @@ + diff --git a/samples/ControlCatalog/MainWindow.xaml b/samples/ControlCatalog/MainWindow.xaml index a0bb956425..1ff23d1233 100644 --- a/samples/ControlCatalog/MainWindow.xaml +++ b/samples/ControlCatalog/MainWindow.xaml @@ -7,6 +7,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:vm="clr-namespace:ControlCatalog.ViewModels" xmlns:v="clr-namespace:ControlCatalog.Views" + TransparencyLevelHint="AcrylicBlur" x:Class="ControlCatalog.MainWindow" WindowState="{Binding WindowState, Mode=TwoWay}" Background="Transparent"> diff --git a/samples/ControlCatalog/Pages/AcrylicPage.xaml b/samples/ControlCatalog/Pages/AcrylicPage.xaml new file mode 100644 index 0000000000..6864551ef7 --- /dev/null +++ b/samples/ControlCatalog/Pages/AcrylicPage.xaml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/ControlCatalog/Pages/AcrylicPage.xaml.cs b/samples/ControlCatalog/Pages/AcrylicPage.xaml.cs new file mode 100644 index 0000000000..44e7c4b92b --- /dev/null +++ b/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); + } + } +} From 831164b865d24d16858863cdcec8362814e2e531 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sat, 30 May 2020 16:18:52 -0300 Subject: [PATCH 05/47] control catalog solid background. --- samples/ControlCatalog/MainView.xaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml index 30c1586ce3..923f5ea456 100644 --- a/samples/ControlCatalog/MainView.xaml +++ b/samples/ControlCatalog/MainView.xaml @@ -61,7 +61,7 @@ - + No Decorations Border Only From f33daa20bf38ab717763bd438ace09888be179c8 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sat, 30 May 2020 16:40:58 -0300 Subject: [PATCH 06/47] acrylic painting takes into account alpha channel and opacity. --- src/Skia/Avalonia.Skia/DrawingContextImpl.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index 307f01ad1a..5003e40fc6 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -711,16 +711,16 @@ namespace Avalonia.Skia acrylicBrush.BackgroundSource == AcrylicBackgroundSource.Digger ? acrylicBrush.TintOpacity : 1; - var noiseOpcity = 0.12; + var noiseOpcity = 0.9; var excl = new SKColor(255, 255, 255, (byte)(255 * acrylicBrush.TintLuminosityOpacity)); - var tint = new SKColor(acrylicBrush.TintColor.R, acrylicBrush.TintColor.G, acrylicBrush.TintColor.B, (byte)(255 * tintOpacity)); + var tint = new SKColor(acrylicBrush.TintColor.R, acrylicBrush.TintColor.G, acrylicBrush.TintColor.B, (byte)(255 * (tintOpacity * acrylicBrush.Opacity * acrylicBrush.TintColor.A))); tint = SimpleColorBurn(excl, tint); var tintShader = SKShader.CreateColor(tint); var noiseShader = - SKShader.CreatePerlinNoiseTurbulence(12.876f, 12.876f, 2, 0.76829314f) + SKShader.CreatePerlinNoiseTurbulence(15.876f, 15.876f, 2, 0.76829314f) .WithColorFilter(CreateAlphaColorFilter(noiseOpcity)); var compose = SKShader.CreateCompose(tintShader, noiseShader); From 1021b0cb017d728006b26e43fd50085c2eb3deb8 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sat, 30 May 2020 16:52:49 -0300 Subject: [PATCH 07/47] correct opacity calculations. --- src/Skia/Avalonia.Skia/DrawingContextImpl.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index 5003e40fc6..b43e20e390 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -711,10 +711,10 @@ namespace Avalonia.Skia acrylicBrush.BackgroundSource == AcrylicBackgroundSource.Digger ? acrylicBrush.TintOpacity : 1; - var noiseOpcity = 0.9; + var noiseOpcity = 0.09 * brush.Opacity; var excl = new SKColor(255, 255, 255, (byte)(255 * acrylicBrush.TintLuminosityOpacity)); - var tint = new SKColor(acrylicBrush.TintColor.R, acrylicBrush.TintColor.G, acrylicBrush.TintColor.B, (byte)(255 * (tintOpacity * acrylicBrush.Opacity * acrylicBrush.TintColor.A))); + var tint = new SKColor(acrylicBrush.TintColor.R, acrylicBrush.TintColor.G, acrylicBrush.TintColor.B, (byte)(255 * (tintOpacity * acrylicBrush.Opacity * (acrylicBrush.TintColor.A / 255.0)))); tint = SimpleColorBurn(excl, tint); From 96f5ccb7f5b1344492e20ccd5a8e5560ffccf76e Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sat, 30 May 2020 18:09:16 -0300 Subject: [PATCH 08/47] implementation of limiting opacity. --- samples/ControlCatalog/Pages/AcrylicPage.xaml | 34 ++- .../Media/ExperimentalAcrylicBrush.cs | 207 +++++++++++++++++- .../Media/IExperimentalAcrylicBrush.cs | 12 +- .../ImmutableExperimentalAcrylicBrush.cs | 17 +- src/Skia/Avalonia.Skia/DrawingContextImpl.cs | 6 +- 5 files changed, 259 insertions(+), 17 deletions(-) diff --git a/samples/ControlCatalog/Pages/AcrylicPage.xaml b/samples/ControlCatalog/Pages/AcrylicPage.xaml index 6864551ef7..ce10956fca 100644 --- a/samples/ControlCatalog/Pages/AcrylicPage.xaml +++ b/samples/ControlCatalog/Pages/AcrylicPage.xaml @@ -6,21 +6,21 @@ x:Class="ControlCatalog.Pages.AcrylicPage"> - - + + @@ -30,7 +30,17 @@ + + + + + + @@ -42,7 +52,17 @@ + + + + + + @@ -52,7 +72,7 @@ diff --git a/src/Avalonia.Visuals/Media/ExperimentalAcrylicBrush.cs b/src/Avalonia.Visuals/Media/ExperimentalAcrylicBrush.cs index 3c093b1919..2250e175b1 100644 --- a/src/Avalonia.Visuals/Media/ExperimentalAcrylicBrush.cs +++ b/src/Avalonia.Visuals/Media/ExperimentalAcrylicBrush.cs @@ -1,4 +1,6 @@ -namespace Avalonia.Media +using System; + +namespace Avalonia.Media { public class ExperimentalAcrylicBrush : Brush, IExperimentalAcrylicBrush { @@ -10,7 +12,7 @@ TintOpacityProperty, TintLuminosityOpacityProperty); } - + /// /// Defines the property. /// @@ -53,7 +55,7 @@ set => SetValue(FallbackColorProperty, value); } - public double TintLuminosityOpacity + public double? TintLuminosityOpacity { get => GetValue(TintLuminosityOpacityProperty); set => SetValue(TintLuminosityOpacityProperty, value); @@ -63,5 +65,204 @@ { return new ImmutableExperimentalAcrylicBrush(this); } + + public struct HsvColor + { + public float Hue { get; set; } + public float Saturation { get; set; } + public float Value { get; set; } + } + + public static Color FromHsv(HsvColor color) + { + var i = (float)Math.Floor(color.Hue * 6f); + var f = color.Hue * 6f - i; + var p = color.Value * (1f - color.Saturation); + var q = color.Value * (1f - f * color.Saturation); + var t = color.Value * (1f - (1f - f) * color.Saturation); + + switch (i % 6) + { + case 0: + return new Color(255, (byte)(255.0 * color.Value), (byte)(255.0 * t), (byte)(255.0 * p)); + case 1: + return new Color(255, (byte)(255.0 * q), (byte)(255.0 * color.Value), (byte)(255.0 * p)); + case 2: + return new Color(255, (byte)(255.0 * p), (byte)(255.0 * color.Value), (byte)(255.0 * t)); + case 3: + return new Color(255, (byte)(255.0 * p), (byte)(255.0 * q), (byte)(255.0 * color.Value)); + case 4: + return new Color(255, (byte)(255.0 * t), (byte)(255.0 * p), (byte)(255.0 * color.Value)); + default: + case 5: + return new Color(255, (byte)(255.0 * color.Value), (byte)(255.0 * p), (byte)(255.0 * q)); + } + } + + public 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 = s = v = max; + + //v = (0.299f * r + 0.587f * g + 0.114f * b); + + 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 }; + } + + public Color GetLuminosityColor () + { + return GetLuminosityColor(TintColor, TintLuminosityOpacity); + } + + Color GetLuminosityColor(Color tintColor, double? luminosityOpacity) + { + var rgbTintColor = tintColor; + + // If luminosity opacity is specified, just use the values as is + if (luminosityOpacity.HasValue) + { + return new Color((byte)(255.0 * Math.Min(1.0, Math.Max(0.0, luminosityOpacity.Value))), tintColor.R, tintColor.G, tintColor.B); + } + else + { + // To create the Luminosity blend input color without luminosity opacity, + // we're taking the TintColor input, converting to HSV, and clamping the V between these values + const double minHsvV = 0.125; + const double maxHsvV = 0.965; + + var hsvTintColor = RgbToHsv(rgbTintColor); + + var clampedHsvV = Math.Max(Math.Min(hsvTintColor.Value, minHsvV), maxHsvV); + var hsvLuminosityColor = hsvTintColor; + hsvLuminosityColor.Value = (float)clampedHsvV; + + var rgbLuminosityColor = FromHsv(hsvLuminosityColor); + + // Now figure out luminosity opacity + // Map original *tint* opacity to this range + const double minLuminosityOpacity = 0.15; + const double maxLuminosityOpacity = 1.03; + + double luminosityOpacityRangeMax = maxLuminosityOpacity - minLuminosityOpacity; + double mappedTintOpacity = ((tintColor.A / 255.0) * luminosityOpacityRangeMax) + minLuminosityOpacity; + + // Finally, combine the luminosity opacity and the HsvV-clamped tint color + return new Color((byte)(255.0 * Math.Min(mappedTintOpacity, 1.0)), rgbLuminosityColor.R, rgbLuminosityColor.G, rgbLuminosityColor.B); + } + + } + + + public Color GetEffectiveTintColor() + { + var tintColor = TintColor; + double tintOpacity = TintOpacity; + + // Update tintColor's alpha with the combined opacity value + // If LuminosityOpacity was specified, we don't intervene into users parameters + if (false)//TintLuminosityOpacity() != nullptr) + { + //tintColor.A = static_cast(round(tintColor.A * tintOpacity)); + } + else + { + double tintOpacityModifier = GetTintOpacityModifier(tintColor); + + tintColor = new Color((byte)(Math.Round(tintColor.A * tintOpacity * tintOpacityModifier)), tintColor.R, tintColor.G, tintColor.B); + } + + return tintColor; + } + + 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.50; // Mid point of HsvV range that these calculations are based on. This is here for easy tuning. + + const double whiteMaxOpacity = 0.45; // 100% luminosity + const double midPointMaxOpacity = 0.90; // 50% luminosity + const double blackMaxOpacity = 0.85; // 0% luminosity + + var hsv = RgbToHsv(tintColor); + + if(tintColor == Colors.Red) + { + + } + + 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; + } } } diff --git a/src/Avalonia.Visuals/Media/IExperimentalAcrylicBrush.cs b/src/Avalonia.Visuals/Media/IExperimentalAcrylicBrush.cs index adc9335eab..af7bf02e15 100644 --- a/src/Avalonia.Visuals/Media/IExperimentalAcrylicBrush.cs +++ b/src/Avalonia.Visuals/Media/IExperimentalAcrylicBrush.cs @@ -1,15 +1,21 @@ -namespace Avalonia.Media +using System.Drawing; + +namespace Avalonia.Media { public interface IExperimentalAcrylicBrush : IBrush { AcrylicBackgroundSource BackgroundSource { get; } - Color TintColor { get; } + Color TintColor { get; } double TintOpacity { get; } - double TintLuminosityOpacity { get; } + double? TintLuminosityOpacity { get; } Color FallbackColor { get; } + + Color GetEffectiveTintColor(); + + Color GetLuminosityColor(); } } diff --git a/src/Avalonia.Visuals/Media/ImmutableExperimentalAcrylicBrush.cs b/src/Avalonia.Visuals/Media/ImmutableExperimentalAcrylicBrush.cs index ad91b5fa42..67a94c4fb3 100644 --- a/src/Avalonia.Visuals/Media/ImmutableExperimentalAcrylicBrush.cs +++ b/src/Avalonia.Visuals/Media/ImmutableExperimentalAcrylicBrush.cs @@ -4,10 +4,13 @@ namespace Avalonia.Media { public readonly struct ImmutableExperimentalAcrylicBrush : IExperimentalAcrylicBrush, IEquatable { + private readonly Color luminosityColor; + public ImmutableExperimentalAcrylicBrush(IExperimentalAcrylicBrush brush) { + luminosityColor = brush.GetLuminosityColor(); BackgroundSource = brush.BackgroundSource; - TintColor = brush.TintColor; + TintColor = brush.GetEffectiveTintColor(); TintOpacity = brush.TintOpacity; TintLuminosityOpacity = brush.TintLuminosityOpacity; FallbackColor = brush.FallbackColor; @@ -20,7 +23,7 @@ namespace Avalonia.Media public double TintOpacity { get; } - public double TintLuminosityOpacity { get; } + public double? TintLuminosityOpacity { get; } public Color FallbackColor { get; } @@ -43,6 +46,11 @@ namespace Avalonia.Media return obj is ImmutableExperimentalAcrylicBrush other && Equals(other); } + public Color GetEffectiveTintColor() + { + return TintColor; + } + public override int GetHashCode() { unchecked @@ -60,6 +68,11 @@ namespace Avalonia.Media } } + public Color GetLuminosityColor() + { + return luminosityColor; + } + public static bool operator ==(ImmutableExperimentalAcrylicBrush left, ImmutableExperimentalAcrylicBrush right) { return left.Equals(right); diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index b43e20e390..ec3c254a4d 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -713,8 +713,10 @@ namespace Avalonia.Skia var noiseOpcity = 0.09 * brush.Opacity; - var excl = new SKColor(255, 255, 255, (byte)(255 * acrylicBrush.TintLuminosityOpacity)); - var tint = new SKColor(acrylicBrush.TintColor.R, acrylicBrush.TintColor.G, acrylicBrush.TintColor.B, (byte)(255 * (tintOpacity * acrylicBrush.Opacity * (acrylicBrush.TintColor.A / 255.0)))); + var tintColor = acrylicBrush.GetEffectiveTintColor(); + var luminosityColor = acrylicBrush.GetLuminosityColor(); + var excl = new SKColor(luminosityColor.R, luminosityColor.G, luminosityColor.B, (byte)(255* (luminosityColor.A /255.0) * 0.1)); + var tint = new SKColor(tintColor.R, tintColor.G, tintColor.B, (byte)(255 * ((tintColor.A /255.0) * acrylicBrush.Opacity))); tint = SimpleColorBurn(excl, tint); From 1ec1fafe9119205c4976784360661b4ff5ace844 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sat, 30 May 2020 21:56:06 -0300 Subject: [PATCH 09/47] simplyfy acrylic brush based on avalonias capabilities. --- samples/ControlCatalog/Pages/AcrylicPage.xaml | 38 ++++++-- .../Media/ExperimentalAcrylicBrush.cs | 92 ++----------------- .../Media/IExperimentalAcrylicBrush.cs | 4 - .../ImmutableExperimentalAcrylicBrush.cs | 15 +-- src/Skia/Avalonia.Skia/DrawingContextImpl.cs | 3 +- 5 files changed, 40 insertions(+), 112 deletions(-) diff --git a/samples/ControlCatalog/Pages/AcrylicPage.xaml b/samples/ControlCatalog/Pages/AcrylicPage.xaml index ce10956fca..6155d6d68d 100644 --- a/samples/ControlCatalog/Pages/AcrylicPage.xaml +++ b/samples/ControlCatalog/Pages/AcrylicPage.xaml @@ -8,13 +8,11 @@ - @@ -32,7 +30,6 @@ @@ -42,7 +39,6 @@ @@ -54,7 +50,6 @@ @@ -64,7 +59,6 @@ @@ -72,9 +66,37 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/src/Avalonia.Visuals/Media/ExperimentalAcrylicBrush.cs b/src/Avalonia.Visuals/Media/ExperimentalAcrylicBrush.cs index 2250e175b1..99c680ce8b 100644 --- a/src/Avalonia.Visuals/Media/ExperimentalAcrylicBrush.cs +++ b/src/Avalonia.Visuals/Media/ExperimentalAcrylicBrush.cs @@ -73,32 +73,6 @@ namespace Avalonia.Media public float Value { get; set; } } - public static Color FromHsv(HsvColor color) - { - var i = (float)Math.Floor(color.Hue * 6f); - var f = color.Hue * 6f - i; - var p = color.Value * (1f - color.Saturation); - var q = color.Value * (1f - f * color.Saturation); - var t = color.Value * (1f - (1f - f) * color.Saturation); - - switch (i % 6) - { - case 0: - return new Color(255, (byte)(255.0 * color.Value), (byte)(255.0 * t), (byte)(255.0 * p)); - case 1: - return new Color(255, (byte)(255.0 * q), (byte)(255.0 * color.Value), (byte)(255.0 * p)); - case 2: - return new Color(255, (byte)(255.0 * p), (byte)(255.0 * color.Value), (byte)(255.0 * t)); - case 3: - return new Color(255, (byte)(255.0 * p), (byte)(255.0 * q), (byte)(255.0 * color.Value)); - case 4: - return new Color(255, (byte)(255.0 * t), (byte)(255.0 * p), (byte)(255.0 * color.Value)); - default: - case 5: - return new Color(255, (byte)(255.0 * color.Value), (byte)(255.0 * p), (byte)(255.0 * q)); - } - } - public static HsvColor RgbToHsv(Color color) { var r = color.R /255.0f; @@ -140,65 +114,20 @@ namespace Avalonia.Media return new HsvColor { Hue = h, Saturation = s, Value = v }; } - public Color GetLuminosityColor () - { - return GetLuminosityColor(TintColor, TintLuminosityOpacity); - } - - Color GetLuminosityColor(Color tintColor, double? luminosityOpacity) - { - var rgbTintColor = tintColor; - - // If luminosity opacity is specified, just use the values as is - if (luminosityOpacity.HasValue) - { - return new Color((byte)(255.0 * Math.Min(1.0, Math.Max(0.0, luminosityOpacity.Value))), tintColor.R, tintColor.G, tintColor.B); - } - else - { - // To create the Luminosity blend input color without luminosity opacity, - // we're taking the TintColor input, converting to HSV, and clamping the V between these values - const double minHsvV = 0.125; - const double maxHsvV = 0.965; - - var hsvTintColor = RgbToHsv(rgbTintColor); - - var clampedHsvV = Math.Max(Math.Min(hsvTintColor.Value, minHsvV), maxHsvV); - var hsvLuminosityColor = hsvTintColor; - hsvLuminosityColor.Value = (float)clampedHsvV; - - var rgbLuminosityColor = FromHsv(hsvLuminosityColor); - - // Now figure out luminosity opacity - // Map original *tint* opacity to this range - const double minLuminosityOpacity = 0.15; - const double maxLuminosityOpacity = 1.03; - - double luminosityOpacityRangeMax = maxLuminosityOpacity - minLuminosityOpacity; - double mappedTintOpacity = ((tintColor.A / 255.0) * luminosityOpacityRangeMax) + minLuminosityOpacity; - - // Finally, combine the luminosity opacity and the HsvV-clamped tint color - return new Color((byte)(255.0 * Math.Min(mappedTintOpacity, 1.0)), rgbLuminosityColor.R, rgbLuminosityColor.G, rgbLuminosityColor.B); - } - - } - - public Color GetEffectiveTintColor() { var tintColor = TintColor; double tintOpacity = TintOpacity; // Update tintColor's alpha with the combined opacity value - // If LuminosityOpacity was specified, we don't intervene into users parameters - if (false)//TintLuminosityOpacity() != nullptr) + double tintOpacityModifier = GetTintOpacityModifier(tintColor); + + if (false) // non-acrylic blue // TODO detect blur level. { - //tintColor.A = static_cast(round(tintColor.A * tintOpacity)); + tintColor = new Color((byte)(Math.Round(tintColor.A * (((tintOpacity * tintOpacityModifier) * 0.25) + 0.75))), tintColor.R, tintColor.G, tintColor.B); } else { - double tintOpacityModifier = GetTintOpacityModifier(tintColor); - tintColor = new Color((byte)(Math.Round(tintColor.A * tintOpacity * tintOpacityModifier)), tintColor.R, tintColor.G, tintColor.B); } @@ -213,19 +142,14 @@ namespace Avalonia.Media // 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.50; // Mid point of HsvV range that these calculations are based on. This is here for easy tuning. + 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.45; // 100% luminosity - const double midPointMaxOpacity = 0.90; // 50% luminosity - const double blackMaxOpacity = 0.85; // 0% luminosity + const double whiteMaxOpacity = 0.40; // 100% luminosity + const double midPointMaxOpacity = 0.50; // 50% luminosity + const double blackMaxOpacity = 0.84; // 0% luminosity var hsv = RgbToHsv(tintColor); - if(tintColor == Colors.Red) - { - - } - double opacityModifier = midPointMaxOpacity; if (hsv.Value != midPoint) diff --git a/src/Avalonia.Visuals/Media/IExperimentalAcrylicBrush.cs b/src/Avalonia.Visuals/Media/IExperimentalAcrylicBrush.cs index af7bf02e15..3e66d5c863 100644 --- a/src/Avalonia.Visuals/Media/IExperimentalAcrylicBrush.cs +++ b/src/Avalonia.Visuals/Media/IExperimentalAcrylicBrush.cs @@ -10,12 +10,8 @@ namespace Avalonia.Media double TintOpacity { get; } - double? TintLuminosityOpacity { get; } - Color FallbackColor { get; } Color GetEffectiveTintColor(); - - Color GetLuminosityColor(); } } diff --git a/src/Avalonia.Visuals/Media/ImmutableExperimentalAcrylicBrush.cs b/src/Avalonia.Visuals/Media/ImmutableExperimentalAcrylicBrush.cs index 67a94c4fb3..6765ecae79 100644 --- a/src/Avalonia.Visuals/Media/ImmutableExperimentalAcrylicBrush.cs +++ b/src/Avalonia.Visuals/Media/ImmutableExperimentalAcrylicBrush.cs @@ -4,15 +4,11 @@ namespace Avalonia.Media { public readonly struct ImmutableExperimentalAcrylicBrush : IExperimentalAcrylicBrush, IEquatable { - private readonly Color luminosityColor; - public ImmutableExperimentalAcrylicBrush(IExperimentalAcrylicBrush brush) - { - luminosityColor = brush.GetLuminosityColor(); + { BackgroundSource = brush.BackgroundSource; TintColor = brush.GetEffectiveTintColor(); TintOpacity = brush.TintOpacity; - TintLuminosityOpacity = brush.TintLuminosityOpacity; FallbackColor = brush.FallbackColor; Opacity = brush.Opacity; } @@ -23,8 +19,6 @@ namespace Avalonia.Media public double TintOpacity { get; } - public double? TintLuminosityOpacity { get; } - public Color FallbackColor { get; } public double Opacity { get; } @@ -37,7 +31,6 @@ namespace Avalonia.Media Opacity == other.Opacity && TintOpacity == other.TintOpacity && BackgroundSource == other.BackgroundSource && - TintLuminosityOpacity == other.TintLuminosityOpacity && FallbackColor == other.FallbackColor; } @@ -58,7 +51,6 @@ namespace Avalonia.Media int hash = 17; hash = (hash * 23) + TintColor.GetHashCode(); - hash = (hash * 23) + TintLuminosityOpacity.GetHashCode(); hash = (hash * 23) + Opacity.GetHashCode(); hash = (hash * 23) + TintOpacity.GetHashCode(); hash = (hash * 23) + BackgroundSource.GetHashCode(); @@ -68,11 +60,6 @@ namespace Avalonia.Media } } - public Color GetLuminosityColor() - { - return luminosityColor; - } - public static bool operator ==(ImmutableExperimentalAcrylicBrush left, ImmutableExperimentalAcrylicBrush right) { return left.Equals(right); diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index ec3c254a4d..2272b28273 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -714,8 +714,7 @@ namespace Avalonia.Skia var noiseOpcity = 0.09 * brush.Opacity; var tintColor = acrylicBrush.GetEffectiveTintColor(); - var luminosityColor = acrylicBrush.GetLuminosityColor(); - var excl = new SKColor(luminosityColor.R, luminosityColor.G, luminosityColor.B, (byte)(255* (luminosityColor.A /255.0) * 0.1)); + var excl = new SKColor(255, 255, 255, (byte)(255 * 0.1)); var tint = new SKColor(tintColor.R, tintColor.G, tintColor.B, (byte)(255 * ((tintColor.A /255.0) * acrylicBrush.Opacity))); tint = SimpleColorBurn(excl, tint); From 25093194491423b3bb90d36f512f78311e98c9c7 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sat, 30 May 2020 22:00:06 -0300 Subject: [PATCH 10/47] black background on control catalog --- samples/ControlCatalog/MainView.xaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml index 923f5ea456..fde8191407 100644 --- a/samples/ControlCatalog/MainView.xaml +++ b/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}"> @@ -61,7 +61,7 @@ - + No Decorations Border Only From ff0e25c1d0e276f8cd0ccf78b6718273c3aa2c1a Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sat, 30 May 2020 22:35:31 -0300 Subject: [PATCH 11/47] tone noise down. --- src/Skia/Avalonia.Skia/DrawingContextImpl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index 2272b28273..2c47edbc48 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -711,7 +711,7 @@ namespace Avalonia.Skia acrylicBrush.BackgroundSource == AcrylicBackgroundSource.Digger ? acrylicBrush.TintOpacity : 1; - var noiseOpcity = 0.09 * brush.Opacity; + var noiseOpcity = 0.04 * brush.Opacity; var tintColor = acrylicBrush.GetEffectiveTintColor(); var excl = new SKColor(255, 255, 255, (byte)(255 * 0.1)); From d06e85abe3dee50f06b8a682454b08f3499189eb Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sat, 30 May 2020 23:32:28 -0300 Subject: [PATCH 12/47] dispose and cache stuff. --- .../Media/ExperimentalAcrylicBrush.cs | 21 ++++++++++--- .../Media/IExperimentalAcrylicBrush.cs | 2 -- .../ImmutableExperimentalAcrylicBrush.cs | 2 +- src/Skia/Avalonia.Skia/DrawingContextImpl.cs | 31 ++++++++++--------- 4 files changed, 33 insertions(+), 23 deletions(-) diff --git a/src/Avalonia.Visuals/Media/ExperimentalAcrylicBrush.cs b/src/Avalonia.Visuals/Media/ExperimentalAcrylicBrush.cs index 99c680ce8b..cda4c1c5a2 100644 --- a/src/Avalonia.Visuals/Media/ExperimentalAcrylicBrush.cs +++ b/src/Avalonia.Visuals/Media/ExperimentalAcrylicBrush.cs @@ -4,6 +4,8 @@ namespace Avalonia.Media { public class ExperimentalAcrylicBrush : Brush, IExperimentalAcrylicBrush { + private Color _effectiveTintColor; + static ExperimentalAcrylicBrush() { AffectsRender( @@ -11,6 +13,16 @@ namespace Avalonia.Media BackgroundSourceProperty, TintOpacityProperty, TintLuminosityOpacityProperty); + + TintColorProperty.Changed.AddClassHandler((b, e) => + { + b._effectiveTintColor = GetEffectiveTintColor(b.TintColor, b.TintOpacity); + }); + + TintOpacityProperty.Changed.AddClassHandler((b, e) => + { + b._effectiveTintColor = GetEffectiveTintColor(b.TintColor, b.TintOpacity); + }); } /// @@ -43,6 +55,8 @@ namespace Avalonia.Media set => SetValue(TintColorProperty, value); } + Color IExperimentalAcrylicBrush.TintColor => _effectiveTintColor; + public double TintOpacity { get => GetValue(TintOpacityProperty); @@ -114,11 +128,8 @@ namespace Avalonia.Media return new HsvColor { Hue = h, Saturation = s, Value = v }; } - public Color GetEffectiveTintColor() + private static Color GetEffectiveTintColor(Color tintColor, double tintOpacity) { - var tintColor = TintColor; - double tintOpacity = TintOpacity; - // Update tintColor's alpha with the combined opacity value double tintOpacityModifier = GetTintOpacityModifier(tintColor); @@ -134,7 +145,7 @@ namespace Avalonia.Media return tintColor; } - double GetTintOpacityModifier(Color tintColor) + 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 diff --git a/src/Avalonia.Visuals/Media/IExperimentalAcrylicBrush.cs b/src/Avalonia.Visuals/Media/IExperimentalAcrylicBrush.cs index 3e66d5c863..fdcf6148fe 100644 --- a/src/Avalonia.Visuals/Media/IExperimentalAcrylicBrush.cs +++ b/src/Avalonia.Visuals/Media/IExperimentalAcrylicBrush.cs @@ -11,7 +11,5 @@ namespace Avalonia.Media double TintOpacity { get; } Color FallbackColor { get; } - - Color GetEffectiveTintColor(); } } diff --git a/src/Avalonia.Visuals/Media/ImmutableExperimentalAcrylicBrush.cs b/src/Avalonia.Visuals/Media/ImmutableExperimentalAcrylicBrush.cs index 6765ecae79..ed3f532035 100644 --- a/src/Avalonia.Visuals/Media/ImmutableExperimentalAcrylicBrush.cs +++ b/src/Avalonia.Visuals/Media/ImmutableExperimentalAcrylicBrush.cs @@ -7,7 +7,7 @@ namespace Avalonia.Media public ImmutableExperimentalAcrylicBrush(IExperimentalAcrylicBrush brush) { BackgroundSource = brush.BackgroundSource; - TintColor = brush.GetEffectiveTintColor(); + TintColor = brush.TintColor; TintOpacity = brush.TintOpacity; FallbackColor = brush.FallbackColor; Opacity = brush.Opacity; diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index 2c47edbc48..039213aecc 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -705,34 +705,35 @@ namespace Avalonia.Skia return paintWrapper; } - if(brush is IExperimentalAcrylicBrush acrylicBrush) + if (brush is IExperimentalAcrylicBrush acrylicBrush) { - var tintOpacity = - acrylicBrush.BackgroundSource == AcrylicBackgroundSource.Digger ? + var tintOpacity = + acrylicBrush.BackgroundSource == AcrylicBackgroundSource.Digger ? acrylicBrush.TintOpacity : 1; var noiseOpcity = 0.04 * brush.Opacity; - var tintColor = acrylicBrush.GetEffectiveTintColor(); + var tintColor = acrylicBrush.TintColor; var excl = new SKColor(255, 255, 255, (byte)(255 * 0.1)); - var tint = new SKColor(tintColor.R, tintColor.G, tintColor.B, (byte)(255 * ((tintColor.A /255.0) * acrylicBrush.Opacity))); + var tint = new SKColor(tintColor.R, tintColor.G, tintColor.B, (byte)(255 * ((tintColor.A / 255.0) * acrylicBrush.Opacity))); tint = SimpleColorBurn(excl, tint); - var tintShader = SKShader.CreateColor(tint); - var noiseShader = + using (var tintShader = SKShader.CreateColor(tint)) + using (var noiseShader = SKShader.CreatePerlinNoiseTurbulence(15.876f, 15.876f, 2, 0.76829314f) - .WithColorFilter(CreateAlphaColorFilter(noiseOpcity)); + .WithColorFilter(CreateAlphaColorFilter(noiseOpcity))) + using (var compose = SKShader.CreateCompose(tintShader, noiseShader)) + { + paint.Shader = compose; - var compose = SKShader.CreateCompose(tintShader, noiseShader); - paint.Shader = compose; + if (acrylicBrush.BackgroundSource == AcrylicBackgroundSource.Digger) + { + paint.BlendMode = SKBlendMode.Src; + } - if (acrylicBrush.BackgroundSource == AcrylicBackgroundSource.Digger) - { - paint.BlendMode = SKBlendMode.Src; + return paintWrapper; } - - return paintWrapper; } paint.Color = new SKColor(255, 255, 255, (byte) (255 * opacity)); From 99c31c506f5507fc290259b47916a63a8ec5ad63 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sat, 30 May 2020 23:44:12 -0300 Subject: [PATCH 13/47] more noise. --- src/Skia/Avalonia.Skia/DrawingContextImpl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index 039213aecc..01eddd2aad 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -711,7 +711,7 @@ namespace Avalonia.Skia acrylicBrush.BackgroundSource == AcrylicBackgroundSource.Digger ? acrylicBrush.TintOpacity : 1; - var noiseOpcity = 0.04 * brush.Opacity; + var noiseOpcity = 0.06 * brush.Opacity; var tintColor = acrylicBrush.TintColor; var excl = new SKColor(255, 255, 255, (byte)(255 * 0.1)); From 91631271e7568693c9b83ceb47d0fdad83e1a13d Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sun, 31 May 2020 12:09:40 -0300 Subject: [PATCH 14/47] use invisible black for accent gradient on win32 acrylic blur. --- src/Windows/Avalonia.Win32/WindowImpl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 0cf5a73b9f..62c6fc742b 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -267,7 +267,7 @@ namespace Avalonia.Win32 } accent.AccentFlags = 2; - accent.GradientColor = 0x00FFFFFF; + accent.GradientColor = 0x01000000; var accentPtr = Marshal.AllocHGlobal(accentStructSize); Marshal.StructureToPtr(accent, accentPtr, false); From d2fa8e490544603581796ccb42f0f5dd830a8135 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sun, 31 May 2020 14:46:13 -0300 Subject: [PATCH 15/47] use embedded noise asset. --- .../Assets/NoiseAsset_256X256_PNG.png | Bin 0 -> 3241 bytes src/Skia/Avalonia.Skia/Avalonia.Skia.csproj | 3 ++ src/Skia/Avalonia.Skia/DrawingContextImpl.cs | 39 +++++++----------- 3 files changed, 18 insertions(+), 24 deletions(-) create mode 100644 src/Skia/Avalonia.Skia/Assets/NoiseAsset_256X256_PNG.png diff --git a/src/Skia/Avalonia.Skia/Assets/NoiseAsset_256X256_PNG.png b/src/Skia/Avalonia.Skia/Assets/NoiseAsset_256X256_PNG.png new file mode 100644 index 0000000000000000000000000000000000000000..41de173d9014b38f8290acf270c490b37fc79a60 GIT binary patch literal 3241 zcmV;a3|8}rP)QN3MgRZ*%*@Q0nVDv0W-~K00002< zk9uhU01PcjL_t(|+U#A4b|N_pEcy8VUmmMU29f{)!qIKbyj=nf-L`d=I=}Qch#V$^Ji@p%8Ow}aVg{OQfFawvx$7QXHNf;JlH z>=OGpehiMY6qvQ>r+31?Qu`coulkvHr8sf?eJ%Pq`{!L3HPrR)ZPuzD>pymc)ERd) zzk>rC1G3S8Q*}_1#B7CMSB{eOXyX??K=#C&ZSfa;COcp~#v&$=7`2lg#z$%Z{KSqq zKgR=5D%9d=VNhEG;T6Bn*jciRjl(~1ghpC5?^iE1h+v&EM5%$(W^~Kq7Js%C)S$8R z&&Vf{$L|gr4t5WQ47&JVk;>Q~QqTw0G|KqPrW>W$rJ#h6`dG*ziIh6YM-(w-d_ahz ziCLs8i6~hT(>ah5w;KCCXvRidq*{|KIJHcYAbB!zNskc|{4NutczYczbgqZ0iFCnW zsml~G<0~cJm8Lcg-XClfsxBV7RD|?NwHq6y+E5ESTM>I)D+Wc$hvpJtO?p~5Qg6~9 zF8L;*25*`-T_?5T2C1B>DSuAuR1~&MUa-f+18ELrq4EvMW)^~QU_#>sU6Lx3Bulc`iuXz5gSp3Y5ql*T!p+08KROn z54-e)hzb7ZOlVZE<|Csr2oqhdDSf2{^pipyR3pxkltWD5Xrhq_c-ch|MO=ug1cWpm zMxhs>VGW|2LkntvY%7y7lu$m>N(aX-J(NM=KYzXps?fCt@nx8THwa5*vx}3~4o$rY z*BxwSC8jhr15tEmW{a>O_$W=9?m*V!GNcejK^+%5b-~I6@?q_4h-amS(hB(R;QgIi z@7)vt#baDZC-o(z7T`pVIV}lw*6WnMs_#O1^^Y)E;=#H#PJDH9cD9E2mG~$N zOJnZDfpysHK41$2Dg5Q&H8Ok@V z_BIh+f=y1!p>PG0gIdFbEOK8D?rLZAEcmOuk3p?&%Q!F5>Z*^s~XRzn`uvpVHt>ZE@mpO;urk=<&lRTw~623`?ognS}u(o9b+f>U8&Q* zHFOIPUL7H^`w+1nC@$BhkhvD1FZq#*&biTE-Dzw~Bf~QmN{fp=)*-%inhk1JtrPqq z2ft2NUhO*alS*|4IZDh2zt8*dHjg7Lh;v25p(#0-{`m$}Q_aLdBgWYQ*((?!iM%(O zP1or$Q1aSNTyS~r;i)TBr#^)PG;Px{svgv6AycK!aD2E3VMS;#=(W)MbhrmeU=a%B zBxkAvXh$I*KkPeI%D~Is%3iiUOSGmV12c4V@Ca_9h!|Q`4+g;!iY;HoHy;~8S zTP&zCebpfC=*h{G7O58rCu7&qj3>;U@S@u_Ii7NznOW0+@>!;X6mcj>D36U%-?uVC+#}<{>nL%O`i}W9)Cw89T@#oNG=G+Jk zV^b@YAIMNeh9rndV+>dDZ*#W%5OgnEO7U|aE-_lFCRdtJy^a@Shyy?fq( z{aWznz$$i>+W#7GPQPE^jk{AenMj2fq~38(QEgh#w&@%UynFh&wJ1D3F#p)p9t-Sy zUFYvh+So}t-^F&0yG!qWGPoj1`QVEwqgee&&EIm@kUJ=@!pg~<->?1xvTc+5 zo^6Iz10A<&2y(3g{B*yNx_kzG=iI04&?am-o&V*u#7?L2Pn(S*bzyN%0~bttxu=i{ z5?Q>nT*OKhVjsy=!A=f@dYP1AmF;hj+ROj>0VTT#EIqgz{6XYBaaiF; znaq2f3oN}K=7uCABu*5kDr4ot6K-)akRlD_ii?7h{ID@t@^g1ll8XRp@~|-04R+n8 z#mI4_+GPPmma|1KPW94PkjTAftZLWTK4kSZVF@HYZj|v>tWgN#@nu6PCbykcZZj6N6P!OHuA zZdsDXdU;>>#TN1LOvMjxroc*W?IZOsu-|>(&xv6oRa)p7ctGa80!{>MGl%D^IZ*)bj$D|0E`ujBi-XnCoQ6P4EZ2k71eLyR@Ce=u=KIg zH!Ni!$DHqS1}swAzLUjISn}BrRw|YM$eJ`#true true + + + diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index 01eddd2aad..63b51dcccb 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -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; /// /// Context create info. @@ -652,21 +653,6 @@ namespace Avalonia.Skia } } - static SKColor SimpleColorBurn(SKColor bg, SKColor fg) - { - using (var bmp = new SKBitmap(1, 1)) - { - bmp.SetPixel(0, 0, bg); - using (var canvas = new SKCanvas(bmp)) - using (var paint = new SKPaint - { - Color = fg - }) - canvas.DrawRect(-1, -1, 3, 3, paint); - return bmp.GetPixel(0, 0); - } - } - static SKColorFilter CreateAlphaColorFilter(double opacity) { if (opacity > 1) @@ -711,19 +697,24 @@ namespace Avalonia.Skia acrylicBrush.BackgroundSource == AcrylicBackgroundSource.Digger ? acrylicBrush.TintOpacity : 1; - var noiseOpcity = 0.06 * brush.Opacity; + const double noiseOpcity = 0.06; - var tintColor = acrylicBrush.TintColor; - var excl = new SKColor(255, 255, 255, (byte)(255 * 0.1)); - var tint = new SKColor(tintColor.R, tintColor.G, tintColor.B, (byte)(255 * ((tintColor.A / 255.0) * acrylicBrush.Opacity))); + var tintColor = acrylicBrush.TintColor; + var tint = new SKColor(tintColor.R, tintColor.G, tintColor.B, (byte)(255 * ((tintColor.A / 255.0) * acrylicBrush.Opacity))); + + 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)); + } - tint = SimpleColorBurn(excl, tint); + } using (var tintShader = SKShader.CreateColor(tint)) - using (var noiseShader = - SKShader.CreatePerlinNoiseTurbulence(15.876f, 15.876f, 2, 0.76829314f) - .WithColorFilter(CreateAlphaColorFilter(noiseOpcity))) - using (var compose = SKShader.CreateCompose(tintShader, noiseShader)) + using (var compose = SKShader.CreateCompose(tintShader, s_acrylicNoiseShader)) { paint.Shader = compose; From 8eb987c99065cf740dc51b6e71cc5eaf9a096648 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sun, 31 May 2020 14:46:29 -0300 Subject: [PATCH 16/47] tweak dark opacity. --- src/Avalonia.Visuals/Media/ExperimentalAcrylicBrush.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Visuals/Media/ExperimentalAcrylicBrush.cs b/src/Avalonia.Visuals/Media/ExperimentalAcrylicBrush.cs index cda4c1c5a2..7712b99b27 100644 --- a/src/Avalonia.Visuals/Media/ExperimentalAcrylicBrush.cs +++ b/src/Avalonia.Visuals/Media/ExperimentalAcrylicBrush.cs @@ -157,7 +157,7 @@ namespace Avalonia.Media const double whiteMaxOpacity = 0.40; // 100% luminosity const double midPointMaxOpacity = 0.50; // 50% luminosity - const double blackMaxOpacity = 0.84; // 0% luminosity + const double blackMaxOpacity = 0.60; // 0% luminosity var hsv = RgbToHsv(tintColor); From 175eff6429f4fb51b2b512fd567f43b1a5cb6535 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 9 Jun 2020 17:00:37 -0300 Subject: [PATCH 17/47] Fix acrylic. --- samples/ControlCatalog/Pages/AcrylicPage.xaml | 10 ++-------- src/Skia/Avalonia.Skia/DrawingContextImpl.cs | 4 ++-- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/samples/ControlCatalog/Pages/AcrylicPage.xaml b/samples/ControlCatalog/Pages/AcrylicPage.xaml index 6155d6d68d..6dc23c4841 100644 --- a/samples/ControlCatalog/Pages/AcrylicPage.xaml +++ b/samples/ControlCatalog/Pages/AcrylicPage.xaml @@ -3,13 +3,7 @@ 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"> - - - + x:Class="ControlCatalog.Pages.AcrylicPage"> @@ -86,7 +80,7 @@ diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index 63b51dcccb..ad2a896508 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -661,7 +661,7 @@ namespace Avalonia.Skia var a = new byte[256]; for (var i = 0; i < 256; i++) { - c[i] = 255; + c[i] = (byte)i; a[i] = (byte)(i * opacity); } @@ -697,7 +697,7 @@ namespace Avalonia.Skia acrylicBrush.BackgroundSource == AcrylicBackgroundSource.Digger ? acrylicBrush.TintOpacity : 1; - const double noiseOpcity = 0.06; + const double noiseOpcity = 0.02; var tintColor = acrylicBrush.TintColor; var tint = new SKColor(tintColor.R, tintColor.G, tintColor.B, (byte)(255 * ((tintColor.A / 255.0) * acrylicBrush.Opacity))); From 4dc8a2a3ee18b92d1551ae1935f30886a9d4b6cf Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 9 Jun 2020 18:28:50 -0300 Subject: [PATCH 18/47] tweaks and fixes to acrylic. --- samples/ControlCatalog/Pages/AcrylicPage.xaml | 6 +++--- src/Avalonia.Visuals/Media/ExperimentalAcrylicBrush.cs | 4 ++-- src/Skia/Avalonia.Skia/DrawingContextImpl.cs | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/samples/ControlCatalog/Pages/AcrylicPage.xaml b/samples/ControlCatalog/Pages/AcrylicPage.xaml index 6dc23c4841..88bbdb0346 100644 --- a/samples/ControlCatalog/Pages/AcrylicPage.xaml +++ b/samples/ControlCatalog/Pages/AcrylicPage.xaml @@ -71,7 +71,7 @@ @@ -89,8 +89,8 @@ diff --git a/src/Avalonia.Visuals/Media/ExperimentalAcrylicBrush.cs b/src/Avalonia.Visuals/Media/ExperimentalAcrylicBrush.cs index 7712b99b27..acc660175f 100644 --- a/src/Avalonia.Visuals/Media/ExperimentalAcrylicBrush.cs +++ b/src/Avalonia.Visuals/Media/ExperimentalAcrylicBrush.cs @@ -135,7 +135,7 @@ namespace Avalonia.Media if (false) // non-acrylic blue // TODO detect blur level. { - tintColor = new Color((byte)(Math.Round(tintColor.A * (((tintOpacity * tintOpacityModifier) * 0.25) + 0.75))), tintColor.R, tintColor.G, tintColor.B); + tintColor = new Color((byte)(Math.Round(tintColor.A * (((tintOpacity * tintOpacityModifier) * 0.15) + 0.85))), tintColor.R, tintColor.G, tintColor.B); } else { @@ -157,7 +157,7 @@ namespace Avalonia.Media const double whiteMaxOpacity = 0.40; // 100% luminosity const double midPointMaxOpacity = 0.50; // 50% luminosity - const double blackMaxOpacity = 0.60; // 0% luminosity + const double blackMaxOpacity = 0.80; // 0% luminosity var hsv = RgbToHsv(tintColor); diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index ad2a896508..d2c7bdaedc 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -697,10 +697,10 @@ namespace Avalonia.Skia acrylicBrush.BackgroundSource == AcrylicBackgroundSource.Digger ? acrylicBrush.TintOpacity : 1; - const double noiseOpcity = 0.02; + const double noiseOpcity = 0.0225; var tintColor = acrylicBrush.TintColor; - var tint = new SKColor(tintColor.R, tintColor.G, tintColor.B, (byte)(255 * ((tintColor.A / 255.0) * acrylicBrush.Opacity))); + var tint = new SKColor(tintColor.R, tintColor.G, tintColor.B, (byte)(255 * ((tintColor.A / 255.0) * acrylicBrush.Opacity * tintOpacity))); if(s_acrylicNoiseShader == null) { From 5ba822f94f95054ebcd655cf0c193151b44480a3 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 9 Jun 2020 20:24:48 -0300 Subject: [PATCH 19/47] adjust opacity for normal blur... --- .../Media/ExperimentalAcrylicBrush.cs | 16 ++++++---- src/Skia/Avalonia.Skia/DrawingContextImpl.cs | 30 +++++++++++++++++-- 2 files changed, 39 insertions(+), 7 deletions(-) diff --git a/src/Avalonia.Visuals/Media/ExperimentalAcrylicBrush.cs b/src/Avalonia.Visuals/Media/ExperimentalAcrylicBrush.cs index acc660175f..e52774ea18 100644 --- a/src/Avalonia.Visuals/Media/ExperimentalAcrylicBrush.cs +++ b/src/Avalonia.Visuals/Media/ExperimentalAcrylicBrush.cs @@ -139,12 +139,18 @@ namespace Avalonia.Media } else { - tintColor = new Color((byte)(Math.Round(tintColor.A * tintOpacity * tintOpacityModifier)), tintColor.R, tintColor.G, tintColor.B); + tintColor = new Color((byte)(255 * ((255.0 / tintColor.A) * tintOpacity) * tintOpacityModifier), tintColor.R, tintColor.G, tintColor.B); } return tintColor; } + private static double AdjustOpacity(double opacity) + { + var result = Math.Max((1.0 - Math.Pow((1.0 - opacity), 3.85)), 0.92); + return result; + } + private static double GetTintOpacityModifier(Color tintColor) { // This method supresses the maximum allowable tint opacity depending on the luminosity and saturation of a color by @@ -155,9 +161,9 @@ namespace Avalonia.Media 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.40; // 100% luminosity - const double midPointMaxOpacity = 0.50; // 50% luminosity - const double blackMaxOpacity = 0.80; // 0% luminosity + double whiteMaxOpacity = AdjustOpacity(0.45); // 100% luminosity + double midPointMaxOpacity = AdjustOpacity(0.40); // 50% luminosity + double blackMaxOpacity = AdjustOpacity(0.60); // 0% luminosity var hsv = RgbToHsv(tintColor); @@ -189,7 +195,7 @@ namespace Avalonia.Media if (hsv.Saturation > 0) { // Dampen opacity suppression based on how much saturation there is - maxOpacitySuppression *= Math.Max(1 - (hsv.Saturation * 2), 0.0); + //maxOpacitySuppression *= Math.Max(1 - (hsv.Saturation * 2), 0.0); } double opacitySuppression = maxOpacitySuppression * normalizedDeviation; diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index d2c7bdaedc..7fcc13ebd3 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -668,6 +668,27 @@ namespace Avalonia.Skia 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 SKColor Blend(SKColor left, SKColor right) + { + var aa = left.Alpha / 255d; + var ab = right.Alpha / 255d; + return new SKColor( + Blend(left.Red, left.Alpha, right.Red, right.Alpha), + Blend(left.Green, left.Alpha, right.Green, right.Alpha), + Blend(left.Blue, left.Alpha, right.Blue, right.Alpha), + (byte)((aa + ab * (1 - aa)) * 255) + ); + } + /// /// Creates paint wrapper for given brush. /// @@ -700,9 +721,14 @@ namespace Avalonia.Skia const double noiseOpcity = 0.0225; var tintColor = acrylicBrush.TintColor; - var tint = new SKColor(tintColor.R, tintColor.G, tintColor.B, (byte)(255 * ((tintColor.A / 255.0) * acrylicBrush.Opacity * tintOpacity))); + var tint = new SKColor(tintColor.R, tintColor.G, tintColor.B, tintColor.A); + var tracingPaper = new SKColor(0xaf, 0x7f, 0x7f, 0x7f); + + //tint = Blend(tracingPaper, tint); + - if(s_acrylicNoiseShader == null) + + if (s_acrylicNoiseShader == null) { using(var stream = typeof(DrawingContextImpl).Assembly.GetManifestResourceStream("Avalonia.Skia.Assets.NoiseAsset_256X256_PNG.png")) using (var bitmap = SKBitmap.Decode(stream)) From 98e7b140232f81330405444ace1d018e08f42229 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 10 Jun 2020 16:35:32 -0300 Subject: [PATCH 20/47] use proper corrections. and blend with mid range gray to allow for readability. --- samples/ControlCatalog/Pages/AcrylicPage.xaml | 22 ++- .../Media/ExperimentalAcrylicBrush.cs | 131 +++++++++++++++++- .../Media/IExperimentalAcrylicBrush.cs | 4 +- .../ImmutableExperimentalAcrylicBrush.cs | 3 + src/Skia/Avalonia.Skia/DrawingContextImpl.cs | 10 +- 5 files changed, 157 insertions(+), 13 deletions(-) diff --git a/samples/ControlCatalog/Pages/AcrylicPage.xaml b/samples/ControlCatalog/Pages/AcrylicPage.xaml index 88bbdb0346..29f5fb8409 100644 --- a/samples/ControlCatalog/Pages/AcrylicPage.xaml +++ b/samples/ControlCatalog/Pages/AcrylicPage.xaml @@ -3,10 +3,21 @@ 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"> + x:Class="ControlCatalog.Pages.AcrylicPage"> + + + + + + + @@ -24,6 +35,7 @@ @@ -33,6 +45,7 @@ @@ -44,6 +57,7 @@ @@ -53,6 +67,7 @@ @@ -62,6 +77,7 @@ @@ -73,6 +89,7 @@ @@ -82,6 +99,7 @@ @@ -90,7 +108,7 @@ diff --git a/src/Avalonia.Visuals/Media/ExperimentalAcrylicBrush.cs b/src/Avalonia.Visuals/Media/ExperimentalAcrylicBrush.cs index e52774ea18..49b0e04cf3 100644 --- a/src/Avalonia.Visuals/Media/ExperimentalAcrylicBrush.cs +++ b/src/Avalonia.Visuals/Media/ExperimentalAcrylicBrush.cs @@ -5,6 +5,7 @@ namespace Avalonia.Media public class ExperimentalAcrylicBrush : Brush, IExperimentalAcrylicBrush { private Color _effectiveTintColor; + private Color _effectiveLuminosityColor; static ExperimentalAcrylicBrush() { @@ -17,11 +18,19 @@ namespace Avalonia.Media TintColorProperty.Changed.AddClassHandler((b, e) => { b._effectiveTintColor = GetEffectiveTintColor(b.TintColor, b.TintOpacity); + b._effectiveLuminosityColor = b.GetEffectiveLuminosityColor(); }); TintOpacityProperty.Changed.AddClassHandler((b, e) => { b._effectiveTintColor = GetEffectiveTintColor(b.TintColor, b.TintOpacity); + b._effectiveLuminosityColor = b.GetEffectiveLuminosityColor(); + }); + + TintLuminosityOpacityProperty.Changed.AddClassHandler((b, e) => + { + b._effectiveTintColor = GetEffectiveTintColor(b.TintColor, b.TintOpacity); + b._effectiveLuminosityColor = b.GetEffectiveLuminosityColor(); }); } @@ -37,8 +46,8 @@ namespace Avalonia.Media public static readonly StyledProperty TintOpacityProperty = AvaloniaProperty.Register(nameof(TintOpacity)); - public static readonly StyledProperty TintLuminosityOpacityProperty = - AvaloniaProperty.Register(nameof(TintLuminosityOpacity)); + public static readonly StyledProperty TintLuminosityOpacityProperty = + AvaloniaProperty.Register(nameof(TintLuminosityOpacity)); public static readonly StyledProperty FallbackColorProperty = AvaloniaProperty.Register(nameof(FallbackColor)); @@ -57,6 +66,8 @@ namespace Avalonia.Media Color IExperimentalAcrylicBrush.TintColor => _effectiveTintColor; + Color IExperimentalAcrylicBrush.LuminosityColor => _effectiveLuminosityColor; + public double TintOpacity { get => GetValue(TintOpacityProperty); @@ -161,9 +172,9 @@ namespace Avalonia.Media const double midPoint = 0.5; // Mid point of HsvV range that these calculations are based on. This is here for easy tuning. - double whiteMaxOpacity = AdjustOpacity(0.45); // 100% luminosity - double midPointMaxOpacity = AdjustOpacity(0.40); // 50% luminosity - double blackMaxOpacity = AdjustOpacity(0.60); // 0% luminosity + double whiteMaxOpacity = 0.45; // 100% luminosity + double midPointMaxOpacity = 0.90; // 50% luminosity + double blackMaxOpacity = 0.85; // 0% luminosity var hsv = RgbToHsv(tintColor); @@ -205,5 +216,115 @@ namespace Avalonia.Media return opacityModifier; } + + Color GetEffectiveLuminosityColor() + { + double tintOpacity = TintOpacity; + + // Purposely leaving out tint opacity modifier here because GetLuminosityColor needs the *original* tint opacity set by the user. + var tintColor = new Color((byte)(Math.Round(TintColor.A * tintOpacity)), TintColor.R, TintColor.G, TintColor.B); + + double? luminosityOpacity = TintLuminosityOpacity; + + return GetLuminosityColor(tintColor, luminosityOpacity); + } + + public static Color FromHsv(HsvColor color) + { + float r = 0; + float g = 0; + float b = 0; + + var i = (float)Math.Floor(color.Hue * 6f); + var f = color.Hue * 6f - i; + var p = color.Value * (1f - color.Saturation); + var q = color.Value * (1f - f * color.Saturation); + var t = color.Value * (1f - (1f - f) * color.Saturation); + + switch (i % 6) + { + case 0: + r = color.Value; + g = t; + b = p; + break; + case 1: + r = q; + g = color.Value; + b = p; + break; + case 2: + r = p; + g = color.Value; + b = t; + break; + case 3: + r = p; + g = q; + b = color.Value; + break; + case 4: + r = t; + g = p; + b = color.Value; + break; + case 5: + r = color.Value; + g = p; + b = q; + break; + } + + return new Color(Trim(r), Trim(g), Trim(b), 255); + } + + 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; + } + + double Luminosity (Color color) + { + return 0.299 * color.R + 0.587 * color.G + 0.114 * color.B; + } + + // The tintColor passed into this method should be the original, unmodified color created using user values for TintColor + TintOpacity + Color GetLuminosityColor(Color tintColor, double? luminosityOpacity) + { + var luminosityColor = new Color(255, 127, 127, 127); + + var modifier = GetTintOpacityModifier(luminosityColor); + + // If luminosity opacity is specified, just use the values as is + if (luminosityOpacity.HasValue) + { + return new Color((byte)(255 * Math.Max(Math.Min(luminosityOpacity.Value * modifier, 1.0), 0.0)), luminosityColor.R, luminosityColor.G, luminosityColor.B); + } + else + { + // Now figure out luminosity opacity + // Map original *tint* opacity to this range + const double minLuminosityOpacity = 0.15; + const double maxLuminosityOpacity = 1.03; + + double luminosityOpacityRangeMax = maxLuminosityOpacity - minLuminosityOpacity; + double mappedTintOpacity = ((tintColor.A / 255.0) * luminosityOpacityRangeMax) + minLuminosityOpacity; + + // Finally, combine the luminosity opacity and the HsvV-clamped tint color + return new Color(Trim(Math.Min(mappedTintOpacity * modifier, 1.0)), luminosityColor.R, luminosityColor.G, luminosityColor.B); + } + + } } } diff --git a/src/Avalonia.Visuals/Media/IExperimentalAcrylicBrush.cs b/src/Avalonia.Visuals/Media/IExperimentalAcrylicBrush.cs index fdcf6148fe..30cbeb331c 100644 --- a/src/Avalonia.Visuals/Media/IExperimentalAcrylicBrush.cs +++ b/src/Avalonia.Visuals/Media/IExperimentalAcrylicBrush.cs @@ -6,7 +6,9 @@ namespace Avalonia.Media { AcrylicBackgroundSource BackgroundSource { get; } - Color TintColor { get; } + Color TintColor { get; } + + Color LuminosityColor { get; } double TintOpacity { get; } diff --git a/src/Avalonia.Visuals/Media/ImmutableExperimentalAcrylicBrush.cs b/src/Avalonia.Visuals/Media/ImmutableExperimentalAcrylicBrush.cs index ed3f532035..e05f851a73 100644 --- a/src/Avalonia.Visuals/Media/ImmutableExperimentalAcrylicBrush.cs +++ b/src/Avalonia.Visuals/Media/ImmutableExperimentalAcrylicBrush.cs @@ -11,12 +11,15 @@ namespace Avalonia.Media TintOpacity = brush.TintOpacity; FallbackColor = brush.FallbackColor; Opacity = brush.Opacity; + LuminosityColor = brush.LuminosityColor; } public AcrylicBackgroundSource BackgroundSource { get; } public Color TintColor { get; } + public Color LuminosityColor { get; } + public double TintOpacity { get; } public Color FallbackColor { get; } diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index 7fcc13ebd3..aefbbab7cc 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -726,8 +726,6 @@ namespace Avalonia.Skia //tint = Blend(tracingPaper, tint); - - if (s_acrylicNoiseShader == null) { using(var stream = typeof(DrawingContextImpl).Assembly.GetManifestResourceStream("Avalonia.Skia.Assets.NoiseAsset_256X256_PNG.png")) @@ -736,11 +734,13 @@ namespace Avalonia.Skia s_acrylicNoiseShader = SKShader.CreateBitmap(bitmap, SKShaderTileMode.Repeat, SKShaderTileMode.Repeat) .WithColorFilter(CreateAlphaColorFilter(noiseOpcity)); } - } - + + using (var backdrop = SKShader.CreateColor(new SKColor(acrylicBrush.LuminosityColor.R, acrylicBrush.LuminosityColor.G, acrylicBrush.LuminosityColor.B, (byte)Math.Round((acrylicBrush.LuminosityColor.A * 0.75))))) + using (var backdropResult = SKShader.CreateCompose(backdrop, backdrop, SKBlendMode.Luminosity)) using (var tintShader = SKShader.CreateColor(tint)) - using (var compose = SKShader.CreateCompose(tintShader, s_acrylicNoiseShader)) + using (var effectiveTint = SKShader.CreateCompose (backdropResult, tintShader)) + using (var compose = SKShader.CreateCompose(effectiveTint, s_acrylicNoiseShader)) { paint.Shader = compose; From e957f75fdb17d588b0a94fa5fa414275489c4afd Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 10 Jun 2020 18:37:57 -0300 Subject: [PATCH 21/47] remove unused code. --- samples/ControlCatalog/Pages/AcrylicPage.xaml | 27 ++++++------ .../Media/ExperimentalAcrylicBrush.cs | 42 ++++++------------- .../ImmutableExperimentalAcrylicBrush.cs | 4 +- src/Skia/Avalonia.Skia/DrawingContextImpl.cs | 29 ++++++------- 4 files changed, 42 insertions(+), 60 deletions(-) diff --git a/samples/ControlCatalog/Pages/AcrylicPage.xaml b/samples/ControlCatalog/Pages/AcrylicPage.xaml index 29f5fb8409..c0b3d164bf 100644 --- a/samples/ControlCatalog/Pages/AcrylicPage.xaml +++ b/samples/ControlCatalog/Pages/AcrylicPage.xaml @@ -7,17 +7,7 @@ - - - - - - - + @@ -107,12 +97,23 @@ + + + + + + diff --git a/src/Avalonia.Visuals/Media/ExperimentalAcrylicBrush.cs b/src/Avalonia.Visuals/Media/ExperimentalAcrylicBrush.cs index 49b0e04cf3..194c32a090 100644 --- a/src/Avalonia.Visuals/Media/ExperimentalAcrylicBrush.cs +++ b/src/Avalonia.Visuals/Media/ExperimentalAcrylicBrush.cs @@ -33,7 +33,7 @@ namespace Avalonia.Media b._effectiveLuminosityColor = b.GetEffectiveLuminosityColor(); }); } - + /// /// Defines the property. /// @@ -46,8 +46,8 @@ namespace Avalonia.Media public static readonly StyledProperty TintOpacityProperty = AvaloniaProperty.Register(nameof(TintOpacity)); - public static readonly StyledProperty TintLuminosityOpacityProperty = - AvaloniaProperty.Register(nameof(TintLuminosityOpacity)); + public static readonly StyledProperty TintLuminosityOpacityProperty = + AvaloniaProperty.Register(nameof(TintLuminosityOpacity), 0.9); public static readonly StyledProperty FallbackColorProperty = AvaloniaProperty.Register(nameof(FallbackColor)); @@ -80,7 +80,7 @@ namespace Avalonia.Media set => SetValue(FallbackColorProperty, value); } - public double? TintLuminosityOpacity + public double TintLuminosityOpacity { get => GetValue(TintLuminosityOpacityProperty); set => SetValue(TintLuminosityOpacityProperty, value); @@ -100,7 +100,7 @@ namespace Avalonia.Media public static HsvColor RgbToHsv(Color color) { - var r = color.R /255.0f; + 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)); @@ -172,10 +172,10 @@ namespace Avalonia.Media const double midPoint = 0.5; // Mid point of HsvV range that these calculations are based on. This is here for easy tuning. - double whiteMaxOpacity = 0.45; // 100% luminosity + double whiteMaxOpacity = 0.65; // 100% luminosity double midPointMaxOpacity = 0.90; // 50% luminosity double blackMaxOpacity = 0.85; // 0% luminosity - + var hsv = RgbToHsv(tintColor); double opacityModifier = midPointMaxOpacity; @@ -218,7 +218,7 @@ namespace Avalonia.Media } Color GetEffectiveLuminosityColor() - { + { double tintOpacity = TintOpacity; // Purposely leaving out tint opacity modifier here because GetLuminosityColor needs the *original* tint opacity set by the user. @@ -226,7 +226,7 @@ namespace Avalonia.Media double? luminosityOpacity = TintLuminosityOpacity; - return GetLuminosityColor(tintColor, luminosityOpacity); + return GetLuminosityColor(luminosityOpacity); } public static Color FromHsv(HsvColor color) @@ -294,37 +294,19 @@ namespace Avalonia.Media return (byte)value; } - double Luminosity (Color color) + double Luminosity(Color color) { return 0.299 * color.R + 0.587 * color.G + 0.114 * color.B; } // The tintColor passed into this method should be the original, unmodified color created using user values for TintColor + TintOpacity - Color GetLuminosityColor(Color tintColor, double? luminosityOpacity) + Color GetLuminosityColor(double? luminosityOpacity) { var luminosityColor = new Color(255, 127, 127, 127); var modifier = GetTintOpacityModifier(luminosityColor); - // If luminosity opacity is specified, just use the values as is - if (luminosityOpacity.HasValue) - { - return new Color((byte)(255 * Math.Max(Math.Min(luminosityOpacity.Value * modifier, 1.0), 0.0)), luminosityColor.R, luminosityColor.G, luminosityColor.B); - } - else - { - // Now figure out luminosity opacity - // Map original *tint* opacity to this range - const double minLuminosityOpacity = 0.15; - const double maxLuminosityOpacity = 1.03; - - double luminosityOpacityRangeMax = maxLuminosityOpacity - minLuminosityOpacity; - double mappedTintOpacity = ((tintColor.A / 255.0) * luminosityOpacityRangeMax) + minLuminosityOpacity; - - // Finally, combine the luminosity opacity and the HsvV-clamped tint color - return new Color(Trim(Math.Min(mappedTintOpacity * modifier, 1.0)), luminosityColor.R, luminosityColor.G, luminosityColor.B); - } - + return new Color((byte)(255 * Math.Max(Math.Min(luminosityOpacity.Value * modifier, 1.0), 0.0)), luminosityColor.R, luminosityColor.G, luminosityColor.B); } } } diff --git a/src/Avalonia.Visuals/Media/ImmutableExperimentalAcrylicBrush.cs b/src/Avalonia.Visuals/Media/ImmutableExperimentalAcrylicBrush.cs index e05f851a73..8cb61dbaf4 100644 --- a/src/Avalonia.Visuals/Media/ImmutableExperimentalAcrylicBrush.cs +++ b/src/Avalonia.Visuals/Media/ImmutableExperimentalAcrylicBrush.cs @@ -34,7 +34,8 @@ namespace Avalonia.Media Opacity == other.Opacity && TintOpacity == other.TintOpacity && BackgroundSource == other.BackgroundSource && - FallbackColor == other.FallbackColor; + FallbackColor == other.FallbackColor && LuminosityColor == other.LuminosityColor; + } public override bool Equals(object obj) @@ -58,6 +59,7 @@ namespace Avalonia.Media hash = (hash * 23) + TintOpacity.GetHashCode(); hash = (hash * 23) + BackgroundSource.GetHashCode(); hash = (hash * 23) + FallbackColor.GetHashCode(); + hash = (hash * 23) + LuminosityColor.GetHashCode(); return hash; } diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index aefbbab7cc..1ddc0980b5 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -677,17 +677,18 @@ namespace Avalonia.Skia var r = (ca * aa + cb * ab * (1 - aa)) / (aa + ab * (1 - aa)); return (byte)(r * 255); } - static SKColor Blend(SKColor left, SKColor right) - { - var aa = left.Alpha / 255d; - var ab = right.Alpha / 255d; - return new SKColor( - Blend(left.Red, left.Alpha, right.Red, right.Alpha), - Blend(left.Green, left.Alpha, right.Green, right.Alpha), - Blend(left.Blue, left.Alpha, right.Blue, right.Alpha), - (byte)((aa + ab * (1 - aa)) * 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) ); - } + } /// /// Creates paint wrapper for given brush. @@ -722,9 +723,6 @@ namespace Avalonia.Skia var tintColor = acrylicBrush.TintColor; var tint = new SKColor(tintColor.R, tintColor.G, tintColor.B, tintColor.A); - var tracingPaper = new SKColor(0xaf, 0x7f, 0x7f, 0x7f); - - //tint = Blend(tracingPaper, tint); if (s_acrylicNoiseShader == null) { @@ -736,10 +734,9 @@ namespace Avalonia.Skia } } - using (var backdrop = SKShader.CreateColor(new SKColor(acrylicBrush.LuminosityColor.R, acrylicBrush.LuminosityColor.G, acrylicBrush.LuminosityColor.B, (byte)Math.Round((acrylicBrush.LuminosityColor.A * 0.75))))) - using (var backdropResult = SKShader.CreateCompose(backdrop, backdrop, SKBlendMode.Luminosity)) + using (var backdrop = SKShader.CreateColor(new SKColor(acrylicBrush.LuminosityColor.R, acrylicBrush.LuminosityColor.G, acrylicBrush.LuminosityColor.B, acrylicBrush.LuminosityColor.A))) using (var tintShader = SKShader.CreateColor(tint)) - using (var effectiveTint = SKShader.CreateCompose (backdropResult, tintShader)) + using (var effectiveTint = SKShader.CreateCompose (backdrop, tintShader)) using (var compose = SKShader.CreateCompose(effectiveTint, s_acrylicNoiseShader)) { paint.Shader = compose; From ecdd8b9c7bd02c683af82384a146db481ba052d7 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 10 Jun 2020 18:50:56 -0300 Subject: [PATCH 22/47] sensible defaults. tidy acrylic demo. --- samples/ControlCatalog/Pages/AcrylicPage.xaml | 20 ++++++++++++++++--- .../Media/ExperimentalAcrylicBrush.cs | 4 ++-- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/samples/ControlCatalog/Pages/AcrylicPage.xaml b/samples/ControlCatalog/Pages/AcrylicPage.xaml index c0b3d164bf..f2785dfcfd 100644 --- a/samples/ControlCatalog/Pages/AcrylicPage.xaml +++ b/samples/ControlCatalog/Pages/AcrylicPage.xaml @@ -6,8 +6,22 @@ x:Class="ControlCatalog.Pages.AcrylicPage"> - - + + + + + + + + + + + + + + @@ -112,7 +126,7 @@ TintOpacity="{Binding #TintOpacitySlider.Value}" TintLuminosityOpacity="{Binding #TintLuminositySlider.Value}" BackgroundSource="Digger" /> - + diff --git a/src/Avalonia.Visuals/Media/ExperimentalAcrylicBrush.cs b/src/Avalonia.Visuals/Media/ExperimentalAcrylicBrush.cs index 194c32a090..475afb8567 100644 --- a/src/Avalonia.Visuals/Media/ExperimentalAcrylicBrush.cs +++ b/src/Avalonia.Visuals/Media/ExperimentalAcrylicBrush.cs @@ -44,10 +44,10 @@ namespace Avalonia.Media AvaloniaProperty.Register(nameof(BackgroundSource)); public static readonly StyledProperty TintOpacityProperty = - AvaloniaProperty.Register(nameof(TintOpacity)); + AvaloniaProperty.Register(nameof(TintOpacity), 0.9); public static readonly StyledProperty TintLuminosityOpacityProperty = - AvaloniaProperty.Register(nameof(TintLuminosityOpacity), 0.9); + AvaloniaProperty.Register(nameof(TintLuminosityOpacity), 0.8); public static readonly StyledProperty FallbackColorProperty = AvaloniaProperty.Register(nameof(FallbackColor)); From 9eaaceff60495c3ccf8f844b28490e3b301b448c Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 12 Jun 2020 16:11:42 -0300 Subject: [PATCH 23/47] Implement Acrylic as a "Material" and a special Decorator to display Acrylic. --- .../ExperimentalAcrylicBorder.cs | 81 ++++++++ ...rush.cs => ExperimentalAcrylicMaterial.cs} | 183 ++++++++++++++++-- .../Media/IExperimentalAcrylicBrush.cs | 17 -- .../ImmutableExperimentalAcrylicBrush.cs | 78 -------- .../IDrawingContextWithAcrylicLikeSupport.cs | 9 + .../SceneGraph/DeferredDrawingContextImpl.cs | 19 +- .../SceneGraph/ExperimentalAcrylicNode.cs | 93 +++++++++ .../Rendering/SceneGraph/RectangleNode.cs | 2 +- src/Skia/Avalonia.Skia/DrawingContextImpl.cs | 120 ++++++++---- 9 files changed, 445 insertions(+), 157 deletions(-) create mode 100644 src/Avalonia.Controls/ExperimentalAcrylicBorder.cs rename src/Avalonia.Visuals/Media/{ExperimentalAcrylicBrush.cs => ExperimentalAcrylicMaterial.cs} (62%) delete mode 100644 src/Avalonia.Visuals/Media/IExperimentalAcrylicBrush.cs delete mode 100644 src/Avalonia.Visuals/Media/ImmutableExperimentalAcrylicBrush.cs create mode 100644 src/Avalonia.Visuals/Platform/IDrawingContextWithAcrylicLikeSupport.cs create mode 100644 src/Avalonia.Visuals/Rendering/SceneGraph/ExperimentalAcrylicNode.cs diff --git a/src/Avalonia.Controls/ExperimentalAcrylicBorder.cs b/src/Avalonia.Controls/ExperimentalAcrylicBorder.cs new file mode 100644 index 0000000000..4a855b9838 --- /dev/null +++ b/src/Avalonia.Controls/ExperimentalAcrylicBorder.cs @@ -0,0 +1,81 @@ +using Avalonia.Controls.Utils; +using Avalonia.Layout; +using Avalonia.Media; +using Avalonia.Platform; + +namespace Avalonia.Controls +{ + public class ExperimentalAcrylicBorder : Decorator + { + public static readonly StyledProperty CornerRadiusProperty = + Border.CornerRadiusProperty.AddOwner(); + + public static readonly StyledProperty MaterialProperty = + AvaloniaProperty.Register(nameof(Material)); + + private readonly BorderRenderHelper _borderRenderHelper = new BorderRenderHelper(); + + static ExperimentalAcrylicBorder() + { + AffectsRender( + MaterialProperty, + CornerRadiusProperty); + } + + + /// + /// Gets or sets the radius of the border rounded corners. + /// + public CornerRadius CornerRadius + { + get { return GetValue(CornerRadiusProperty); } + set { SetValue(CornerRadiusProperty, value); } + } + + public ExperimentalAcrylicMaterial Material + { + get => GetValue(MaterialProperty); + set => SetValue(MaterialProperty, value); + } + + + 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); + } + } + + /// + /// Measures the control. + /// + /// The available size. + /// The desired size of the control. + protected override Size MeasureOverride(Size availableSize) + { + return LayoutHelper.MeasureChild(Child, availableSize, Padding); + } + + /// + /// Arranges the control's child. + /// + /// The size allocated to the control. + /// The space taken. + protected override Size ArrangeOverride(Size finalSize) + { + return LayoutHelper.ArrangeChild(Child, finalSize, Padding); + } + } +} diff --git a/src/Avalonia.Visuals/Media/ExperimentalAcrylicBrush.cs b/src/Avalonia.Visuals/Media/ExperimentalAcrylicMaterial.cs similarity index 62% rename from src/Avalonia.Visuals/Media/ExperimentalAcrylicBrush.cs rename to src/Avalonia.Visuals/Media/ExperimentalAcrylicMaterial.cs index 475afb8567..507931da5a 100644 --- a/src/Avalonia.Visuals/Media/ExperimentalAcrylicBrush.cs +++ b/src/Avalonia.Visuals/Media/ExperimentalAcrylicMaterial.cs @@ -2,32 +2,144 @@ namespace Avalonia.Media { - public class ExperimentalAcrylicBrush : Brush, IExperimentalAcrylicBrush + /// + /// Represents a mutable brush which can return an immutable clone of itself. + /// + public interface IMutableExperimentalAcrylicMaterial : IExperimentalAcrylicMaterial, IAffectsRender + { + /// + /// Creates an immutable clone of the brush. + /// + /// The immutable clone. + IExperimentalAcrylicMaterial ToImmutable(); + } + + public interface IExperimentalAcrylicMaterial + { + AcrylicBackgroundSource BackgroundSource { get; } + + Color TintColor { get; } + + Color LuminosityColor { get; } + + double TintOpacity { get; } + + Color FallbackColor { get; } + } + + public static class MaterialExtensions + { + /// + /// Converts a brush to an immutable brush. + /// + /// The brush. + /// + /// The result of calling if the brush is mutable, + /// otherwise . + /// + public static IExperimentalAcrylicMaterial ToImmutable(this IExperimentalAcrylicMaterial material) + { + Contract.Requires(material != null); + + return (material as IMutableExperimentalAcrylicMaterial)?.ToImmutable() ?? material; + } + } + + public readonly struct ImmutableExperimentalAcrylicMaterial : IExperimentalAcrylicMaterial, IEquatable + { + public ImmutableExperimentalAcrylicMaterial(IExperimentalAcrylicMaterial brush) + { + BackgroundSource = brush.BackgroundSource; + TintColor = brush.TintColor; + TintOpacity = brush.TintOpacity; + FallbackColor = brush.FallbackColor; + LuminosityColor = brush.LuminosityColor; + } + + public AcrylicBackgroundSource BackgroundSource { get; } + + public Color TintColor { get; } + + public Color LuminosityColor { 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 && LuminosityColor == other.LuminosityColor; + + } + + 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) + LuminosityColor.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); + } + } + + public class ExperimentalAcrylicMaterial : AvaloniaObject, IMutableExperimentalAcrylicMaterial { private Color _effectiveTintColor; private Color _effectiveLuminosityColor; - static ExperimentalAcrylicBrush() + static ExperimentalAcrylicMaterial() { - AffectsRender( + AffectsRender( TintColorProperty, BackgroundSourceProperty, TintOpacityProperty, TintLuminosityOpacityProperty); - TintColorProperty.Changed.AddClassHandler((b, e) => + TintColorProperty.Changed.AddClassHandler((b, e) => { b._effectiveTintColor = GetEffectiveTintColor(b.TintColor, b.TintOpacity); b._effectiveLuminosityColor = b.GetEffectiveLuminosityColor(); }); - TintOpacityProperty.Changed.AddClassHandler((b, e) => + TintOpacityProperty.Changed.AddClassHandler((b, e) => { b._effectiveTintColor = GetEffectiveTintColor(b.TintColor, b.TintOpacity); b._effectiveLuminosityColor = b.GetEffectiveLuminosityColor(); }); - TintLuminosityOpacityProperty.Changed.AddClassHandler((b, e) => + TintLuminosityOpacityProperty.Changed.AddClassHandler((b, e) => { b._effectiveTintColor = GetEffectiveTintColor(b.TintColor, b.TintOpacity); b._effectiveLuminosityColor = b.GetEffectiveLuminosityColor(); @@ -38,19 +150,22 @@ namespace Avalonia.Media /// Defines the property. /// public static readonly StyledProperty TintColorProperty = - AvaloniaProperty.Register(nameof(TintColor)); + AvaloniaProperty.Register(nameof(TintColor)); public static readonly StyledProperty BackgroundSourceProperty = - AvaloniaProperty.Register(nameof(BackgroundSource)); + AvaloniaProperty.Register(nameof(BackgroundSource)); public static readonly StyledProperty TintOpacityProperty = - AvaloniaProperty.Register(nameof(TintOpacity), 0.9); + AvaloniaProperty.Register(nameof(TintOpacity), 0.8); public static readonly StyledProperty TintLuminosityOpacityProperty = - AvaloniaProperty.Register(nameof(TintLuminosityOpacity), 0.8); + AvaloniaProperty.Register(nameof(TintLuminosityOpacity), 0.5); public static readonly StyledProperty FallbackColorProperty = - AvaloniaProperty.Register(nameof(FallbackColor)); + AvaloniaProperty.Register(nameof(FallbackColor)); + + /// + public event EventHandler Invalidated; public AcrylicBackgroundSource BackgroundSource { @@ -64,10 +179,6 @@ namespace Avalonia.Media set => SetValue(TintColorProperty, value); } - Color IExperimentalAcrylicBrush.TintColor => _effectiveTintColor; - - Color IExperimentalAcrylicBrush.LuminosityColor => _effectiveLuminosityColor; - public double TintOpacity { get => GetValue(TintOpacityProperty); @@ -86,10 +197,9 @@ namespace Avalonia.Media set => SetValue(TintLuminosityOpacityProperty, value); } - public override IBrush ToImmutable() - { - return new ImmutableExperimentalAcrylicBrush(this); - } + Color IExperimentalAcrylicMaterial.LuminosityColor => _effectiveLuminosityColor; + + Color IExperimentalAcrylicMaterial.TintColor => _effectiveTintColor; public struct HsvColor { @@ -172,7 +282,7 @@ namespace Avalonia.Media const double midPoint = 0.5; // Mid point of HsvV range that these calculations are based on. This is here for easy tuning. - double whiteMaxOpacity = 0.65; // 100% luminosity + double whiteMaxOpacity = 0.45; // 100% luminosity double midPointMaxOpacity = 0.90; // 50% luminosity double blackMaxOpacity = 0.85; // 0% luminosity @@ -308,5 +418,38 @@ namespace Avalonia.Media return new Color((byte)(255 * Math.Max(Math.Min(luminosityOpacity.Value * modifier, 1.0), 0.0)), luminosityColor.R, luminosityColor.G, luminosityColor.B); } + + /// + /// Marks a property as affecting the brush's visual representation. + /// + /// The properties. + /// + /// After a call to this method in a brush's static constructor, any change to the + /// property will cause the event to be raised on the brush. + /// + protected static void AffectsRender(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)); + } + } + + /// + /// Raises the event. + /// + /// The event args. + protected void RaiseInvalidated(EventArgs e) => Invalidated?.Invoke(this, e); + + public IExperimentalAcrylicMaterial ToImmutable() + { + return new ImmutableExperimentalAcrylicMaterial(this); + } } } diff --git a/src/Avalonia.Visuals/Media/IExperimentalAcrylicBrush.cs b/src/Avalonia.Visuals/Media/IExperimentalAcrylicBrush.cs deleted file mode 100644 index 30cbeb331c..0000000000 --- a/src/Avalonia.Visuals/Media/IExperimentalAcrylicBrush.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System.Drawing; - -namespace Avalonia.Media -{ - public interface IExperimentalAcrylicBrush : IBrush - { - AcrylicBackgroundSource BackgroundSource { get; } - - Color TintColor { get; } - - Color LuminosityColor { get; } - - double TintOpacity { get; } - - Color FallbackColor { get; } - } -} diff --git a/src/Avalonia.Visuals/Media/ImmutableExperimentalAcrylicBrush.cs b/src/Avalonia.Visuals/Media/ImmutableExperimentalAcrylicBrush.cs deleted file mode 100644 index 8cb61dbaf4..0000000000 --- a/src/Avalonia.Visuals/Media/ImmutableExperimentalAcrylicBrush.cs +++ /dev/null @@ -1,78 +0,0 @@ -using System; - -namespace Avalonia.Media -{ - public readonly struct ImmutableExperimentalAcrylicBrush : IExperimentalAcrylicBrush, IEquatable - { - public ImmutableExperimentalAcrylicBrush(IExperimentalAcrylicBrush brush) - { - BackgroundSource = brush.BackgroundSource; - TintColor = brush.TintColor; - TintOpacity = brush.TintOpacity; - FallbackColor = brush.FallbackColor; - Opacity = brush.Opacity; - LuminosityColor = brush.LuminosityColor; - } - - public AcrylicBackgroundSource BackgroundSource { get; } - - public Color TintColor { get; } - - public Color LuminosityColor { get; } - - public double TintOpacity { get; } - - public Color FallbackColor { get; } - - public double Opacity { get; } - - public bool Equals(ImmutableExperimentalAcrylicBrush other) - { - // ReSharper disable once CompareOfFloatsByEqualityOperator - return - TintColor == other.TintColor && - Opacity == other.Opacity && - TintOpacity == other.TintOpacity && - BackgroundSource == other.BackgroundSource && - FallbackColor == other.FallbackColor && LuminosityColor == other.LuminosityColor; - - } - - public override bool Equals(object obj) - { - return obj is ImmutableExperimentalAcrylicBrush other && Equals(other); - } - - public Color GetEffectiveTintColor() - { - return TintColor; - } - - public override int GetHashCode() - { - unchecked - { - int hash = 17; - - hash = (hash * 23) + TintColor.GetHashCode(); - hash = (hash * 23) + Opacity.GetHashCode(); - hash = (hash * 23) + TintOpacity.GetHashCode(); - hash = (hash * 23) + BackgroundSource.GetHashCode(); - hash = (hash * 23) + FallbackColor.GetHashCode(); - hash = (hash * 23) + LuminosityColor.GetHashCode(); - - return hash; - } - } - - public static bool operator ==(ImmutableExperimentalAcrylicBrush left, ImmutableExperimentalAcrylicBrush right) - { - return left.Equals(right); - } - - public static bool operator !=(ImmutableExperimentalAcrylicBrush left, ImmutableExperimentalAcrylicBrush right) - { - return !left.Equals(right); - } - } -} diff --git a/src/Avalonia.Visuals/Platform/IDrawingContextWithAcrylicLikeSupport.cs b/src/Avalonia.Visuals/Platform/IDrawingContextWithAcrylicLikeSupport.cs new file mode 100644 index 0000000000..a3cf0298dd --- /dev/null +++ b/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); + } +} diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs index 6ad71ac111..0ca7e876cb 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs @@ -11,7 +11,7 @@ namespace Avalonia.Rendering.SceneGraph /// /// A drawing context which builds a scene graph. /// - internal class DeferredDrawingContextImpl : IDrawingContextImpl + internal class DeferredDrawingContextImpl : IDrawingContextImpl, IDrawingContextWithAcrylicLikeSupport { private readonly ISceneBuilder _sceneBuilder; private VisualNode _node; @@ -163,7 +163,22 @@ namespace Avalonia.Rendering.SceneGraph ++_drawOperationindex; } } - + + /// + public void DrawRectangle(IExperimentalAcrylicMaterial material, RoundedRect rect) + { + var next = NextDrawAs(); + + 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(); diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/ExperimentalAcrylicNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/ExperimentalAcrylicNode.cs new file mode 100644 index 0000000000..9969652a8b --- /dev/null +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/ExperimentalAcrylicNode.cs @@ -0,0 +1,93 @@ +using System; +using Avalonia.Media; +using Avalonia.Platform; + +namespace Avalonia.Rendering.SceneGraph +{ + /// + /// A node in the scene graph which represents a rectangle draw. + /// + internal class ExperimentalAcrylicNode : DrawOperation + { + /// + /// Initializes a new instance of the class. + /// + /// The transform. + /// The rectangle to draw. + /// The box shadow parameters + /// Child scenes for drawing visual brushes. + public ExperimentalAcrylicNode( + Matrix transform, + IExperimentalAcrylicMaterial material, + RoundedRect rect) + : base(rect.Rect, transform) + { + Transform = transform; + Material = material?.ToImmutable(); + Rect = rect; + } + + /// + /// Gets the transform with which the node will be drawn. + /// + public Matrix Transform { get; } + + public IExperimentalAcrylicMaterial Material { get; } + + /// + /// Gets the rectangle to draw. + /// + public RoundedRect Rect { get; } + + /// + /// Determines if this draw operation equals another. + /// + /// The transform of the other draw operation. + /// The fill of the other draw operation. + /// The rectangle of the other draw operation. + /// True if the draw operations are the same, otherwise false. + /// + /// The properties of the other draw operation are passed in as arguments to prevent + /// allocation of a not-yet-constructed draw operation object. + /// + public bool Equals(Matrix transform, IExperimentalAcrylicMaterial material, RoundedRect rect) + { + return transform == Transform && + Equals(material, Material) && + rect.Equals(Rect); + } + + /// + public override void Render(IDrawingContextImpl context) + { + context.Transform = Transform; + + if(context is IDrawingContextWithAcrylicLikeSupport idc) + { + idc.DrawRectangle(Material, Rect); + } + else + { + context.DrawRectangle(new SolidColorBrush(Material.FallbackColor), null, Rect); + } + } + + /// + 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; + } + } +} diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs index 5059a6d042..ec1a7753b1 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs @@ -19,7 +19,7 @@ namespace Avalonia.Rendering.SceneGraph /// The fill brush. /// The stroke pen. /// The rectangle to draw. - /// The box shadow parameters + /// The box shadow parameters /// Child scenes for drawing visual brushes. public RectangleNode( Matrix transform, diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index 1ddc0980b5..47139b19d9 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -17,7 +17,7 @@ namespace Avalonia.Skia /// /// Skia based drawing context. /// - internal class DrawingContextImpl : IDrawingContextImpl, ISkiaDrawingContextImpl + internal class DrawingContextImpl : IDrawingContextImpl, ISkiaDrawingContextImpl, IDrawingContextWithAcrylicLikeSupport { private IDisposable[] _disposables; private readonly Vector _dpi; @@ -228,6 +228,42 @@ namespace Avalonia.Skia return bounds; } + /// + 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, rect.Rect.Size)) + { + if (isRounded) + { + Canvas.DrawRoundRect(skRoundRect, paint.Paint); + } + else + { + Canvas.DrawRect(rc, paint.Paint); + } + + } + } + } + /// public void DrawRectangle(IBrush brush, IPen pen, RoundedRect rect, BoxShadows boxShadows = default) { @@ -690,6 +726,49 @@ namespace Avalonia.Skia ); } + internal PaintWrapper CreateAcrylicPaint (SKPaint paint, IExperimentalAcrylicMaterial material, Size targetSize, 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.LuminosityColor.R, material.LuminosityColor.G, material.LuminosityColor.B, material.LuminosityColor.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; + } + } + /// /// Creates paint wrapper for given brush. /// @@ -713,43 +792,6 @@ namespace Avalonia.Skia return paintWrapper; } - if (brush is IExperimentalAcrylicBrush acrylicBrush) - { - var tintOpacity = - acrylicBrush.BackgroundSource == AcrylicBackgroundSource.Digger ? - acrylicBrush.TintOpacity : 1; - - const double noiseOpcity = 0.0225; - - var tintColor = acrylicBrush.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(acrylicBrush.LuminosityColor.R, acrylicBrush.LuminosityColor.G, acrylicBrush.LuminosityColor.B, acrylicBrush.LuminosityColor.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 (acrylicBrush.BackgroundSource == AcrylicBackgroundSource.Digger) - { - paint.BlendMode = SKBlendMode.Src; - } - - return paintWrapper; - } - } - paint.Color = new SKColor(255, 255, 255, (byte) (255 * opacity)); if (brush is IGradientBrush gradient) @@ -879,7 +921,7 @@ namespace Avalonia.Skia }; return new SurfaceRenderTarget(createInfo); - } + } /// /// Skia cached paint state. From f96ad2f7f6697c478420c0418e3e5c36d20fcd8a Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 12 Jun 2020 16:15:00 -0300 Subject: [PATCH 24/47] update acrylic page. --- samples/ControlCatalog/Pages/AcrylicPage.xaml | 112 +++++++++--------- 1 file changed, 57 insertions(+), 55 deletions(-) diff --git a/samples/ControlCatalog/Pages/AcrylicPage.xaml b/samples/ControlCatalog/Pages/AcrylicPage.xaml index f2785dfcfd..214d012aff 100644 --- a/samples/ControlCatalog/Pages/AcrylicPage.xaml +++ b/samples/ControlCatalog/Pages/AcrylicPage.xaml @@ -7,12 +7,12 @@ - - - + + - + @@ -21,113 +21,115 @@ - + + - - - + + - - + + - - - + + - - + + - - - + + - - + + + - - - + + - - + + - - - + + - - + + - - - + + - - + + - - - + + - - + + - - - + + - - + + - - - + + - - + + - - - + + - - + + From 65df446f2ab604a4971ae497bad82fb1aaa873ac Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 12 Jun 2020 16:22:45 -0300 Subject: [PATCH 25/47] use immutable solidcolor brush for fallback in drawing node. --- .../Rendering/SceneGraph/ExperimentalAcrylicNode.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/ExperimentalAcrylicNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/ExperimentalAcrylicNode.cs index 9969652a8b..336d11e3fd 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/ExperimentalAcrylicNode.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/ExperimentalAcrylicNode.cs @@ -1,5 +1,6 @@ using System; using Avalonia.Media; +using Avalonia.Media.Immutable; using Avalonia.Platform; namespace Avalonia.Rendering.SceneGraph @@ -68,7 +69,7 @@ namespace Avalonia.Rendering.SceneGraph } else { - context.DrawRectangle(new SolidColorBrush(Material.FallbackColor), null, Rect); + context.DrawRectangle(new ImmutableSolidColorBrush(Material.FallbackColor), null, Rect); } } From 62b5932c11a8eede1c101eee40d7af1a67a94a77 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 12 Jun 2020 18:02:11 -0300 Subject: [PATCH 26/47] remove unused arguments. --- src/Skia/Avalonia.Skia/DrawingContextImpl.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index 47139b19d9..cba96c4a26 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -249,7 +249,7 @@ namespace Avalonia.Skia if (material != null) { - using (var paint = CreateAcrylicPaint(_fillPaint, material, rect.Rect.Size)) + using (var paint = CreateAcrylicPaint(_fillPaint, material)) { if (isRounded) { @@ -726,7 +726,7 @@ namespace Avalonia.Skia ); } - internal PaintWrapper CreateAcrylicPaint (SKPaint paint, IExperimentalAcrylicMaterial material, Size targetSize, bool disposePaint = false) + internal PaintWrapper CreateAcrylicPaint (SKPaint paint, IExperimentalAcrylicMaterial material, bool disposePaint = false) { var paintWrapper = new PaintWrapper(paint, disposePaint); From 88b016970e006fa48a6a3832263ab62e031177a3 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 12 Jun 2020 18:02:22 -0300 Subject: [PATCH 27/47] tidy acrylic page. --- samples/ControlCatalog/Pages/AcrylicPage.xaml | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/samples/ControlCatalog/Pages/AcrylicPage.xaml b/samples/ControlCatalog/Pages/AcrylicPage.xaml index 214d012aff..14ce1cff55 100644 --- a/samples/ControlCatalog/Pages/AcrylicPage.xaml +++ b/samples/ControlCatalog/Pages/AcrylicPage.xaml @@ -13,13 +13,17 @@ TintColor="White" BackgroundSource="Digger" /> - - - - - - - + + + + + + + + + + + From d7f7acc66fdc067c337c87f2103397bccb25f831 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 12 Jun 2020 18:02:50 -0300 Subject: [PATCH 28/47] seperate into files acrylic media classes. --- .../Media/ExperimentalAcrylicMaterial.cs | 152 +++--------------- .../Media/IExperimentalAcrylicMaterial.cs | 15 ++ .../IMutableExperimentalAcrylicMaterial.cs | 14 ++ .../ImmutableExperimentalAcrylicMaterial.cs | 73 +++++++++ .../Media/MaterialExtensions.cs | 22 +++ 5 files changed, 149 insertions(+), 127 deletions(-) create mode 100644 src/Avalonia.Visuals/Media/IExperimentalAcrylicMaterial.cs create mode 100644 src/Avalonia.Visuals/Media/IMutableExperimentalAcrylicMaterial.cs create mode 100644 src/Avalonia.Visuals/Media/ImmutableExperimentalAcrylicMaterial.cs create mode 100644 src/Avalonia.Visuals/Media/MaterialExtensions.cs diff --git a/src/Avalonia.Visuals/Media/ExperimentalAcrylicMaterial.cs b/src/Avalonia.Visuals/Media/ExperimentalAcrylicMaterial.cs index 507931da5a..c96dfda4a0 100644 --- a/src/Avalonia.Visuals/Media/ExperimentalAcrylicMaterial.cs +++ b/src/Avalonia.Visuals/Media/ExperimentalAcrylicMaterial.cs @@ -2,118 +2,6 @@ namespace Avalonia.Media { - /// - /// Represents a mutable brush which can return an immutable clone of itself. - /// - public interface IMutableExperimentalAcrylicMaterial : IExperimentalAcrylicMaterial, IAffectsRender - { - /// - /// Creates an immutable clone of the brush. - /// - /// The immutable clone. - IExperimentalAcrylicMaterial ToImmutable(); - } - - public interface IExperimentalAcrylicMaterial - { - AcrylicBackgroundSource BackgroundSource { get; } - - Color TintColor { get; } - - Color LuminosityColor { get; } - - double TintOpacity { get; } - - Color FallbackColor { get; } - } - - public static class MaterialExtensions - { - /// - /// Converts a brush to an immutable brush. - /// - /// The brush. - /// - /// The result of calling if the brush is mutable, - /// otherwise . - /// - public static IExperimentalAcrylicMaterial ToImmutable(this IExperimentalAcrylicMaterial material) - { - Contract.Requires(material != null); - - return (material as IMutableExperimentalAcrylicMaterial)?.ToImmutable() ?? material; - } - } - - public readonly struct ImmutableExperimentalAcrylicMaterial : IExperimentalAcrylicMaterial, IEquatable - { - public ImmutableExperimentalAcrylicMaterial(IExperimentalAcrylicMaterial brush) - { - BackgroundSource = brush.BackgroundSource; - TintColor = brush.TintColor; - TintOpacity = brush.TintOpacity; - FallbackColor = brush.FallbackColor; - LuminosityColor = brush.LuminosityColor; - } - - public AcrylicBackgroundSource BackgroundSource { get; } - - public Color TintColor { get; } - - public Color LuminosityColor { 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 && LuminosityColor == other.LuminosityColor; - - } - - 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) + LuminosityColor.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); - } - } - public class ExperimentalAcrylicMaterial : AvaloniaObject, IMutableExperimentalAcrylicMaterial { private Color _effectiveTintColor; @@ -266,12 +154,6 @@ namespace Avalonia.Media return tintColor; } - private static double AdjustOpacity(double opacity) - { - var result = Math.Max((1.0 - Math.Pow((1.0 - opacity), 3.85)), 0.92); - return result; - } - private static double GetTintOpacityModifier(Color tintColor) { // This method supresses the maximum allowable tint opacity depending on the luminosity and saturation of a color by @@ -282,9 +164,9 @@ namespace Avalonia.Media const double midPoint = 0.5; // Mid point of HsvV range that these calculations are based on. This is here for easy tuning. - double whiteMaxOpacity = 0.45; // 100% luminosity - double midPointMaxOpacity = 0.90; // 50% luminosity - double blackMaxOpacity = 0.85; // 0% luminosity + const double whiteMaxOpacity = 0.45; // 100% luminosity + const double midPointMaxOpacity = 0.50; // 50% luminosity + const double blackMaxOpacity = 0.85; // 0% luminosity var hsv = RgbToHsv(tintColor); @@ -316,7 +198,7 @@ namespace Avalonia.Media if (hsv.Saturation > 0) { // Dampen opacity suppression based on how much saturation there is - //maxOpacitySuppression *= Math.Max(1 - (hsv.Saturation * 2), 0.0); + maxOpacitySuppression *= Math.Max(1 - (hsv.Saturation * 2), 0.0); } double opacitySuppression = maxOpacitySuppression * normalizedDeviation; @@ -404,19 +286,35 @@ namespace Avalonia.Media return (byte)value; } - double Luminosity(Color color) + private static float RGBMax(Color color) { - return 0.299 * color.R + 0.587 * color.G + 0.114 * color.B; + 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 Color GetLuminosityColor(double? luminosityOpacity) { - var luminosityColor = new Color(255, 127, 127, 127); + var hsv = RgbToHsv(TintColor); + + var max = (float)RGBMax(TintColor) / 255.0f; + var min = (float)RGBMin(TintColor) / 255.0f; + + var lightness = (max + min) / 2.0f; - var modifier = GetTintOpacityModifier(luminosityColor); + var luminosityColor = new Color(255, Trim(lightness), Trim(lightness), Trim(lightness)); - return new Color((byte)(255 * Math.Max(Math.Min(luminosityOpacity.Value * modifier, 1.0), 0.0)), luminosityColor.R, luminosityColor.G, luminosityColor.B); + return new Color((byte)(255 * Math.Max(Math.Min(((luminosityOpacity.Value)), 1.0), 0.0)), luminosityColor.R, luminosityColor.G, luminosityColor.B); } /// diff --git a/src/Avalonia.Visuals/Media/IExperimentalAcrylicMaterial.cs b/src/Avalonia.Visuals/Media/IExperimentalAcrylicMaterial.cs new file mode 100644 index 0000000000..bcd3bbcc0b --- /dev/null +++ b/src/Avalonia.Visuals/Media/IExperimentalAcrylicMaterial.cs @@ -0,0 +1,15 @@ +namespace Avalonia.Media +{ + public interface IExperimentalAcrylicMaterial + { + AcrylicBackgroundSource BackgroundSource { get; } + + Color TintColor { get; } + + Color LuminosityColor { get; } + + double TintOpacity { get; } + + Color FallbackColor { get; } + } +} diff --git a/src/Avalonia.Visuals/Media/IMutableExperimentalAcrylicMaterial.cs b/src/Avalonia.Visuals/Media/IMutableExperimentalAcrylicMaterial.cs new file mode 100644 index 0000000000..fcfe4631a6 --- /dev/null +++ b/src/Avalonia.Visuals/Media/IMutableExperimentalAcrylicMaterial.cs @@ -0,0 +1,14 @@ +namespace Avalonia.Media +{ + /// + /// Represents a mutable brush which can return an immutable clone of itself. + /// + public interface IMutableExperimentalAcrylicMaterial : IExperimentalAcrylicMaterial, IAffectsRender + { + /// + /// Creates an immutable clone of the brush. + /// + /// The immutable clone. + IExperimentalAcrylicMaterial ToImmutable(); + } +} diff --git a/src/Avalonia.Visuals/Media/ImmutableExperimentalAcrylicMaterial.cs b/src/Avalonia.Visuals/Media/ImmutableExperimentalAcrylicMaterial.cs new file mode 100644 index 0000000000..c5cf420509 --- /dev/null +++ b/src/Avalonia.Visuals/Media/ImmutableExperimentalAcrylicMaterial.cs @@ -0,0 +1,73 @@ +using System; + +namespace Avalonia.Media +{ + public readonly struct ImmutableExperimentalAcrylicMaterial : IExperimentalAcrylicMaterial, IEquatable + { + public ImmutableExperimentalAcrylicMaterial(IExperimentalAcrylicMaterial brush) + { + BackgroundSource = brush.BackgroundSource; + TintColor = brush.TintColor; + TintOpacity = brush.TintOpacity; + FallbackColor = brush.FallbackColor; + LuminosityColor = brush.LuminosityColor; + } + + public AcrylicBackgroundSource BackgroundSource { get; } + + public Color TintColor { get; } + + public Color LuminosityColor { 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 && LuminosityColor == other.LuminosityColor; + + } + + 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) + LuminosityColor.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); + } + } +} diff --git a/src/Avalonia.Visuals/Media/MaterialExtensions.cs b/src/Avalonia.Visuals/Media/MaterialExtensions.cs new file mode 100644 index 0000000000..c0b445c357 --- /dev/null +++ b/src/Avalonia.Visuals/Media/MaterialExtensions.cs @@ -0,0 +1,22 @@ +using System; + +namespace Avalonia.Media +{ + public static class MaterialExtensions + { + /// + /// Converts a brush to an immutable brush. + /// + /// The brush. + /// + /// The result of calling if the brush is mutable, + /// otherwise . + /// + public static IExperimentalAcrylicMaterial ToImmutable(this IExperimentalAcrylicMaterial material) + { + Contract.Requires(material != null); + + return (material as IMutableExperimentalAcrylicMaterial)?.ToImmutable() ?? material; + } + } +} From effb994823c18ce8219a3cb8bdb45ec8bb1660f5 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 12 Jun 2020 18:28:31 -0300 Subject: [PATCH 29/47] Add a way for acrylic material to compensate for different transparency levels. --- .../ExperimentalAcrylicBorder.cs | 33 +++++++++++++++++++ .../Media/ExperimentalAcrylicMaterial.cs | 22 +++++++++++-- 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/ExperimentalAcrylicBorder.cs b/src/Avalonia.Controls/ExperimentalAcrylicBorder.cs index 4a855b9838..2c013fd244 100644 --- a/src/Avalonia.Controls/ExperimentalAcrylicBorder.cs +++ b/src/Avalonia.Controls/ExperimentalAcrylicBorder.cs @@ -2,6 +2,7 @@ using Avalonia.Layout; using Avalonia.Media; using Avalonia.Platform; +using System; namespace Avalonia.Controls { @@ -38,6 +39,38 @@ namespace Avalonia.Controls set => SetValue(MaterialProperty, value); } + protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) + { + base.OnAttachedToVisualTree(e); + + var tl = (e.Root as TopLevel); + + tl.GetObservable(TopLevel.ActualTransparencyLevelProperty) + .Subscribe(x => + { + switch (x) + { + case WindowTransparencyLevel.Transparent: + case WindowTransparencyLevel.None: + Material.PlatformTransparencyCompensationLevel = 1; + break; + + case WindowTransparencyLevel.Blur: + Material.PlatformTransparencyCompensationLevel = 0.85; + break; + + case WindowTransparencyLevel.AcrylicBlur: + Material.PlatformTransparencyCompensationLevel = 0; + break; + } + }); + } + + protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) + { + base.OnDetachedFromVisualTree(e); + } + public override void Render(DrawingContext context) { diff --git a/src/Avalonia.Visuals/Media/ExperimentalAcrylicMaterial.cs b/src/Avalonia.Visuals/Media/ExperimentalAcrylicMaterial.cs index c96dfda4a0..c0004e8fd1 100644 --- a/src/Avalonia.Visuals/Media/ExperimentalAcrylicMaterial.cs +++ b/src/Avalonia.Visuals/Media/ExperimentalAcrylicMaterial.cs @@ -13,7 +13,8 @@ namespace Avalonia.Media TintColorProperty, BackgroundSourceProperty, TintOpacityProperty, - TintLuminosityOpacityProperty); + TintLuminosityOpacityProperty, + PlatformTransparencyCompensationLevelProperty); TintColorProperty.Changed.AddClassHandler((b, e) => { @@ -32,6 +33,12 @@ namespace Avalonia.Media b._effectiveTintColor = GetEffectiveTintColor(b.TintColor, b.TintOpacity); b._effectiveLuminosityColor = b.GetEffectiveLuminosityColor(); }); + + PlatformTransparencyCompensationLevelProperty.Changed.AddClassHandler((b, e) => + { + b._effectiveTintColor = GetEffectiveTintColor(b.TintColor, b.TintOpacity); + b._effectiveLuminosityColor = b.GetEffectiveLuminosityColor(); + }); } /// @@ -49,6 +56,9 @@ namespace Avalonia.Media public static readonly StyledProperty TintLuminosityOpacityProperty = AvaloniaProperty.Register(nameof(TintLuminosityOpacity), 0.5); + public static readonly StyledProperty PlatformTransparencyCompensationLevelProperty = + AvaloniaProperty.Register(nameof(PlatformTransparencyCompensationLevel), 0.0); + public static readonly StyledProperty FallbackColorProperty = AvaloniaProperty.Register(nameof(FallbackColor)); @@ -85,6 +95,12 @@ namespace Avalonia.Media set => SetValue(TintLuminosityOpacityProperty, value); } + public double PlatformTransparencyCompensationLevel + { + get => GetValue(PlatformTransparencyCompensationLevelProperty); + set => SetValue(PlatformTransparencyCompensationLevelProperty, value); + } + Color IExperimentalAcrylicMaterial.LuminosityColor => _effectiveLuminosityColor; Color IExperimentalAcrylicMaterial.TintColor => _effectiveTintColor; @@ -314,7 +330,9 @@ namespace Avalonia.Media var luminosityColor = new Color(255, Trim(lightness), Trim(lightness), Trim(lightness)); - return new Color((byte)(255 * Math.Max(Math.Min(((luminosityOpacity.Value)), 1.0), 0.0)), luminosityColor.R, luminosityColor.G, luminosityColor.B); + 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); } /// From bba17006ecc657c47ce263ab7b6d5a7035b6c25e Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 12 Jun 2020 18:51:00 -0300 Subject: [PATCH 30/47] tweak platform compensation. --- src/Avalonia.Controls/ExperimentalAcrylicBorder.cs | 2 +- src/Avalonia.Visuals/Media/ExperimentalAcrylicMaterial.cs | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Controls/ExperimentalAcrylicBorder.cs b/src/Avalonia.Controls/ExperimentalAcrylicBorder.cs index 2c013fd244..05d261a921 100644 --- a/src/Avalonia.Controls/ExperimentalAcrylicBorder.cs +++ b/src/Avalonia.Controls/ExperimentalAcrylicBorder.cs @@ -56,7 +56,7 @@ namespace Avalonia.Controls break; case WindowTransparencyLevel.Blur: - Material.PlatformTransparencyCompensationLevel = 0.85; + Material.PlatformTransparencyCompensationLevel = 0.80; break; case WindowTransparencyLevel.AcrylicBlur: diff --git a/src/Avalonia.Visuals/Media/ExperimentalAcrylicMaterial.cs b/src/Avalonia.Visuals/Media/ExperimentalAcrylicMaterial.cs index c0004e8fd1..35f186f504 100644 --- a/src/Avalonia.Visuals/Media/ExperimentalAcrylicMaterial.cs +++ b/src/Avalonia.Visuals/Media/ExperimentalAcrylicMaterial.cs @@ -321,12 +321,13 @@ namespace Avalonia.Media // The tintColor passed into this method should be the original, unmodified color created using user values for TintColor + TintOpacity Color GetLuminosityColor(double? luminosityOpacity) { - var hsv = RgbToHsv(TintColor); - + // 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.0f; + var lightness = (max + min) / 2.0; + + lightness = 0.125 + (lightness * 0.75); // centralise and reduce the range of the opaque layer. var luminosityColor = new Color(255, Trim(lightness), Trim(lightness), Trim(lightness)); From cc2aa697422217b6a727ee00f2a5e65c1b5af20c Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 12 Jun 2020 18:55:06 -0300 Subject: [PATCH 31/47] rename properties that control "tracking paper effect." --- samples/ControlCatalog/Pages/AcrylicPage.xaml | 20 ++++++++-------- .../Media/ExperimentalAcrylicMaterial.cs | 23 +++++++++++-------- .../Media/IExperimentalAcrylicMaterial.cs | 4 ++-- .../ImmutableExperimentalAcrylicMaterial.cs | 8 +++---- src/Skia/Avalonia.Skia/DrawingContextImpl.cs | 2 +- 5 files changed, 30 insertions(+), 27 deletions(-) diff --git a/samples/ControlCatalog/Pages/AcrylicPage.xaml b/samples/ControlCatalog/Pages/AcrylicPage.xaml index 14ce1cff55..fd9487190b 100644 --- a/samples/ControlCatalog/Pages/AcrylicPage.xaml +++ b/samples/ControlCatalog/Pages/AcrylicPage.xaml @@ -34,7 +34,7 @@ @@ -44,7 +44,7 @@ @@ -54,7 +54,7 @@ @@ -67,7 +67,7 @@ @@ -77,7 +77,7 @@ @@ -87,7 +87,7 @@ @@ -99,7 +99,7 @@ @@ -109,7 +109,7 @@ @@ -119,7 +119,7 @@ @@ -130,7 +130,7 @@ diff --git a/src/Avalonia.Visuals/Media/ExperimentalAcrylicMaterial.cs b/src/Avalonia.Visuals/Media/ExperimentalAcrylicMaterial.cs index 35f186f504..5d6e1348f9 100644 --- a/src/Avalonia.Visuals/Media/ExperimentalAcrylicMaterial.cs +++ b/src/Avalonia.Visuals/Media/ExperimentalAcrylicMaterial.cs @@ -13,7 +13,7 @@ namespace Avalonia.Media TintColorProperty, BackgroundSourceProperty, TintOpacityProperty, - TintLuminosityOpacityProperty, + MaterialOpacityProperty, PlatformTransparencyCompensationLevelProperty); TintColorProperty.Changed.AddClassHandler((b, e) => @@ -28,7 +28,7 @@ namespace Avalonia.Media b._effectiveLuminosityColor = b.GetEffectiveLuminosityColor(); }); - TintLuminosityOpacityProperty.Changed.AddClassHandler((b, e) => + MaterialOpacityProperty.Changed.AddClassHandler((b, e) => { b._effectiveTintColor = GetEffectiveTintColor(b.TintColor, b.TintOpacity); b._effectiveLuminosityColor = b.GetEffectiveLuminosityColor(); @@ -53,8 +53,8 @@ namespace Avalonia.Media public static readonly StyledProperty TintOpacityProperty = AvaloniaProperty.Register(nameof(TintOpacity), 0.8); - public static readonly StyledProperty TintLuminosityOpacityProperty = - AvaloniaProperty.Register(nameof(TintLuminosityOpacity), 0.5); + public static readonly StyledProperty MaterialOpacityProperty = + AvaloniaProperty.Register(nameof(MaterialOpacity), 0.5); public static readonly StyledProperty PlatformTransparencyCompensationLevelProperty = AvaloniaProperty.Register(nameof(PlatformTransparencyCompensationLevel), 0.0); @@ -89,10 +89,10 @@ namespace Avalonia.Media set => SetValue(FallbackColorProperty, value); } - public double TintLuminosityOpacity + public double MaterialOpacity { - get => GetValue(TintLuminosityOpacityProperty); - set => SetValue(TintLuminosityOpacityProperty, value); + get => GetValue(MaterialOpacityProperty); + set => SetValue(MaterialOpacityProperty, value); } public double PlatformTransparencyCompensationLevel @@ -101,7 +101,7 @@ namespace Avalonia.Media set => SetValue(PlatformTransparencyCompensationLevelProperty, value); } - Color IExperimentalAcrylicMaterial.LuminosityColor => _effectiveLuminosityColor; + Color IExperimentalAcrylicMaterial.MaterialColor => _effectiveLuminosityColor; Color IExperimentalAcrylicMaterial.TintColor => _effectiveTintColor; @@ -232,7 +232,7 @@ namespace Avalonia.Media // Purposely leaving out tint opacity modifier here because GetLuminosityColor needs the *original* tint opacity set by the user. var tintColor = new Color((byte)(Math.Round(TintColor.A * tintOpacity)), TintColor.R, TintColor.G, TintColor.B); - double? luminosityOpacity = TintLuminosityOpacity; + double? luminosityOpacity = MaterialOpacity; return GetLuminosityColor(luminosityOpacity); } @@ -327,7 +327,10 @@ namespace Avalonia.Media var lightness = (max + min) / 2.0; - lightness = 0.125 + (lightness * 0.75); // centralise and reduce the range of the opaque layer. + // centralise and reduce the range of the opaque layer. + // this is done so pure white and pure black still have the effect of being blended with something. + // i.e. they get a little darker / lighter. + lightness = 0.125 + (lightness * 0.75); var luminosityColor = new Color(255, Trim(lightness), Trim(lightness), Trim(lightness)); diff --git a/src/Avalonia.Visuals/Media/IExperimentalAcrylicMaterial.cs b/src/Avalonia.Visuals/Media/IExperimentalAcrylicMaterial.cs index bcd3bbcc0b..a656c75d46 100644 --- a/src/Avalonia.Visuals/Media/IExperimentalAcrylicMaterial.cs +++ b/src/Avalonia.Visuals/Media/IExperimentalAcrylicMaterial.cs @@ -6,10 +6,10 @@ Color TintColor { get; } - Color LuminosityColor { get; } - double TintOpacity { get; } + Color MaterialColor { get; } + Color FallbackColor { get; } } } diff --git a/src/Avalonia.Visuals/Media/ImmutableExperimentalAcrylicMaterial.cs b/src/Avalonia.Visuals/Media/ImmutableExperimentalAcrylicMaterial.cs index c5cf420509..f46d76cf3f 100644 --- a/src/Avalonia.Visuals/Media/ImmutableExperimentalAcrylicMaterial.cs +++ b/src/Avalonia.Visuals/Media/ImmutableExperimentalAcrylicMaterial.cs @@ -10,14 +10,14 @@ namespace Avalonia.Media TintColor = brush.TintColor; TintOpacity = brush.TintOpacity; FallbackColor = brush.FallbackColor; - LuminosityColor = brush.LuminosityColor; + MaterialColor = brush.MaterialColor; } public AcrylicBackgroundSource BackgroundSource { get; } public Color TintColor { get; } - public Color LuminosityColor { get; } + public Color MaterialColor { get; } public double TintOpacity { get; } @@ -30,7 +30,7 @@ namespace Avalonia.Media TintColor == other.TintColor && TintOpacity == other.TintOpacity && BackgroundSource == other.BackgroundSource && - FallbackColor == other.FallbackColor && LuminosityColor == other.LuminosityColor; + FallbackColor == other.FallbackColor && MaterialColor == other.MaterialColor; } @@ -54,7 +54,7 @@ namespace Avalonia.Media hash = (hash * 23) + TintOpacity.GetHashCode(); hash = (hash * 23) + BackgroundSource.GetHashCode(); hash = (hash * 23) + FallbackColor.GetHashCode(); - hash = (hash * 23) + LuminosityColor.GetHashCode(); + hash = (hash * 23) + MaterialColor.GetHashCode(); return hash; } diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index cba96c4a26..0573aeee1e 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -753,7 +753,7 @@ namespace Avalonia.Skia } } - using (var backdrop = SKShader.CreateColor(new SKColor(material.LuminosityColor.R, material.LuminosityColor.G, material.LuminosityColor.B, material.LuminosityColor.A))) + 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)) From a6bc06f7ad4902ac1fa131122c5501b009259a10 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 12 Jun 2020 18:59:03 -0300 Subject: [PATCH 32/47] make names on acrylic page make sense. --- samples/ControlCatalog/Pages/AcrylicPage.xaml | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/samples/ControlCatalog/Pages/AcrylicPage.xaml b/samples/ControlCatalog/Pages/AcrylicPage.xaml index fd9487190b..96cfcc5288 100644 --- a/samples/ControlCatalog/Pages/AcrylicPage.xaml +++ b/samples/ControlCatalog/Pages/AcrylicPage.xaml @@ -20,9 +20,9 @@ - - - + + + @@ -34,7 +34,7 @@ @@ -44,7 +44,7 @@ @@ -54,7 +54,7 @@ @@ -67,7 +67,7 @@ @@ -77,7 +77,7 @@ @@ -87,7 +87,7 @@ @@ -99,7 +99,7 @@ @@ -109,7 +109,7 @@ @@ -119,7 +119,7 @@ @@ -130,7 +130,7 @@ From 3b5c7c53145931704bb453684b88eb6151a85729 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sat, 13 Jun 2020 12:08:24 -0300 Subject: [PATCH 33/47] change tracing paper effect algorithm. --- .../ExperimentalAcrylicBorder.cs | 4 ++-- .../Media/ExperimentalAcrylicMaterial.cs | 15 +++++++-------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/Avalonia.Controls/ExperimentalAcrylicBorder.cs b/src/Avalonia.Controls/ExperimentalAcrylicBorder.cs index 05d261a921..a6e7b52cb0 100644 --- a/src/Avalonia.Controls/ExperimentalAcrylicBorder.cs +++ b/src/Avalonia.Controls/ExperimentalAcrylicBorder.cs @@ -56,11 +56,11 @@ namespace Avalonia.Controls break; case WindowTransparencyLevel.Blur: - Material.PlatformTransparencyCompensationLevel = 0.80; + Material.PlatformTransparencyCompensationLevel = 0.90; break; case WindowTransparencyLevel.AcrylicBlur: - Material.PlatformTransparencyCompensationLevel = 0; + Material.PlatformTransparencyCompensationLevel = 0.70; break; } }); diff --git a/src/Avalonia.Visuals/Media/ExperimentalAcrylicMaterial.cs b/src/Avalonia.Visuals/Media/ExperimentalAcrylicMaterial.cs index 5d6e1348f9..503554be2d 100644 --- a/src/Avalonia.Visuals/Media/ExperimentalAcrylicMaterial.cs +++ b/src/Avalonia.Visuals/Media/ExperimentalAcrylicMaterial.cs @@ -182,7 +182,7 @@ namespace Avalonia.Media const double whiteMaxOpacity = 0.45; // 100% luminosity const double midPointMaxOpacity = 0.50; // 50% luminosity - const double blackMaxOpacity = 0.85; // 0% luminosity + const double blackMaxOpacity = 0.45; // 0% luminosity var hsv = RgbToHsv(tintColor); @@ -327,16 +327,15 @@ namespace Avalonia.Media var lightness = (max + min) / 2.0; - // centralise and reduce the range of the opaque layer. - // this is done so pure white and pure black still have the effect of being blended with something. - // i.e. they get a little darker / lighter. - lightness = 0.125 + (lightness * 0.75); + // 0 opacity closer to white, 1 opacity, close to the lightness of the pixel. + // TintOpacity multiplied in, so we endup with pure white if there is no TintOpacity. + lightness = 1 - (1 - lightness) * luminosityOpacity.Value * TintOpacity; 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); + //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); + return new Color((byte)(255 * Math.Max(Math.Min((luminosityOpacity.Value), 1.0), 0.0)), luminosityColor.R, luminosityColor.G, luminosityColor.B); } /// From 79bad75e6c63f58313178d8fb7ce0c8fac0fa7a9 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sat, 13 Jun 2020 13:27:49 -0300 Subject: [PATCH 34/47] very good settings for tracing paper effect. --- .../Media/ExperimentalAcrylicMaterial.cs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/Avalonia.Visuals/Media/ExperimentalAcrylicMaterial.cs b/src/Avalonia.Visuals/Media/ExperimentalAcrylicMaterial.cs index 503554be2d..dd74269297 100644 --- a/src/Avalonia.Visuals/Media/ExperimentalAcrylicMaterial.cs +++ b/src/Avalonia.Visuals/Media/ExperimentalAcrylicMaterial.cs @@ -180,9 +180,9 @@ namespace Avalonia.Media 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.45; // 100% luminosity - const double midPointMaxOpacity = 0.50; // 50% luminosity - const double blackMaxOpacity = 0.45; // 0% luminosity + const double whiteMaxOpacity = 0.15; // 100% luminosity + const double midPointMaxOpacity = 0.45; // 50% luminosity + const double blackMaxOpacity = 0.40; // 0% luminosity var hsv = RgbToHsv(tintColor); @@ -325,13 +325,15 @@ namespace Avalonia.Media var max = (float)RGBMax(TintColor) / 255.0f; var min = (float)RGBMin(TintColor) / 255.0f; - var lightness = (max + min) / 2.0; + var lightness = (max + min) / 2.0; // 0 opacity closer to white, 1 opacity, close to the lightness of the pixel. - // TintOpacity multiplied in, so we endup with pure white if there is no TintOpacity. - lightness = 1 - (1 - lightness) * luminosityOpacity.Value * TintOpacity; + lightness = 1 -((1 - lightness) * luminosityOpacity.Value); - var luminosityColor = new Color(255, Trim(lightness), Trim(lightness), Trim(lightness)); + lightness = 0.13 + (lightness * 0.74); + var luminosityColor = new Color(255, Trim(lightness), Trim(lightness), Trim(lightness)); + + //luminosityColor = new Color(255, Trim(lightness * modifier), Trim(lightness * modifier), Trim(lightness * modifier)); //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); From ed2fd5f3ee5be679d9341032cb7d21f9679a5498 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sat, 13 Jun 2020 14:10:13 -0300 Subject: [PATCH 35/47] use blending technique to colourise the tracing paper. --- .../Media/ExperimentalAcrylicMaterial.cs | 32 ++++++++++++++++--- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.Visuals/Media/ExperimentalAcrylicMaterial.cs b/src/Avalonia.Visuals/Media/ExperimentalAcrylicMaterial.cs index dd74269297..614417ed20 100644 --- a/src/Avalonia.Visuals/Media/ExperimentalAcrylicMaterial.cs +++ b/src/Avalonia.Visuals/Media/ExperimentalAcrylicMaterial.cs @@ -318,6 +318,28 @@ namespace Avalonia.Media return (color.G < color.B) ? color.G : color.B; } + 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) + ); + } + // The tintColor passed into this method should be the original, unmodified color created using user values for TintColor + TintOpacity Color GetLuminosityColor(double? luminosityOpacity) { @@ -325,15 +347,15 @@ namespace Avalonia.Media var max = (float)RGBMax(TintColor) / 255.0f; var min = (float)RGBMin(TintColor) / 255.0f; - var lightness = (max + min) / 2.0; + var lightness = (max + min) / 2.0; - // 0 opacity closer to white, 1 opacity, close to the lightness of the pixel. - lightness = 1 -((1 - lightness) * luminosityOpacity.Value); + lightness = 1 - ((1 - lightness) * luminosityOpacity.Value); lightness = 0.13 + (lightness * 0.74); - var luminosityColor = new Color(255, Trim(lightness), Trim(lightness), Trim(lightness)); - //luminosityColor = new Color(255, Trim(lightness * modifier), Trim(lightness * modifier), Trim(lightness * modifier)); + var luminosityColor = new Color(255, Trim(lightness), Trim(lightness), Trim(lightness)); + + luminosityColor = Blend(luminosityColor, new Color((byte)Math.Round(255 * TintOpacity), TintColor.R, TintColor.G, TintColor.B)); //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); From 1e9873e6d4a891fcd9f2b40d0dc7f5adef384236 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sat, 13 Jun 2020 14:33:06 -0300 Subject: [PATCH 36/47] fix blend. --- src/Avalonia.Visuals/Media/ExperimentalAcrylicMaterial.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Visuals/Media/ExperimentalAcrylicMaterial.cs b/src/Avalonia.Visuals/Media/ExperimentalAcrylicMaterial.cs index 614417ed20..9c253df9dd 100644 --- a/src/Avalonia.Visuals/Media/ExperimentalAcrylicMaterial.cs +++ b/src/Avalonia.Visuals/Media/ExperimentalAcrylicMaterial.cs @@ -355,7 +355,7 @@ namespace Avalonia.Media var luminosityColor = new Color(255, Trim(lightness), Trim(lightness), Trim(lightness)); - luminosityColor = Blend(luminosityColor, new Color((byte)Math.Round(255 * TintOpacity), TintColor.R, TintColor.G, TintColor.B)); + luminosityColor = Blend(luminosityColor, new Color(255, TintColor.R, TintColor.G, TintColor.B)); //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); From f6162fa54298695395f4fa16355425ab06692c74 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sat, 13 Jun 2020 14:33:21 -0300 Subject: [PATCH 37/47] dampen saturated opacities. --- src/Avalonia.Visuals/Media/ExperimentalAcrylicMaterial.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Visuals/Media/ExperimentalAcrylicMaterial.cs b/src/Avalonia.Visuals/Media/ExperimentalAcrylicMaterial.cs index 9c253df9dd..964102e50e 100644 --- a/src/Avalonia.Visuals/Media/ExperimentalAcrylicMaterial.cs +++ b/src/Avalonia.Visuals/Media/ExperimentalAcrylicMaterial.cs @@ -181,7 +181,7 @@ namespace Avalonia.Media 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.15; // 100% luminosity - const double midPointMaxOpacity = 0.45; // 50% luminosity + const double midPointMaxOpacity = 0.40; // 50% luminosity const double blackMaxOpacity = 0.40; // 0% luminosity var hsv = RgbToHsv(tintColor); From dd17ffe0b7aed279c95063b03dbdbd631e8ca669 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sat, 13 Jun 2020 14:59:53 -0300 Subject: [PATCH 38/47] restore platform compensation. --- src/Avalonia.Visuals/Media/ExperimentalAcrylicMaterial.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Visuals/Media/ExperimentalAcrylicMaterial.cs b/src/Avalonia.Visuals/Media/ExperimentalAcrylicMaterial.cs index 964102e50e..7d2023cb21 100644 --- a/src/Avalonia.Visuals/Media/ExperimentalAcrylicMaterial.cs +++ b/src/Avalonia.Visuals/Media/ExperimentalAcrylicMaterial.cs @@ -357,9 +357,8 @@ namespace Avalonia.Media luminosityColor = Blend(luminosityColor, new Color(255, TintColor.R, TintColor.G, TintColor.B)); - //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); - return new Color((byte)(255 * Math.Max(Math.Min((luminosityOpacity.Value), 1.0), 0.0)), luminosityColor.R, luminosityColor.G, luminosityColor.B); + 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); } /// From 666892a52f4f2d55123c3ff6e01c641926b613b2 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sat, 13 Jun 2020 15:03:39 -0300 Subject: [PATCH 39/47] transparency level ajustments. --- src/Avalonia.Controls/ExperimentalAcrylicBorder.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/ExperimentalAcrylicBorder.cs b/src/Avalonia.Controls/ExperimentalAcrylicBorder.cs index a6e7b52cb0..6c283665f1 100644 --- a/src/Avalonia.Controls/ExperimentalAcrylicBorder.cs +++ b/src/Avalonia.Controls/ExperimentalAcrylicBorder.cs @@ -56,11 +56,11 @@ namespace Avalonia.Controls break; case WindowTransparencyLevel.Blur: - Material.PlatformTransparencyCompensationLevel = 0.90; + Material.PlatformTransparencyCompensationLevel = 0.80; break; case WindowTransparencyLevel.AcrylicBlur: - Material.PlatformTransparencyCompensationLevel = 0.70; + Material.PlatformTransparencyCompensationLevel = 0.0; break; } }); From 00db860270782d1963d3dbbf9bcf57ef65f368b7 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sat, 13 Jun 2020 15:16:40 -0300 Subject: [PATCH 40/47] keep color balancing proportional to win ui. --- src/Avalonia.Visuals/Media/ExperimentalAcrylicMaterial.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Visuals/Media/ExperimentalAcrylicMaterial.cs b/src/Avalonia.Visuals/Media/ExperimentalAcrylicMaterial.cs index 7d2023cb21..7071df063a 100644 --- a/src/Avalonia.Visuals/Media/ExperimentalAcrylicMaterial.cs +++ b/src/Avalonia.Visuals/Media/ExperimentalAcrylicMaterial.cs @@ -180,9 +180,9 @@ namespace Avalonia.Media 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.15; // 100% luminosity - const double midPointMaxOpacity = 0.40; // 50% luminosity - const double blackMaxOpacity = 0.40; // 0% luminosity + const double whiteMaxOpacity = 0.2; // 100% luminosity + const double midPointMaxOpacity = 0.45; // 50% luminosity + const double blackMaxOpacity = 0.42; // 0% luminosity var hsv = RgbToHsv(tintColor); From e867a3575a67f5292fc72c2aea8edeacaf3963a1 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sun, 5 Jul 2020 11:51:25 -0300 Subject: [PATCH 41/47] add AcrylicPlatformCompensationLevels. --- .../AcrylicPlatformCompensationLevels.cs | 16 ++++++++++++++++ src/Avalonia.Controls/Platform/ITopLevelImpl.cs | 5 +++++ 2 files changed, 21 insertions(+) create mode 100644 src/Avalonia.Controls/AcrylicPlatformCompensationLevels.cs diff --git a/src/Avalonia.Controls/AcrylicPlatformCompensationLevels.cs b/src/Avalonia.Controls/AcrylicPlatformCompensationLevels.cs new file mode 100644 index 0000000000..13dc736463 --- /dev/null +++ b/src/Avalonia.Controls/AcrylicPlatformCompensationLevels.cs @@ -0,0 +1,16 @@ +namespace Avalonia.Controls +{ + /// + /// 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. + /// + public struct AcrylicPlatformCompensationLevels + { + public double TransparentLevel { get; } + + public double BlurLevel { get; } + + public double AcrylicBlurLevel { get; } + } +} diff --git a/src/Avalonia.Controls/Platform/ITopLevelImpl.cs b/src/Avalonia.Controls/Platform/ITopLevelImpl.cs index 6472fba48e..0d77cbf802 100644 --- a/src/Avalonia.Controls/Platform/ITopLevelImpl.cs +++ b/src/Avalonia.Controls/Platform/ITopLevelImpl.cs @@ -127,5 +127,10 @@ namespace Avalonia.Platform /// Gets the current of the TopLevel. /// WindowTransparencyLevel TransparencyLevel { get; } + + /// + /// Gets the for the platform. + /// + AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } } } From 7b7411a3a224242da246cbf615b0101f3efafaa3 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sun, 5 Jul 2020 12:08:15 -0300 Subject: [PATCH 42/47] add platform specific compensation levels. --- src/Avalonia.Controls/AcrylicPlatformCompensationLevels.cs | 7 +++++++ .../Embedding/Offscreen/OffscreenTopLevelImpl.cs | 3 +++ src/Avalonia.Controls/ExperimentalAcrylicBorder.cs | 6 +++--- src/Avalonia.DesignerSupport/Remote/Stubs.cs | 2 ++ src/Avalonia.Native/WindowImplBase.cs | 2 ++ src/Avalonia.X11/X11Window.cs | 2 ++ .../Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs | 2 ++ src/Windows/Avalonia.Win32/WindowImpl.cs | 3 +++ 8 files changed, 24 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Controls/AcrylicPlatformCompensationLevels.cs b/src/Avalonia.Controls/AcrylicPlatformCompensationLevels.cs index 13dc736463..e1f1c4029e 100644 --- a/src/Avalonia.Controls/AcrylicPlatformCompensationLevels.cs +++ b/src/Avalonia.Controls/AcrylicPlatformCompensationLevels.cs @@ -7,6 +7,13 @@ /// 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; } diff --git a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs index 48fc8d182f..f8bd2878d9 100644 --- a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs +++ b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs @@ -52,6 +52,9 @@ namespace Avalonia.Controls.Embedding.Offscreen public Action TransparencyLevelChanged { get; set; } + /// + 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); diff --git a/src/Avalonia.Controls/ExperimentalAcrylicBorder.cs b/src/Avalonia.Controls/ExperimentalAcrylicBorder.cs index 6c283665f1..7306a8364f 100644 --- a/src/Avalonia.Controls/ExperimentalAcrylicBorder.cs +++ b/src/Avalonia.Controls/ExperimentalAcrylicBorder.cs @@ -52,15 +52,15 @@ namespace Avalonia.Controls { case WindowTransparencyLevel.Transparent: case WindowTransparencyLevel.None: - Material.PlatformTransparencyCompensationLevel = 1; + Material.PlatformTransparencyCompensationLevel = tl.PlatformImpl.AcrylicCompensationLevels.TransparentLevel; break; case WindowTransparencyLevel.Blur: - Material.PlatformTransparencyCompensationLevel = 0.80; + Material.PlatformTransparencyCompensationLevel = tl.PlatformImpl.AcrylicCompensationLevels.BlurLevel; break; case WindowTransparencyLevel.AcrylicBlur: - Material.PlatformTransparencyCompensationLevel = 0.0; + Material.PlatformTransparencyCompensationLevel = tl.PlatformImpl.AcrylicCompensationLevels.AcrylicBlurLevel; break; } }); diff --git a/src/Avalonia.DesignerSupport/Remote/Stubs.cs b/src/Avalonia.DesignerSupport/Remote/Stubs.cs index 84c52d6fbf..168cdbc03f 100644 --- a/src/Avalonia.DesignerSupport/Remote/Stubs.cs +++ b/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 diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs index f19f9e97aa..f916e95d7c 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/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; } } } diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 56ac7c833e..2a13999e8d 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/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; } } diff --git a/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs b/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs index 2153344363..b8ae2eb4d8 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs +++ b/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); } } diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 21d1e25f04..6e9cb81b11 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -1071,6 +1071,9 @@ namespace Avalonia.Win32 /// public Thickness OffScreenMargin => _offScreenMargin; + /// + public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = new AcrylicPlatformCompensationLevels(1, 0.8, 0); + private struct SavedWindowInfo { public WindowStyles Style { get; set; } From ae8721683a682fa1ce44123d220b2cd698c2bcfb Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sun, 5 Jul 2020 12:14:55 -0300 Subject: [PATCH 43/47] use both comboboxes in control catalog to select transparency level. --- samples/ControlCatalog/MainView.xaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml index c1bd740ab9..d812818ed8 100644 --- a/samples/ControlCatalog/MainView.xaml +++ b/samples/ControlCatalog/MainView.xaml @@ -81,7 +81,7 @@ Simple - Light Simple - Dark - + None Transparent Blur From bd07044777338ed98ab5befb2df37f4171253a14 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sun, 5 Jul 2020 12:30:26 -0300 Subject: [PATCH 44/47] tidy acrylic material class. --- .../Media/ExperimentalAcrylicMaterial.cs | 105 ++---------------- 1 file changed, 8 insertions(+), 97 deletions(-) diff --git a/src/Avalonia.Visuals/Media/ExperimentalAcrylicMaterial.cs b/src/Avalonia.Visuals/Media/ExperimentalAcrylicMaterial.cs index 7071df063a..19498b7f86 100644 --- a/src/Avalonia.Visuals/Media/ExperimentalAcrylicMaterial.cs +++ b/src/Avalonia.Visuals/Media/ExperimentalAcrylicMaterial.cs @@ -105,14 +105,14 @@ namespace Avalonia.Media Color IExperimentalAcrylicMaterial.TintColor => _effectiveTintColor; - public struct HsvColor + private struct HsvColor { public float Hue { get; set; } public float Saturation { get; set; } public float Value { get; set; } } - public static HsvColor RgbToHsv(Color color) + private static HsvColor RgbToHsv(Color color) { var r = color.R / 255.0f; var g = color.G / 255.0f; @@ -121,9 +121,7 @@ namespace Avalonia.Media var min = Math.Min(r, Math.Min(g, b)); float h, s, v; - h = s = v = max; - - //v = (0.299f * r + 0.587f * g + 0.114f * b); + h = v = max; var d = max - min; s = max == 0 ? 0 : d / max; @@ -158,16 +156,7 @@ namespace Avalonia.Media // Update tintColor's alpha with the combined opacity value double tintOpacityModifier = GetTintOpacityModifier(tintColor); - if (false) // non-acrylic blue // TODO detect blur level. - { - tintColor = new Color((byte)(Math.Round(tintColor.A * (((tintOpacity * tintOpacityModifier) * 0.15) + 0.85))), tintColor.R, tintColor.G, tintColor.B); - } - else - { - tintColor = new Color((byte)(255 * ((255.0 / tintColor.A) * tintOpacity) * tintOpacityModifier), tintColor.R, tintColor.G, tintColor.B); - } - - return tintColor; + return new Color((byte)(255 * ((255.0 / tintColor.A) * tintOpacity) * tintOpacityModifier), tintColor.R, tintColor.G, tintColor.B); } private static double GetTintOpacityModifier(Color tintColor) @@ -182,7 +171,7 @@ namespace Avalonia.Media const double whiteMaxOpacity = 0.2; // 100% luminosity const double midPointMaxOpacity = 0.45; // 50% luminosity - const double blackMaxOpacity = 0.42; // 0% luminosity + const double blackMaxOpacity = 0.45; // 0% luminosity var hsv = RgbToHsv(tintColor); @@ -225,67 +214,13 @@ namespace Avalonia.Media return opacityModifier; } - Color GetEffectiveLuminosityColor() + private Color GetEffectiveLuminosityColor() { - double tintOpacity = TintOpacity; - - // Purposely leaving out tint opacity modifier here because GetLuminosityColor needs the *original* tint opacity set by the user. - var tintColor = new Color((byte)(Math.Round(TintColor.A * tintOpacity)), TintColor.R, TintColor.G, TintColor.B); - double? luminosityOpacity = MaterialOpacity; return GetLuminosityColor(luminosityOpacity); } - public static Color FromHsv(HsvColor color) - { - float r = 0; - float g = 0; - float b = 0; - - var i = (float)Math.Floor(color.Hue * 6f); - var f = color.Hue * 6f - i; - var p = color.Value * (1f - color.Saturation); - var q = color.Value * (1f - f * color.Saturation); - var t = color.Value * (1f - (1f - f) * color.Saturation); - - switch (i % 6) - { - case 0: - r = color.Value; - g = t; - b = p; - break; - case 1: - r = q; - g = color.Value; - b = p; - break; - case 2: - r = p; - g = color.Value; - b = t; - break; - case 3: - r = p; - g = q; - b = color.Value; - break; - case 4: - r = t; - g = p; - b = color.Value; - break; - case 5: - r = color.Value; - g = p; - b = q; - break; - } - - return new Color(Trim(r), Trim(g), Trim(b), 255); - } - private static byte Trim(double value) { value = Math.Min(Math.Floor(value * 256), 255); @@ -318,30 +253,8 @@ namespace Avalonia.Media return (color.G < color.B) ? color.G : color.B; } - 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) - ); - } - // The tintColor passed into this method should be the original, unmodified color created using user values for TintColor + TintOpacity - Color GetLuminosityColor(double? luminosityOpacity) + private Color GetLuminosityColor(double? luminosityOpacity) { // Calculate the HSL lightness value of the color. var max = (float)RGBMax(TintColor) / 255.0f; @@ -355,10 +268,8 @@ namespace Avalonia.Media var luminosityColor = new Color(255, Trim(lightness), Trim(lightness), Trim(lightness)); - luminosityColor = Blend(luminosityColor, new Color(255, TintColor.R, TintColor.G, TintColor.B)); - 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); + return new Color((byte)(255 * Math.Max(Math.Min(PlatformTransparencyCompensationLevel + (luminosityOpacity.Value * compensationMultiplier), 1.0), 0.0)), luminosityColor.R, luminosityColor.G, luminosityColor.B); } /// From 8da49f2b21837939a5f55475588de8737ec53127 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sun, 5 Jul 2020 12:39:24 -0300 Subject: [PATCH 45/47] add docs. --- .../Media/AcrylicBackgroundSource.cs | 12 ++++++ .../Media/ExperimentalAcrylicMaterial.cs | 37 +++++++++++++++++++ .../Media/IExperimentalAcrylicMaterial.cs | 18 +++++++++ 3 files changed, 67 insertions(+) diff --git a/src/Avalonia.Visuals/Media/AcrylicBackgroundSource.cs b/src/Avalonia.Visuals/Media/AcrylicBackgroundSource.cs index d902752f85..34cede4018 100644 --- a/src/Avalonia.Visuals/Media/AcrylicBackgroundSource.cs +++ b/src/Avalonia.Visuals/Media/AcrylicBackgroundSource.cs @@ -1,8 +1,20 @@ namespace Avalonia.Media { + /// + /// Background Sources for Acrylic. + /// public enum AcrylicBackgroundSource { + /// + /// The acrylic has no background. + /// None, + + /// + /// 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. + /// Digger } } diff --git a/src/Avalonia.Visuals/Media/ExperimentalAcrylicMaterial.cs b/src/Avalonia.Visuals/Media/ExperimentalAcrylicMaterial.cs index 19498b7f86..6b699acd2e 100644 --- a/src/Avalonia.Visuals/Media/ExperimentalAcrylicMaterial.cs +++ b/src/Avalonia.Visuals/Media/ExperimentalAcrylicMaterial.cs @@ -47,54 +47,91 @@ namespace Avalonia.Media public static readonly StyledProperty TintColorProperty = AvaloniaProperty.Register(nameof(TintColor)); + /// + /// Defines the property. + /// public static readonly StyledProperty BackgroundSourceProperty = AvaloniaProperty.Register(nameof(BackgroundSource)); + /// + /// Defines the property. + /// public static readonly StyledProperty TintOpacityProperty = AvaloniaProperty.Register(nameof(TintOpacity), 0.8); + /// + /// Defines the property. + /// public static readonly StyledProperty MaterialOpacityProperty = AvaloniaProperty.Register(nameof(MaterialOpacity), 0.5); + /// + /// Defines the property. + /// public static readonly StyledProperty PlatformTransparencyCompensationLevelProperty = AvaloniaProperty.Register(nameof(PlatformTransparencyCompensationLevel), 0.0); + /// + /// Defines the property. + /// public static readonly StyledProperty FallbackColorProperty = AvaloniaProperty.Register(nameof(FallbackColor)); /// public event EventHandler Invalidated; + /// + /// Gets or Sets the BackgroundSource . + /// public AcrylicBackgroundSource BackgroundSource { get => GetValue(BackgroundSourceProperty); set => SetValue(BackgroundSourceProperty, value); } + /// + /// Gets or Sets the TintColor. + /// public Color TintColor { get => GetValue(TintColorProperty); set => SetValue(TintColorProperty, value); } + /// + /// Gets or Sets the Tint Opacity. + /// public double TintOpacity { get => GetValue(TintOpacityProperty); set => SetValue(TintOpacityProperty, value); } + /// + /// Gets or Sets the Fallback Color. + /// This is used on rendering plaforms that dont support acrylic. + /// public Color FallbackColor { get => GetValue(FallbackColorProperty); set => SetValue(FallbackColorProperty, value); } + /// + /// Gets or Sets the MaterialOpacity. + /// This makes the material more or less opaque. + /// public double MaterialOpacity { get => GetValue(MaterialOpacityProperty); set => SetValue(MaterialOpacityProperty, value); } + /// + /// Gets or Sets the PlatformTransparencyCompensationLevel. + /// This value defines the minimum that can be used. + /// It means material opacity is re-scaled from this value to 1. + /// public double PlatformTransparencyCompensationLevel { get => GetValue(PlatformTransparencyCompensationLevelProperty); diff --git a/src/Avalonia.Visuals/Media/IExperimentalAcrylicMaterial.cs b/src/Avalonia.Visuals/Media/IExperimentalAcrylicMaterial.cs index a656c75d46..e71584258a 100644 --- a/src/Avalonia.Visuals/Media/IExperimentalAcrylicMaterial.cs +++ b/src/Avalonia.Visuals/Media/IExperimentalAcrylicMaterial.cs @@ -1,15 +1,33 @@ namespace Avalonia.Media { + /// + /// Experimental Interface for producing Acrylic-like materials. + /// public interface IExperimentalAcrylicMaterial { + /// + /// Gets the of the material. + /// AcrylicBackgroundSource BackgroundSource { get; } + /// + /// Gets the TintColor of the material. + /// Color TintColor { get; } + /// + /// Gets the TintOpacity of the material. + /// double TintOpacity { get; } + /// + /// Gets the effective material color. + /// Color MaterialColor { get; } + /// + /// Gets the fallback color. + /// Color FallbackColor { get; } } } From 15832ed70905fa8320485ba3c8326d7462296c8a Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sun, 5 Jul 2020 14:04:26 -0300 Subject: [PATCH 46/47] fix wpf interop --- src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs index f8366abb81..f5d83611bb 100644 --- a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs +++ b/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); } } From 60c2e454371cfc8e9cd87772965fe3fce8337a42 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 6 Jul 2020 13:09:04 -0300 Subject: [PATCH 47/47] fix unsubscribe handler. --- src/Avalonia.Controls/ExperimentalAcrylicBorder.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.Controls/ExperimentalAcrylicBorder.cs b/src/Avalonia.Controls/ExperimentalAcrylicBorder.cs index 7306a8364f..0a01767a07 100644 --- a/src/Avalonia.Controls/ExperimentalAcrylicBorder.cs +++ b/src/Avalonia.Controls/ExperimentalAcrylicBorder.cs @@ -16,6 +16,8 @@ namespace Avalonia.Controls private readonly BorderRenderHelper _borderRenderHelper = new BorderRenderHelper(); + private IDisposable _subscription; + static ExperimentalAcrylicBorder() { AffectsRender( @@ -45,7 +47,7 @@ namespace Avalonia.Controls var tl = (e.Root as TopLevel); - tl.GetObservable(TopLevel.ActualTransparencyLevelProperty) + _subscription = tl.GetObservable(TopLevel.ActualTransparencyLevelProperty) .Subscribe(x => { switch (x) @@ -69,19 +71,20 @@ namespace Avalonia.Controls protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) { base.OnDetachedFromVisualTree(e); - } + _subscription?.Dispose(); + } public override void Render(DrawingContext context) { - if(context.PlatformImpl is IDrawingContextWithAcrylicLikeSupport idc) + if (context.PlatformImpl is IDrawingContextWithAcrylicLikeSupport idc) { var cornerRadius = CornerRadius; idc.DrawRectangle( - Material, + Material, new RoundedRect( - new Rect(Bounds.Size), + new Rect(Bounds.Size), cornerRadius.TopLeft, cornerRadius.TopRight, cornerRadius.BottomRight, cornerRadius.BottomLeft)); }