From e4fd1ad4f3dbfca9f713e0faf0d07734dac4275a Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sat, 30 May 2020 14:41:12 -0300 Subject: [PATCH] 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)